Module: Xshellz::Keys

Defined in:
lib/xshellz/keys.rb

Overview

In-memory ed25519 SSH keypair generation.

The keypair is generated per Sandbox.create and never touches disk; the control plane only ever sees the public half. Both halves are encoded by hand (RFC 4253 public line, openssh-key-v1 private container) so the private key can be handed to net-ssh via key_data without any file IO.

Defined Under Namespace

Classes: KeyPair

Constant Summary collapse

DEFAULT_KEY_COMMENT =
"xshellz-sdk"
AUTH_MAGIC =
"openssh-key-v1\0"
KEY_TYPE =
"ssh-ed25519"
BLOCK_SIZE =
8

Class Method Summary collapse

Class Method Details

.base64(data) ⇒ Object

Strict (unwrapped) base64 without pulling in the base64 gem.



89
90
91
# File 'lib/xshellz/keys.rb', line 89

def base64(data)
  [data].pack("m0")
end

.encode_openssh_private_key(seed, public_bytes, comment) ⇒ Object

Serialize the private key into the openssh-key-v1 container (unencrypted: cipher "none", kdf "none") - the format ssh-keygen writes and the one net-ssh's ED25519 support parses from key_data.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/xshellz/keys.rb', line 60

def encode_openssh_private_key(seed, public_bytes, comment)
  check = SecureRandom.bytes(4)

  section = "".b
  section << check << check
  section << ssh_string(KEY_TYPE)
  section << ssh_string(public_bytes)
  section << ssh_string(seed + public_bytes)
  section << ssh_string(comment)
  pad = 1
  until (section.bytesize % BLOCK_SIZE).zero?
    section << [pad].pack("C")
    pad += 1
  end

  blob = "".b
  blob << AUTH_MAGIC.b
  blob << ssh_string("none") # ciphername
  blob << ssh_string("none") # kdfname
  blob << ssh_string("")     # kdfoptions
  blob << [1].pack("N")      # number of keys
  blob << ssh_string(public_key_blob(public_bytes))
  blob << ssh_string(section)

  body = base64(blob).scan(/.{1,70}/).join("\n")
  "-----BEGIN OPENSSH PRIVATE KEY-----\n#{body}\n-----END OPENSSH PRIVATE KEY-----\n"
end

.generate(comment: DEFAULT_KEY_COMMENT) ⇒ KeyPair

Generate a fresh in-memory ed25519 keypair.

Returns:



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/xshellz/keys.rb', line 33

def generate(comment: DEFAULT_KEY_COMMENT)
  signing_key = Ed25519::SigningKey.generate
  seed = signing_key.to_bytes
  public_bytes = signing_key.verify_key.to_bytes

  line = "#{KEY_TYPE} #{base64(public_key_blob(public_bytes))}"
  line = "#{line} #{comment}" unless comment.to_s.empty?

  KeyPair.new(
    private_key_openssh: encode_openssh_private_key(seed, public_bytes, comment.to_s),
    public_key_line: line
  )
end

.public_key_blob(public_bytes) ⇒ Object

The RFC 4253 public key blob: string "ssh-ed25519" + string key-bytes.



48
49
50
# File 'lib/xshellz/keys.rb', line 48

def public_key_blob(public_bytes)
  ssh_string(KEY_TYPE) + ssh_string(public_bytes)
end

.ssh_string(value) ⇒ Object

A length-prefixed SSH wire string.



53
54
55
# File 'lib/xshellz/keys.rb', line 53

def ssh_string(value)
  [value.bytesize].pack("N") + value.dup.force_encoding(Encoding::BINARY)
end