jose 0.3.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|