Add new key pair types
Adds ECDSA key types (256, 384, and 521) to supported types that can be generated for key replacement on guest.
This commit is contained in:
parent
4e61783008
commit
b934bd675c
@ -10,15 +10,43 @@ require "vagrant/util/retryable"
|
||||
module Vagrant
|
||||
module Util
|
||||
class Keypair
|
||||
# Magic string header
|
||||
AUTH_MAGIC = "openssh-key-v1".freeze
|
||||
# Header of private key file content
|
||||
PRIVATE_KEY_START = "-----BEGIN OPENSSH PRIVATE KEY-----\n".freeze
|
||||
# Footer of private key file content
|
||||
PRIVATE_KEY_END = "-----END OPENSSH PRIVATE KEY-----\n".freeze
|
||||
|
||||
# Check if provided key is a supported key type
|
||||
#
|
||||
# @param [Symbol] key Key type to check
|
||||
# @return [Boolean] key type is supported
|
||||
def self.valid_type?(key)
|
||||
VALID_TYPES.keys.include?(key)
|
||||
end
|
||||
|
||||
# @return [Array<Symbol>] list of supported key types
|
||||
def self.available_types
|
||||
PREFER_KEY_TYPES.values
|
||||
end
|
||||
|
||||
# Create a new keypair
|
||||
#
|
||||
# @param [String] password Password for the key or nil for no password (only supported for rsa type)
|
||||
# @param [Symbol] type Key type to generate
|
||||
# @return [Array<String, String, String>] Public key, openssh private key, openssh public key with comment
|
||||
def self.create(password=nil, type: :rsa)
|
||||
if !VALID_TYPES.key?(type)
|
||||
raise ArgumentError,
|
||||
"Invalid key type requested (supported types: #{available_types.map(&:inspect).join(", ")})"
|
||||
end
|
||||
|
||||
VALID_TYPES[type].create(password)
|
||||
end
|
||||
|
||||
class Ed25519
|
||||
# Magic string header
|
||||
AUTH_MAGIC = "openssh-key-v1".freeze
|
||||
# Key type identifier
|
||||
KEY_TYPE = "ssh-ed25519".freeze
|
||||
# Header of private key file content
|
||||
PRIVATE_KEY_START = "-----BEGIN OPENSSH PRIVATE KEY-----\n".freeze
|
||||
# Footer of private key file content
|
||||
PRIVATE_KEY_END = "-----END OPENSSH PRIVATE KEY-----\n".freeze
|
||||
|
||||
# Encodes given string
|
||||
#
|
||||
@ -95,6 +123,9 @@ module Vagrant
|
||||
class Rsa
|
||||
extend Retryable
|
||||
|
||||
# Key type identifier
|
||||
KEY_TYPE = "ssh-rsa"
|
||||
|
||||
# Creates an SSH keypair and returns it.
|
||||
#
|
||||
# @param [String] password Password for the key, or nil for no password.
|
||||
@ -140,19 +171,123 @@ module Vagrant
|
||||
end
|
||||
end
|
||||
|
||||
# Supported key types.
|
||||
VALID_TYPES = {ed25519: Ed25519, rsa: Rsa}.freeze
|
||||
# Ordered mapping of openssh key type name to lookup name
|
||||
PREFER_KEY_TYPES = {"ssh-ed25519".freeze => :ed25519, "ssh-rsa".freeze => :rsa}.freeze
|
||||
|
||||
def self.create(password=nil, type: :rsa)
|
||||
if !VALID_TYPES.key?(type)
|
||||
raise ArgumentError,
|
||||
"Invalid key type requested (supported types: #{VALID_TYPES.keys.map(&:inspect)})"
|
||||
# Base class for Ecdsa type keys to subclass
|
||||
class Ecdsa
|
||||
# Encodes given string
|
||||
#
|
||||
# @param [String] s String to encode
|
||||
# @return [String]
|
||||
def self.string(s)
|
||||
[s.length].pack("N") + s
|
||||
end
|
||||
|
||||
VALID_TYPES[type].create(password)
|
||||
# Encodes given string with padding to block size
|
||||
#
|
||||
# @param [String] s String to encode
|
||||
# @param [Integer] blocksize Defined block size
|
||||
# @return [String]
|
||||
def self.padded_string(s, blocksize)
|
||||
pad = blocksize - (s.length % blocksize)
|
||||
string(s + Array(1..pad).pack("c*"))
|
||||
end
|
||||
|
||||
# Creates an ed25519 SSH key pair
|
||||
# @return [Array<String, String, String>] Public key, openssh private key, openssh public key with comment
|
||||
# @note Password support was not included as it's not actively used anywhere. If it ends up being
|
||||
# something that's needed, it can be revisited
|
||||
def self.create(password=nil)
|
||||
if password
|
||||
raise NotImplementedError,
|
||||
"Ecdsa key pair generation does not support passwords"
|
||||
end
|
||||
|
||||
# Generate the key
|
||||
base_key = OpenSSL::PKey::EC.generate(self.const_get(:OPENSSL_CURVE))
|
||||
# Define the comment used for the key
|
||||
comment = "vagrant"
|
||||
|
||||
# Grab the raw public key
|
||||
public_key = base_key.public_key.to_bn.to_s(2)
|
||||
# Encode the public key for use building the openssh private key
|
||||
encoded_public_key = string(self.const_get(:KEY_TYPE)) + string(self.const_get(:OPENSSH_CURVE)) + string(public_key)
|
||||
# Format the public key into the openssh public key format for writing
|
||||
openssh_public_key = "#{self.const_get(:KEY_TYPE)} #{Base64.encode64(encoded_public_key).gsub("\n", "")} #{comment}"
|
||||
|
||||
pk_value = base_key.private_key.to_s(2)
|
||||
# Pad the start of the key if required
|
||||
if pk_value.length % 8 == 0
|
||||
pk_value = "\0#{pk_value}"
|
||||
end
|
||||
|
||||
# Agent encoded private key is used when building the openssh private key
|
||||
# (https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-4.2.3)
|
||||
# (https://dnaeon.github.io/openssh-private-key-binary-format/)
|
||||
agent_private_key = [
|
||||
([SecureRandom.random_number((2**32)-1)] * 2).pack("NN"), # checkint, random uint32 value, twice (used for encryption verification)
|
||||
encoded_public_key, # includes the key type and public key
|
||||
string(pk_value), # private key
|
||||
string(comment), # comment for the key
|
||||
].join
|
||||
|
||||
# Build openssh private key data (https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key)
|
||||
private_key = [
|
||||
AUTH_MAGIC + "\0", # Magic string
|
||||
string("none"), # cipher name, no encryption, so none
|
||||
string("none"), # kdf name, no encryption, so none
|
||||
string(""), # kdf options/data, no encryption, so empty string
|
||||
[1].pack("N"), # Number of keys (just one)
|
||||
string(encoded_public_key), # The public key
|
||||
padded_string(agent_private_key, 8) # Private key encoded with agent rules, padded for 8 byte block size
|
||||
].join
|
||||
|
||||
# Create the openssh private key content
|
||||
openssh_private_key = [
|
||||
PRIVATE_KEY_START,
|
||||
Base64.encode64(private_key),
|
||||
PRIVATE_KEY_END,
|
||||
].join
|
||||
|
||||
return [public_key, openssh_private_key, openssh_public_key]
|
||||
end
|
||||
end
|
||||
|
||||
class Ecdsa256 < Ecdsa
|
||||
KEY_TYPE = "ecdsa-sha2-nistp256".freeze
|
||||
OPENSSH_CURVE = "nistp256".freeze
|
||||
OPENSSL_CURVE = "prime256v1".freeze
|
||||
end
|
||||
|
||||
class Ecdsa384 < Ecdsa
|
||||
KEY_TYPE = "ecdsa-sha2-nistp384".freeze
|
||||
OPENSSH_CURVE = "nistp384".freeze
|
||||
OPENSSL_CURVE = "secp384r1".freeze
|
||||
end
|
||||
|
||||
class Ecdsa521 < Ecdsa
|
||||
KEY_TYPE = "ecdsa-sha2-nistp521".freeze
|
||||
OPENSSH_CURVE = "nistp521".freeze
|
||||
OPENSSL_CURVE = "secp521r1".freeze
|
||||
end
|
||||
|
||||
# Supported key types.
|
||||
VALID_TYPES = {
|
||||
ecdsa256: Ecdsa256,
|
||||
ecdsa384: Ecdsa384,
|
||||
ecdsa521: Ecdsa521,
|
||||
ed25519: Ed25519,
|
||||
rsa: Rsa
|
||||
}.freeze
|
||||
|
||||
# Ordered mapping of openssh key type name to lookup name. The
|
||||
# order defined here is based on preference. Note that ecdsa
|
||||
# ordering is based on performance
|
||||
PREFER_KEY_TYPES = {
|
||||
Ed25519::KEY_TYPE => :ed25519,
|
||||
Ecdsa256::KEY_TYPE => :ecdsa256,
|
||||
Ecdsa521::KEY_TYPE => :ecdsa521,
|
||||
Ecdsa384::KEY_TYPE => :ecdsa384,
|
||||
Rsa::KEY_TYPE => :rsa,
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user