jose 0.3.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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('CA*A*'), 2)
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('xA32A32')
41
+ ec_point.unpack('xa32a32')
42
42
  when 97
43
- ec_point.unpack('xA48A48')
43
+ ec_point.unpack('xa48a48')
44
44
  when 133
45
- ec_point.unpack('xA66A66')
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)
@@ -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
@@ -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