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.
@@ -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