jose 0.3.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +5 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +7 -0
- data/{LICENSE.txt → LICENSE.md} +0 -0
- data/README.md +10 -3
- data/docs/EncryptionAlgorithms.md +55 -0
- data/docs/GettingStarted.md +137 -0
- data/docs/KeyGeneration.md +263 -0
- data/docs/SignatureAlgorithms.md +35 -0
- data/lib/jose.rb +86 -22
- data/lib/jose/jwa.rb +293 -0
- data/lib/jose/jwa/concat_kdf.rb +2 -0
- data/lib/jose/jwa/curve25519_ruby.rb +1 -1
- data/lib/jose/jwa/curve448_ruby.rb +1 -1
- data/lib/jose/jwa/ed448.rb +8 -8
- data/lib/jose/jwe.rb +819 -11
- data/lib/jose/jwe/alg_aes_gcm_kw.rb +1 -1
- data/lib/jose/jwe/alg_aes_kw.rb +1 -1
- data/lib/jose/jwe/alg_dir.rb +4 -4
- data/lib/jose/jwe/alg_ecdh_es.rb +45 -18
- data/lib/jose/jwe/alg_pbes2.rb +3 -3
- data/lib/jose/jwe/alg_rsa.rb +1 -1
- data/lib/jose/jwk.rb +639 -48
- data/lib/jose/jwk/kty_ec.rb +22 -4
- data/lib/jose/jwk/kty_oct.rb +16 -0
- data/lib/jose/jwk/kty_okp_ed25519.rb +36 -0
- data/lib/jose/jwk/kty_okp_ed25519ph.rb +36 -0
- data/lib/jose/jwk/kty_okp_ed448.rb +36 -0
- data/lib/jose/jwk/kty_okp_ed448ph.rb +36 -0
- data/lib/jose/jwk/kty_okp_x25519.rb +28 -0
- data/lib/jose/jwk/kty_okp_x448.rb +28 -0
- data/lib/jose/jwk/kty_rsa.rb +8 -0
- data/lib/jose/jwk/openssh_key.rb +278 -0
- data/lib/jose/jws.rb +486 -21
- data/lib/jose/jws/alg_none.rb +2 -2
- data/lib/jose/jwt.rb +208 -14
- data/lib/jose/version.rb +1 -1
- metadata +9 -3
data/lib/jose/jwk/kty_ec.rb
CHANGED
@@ -19,7 +19,7 @@ class JOSE::JWK::KTY_EC < Struct.new(:key)
|
|
19
19
|
y = JOSE.urlsafe_decode64(fields['y'])
|
20
20
|
ec.public_key = OpenSSL::PKey::EC::Point.new(
|
21
21
|
OpenSSL::PKey::EC::Group.new(crv),
|
22
|
-
OpenSSL::BN.new([0x04, x, y].pack('
|
22
|
+
OpenSSL::BN.new([0x04, x, y].pack('Ca*a*'), 2)
|
23
23
|
)
|
24
24
|
if fields['d'].is_a?(String)
|
25
25
|
ec.private_key = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['d']), 2)
|
@@ -38,11 +38,11 @@ class JOSE::JWK::KTY_EC < Struct.new(:key)
|
|
38
38
|
ec_point = key.public_key.to_bn.to_s(2)
|
39
39
|
ec_point_x, ec_point_y = case ec_point.bytesize
|
40
40
|
when 65
|
41
|
-
ec_point.unpack('
|
41
|
+
ec_point.unpack('xa32a32')
|
42
42
|
when 97
|
43
|
-
ec_point.unpack('
|
43
|
+
ec_point.unpack('xa48a48')
|
44
44
|
when 133
|
45
|
-
ec_point.unpack('
|
45
|
+
ec_point.unpack('xa66a66')
|
46
46
|
else
|
47
47
|
raise ArgumentError, "unhandled EC point size: #{ec_point.bytesize.inspect}"
|
48
48
|
end
|
@@ -178,6 +178,24 @@ class JOSE::JWK::KTY_EC < Struct.new(:key)
|
|
178
178
|
end
|
179
179
|
end
|
180
180
|
|
181
|
+
def verifier(fields)
|
182
|
+
if fields and fields['use'] == 'sig' and not fields['alg'].nil?
|
183
|
+
return [fields['alg']]
|
184
|
+
else
|
185
|
+
alg = case key.group.curve_name
|
186
|
+
when 'prime256v1', 'secp256r1'
|
187
|
+
'ES256'
|
188
|
+
when 'secp384r1'
|
189
|
+
'ES384'
|
190
|
+
when 'secp521r1'
|
191
|
+
'ES512'
|
192
|
+
else
|
193
|
+
raise ArgumentError, "unhandled EC curve name: #{key.group.curve_name.inspect}"
|
194
|
+
end
|
195
|
+
return [alg]
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
181
199
|
def verify(message, digest_type, signature)
|
182
200
|
n = signature.bytesize.div(2)
|
183
201
|
r = OpenSSL::BN.new(signature[0..(n-1)], 2)
|
data/lib/jose/jwk/kty_oct.rb
CHANGED
@@ -108,6 +108,22 @@ class JOSE::JWK::KTY_oct < Struct.new(:oct)
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
|
+
def verifier(fields)
|
112
|
+
if fields and fields['use'] == 'sig' and not fields['alg'].nil?
|
113
|
+
return [fields['alg']]
|
114
|
+
else
|
115
|
+
bitsize = (oct.bytesize * 8)
|
116
|
+
algs = if bitsize < 384
|
117
|
+
['HS256']
|
118
|
+
elsif bitsize < 512
|
119
|
+
['HS256', 'HS384']
|
120
|
+
else
|
121
|
+
['HS256', 'HS384', 'HS512']
|
122
|
+
end
|
123
|
+
return algs
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
111
127
|
def verify(message, digest_type, signature)
|
112
128
|
return JOSE::JWA.constant_time_compare(signature, sign(message, digest_type))
|
113
129
|
end
|
@@ -94,6 +94,14 @@ class JOSE::JWK::KTY_OKP_Ed25519 < Struct.new(:okp)
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
+
def verifier(fields)
|
98
|
+
if fields and fields['use'] == 'sig' and not fields['alg'].nil?
|
99
|
+
return [fields['alg']]
|
100
|
+
else
|
101
|
+
return ['Ed25519']
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
97
105
|
def verify(message, digest_type, signature)
|
98
106
|
raise ArgumentError, "'digest_type' must be :Ed25519" if digest_type != :Ed25519
|
99
107
|
pk = okp
|
@@ -111,8 +119,36 @@ class JOSE::JWK::KTY_OKP_Ed25519 < Struct.new(:okp)
|
|
111
119
|
end
|
112
120
|
end
|
113
121
|
|
122
|
+
def self.from_openssh_key(key)
|
123
|
+
type, _, sk, comment = key
|
124
|
+
if type and sk and type == 'ssh-ed25519' and sk.bytesize == SK_BYTES
|
125
|
+
if comment == '' or comment.nil?
|
126
|
+
return from_okp([:Ed25519, sk])
|
127
|
+
else
|
128
|
+
kty, fields = from_okp([:Ed25519, sk])
|
129
|
+
return kty, fields.merge('kid' => comment)
|
130
|
+
end
|
131
|
+
else
|
132
|
+
raise ArgumentError, "unrecognized openssh key type: #{type.inspect}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
114
136
|
def to_okp
|
115
137
|
return [:Ed25519, okp]
|
116
138
|
end
|
117
139
|
|
140
|
+
def to_openssh_key(fields)
|
141
|
+
comment = fields['kid'] || ''
|
142
|
+
pk = JOSE::JWA::Curve25519.ed25519_secret_to_public(okp)
|
143
|
+
sk = okp
|
144
|
+
return JOSE::JWK::OpenSSHKey.to_binary([
|
145
|
+
[
|
146
|
+
[
|
147
|
+
['ssh-ed25519', pk],
|
148
|
+
['ssh-ed25519', pk, sk, comment]
|
149
|
+
]
|
150
|
+
]
|
151
|
+
])
|
152
|
+
end
|
153
|
+
|
118
154
|
end
|
@@ -94,6 +94,14 @@ class JOSE::JWK::KTY_OKP_Ed25519ph < Struct.new(:okp)
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
+
def verifier(fields)
|
98
|
+
if fields and fields['use'] == 'sig' and not fields['alg'].nil?
|
99
|
+
return [fields['alg']]
|
100
|
+
else
|
101
|
+
return ['Ed25519ph']
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
97
105
|
def verify(message, sign_type, signature)
|
98
106
|
raise ArgumentError, "'sign_type' must be :Ed25519ph" if sign_type != :Ed25519ph
|
99
107
|
pk = okp
|
@@ -111,8 +119,36 @@ class JOSE::JWK::KTY_OKP_Ed25519ph < Struct.new(:okp)
|
|
111
119
|
end
|
112
120
|
end
|
113
121
|
|
122
|
+
def self.from_openssh_key(key)
|
123
|
+
type, _, sk, comment = key
|
124
|
+
if type and sk and type == 'ssh-ed25519ph' and sk.bytesize == SK_BYTES
|
125
|
+
if comment == '' or comment.nil?
|
126
|
+
return from_okp([:Ed25519ph, sk])
|
127
|
+
else
|
128
|
+
kty, fields = from_okp([:Ed25519ph, sk])
|
129
|
+
return kty, fields.merge('kid' => comment)
|
130
|
+
end
|
131
|
+
else
|
132
|
+
raise ArgumentError, "unrecognized openssh key type: #{type.inspect}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
114
136
|
def to_okp
|
115
137
|
return [:Ed25519, okp]
|
116
138
|
end
|
117
139
|
|
140
|
+
def to_openssh_key(fields)
|
141
|
+
comment = fields['kid'] || ''
|
142
|
+
pk = JOSE::JWA::Curve25519.ed25519ph_secret_to_public(okp)
|
143
|
+
sk = okp
|
144
|
+
return JOSE::JWK::OpenSSHKey.to_binary([
|
145
|
+
[
|
146
|
+
[
|
147
|
+
['ssh-ed25519ph', pk],
|
148
|
+
['ssh-ed25519ph', pk, sk, comment]
|
149
|
+
]
|
150
|
+
]
|
151
|
+
])
|
152
|
+
end
|
153
|
+
|
118
154
|
end
|
@@ -103,6 +103,14 @@ class JOSE::JWK::KTY_OKP_Ed448 < Struct.new(:okp)
|
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
|
+
def verifier(fields)
|
107
|
+
if fields and fields['use'] == 'sig' and not fields['alg'].nil?
|
108
|
+
return [fields['alg']]
|
109
|
+
else
|
110
|
+
return ['Ed448']
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
106
114
|
def verify(message, digest_type, signature)
|
107
115
|
raise ArgumentError, "'digest_type' must be :Ed448" if digest_type != :Ed448
|
108
116
|
pk = okp
|
@@ -120,8 +128,36 @@ class JOSE::JWK::KTY_OKP_Ed448 < Struct.new(:okp)
|
|
120
128
|
end
|
121
129
|
end
|
122
130
|
|
131
|
+
def self.from_openssh_key(key)
|
132
|
+
type, _, sk, comment = key
|
133
|
+
if type and sk and type == 'ssh-ed448' and sk.bytesize == SK_BYTES
|
134
|
+
if comment == '' or comment.nil?
|
135
|
+
return from_okp([:Ed448, sk])
|
136
|
+
else
|
137
|
+
kty, fields = from_okp([:Ed448, sk])
|
138
|
+
return kty, fields.merge('kid' => comment)
|
139
|
+
end
|
140
|
+
else
|
141
|
+
raise ArgumentError, "unrecognized openssh key type: #{type.inspect}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
123
145
|
def to_okp
|
124
146
|
return [:Ed448, okp]
|
125
147
|
end
|
126
148
|
|
149
|
+
def to_openssh_key(fields)
|
150
|
+
comment = fields['kid'] || ''
|
151
|
+
pk = JOSE::JWA::Curve448.ed448_secret_to_public(okp)
|
152
|
+
sk = okp
|
153
|
+
return JOSE::JWK::OpenSSHKey.to_binary([
|
154
|
+
[
|
155
|
+
[
|
156
|
+
['ssh-ed448', pk],
|
157
|
+
['ssh-ed448', pk, sk, comment]
|
158
|
+
]
|
159
|
+
]
|
160
|
+
])
|
161
|
+
end
|
162
|
+
|
127
163
|
end
|
@@ -103,6 +103,14 @@ class JOSE::JWK::KTY_OKP_Ed448ph < Struct.new(:okp)
|
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
|
+
def verifier(fields)
|
107
|
+
if fields and fields['use'] == 'sig' and not fields['alg'].nil?
|
108
|
+
return [fields['alg']]
|
109
|
+
else
|
110
|
+
return ['Ed448ph']
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
106
114
|
def verify(message, digest_type, signature)
|
107
115
|
raise ArgumentError, "'digest_type' must be :Ed448ph" if digest_type != :Ed448ph
|
108
116
|
pk = okp
|
@@ -120,8 +128,36 @@ class JOSE::JWK::KTY_OKP_Ed448ph < Struct.new(:okp)
|
|
120
128
|
end
|
121
129
|
end
|
122
130
|
|
131
|
+
def self.from_openssh_key(key)
|
132
|
+
type, _, sk, comment = key
|
133
|
+
if type and sk and type == 'ssh-ed448ph' and sk.bytesize == SK_BYTES
|
134
|
+
if comment == '' or comment.nil?
|
135
|
+
return from_okp([:Ed448ph, sk])
|
136
|
+
else
|
137
|
+
kty, fields = from_okp([:Ed448ph, sk])
|
138
|
+
return kty, fields.merge('kid' => comment)
|
139
|
+
end
|
140
|
+
else
|
141
|
+
raise ArgumentError, "unrecognized openssh key type: #{type.inspect}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
123
145
|
def to_okp
|
124
146
|
return [:Ed448ph, okp]
|
125
147
|
end
|
126
148
|
|
149
|
+
def to_openssh_key(fields)
|
150
|
+
comment = fields['kid'] || ''
|
151
|
+
pk = JOSE::JWA::Curve448.ed448ph_secret_to_public(okp)
|
152
|
+
sk = okp
|
153
|
+
return JOSE::JWK::OpenSSHKey.to_binary([
|
154
|
+
[
|
155
|
+
[
|
156
|
+
['ssh-ed448ph', pk],
|
157
|
+
['ssh-ed448ph', pk, sk, comment]
|
158
|
+
]
|
159
|
+
]
|
160
|
+
])
|
161
|
+
end
|
162
|
+
|
127
163
|
end
|
@@ -130,8 +130,36 @@ class JOSE::JWK::KTY_OKP_X25519 < Struct.new(:okp)
|
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
133
|
+
def self.from_openssh_key(key)
|
134
|
+
type, _, sk, comment = key
|
135
|
+
if type and sk and type == 'ssh-x25519' and sk.bytesize == SK_BYTES
|
136
|
+
if comment == '' or comment.nil?
|
137
|
+
return from_okp([:X25519, sk])
|
138
|
+
else
|
139
|
+
kty, fields = from_okp([:X25519, sk])
|
140
|
+
return kty, fields.merge('kid' => comment)
|
141
|
+
end
|
142
|
+
else
|
143
|
+
raise ArgumentError, "unrecognized openssh key type: #{type.inspect}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
133
147
|
def to_okp
|
134
148
|
return [:X25519, okp]
|
135
149
|
end
|
136
150
|
|
151
|
+
def to_openssh_key(fields)
|
152
|
+
comment = fields['kid'] || ''
|
153
|
+
pk = JOSE::JWA::Curve25519.x25519_secret_to_public(okp[0, SECRET_BYTES])
|
154
|
+
sk = okp
|
155
|
+
return JOSE::JWK::OpenSSHKey.to_binary([
|
156
|
+
[
|
157
|
+
[
|
158
|
+
['ssh-x25519', pk],
|
159
|
+
['ssh-x25519', pk, sk, comment]
|
160
|
+
]
|
161
|
+
]
|
162
|
+
])
|
163
|
+
end
|
164
|
+
|
137
165
|
end
|
@@ -130,8 +130,36 @@ class JOSE::JWK::KTY_OKP_X448 < Struct.new(:okp)
|
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
133
|
+
def self.from_openssh_key(key)
|
134
|
+
type, _, sk, comment = key
|
135
|
+
if type and sk and type == 'ssh-x448' and sk.bytesize == SK_BYTES
|
136
|
+
if comment == '' or comment.nil?
|
137
|
+
return from_okp([:X448, sk])
|
138
|
+
else
|
139
|
+
kty, fields = from_okp([:X448, sk])
|
140
|
+
return kty, fields.merge('kid' => comment)
|
141
|
+
end
|
142
|
+
else
|
143
|
+
raise ArgumentError, "unrecognized openssh key type: #{type.inspect}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
133
147
|
def to_okp
|
134
148
|
return [:X448, okp]
|
135
149
|
end
|
136
150
|
|
151
|
+
def to_openssh_key(fields)
|
152
|
+
comment = fields['kid'] || ''
|
153
|
+
pk = JOSE::JWA::Curve448.x448_secret_to_public(okp[0, SECRET_BYTES])
|
154
|
+
sk = okp
|
155
|
+
return JOSE::JWK::OpenSSHKey.to_binary([
|
156
|
+
[
|
157
|
+
[
|
158
|
+
['ssh-x448', pk],
|
159
|
+
['ssh-x448', pk, sk, comment]
|
160
|
+
]
|
161
|
+
]
|
162
|
+
])
|
163
|
+
end
|
164
|
+
|
137
165
|
end
|
data/lib/jose/jwk/kty_rsa.rb
CHANGED
@@ -163,6 +163,14 @@ class JOSE::JWK::KTY_RSA < Struct.new(:key)
|
|
163
163
|
end
|
164
164
|
end
|
165
165
|
|
166
|
+
def verifier(fields)
|
167
|
+
if fields and fields['use'] == 'sig' and not fields['alg'].nil?
|
168
|
+
return [fields['alg']]
|
169
|
+
else
|
170
|
+
return ['PS256', 'PS384', 'PS512', 'RS256', 'RS384', 'RS512']
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
166
174
|
def verify(message, digest_type, signature, padding: :rsa_pkcs1_padding)
|
167
175
|
case padding
|
168
176
|
when :rsa_pkcs1_padding
|
@@ -0,0 +1,278 @@
|
|
1
|
+
module JOSE::JWK::OpenSSHKey
|
2
|
+
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def from_binary(binary, password = nil)
|
6
|
+
return parse_keys(StringIO.new(binary))
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_binary(list, password = nil)
|
10
|
+
return list.flat_map do |key_list|
|
11
|
+
next [
|
12
|
+
"-----BEGIN OPENSSH PRIVATE KEY-----\n",
|
13
|
+
chunk(Base64.encode64(write_keylist(*key_list.transpose)), 70),
|
14
|
+
"-----END OPENSSH PRIVATE KEY-----\n"
|
15
|
+
]
|
16
|
+
end.join
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Internal encode functions
|
22
|
+
|
23
|
+
def chunk(binary, size)
|
24
|
+
return binary.gsub(/[\r\n]/, '').scan(/.{0,#{size}}/).join("\n")
|
25
|
+
end
|
26
|
+
|
27
|
+
def write_keylist(pks, sks)
|
28
|
+
raise ArgumentError, "pk list and sk list lengths do not match" if pks.length != sks.length
|
29
|
+
n = pks.length
|
30
|
+
pk_bin = write_publickeys(pks)
|
31
|
+
sk_bin = write_secretkeys(sks)
|
32
|
+
check = SecureRandom.random_bytes(4)
|
33
|
+
unpadded = [check, check, sk_bin].pack('a4a4a*')
|
34
|
+
padded = add_padding(unpadded, 0)
|
35
|
+
cipher_name = 'none'
|
36
|
+
cipher_name_len = cipher_name.bytesize
|
37
|
+
kdf_name = 'none'
|
38
|
+
kdf_name_len = kdf_name.bytesize
|
39
|
+
kdf_options = ''
|
40
|
+
kdf_options_len = kdf_options.bytesize
|
41
|
+
padded_len = padded.bytesize
|
42
|
+
return [
|
43
|
+
'openssh-key-v1',
|
44
|
+
0x00,
|
45
|
+
cipher_name_len,
|
46
|
+
cipher_name,
|
47
|
+
kdf_name_len,
|
48
|
+
kdf_name,
|
49
|
+
kdf_options_len,
|
50
|
+
kdf_options,
|
51
|
+
n,
|
52
|
+
pk_bin,
|
53
|
+
padded_len,
|
54
|
+
padded
|
55
|
+
].pack("a*CNa#{cipher_name_len}Na#{kdf_name_len}Na#{kdf_options_len}Na*Na#{padded_len}")
|
56
|
+
end
|
57
|
+
|
58
|
+
def write_publickeys(pks)
|
59
|
+
return pks.map do |pk|
|
60
|
+
type, key = pk
|
61
|
+
if type and key
|
62
|
+
type_len = type.bytesize
|
63
|
+
key_len = key.bytesize
|
64
|
+
pk = [
|
65
|
+
type_len,
|
66
|
+
type,
|
67
|
+
key_len,
|
68
|
+
key
|
69
|
+
].pack("Na#{type_len}Na#{key_len}")
|
70
|
+
end
|
71
|
+
pk_size = pk.bytesize
|
72
|
+
next [
|
73
|
+
pk_size,
|
74
|
+
pk
|
75
|
+
].pack("Na#{pk_size}")
|
76
|
+
end.join
|
77
|
+
end
|
78
|
+
|
79
|
+
def write_secretkeys(sks)
|
80
|
+
return sks.map do |(type, pk, sk, comment)|
|
81
|
+
type_len = type.bytesize
|
82
|
+
pk_len = pk.bytesize
|
83
|
+
sk_len = sk.bytesize
|
84
|
+
comment_len = comment.bytesize
|
85
|
+
next [
|
86
|
+
type_len,
|
87
|
+
type,
|
88
|
+
pk_len,
|
89
|
+
pk,
|
90
|
+
sk_len,
|
91
|
+
sk,
|
92
|
+
comment_len,
|
93
|
+
comment
|
94
|
+
].pack("Na#{type_len}Na#{pk_len}Na#{sk_len}Na#{comment_len}")
|
95
|
+
end.join
|
96
|
+
end
|
97
|
+
|
98
|
+
def add_padding(u, p)
|
99
|
+
return add_padding(u, p + 1) if (u.bytesize + p) % 8 != 0
|
100
|
+
return [
|
101
|
+
u,
|
102
|
+
*(1..p).to_a
|
103
|
+
].pack("a*C#{p}")
|
104
|
+
end
|
105
|
+
|
106
|
+
# Internal decode functions
|
107
|
+
|
108
|
+
def parse_keys(buffer, keys = [])
|
109
|
+
pos = buffer.pos
|
110
|
+
chr = buffer.getc
|
111
|
+
if chr.nil?
|
112
|
+
return keys
|
113
|
+
elsif chr == '-'
|
114
|
+
if buffer.read(34) == '----BEGIN OPENSSH PRIVATE KEY-----'
|
115
|
+
key, rest = parse_key(buffer)
|
116
|
+
keys.push(key) if key
|
117
|
+
return parse_keys(rest, keys)
|
118
|
+
else
|
119
|
+
buffer.pos = pos + 1
|
120
|
+
return parse_keys(buffer, keys)
|
121
|
+
end
|
122
|
+
else
|
123
|
+
return parse_keys(buffer, keys)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def parse_key(buffer, body = StringIO.new)
|
128
|
+
pos = buffer.pos
|
129
|
+
chr = buffer.getc
|
130
|
+
if chr.nil?
|
131
|
+
return nil, buffer
|
132
|
+
elsif chr == "\r" or chr == "\n" or chr == "\s" or chr == "\t"
|
133
|
+
return parse_key(buffer, body)
|
134
|
+
elsif chr == '-'
|
135
|
+
if buffer.read(32) == '----END OPENSSH PRIVATE KEY-----'
|
136
|
+
key = parse_key_body(StringIO.new(Base64.decode64(body.string)))
|
137
|
+
return key, buffer
|
138
|
+
else
|
139
|
+
buffer.pos = pos + 1
|
140
|
+
return parse_key(buffer, body)
|
141
|
+
end
|
142
|
+
else
|
143
|
+
body.write(chr)
|
144
|
+
return parse_key(buffer, body)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def parse_key_body(body)
|
149
|
+
pos = body.pos
|
150
|
+
chr = body.getc
|
151
|
+
if chr.nil?
|
152
|
+
return nil
|
153
|
+
elsif chr == 'o' and body.read(13) == 'penssh-key-v1' and body.getbyte == 0
|
154
|
+
if cipher_name_len = body.read(4) and cipher_name_len.bytesize == 4
|
155
|
+
cipher_name_len, = cipher_name_len.unpack('N')
|
156
|
+
if cipher_name = body.read(cipher_name_len) and cipher_name.bytesize == cipher_name_len
|
157
|
+
if kdf_name_len = body.read(4) and kdf_name_len.bytesize == 4
|
158
|
+
kdf_name_len, = kdf_name_len.unpack('N')
|
159
|
+
if kdf_name = body.read(kdf_name_len) and kdf_name.bytesize == kdf_name_len
|
160
|
+
if kdf_options_len = body.read(4) and kdf_options_len.bytesize == 4
|
161
|
+
kdf_options_len, = kdf_options_len.unpack('N')
|
162
|
+
if kdf_options = body.read(kdf_options_len) and kdf_options.bytesize == kdf_options_len
|
163
|
+
if n = body.read(4) and n.bytesize == 4
|
164
|
+
n, = n.unpack('N')
|
165
|
+
pks, enc = parse_publickeys(body, n)
|
166
|
+
return nil if pks.nil?
|
167
|
+
if encrypted_len = enc.read(4) and encrypted_len.bytesize == 4
|
168
|
+
encrypted_len, = encrypted_len.unpack('N')
|
169
|
+
if encrypted = enc.read(encrypted_len) and encrypted.bytesize == encrypted_len
|
170
|
+
header = [cipher_name, kdf_name, kdf_options, n]
|
171
|
+
key = maybe_parse_secretkeys(header, pks, StringIO.new(encrypted))
|
172
|
+
if key
|
173
|
+
return key
|
174
|
+
else
|
175
|
+
return [header, pks, encrypted]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
body.pos = pos + 1
|
188
|
+
return parse_key_body(body)
|
189
|
+
end
|
190
|
+
|
191
|
+
def parse_publickeys(body, n, pks = [])
|
192
|
+
return pks, body if n == 0
|
193
|
+
pos = body.pos
|
194
|
+
if pk_len = body.read(4) and pk_len.bytesize == 4
|
195
|
+
pk_len, = pk_len.unpack('N')
|
196
|
+
if pk = body.read(pk_len) and pk.bytesize == pk_len
|
197
|
+
pk = StringIO.new(pk)
|
198
|
+
if type_len = pk.read(4) and type_len.bytesize == 4
|
199
|
+
type_len, = type_len.unpack('N')
|
200
|
+
if type = pk.read(type_len) and type.bytesize == type_len
|
201
|
+
if key_len = pk.read(4) and key_len.bytesize == 4
|
202
|
+
key_len, = key_len.unpack('N')
|
203
|
+
if key = pk.read(key_len) and key.bytesize == key_len
|
204
|
+
pks.push([type, key])
|
205
|
+
return parse_publickeys(body, n - 1, pks)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
pks.push(pk.string)
|
211
|
+
return parse_publickeys(body, n - 1, pks)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
return nil
|
215
|
+
end
|
216
|
+
|
217
|
+
def maybe_parse_secretkeys(header, pks, encrypted)
|
218
|
+
cipher_name, kdf_name, kdf_options, n = header
|
219
|
+
if cipher_name == 'none' and kdf_name == 'none' and kdf_options == ''
|
220
|
+
if check1 = encrypted.read(4) and check1.bytesize == 4 and
|
221
|
+
check2 = encrypted.read(4) and check2.bytesize == 4 and
|
222
|
+
check1 == check2
|
223
|
+
sks = parse_secretkeys(del_padding(encrypted), n)
|
224
|
+
if sks
|
225
|
+
return pks.zip(sks)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
return nil
|
230
|
+
end
|
231
|
+
|
232
|
+
def del_padding(padded)
|
233
|
+
return StringIO.new if padded.eof?
|
234
|
+
padded = padded.read
|
235
|
+
padding = padded.getbyte(-1)
|
236
|
+
if padding > padded.bytesize
|
237
|
+
return StringIO.new
|
238
|
+
else
|
239
|
+
while padding > 0
|
240
|
+
if padded.getbyte(-1) == padding
|
241
|
+
padded.chop!
|
242
|
+
padding = padding - 1
|
243
|
+
else
|
244
|
+
return StringIO.new
|
245
|
+
end
|
246
|
+
end
|
247
|
+
return StringIO.new(padded)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def parse_secretkeys(buffer, n, sks = [])
|
252
|
+
return sks if n == 0
|
253
|
+
if type_len = buffer.read(4) and type_len.bytesize == 4
|
254
|
+
type_len, = type_len.unpack('N')
|
255
|
+
if type = buffer.read(type_len) and type.bytesize == type_len
|
256
|
+
if pk_len = buffer.read(4) and pk_len.bytesize == 4
|
257
|
+
pk_len, = pk_len.unpack('N')
|
258
|
+
if pk = buffer.read(pk_len) and pk.bytesize == pk_len
|
259
|
+
if sk_len = buffer.read(4) and sk_len.bytesize == 4
|
260
|
+
sk_len, = sk_len.unpack('N')
|
261
|
+
if sk = buffer.read(sk_len) and sk.bytesize == sk_len
|
262
|
+
if comment_len = buffer.read(4) and comment_len.bytesize == 4
|
263
|
+
comment_len, = comment_len.unpack('N')
|
264
|
+
if comment = buffer.read(comment_len) and comment.bytesize == comment_len
|
265
|
+
sks.push([type, pk, sk, comment])
|
266
|
+
return parse_secretkeys(buffer, n - 1, sks)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
return nil
|
276
|
+
end
|
277
|
+
|
278
|
+
end
|