Class: Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader

Inherits:
Object
  • Object
show all
Defined in:
lib/net/ssh/authentication/ed25519.rb

Defined Under Namespace

Classes: DecryptError

Constant Summary collapse

CipherFactory =
Net::SSH::Transport::CipherFactory
MBEGIN =
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
MEND =
"-----END OPENSSH PRIVATE KEY-----"
MAGIC =
"openssh-key-v1"

Class Method Summary collapse

Class Method Details

.bcrypt_pbkdf_key(password, salt, length, rounds) ⇒ Object

Raises:



142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/net/ssh/authentication/ed25519.rb', line 142

def self.bcrypt_pbkdf_key(password, salt, length, rounds)
  begin
    require_bcrypt_pbkdf
  rescue LoadError
    raise DecryptError.new("bcrypt_pbkdf is required to decrypt bcrypt-encrypted OpenSSH private keys")
  end

  key = BCryptPbkdf::key(password, salt, length, rounds)
  raise DecryptError.new("BCryptPbkdf failed", encrypted_key: true) unless key

  key
end

.decrypt_private_key(ciphername, encrypted_private, auth_tag, options) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/net/ssh/authentication/ed25519.rb', line 159

def self.decrypt_private_key(ciphername, encrypted_private, auth_tag, options)
  if auth_tag
    return CipherFactory.decrypt_private_key(
      ciphername,
      encrypted_private,
      auth_tag,
      options[:key],
      options[:iv]
    )
  end

  cipher = CipherFactory.get(ciphername, key: options[:key], iv: options[:iv], decrypt: true)
  decoded = cipher.update(encrypted_private)
  decoded << cipher.final
rescue Net::SSH::Exception, OpenSSL::Cipher::CipherError
  raise DecryptError.new("Decrypt failed on private key", encrypted_key: options[:encrypted_key])
end

.read(datafull, password) ⇒ Object

Raises:

  • (ArgumentError)


73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/net/ssh/authentication/ed25519.rb', line 73

def self.read(datafull, password)
  datafull = datafull.strip
  raise ArgumentError.new("Expected #{MBEGIN} at start of private key") unless datafull.start_with?(MBEGIN)
  raise ArgumentError.new("Expected #{MEND} at end of private key") unless datafull.end_with?(MEND)

  datab64 = datafull[MBEGIN.size...-MEND.size]
  data = datab64.unpack1("m")
  raise ArgumentError.new("Expected #{MAGIC} at start of decoded private key") unless data.start_with?(MAGIC)

  buffer = Net::SSH::Buffer.new(data[(MAGIC.size + 1)..-1])

  ciphername = buffer.read_string
  raise ArgumentError.new("#{ciphername} in private key is not supported") unless
    CipherFactory.supported?(ciphername)

  kdfname = buffer.read_string
  raise ArgumentError.new("Expected #{kdfname} to be or none or bcrypt") unless %w[none bcrypt].include?(kdfname)

  kdfopts = Net::SSH::Buffer.new(buffer.read_string)
  num_keys = buffer.read_long
  raise ArgumentError.new("Only 1 key is supported in ssh keys #{num_keys} was in private key") unless num_keys == 1

  _pubkey = buffer.read_string

  len = buffer.read_long
  encrypted_private = buffer.read(len)

  keylen, blocksize, ivlen = CipherFactory.get_lengths(ciphername, iv_len: true)
  raise ArgumentError.new("Private key len:#{len} is not a multiple of #{blocksize}") if
    (len < blocksize) || ((blocksize > 0) && (len % blocksize) != 0)

  raise ArgumentError.new("Private key len:#{len} exceeds available data") unless encrypted_private.bytesize == len

  authlen = CipherFactory.auth_length(ciphername)
  auth_tag = authlen > 0 ? buffer.read(authlen) : nil

  if kdfname == 'bcrypt'
    salt = kdfopts.read_string
    rounds = kdfopts.read_long

    key = bcrypt_pbkdf_key(password, salt, keylen + ivlen, rounds)
  else
    key = "\x00" * (keylen + ivlen)
  end

  decoded = decrypt_private_key(
    ciphername,
    encrypted_private,
    auth_tag,
    key: key[0...keylen],
    iv: key[keylen...(keylen + ivlen)],
    encrypted_key: kdfname == 'bcrypt'
  )

  decoded = Net::SSH::Buffer.new(decoded)
  check1 = decoded.read_long
  check2 = decoded.read_long

  raise DecryptError.new("Decrypt failed on private key", encrypted_key: kdfname == 'bcrypt') if (check1 != check2)

  type_name = decoded.read_string
  case type_name
  when "ssh-ed25519"
    PrivKey.new(decoded)
  else
    decoded.read_private_keyblob(type_name)
  end
end

.require_bcrypt_pbkdfObject



155
156
157
# File 'lib/net/ssh/authentication/ed25519.rb', line 155

def self.require_bcrypt_pbkdf
  require 'bcrypt_pbkdf'
end