class Vagrant::Util::Keypair::Ecdsa

Base class for Ecdsa type keys to subclass

Public Class Methods

create(password=nil) click to toggle source

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

# File lib/vagrant/util/keypair.rb, line 198
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
padded_string(s, blocksize) click to toggle source

Encodes given string with padding to block size

@param [String] s String to encode @param [Integer] blocksize Defined block size @return [String]

# File lib/vagrant/util/keypair.rb, line 189
def self.padded_string(s, blocksize)
  pad = blocksize - (s.length % blocksize)
  string(s + Array(1..pad).pack("c*"))
end
string(s) click to toggle source

Encodes given string

@param [String] s String to encode @return [String]

# File lib/vagrant/util/keypair.rb, line 180
def self.string(s)
  [s.length].pack("N") + s
end