1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
gem 'ed25519', '~> 1.2'
gem 'bcrypt_pbkdf', '~> 1.0' unless RUBY_PLATFORM == "java"
require 'ed25519'
require 'base64'
require 'net/ssh/transport/cipher_factory'
require 'net/ssh/authentication/pub_key_fingerprint'
require 'bcrypt_pbkdf' unless RUBY_PLATFORM == "java"
module Net
module SSH
module Authentication
module ED25519
class SigningKeyFromFile < SimpleDelegator
def initialize(pk, sk)
key = ::Ed25519::SigningKey.from_keypair(sk)
raise ArgumentError, "pk does not match sk" unless pk == key.verify_key.to_bytes
super(key)
end
end
class OpenSSHPrivateKeyLoader
CipherFactory = Net::SSH::Transport::CipherFactory
MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
MEND = "-----END OPENSSH PRIVATE KEY-----"
MAGIC = "openssh-key-v1"
class DecryptError < ArgumentError
def initialize(message, encrypted_key: false)
super(message)
@encrypted_key = encrypted_key
end
def encrypted_key?
return @encrypted_key
end
end
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 = Base64.decode64(datab64)
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
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))
if kdfname == 'bcrypt'
salt = kdfopts.read_string
rounds = kdfopts.read_long
raise "BCryptPbkdf is not implemented for jruby" if RUBY_PLATFORM == "java"
key = BCryptPbkdf::key(password, salt, keylen + ivlen, rounds)
else
key = '\x00' * (keylen + ivlen)
end
cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv: key[keylen...keylen + ivlen], decrypt: true)
decoded = cipher.update(buffer.remainder_as_buffer.to_s)
decoded << cipher.final
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
end
class PubKey
include Net::SSH::Authentication::PubKeyFingerprint
attr_reader :verify_key
def initialize(data)
@verify_key = ::Ed25519::VerifyKey.new(data)
end
def self.read_keyblob(buffer)
PubKey.new(buffer.read_string)
end
def to_blob
Net::SSH::Buffer.from(:mstring, "ssh-ed25519".dup, :string, @verify_key.to_bytes).to_s
end
def ssh_type
"ssh-ed25519"
end
def ssh_signature_type
ssh_type
end
def ssh_do_verify(sig, data, options = {})
@verify_key.verify(sig, data)
end
def to_pem
# TODO this is not pem
ssh_type + Base64.encode64(@verify_key.to_bytes)
end
end
class PrivKey
CipherFactory = Net::SSH::Transport::CipherFactory
MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
MEND = "-----END OPENSSH PRIVATE KEY-----\n"
MAGIC = "openssh-key-v1"
attr_reader :sign_key
def initialize(buffer)
pk = buffer.read_string
sk = buffer.read_string
_comment = buffer.read_string
@pk = pk
@sign_key = SigningKeyFromFile.new(pk, sk)
end
def to_blob
public_key.to_blob
end
def ssh_type
"ssh-ed25519"
end
def ssh_signature_type
ssh_type
end
def public_key
PubKey.new(@pk)
end
def ssh_do_sign(data, sig_alg = nil)
@sign_key.sign(data)
end
def self.read(data, password)
OpenSSHPrivateKeyLoader.read(data, password)
end
end
end
end
end
end
|