jose 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +20 -0
- data/.gitignore +9 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +20 -0
- data/LICENSE.txt +373 -0
- data/README.md +41 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/jose.gemspec +36 -0
- data/lib/jose.rb +61 -0
- data/lib/jose/jwa.rb +21 -0
- data/lib/jose/jwa/aes_kw.rb +105 -0
- data/lib/jose/jwa/concat_kdf.rb +50 -0
- data/lib/jose/jwa/pkcs1.rb +269 -0
- data/lib/jose/jwa/pkcs7.rb +23 -0
- data/lib/jose/jwe.rb +290 -0
- data/lib/jose/jwe/alg.rb +12 -0
- data/lib/jose/jwe/alg_aes_gcm_kw.rb +98 -0
- data/lib/jose/jwe/alg_aes_kw.rb +57 -0
- data/lib/jose/jwe/alg_dir.rb +40 -0
- data/lib/jose/jwe/alg_ecdh_es.rb +123 -0
- data/lib/jose/jwe/alg_pbes2.rb +90 -0
- data/lib/jose/jwe/alg_rsa.rb +63 -0
- data/lib/jose/jwe/enc.rb +8 -0
- data/lib/jose/jwe/enc_aes_cbc_hmac.rb +80 -0
- data/lib/jose/jwe/enc_aes_gcm.rb +68 -0
- data/lib/jose/jwe/zip.rb +7 -0
- data/lib/jose/jwe/zip_def.rb +28 -0
- data/lib/jose/jwk.rb +347 -0
- data/lib/jose/jwk/kty.rb +34 -0
- data/lib/jose/jwk/kty_ec.rb +179 -0
- data/lib/jose/jwk/kty_oct.rb +104 -0
- data/lib/jose/jwk/kty_rsa.rb +185 -0
- data/lib/jose/jwk/pem.rb +19 -0
- data/lib/jose/jwk/set.rb +2 -0
- data/lib/jose/jws.rb +242 -0
- data/lib/jose/jws/alg.rb +10 -0
- data/lib/jose/jws/alg_ecdsa.rb +41 -0
- data/lib/jose/jws/alg_hmac.rb +41 -0
- data/lib/jose/jws/alg_rsa_pkcs1_v1_5.rb +41 -0
- data/lib/jose/jws/alg_rsa_pss.rb +41 -0
- data/lib/jose/jwt.rb +145 -0
- data/lib/jose/version.rb +3 -0
- metadata +162 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
class JOSE::JWK::KTY_oct < Struct.new(:oct)
|
2
|
+
|
3
|
+
# JOSE::JWK callbacks
|
4
|
+
|
5
|
+
def self.from_map(fields)
|
6
|
+
if fields['kty'] == 'oct' and fields['k'].is_a?(String)
|
7
|
+
return JOSE::JWK::KTY_oct.new(JOSE.urlsafe_decode64(fields['k'])), fields.except('kty', 'k')
|
8
|
+
else
|
9
|
+
raise ArgumentError, "invalid 'oct' JWK"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_key
|
14
|
+
return oct
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_map(fields)
|
18
|
+
return fields.put('k', JOSE.urlsafe_encode64(oct)).put('kty', 'oct')
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_public_map(fields)
|
22
|
+
return to_map(fields)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_thumbprint_map(fields)
|
26
|
+
return to_public_map(fields).slice('k', 'kty')
|
27
|
+
end
|
28
|
+
|
29
|
+
# JOSE::JWK::KTY callbacks
|
30
|
+
|
31
|
+
def block_encryptor(fields, plain_text)
|
32
|
+
enc = case (oct.bytesize * 8)
|
33
|
+
when 128
|
34
|
+
'A128GCM'
|
35
|
+
when 192
|
36
|
+
'A192GCM'
|
37
|
+
when 256
|
38
|
+
'A256GCM'
|
39
|
+
when 384
|
40
|
+
'A192CBC-HS384'
|
41
|
+
when 512
|
42
|
+
'A256CBC-HS512'
|
43
|
+
else
|
44
|
+
raise ArgumentError, "oct of size #{oct.bytesize * 8} has no default block encryptor"
|
45
|
+
end
|
46
|
+
return JOSE::Map[
|
47
|
+
'alg' => 'dir',
|
48
|
+
'enc' => enc
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
def derive_key
|
53
|
+
return oct
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.generate_key(size)
|
57
|
+
if size.is_a?(Array) and size.length == 2 and size[0] == :oct
|
58
|
+
size = size[1]
|
59
|
+
end
|
60
|
+
case size
|
61
|
+
when Integer
|
62
|
+
return from_oct(SecureRandom.random_bytes(size))
|
63
|
+
else
|
64
|
+
raise ArgumentError, "'size' must be an Integer"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def generate_key(fields)
|
69
|
+
kty, other_fields = JOSE::JWK::KTY_oct.generate_key(oct.bytesize)
|
70
|
+
return kty, fields.delete('kid').merge(other_fields)
|
71
|
+
end
|
72
|
+
|
73
|
+
def key_encryptor(fields, key)
|
74
|
+
return JOSE::JWK::KTY.key_encryptor(self, fields, key)
|
75
|
+
end
|
76
|
+
|
77
|
+
def sign(message, digest_type)
|
78
|
+
return OpenSSL::HMAC.digest(digest_type.new, oct, message)
|
79
|
+
end
|
80
|
+
|
81
|
+
def signer(fields = nil, plain_text = nil)
|
82
|
+
return JOSE::Map['alg' => 'HS256']
|
83
|
+
end
|
84
|
+
|
85
|
+
def verify(message, digest_type, signature)
|
86
|
+
return JOSE::JWA.constant_time_compare(signature, sign(message, digest_type))
|
87
|
+
end
|
88
|
+
|
89
|
+
# API functions
|
90
|
+
|
91
|
+
def self.from_oct(binary)
|
92
|
+
case binary
|
93
|
+
when String
|
94
|
+
return JOSE::JWK::KTY_oct.new(binary), JOSE::Map[]
|
95
|
+
else
|
96
|
+
raise ArgumentError, "'binary' must be a String"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_oct
|
101
|
+
return oct
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
class JOSE::JWK::KTY_RSA < Struct.new(:key)
|
2
|
+
|
3
|
+
# JOSE::JWK callbacks
|
4
|
+
|
5
|
+
def self.from_map(fields)
|
6
|
+
if fields['kty'] == 'RSA' and fields['e'].is_a?(String) and fields['n'].is_a?(String)
|
7
|
+
if fields['oth'].is_a?(Array)
|
8
|
+
raise ArgumentError, "multi-prime RSA keys are not supported"
|
9
|
+
elsif fields['d'].is_a?(String)
|
10
|
+
if fields['dp'].is_a?(String) and fields['dq'].is_a?(String) and fields['p'].is_a?(String) and fields['q'].is_a?(String) and fields['qi'].is_a?(String)
|
11
|
+
rsa = OpenSSL::PKey::RSA.new
|
12
|
+
rsa.d = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['d']), 2)
|
13
|
+
rsa.dmp1 = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['dp']), 2)
|
14
|
+
rsa.dmq1 = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['dq']), 2)
|
15
|
+
rsa.e = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['e']), 2)
|
16
|
+
rsa.n = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['n']), 2)
|
17
|
+
rsa.p = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['p']), 2)
|
18
|
+
rsa.q = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['q']), 2)
|
19
|
+
rsa.iqmp = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['qi']), 2)
|
20
|
+
return JOSE::JWK::KTY_RSA.new(rsa), fields.except('kty', 'd', 'dp', 'dq', 'e', 'n', 'p', 'q', 'qi')
|
21
|
+
else
|
22
|
+
raise ArgumentError, "invalid 'RSA' JWK"
|
23
|
+
end
|
24
|
+
else
|
25
|
+
rsa = OpenSSL::PKey::RSA.new
|
26
|
+
rsa.e = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['e']), 2)
|
27
|
+
rsa.n = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['n']), 2)
|
28
|
+
return JOSE::JWK::KTY_RSA.new(rsa), fields.except('kty', 'e', 'n')
|
29
|
+
end
|
30
|
+
else
|
31
|
+
raise ArgumentError, "invalid 'RSA' JWK"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_key
|
36
|
+
return key
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_map(fields)
|
40
|
+
if key.private?
|
41
|
+
return fields.
|
42
|
+
put('d', JOSE.urlsafe_encode64(key.d.to_s(2))).
|
43
|
+
put('dp', JOSE.urlsafe_encode64(key.dmp1.to_s(2))).
|
44
|
+
put('dq', JOSE.urlsafe_encode64(key.dmq1.to_s(2))).
|
45
|
+
put('e', JOSE.urlsafe_encode64(key.e.to_s(2))).
|
46
|
+
put('kty', 'RSA').
|
47
|
+
put('n', JOSE.urlsafe_encode64(key.n.to_s(2))).
|
48
|
+
put('p', JOSE.urlsafe_encode64(key.p.to_s(2))).
|
49
|
+
put('q', JOSE.urlsafe_encode64(key.q.to_s(2))).
|
50
|
+
put('qi', JOSE.urlsafe_encode64(key.iqmp.to_s(2))).
|
51
|
+
put('q', JOSE.urlsafe_encode64(key.q.to_s(2)))
|
52
|
+
else
|
53
|
+
return fields.
|
54
|
+
put('e', JOSE.urlsafe_encode64(key.e.to_s(2))).
|
55
|
+
put('kty', 'RSA').
|
56
|
+
put('n', JOSE.urlsafe_encode64(key.n.to_s(2)))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_public_map(fields)
|
61
|
+
return to_map(fields).except('d', 'dp', 'dq', 'p', 'q', 'qi', 'oth')
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_thumbprint_map(fields)
|
65
|
+
return to_public_map(fields).slice('e', 'kty', 'n')
|
66
|
+
end
|
67
|
+
|
68
|
+
# JOSE::JWK::KTY callbacks
|
69
|
+
|
70
|
+
def block_encryptor(fields, plain_text)
|
71
|
+
return JOSE::Map[
|
72
|
+
'alg' => 'RSA-OAEP',
|
73
|
+
'enc' => 'A128GCM'
|
74
|
+
]
|
75
|
+
end
|
76
|
+
|
77
|
+
def decrypt_private(cipher_text, rsa_padding: :rsa_pkcs1_padding, rsa_oaep_md: nil)
|
78
|
+
case rsa_padding
|
79
|
+
when :rsa_pkcs1_padding
|
80
|
+
return key.private_decrypt(cipher_text, OpenSSL::PKey::RSA::PKCS1_PADDING)
|
81
|
+
when :rsa_pkcs1_oaep_padding
|
82
|
+
rsa_oaep_md ||= OpenSSL::Digest::SHA1
|
83
|
+
if rsa_oaep_md == OpenSSL::Digest::SHA1
|
84
|
+
return key.private_decrypt(cipher_text, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
|
85
|
+
elsif rsa_oaep_md == OpenSSL::Digest::SHA256
|
86
|
+
return JOSE::JWA::PKCS1.rsaes_oaep_decrypt(rsa_oaep_md, cipher_text, key)
|
87
|
+
else
|
88
|
+
raise ArgumentError, "unsupported RSA OAEP md: #{rsa_oaep_md.inspect}"
|
89
|
+
end
|
90
|
+
else
|
91
|
+
raise ArgumentError, "unsupported RSA padding: #{rsa_padding.inspect}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def encrypt_public(plain_text, rsa_padding: :rsa_pkcs1_padding, rsa_oaep_md: nil)
|
96
|
+
case rsa_padding
|
97
|
+
when :rsa_pkcs1_padding
|
98
|
+
return key.public_encrypt(plain_text, OpenSSL::PKey::RSA::PKCS1_PADDING)
|
99
|
+
when :rsa_pkcs1_oaep_padding
|
100
|
+
rsa_oaep_md ||= OpenSSL::Digest::SHA1
|
101
|
+
if rsa_oaep_md == OpenSSL::Digest::SHA1
|
102
|
+
return key.public_encrypt(plain_text, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
|
103
|
+
elsif rsa_oaep_md == OpenSSL::Digest::SHA256
|
104
|
+
return JOSE::JWA::PKCS1.rsaes_oaep_encrypt(rsa_oaep_md, plain_text, key)
|
105
|
+
else
|
106
|
+
raise ArgumentError, "unsupported RSA OAEP md: #{rsa_oaep_md.inspect}"
|
107
|
+
end
|
108
|
+
else
|
109
|
+
raise ArgumentError, "unsupported RSA padding: #{rsa_padding.inspect}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.generate_key(modulus_size, exponent_size = nil)
|
114
|
+
if modulus_size.is_a?(Array)
|
115
|
+
if modulus_size.length == 2 and modulus_size[0] == :rsa
|
116
|
+
modulus_size = modulus_size[1]
|
117
|
+
elsif modulus_size.length == 3 and modulus_size[0] == :rsa
|
118
|
+
exponent_size = modulus_size[2]
|
119
|
+
modulus_size = modulus_size[1]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
if modulus_size.is_a?(Integer) and (exponent_size.nil? or exponent_size.is_a?(Integer))
|
123
|
+
return from_key(OpenSSL::PKey::RSA.generate(modulus_size, exponent_size))
|
124
|
+
else
|
125
|
+
raise ArgumentError, "'modulus_size' must be an Integer and 'exponent_size' must be nil or an Integer"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def generate_key(fields)
|
130
|
+
kty, other_fields = JOSE::JWK::KTY_RSA.generate_key([:rsa, key.n.num_bits, key.e.to_i])
|
131
|
+
return kty, fields.delete('kid').merge(other_fields)
|
132
|
+
end
|
133
|
+
|
134
|
+
def key_encryptor(fields, key)
|
135
|
+
return JOSE::JWK::KTY.key_encryptor(self, fields, key)
|
136
|
+
end
|
137
|
+
|
138
|
+
def sign(message, digest_type, padding: :rsa_pkcs1_padding)
|
139
|
+
case padding
|
140
|
+
when :rsa_pkcs1_padding
|
141
|
+
return key.sign(digest_type.new, message)
|
142
|
+
when :rsa_pkcs1_pss_padding
|
143
|
+
return JOSE::JWA::PKCS1.rsassa_pss_sign(digest_type, message, key)
|
144
|
+
else
|
145
|
+
raise ArgumentError, "unsupported RSA padding: #{padding.inspect}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def signer(fields = nil, plain_text = nil)
|
150
|
+
if key.private?
|
151
|
+
return JOSE::Map['alg' => 'RS256']
|
152
|
+
else
|
153
|
+
raise ArgumentError, "signing not supported for public keys"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def verify(message, digest_type, signature, padding: :rsa_pkcs1_padding)
|
158
|
+
case padding
|
159
|
+
when :rsa_pkcs1_padding
|
160
|
+
return key.verify(digest_type.new, signature, message)
|
161
|
+
when :rsa_pkcs1_pss_padding
|
162
|
+
return JOSE::JWA::PKCS1.rsassa_pss_verify(digest_type, message, signature, key)
|
163
|
+
else
|
164
|
+
raise ArgumentError, "unsupported RSA padding: #{padding.inspect}"
|
165
|
+
end
|
166
|
+
rescue OpenSSL::PKey::PKeyError # jruby raises this error if the signature is invalid
|
167
|
+
false
|
168
|
+
end
|
169
|
+
|
170
|
+
# API functions
|
171
|
+
|
172
|
+
def self.from_key(key)
|
173
|
+
case key
|
174
|
+
when OpenSSL::PKey::RSA
|
175
|
+
return JOSE::JWK::KTY_RSA.new(key), JOSE::Map[]
|
176
|
+
else
|
177
|
+
raise ArgumentError, "'key' must be a OpenSSL::PKey::RSA"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def to_pem(password = nil)
|
182
|
+
return JOSE::JWK::PEM.to_binary(key, password)
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
data/lib/jose/jwk/pem.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module JOSE::JWK::PEM
|
2
|
+
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def from_binary(object, password = nil)
|
6
|
+
pkey = OpenSSL::PKey.read(object, password)
|
7
|
+
return JOSE::JWK::KTY.from_key(pkey)
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_binary(key, password = nil)
|
11
|
+
if password
|
12
|
+
cipher = OpenSSL::Cipher.new('DES-EDE3-CBC')
|
13
|
+
return key.to_pem(cipher, password)
|
14
|
+
else
|
15
|
+
return key.to_pem
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/lib/jose/jwk/set.rb
ADDED
data/lib/jose/jws.rb
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
module JOSE
|
2
|
+
|
3
|
+
class SignedBinary < ::String
|
4
|
+
def expand
|
5
|
+
return JOSE::JWS.expand(self)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class SignedMap < JOSE::Map
|
10
|
+
def compact
|
11
|
+
return JOSE::JWS.compact(self)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class JWS < Struct.new(:alg, :b64, :fields)
|
16
|
+
|
17
|
+
# Decode API
|
18
|
+
|
19
|
+
def self.from(object, modules = {})
|
20
|
+
case object
|
21
|
+
when JOSE::Map, Hash
|
22
|
+
return from_map(object, modules)
|
23
|
+
when String
|
24
|
+
return from_binary(object, modules)
|
25
|
+
when JOSE::JWS
|
26
|
+
return object
|
27
|
+
else
|
28
|
+
raise ArgumentError, "'object' must be a Hash, String, or JOSE::JWS"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.from_binary(object, modules = {})
|
33
|
+
case object
|
34
|
+
when String
|
35
|
+
return from_map(JOSE.decode(object), modules)
|
36
|
+
else
|
37
|
+
raise ArgumentError, "'object' must be a String"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.from_file(file, modules = {})
|
42
|
+
return from_binary(File.binread(file), modules)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.from_map(object, modules = {})
|
46
|
+
case object
|
47
|
+
when JOSE::Map, Hash
|
48
|
+
return from_fields(JOSE::JWS.new(nil, nil, JOSE::Map.new(object)), modules)
|
49
|
+
else
|
50
|
+
raise ArgumentError, "'object' must be a Hash"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Encode API
|
55
|
+
|
56
|
+
def self.to_binary(jws)
|
57
|
+
return from(jws).to_binary
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_binary
|
61
|
+
return JOSE.encode(to_map)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.to_file(jws, file)
|
65
|
+
return from(jws).to_file(file)
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_file(file)
|
69
|
+
return File.binwrite(file, to_binary)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.to_map(jws)
|
73
|
+
return from(jws).to_map
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_map
|
77
|
+
return alg.to_map(fields)
|
78
|
+
end
|
79
|
+
|
80
|
+
# API
|
81
|
+
|
82
|
+
def self.compact(map)
|
83
|
+
if map.is_a?(Hash) or map.is_a?(JOSE::Map)
|
84
|
+
return JOSE::SignedBinary.new([
|
85
|
+
map['protected'] || '',
|
86
|
+
'.',
|
87
|
+
map['payload'] || '',
|
88
|
+
'.',
|
89
|
+
map['signature'] || ''
|
90
|
+
].join)
|
91
|
+
else
|
92
|
+
raise ArgumentError, "'map' must be a Hash or a JOSE::Map"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.expand(binary)
|
97
|
+
if binary.is_a?(String)
|
98
|
+
parts = binary.split('.')
|
99
|
+
if parts.length == 3
|
100
|
+
protected_binary, payload, signature = parts
|
101
|
+
return JOSE::SignedMap[
|
102
|
+
'payload' => payload,
|
103
|
+
'protected' => protected_binary,
|
104
|
+
'signature' => signature
|
105
|
+
]
|
106
|
+
else
|
107
|
+
raise ArgumentError, "'binary' is not a valid signed String"
|
108
|
+
end
|
109
|
+
else
|
110
|
+
raise ArgumentError, "'binary' must be a String"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.peek_payload(signed)
|
115
|
+
if signed.is_a?(String)
|
116
|
+
signed = expand(signed)
|
117
|
+
end
|
118
|
+
return JOSE.urlsafe_decode64(signed['payload'])
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.peek_protected(signed)
|
122
|
+
if signed.is_a?(String)
|
123
|
+
signed = expand(signed)
|
124
|
+
end
|
125
|
+
return JOSE::Map.new(JOSE.decode(JOSE.urlsafe_decode64(signed['protected'])))
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.sign(key, plain_text, jws, header = nil)
|
129
|
+
return from(jws).sign(key, plain_text, header)
|
130
|
+
end
|
131
|
+
|
132
|
+
def sign(key, plain_text, header = nil)
|
133
|
+
protected_binary = JOSE.urlsafe_encode64(to_binary)
|
134
|
+
payload = JOSE.urlsafe_encode64(plain_text)
|
135
|
+
signing_input = signing_input(plain_text, protected_binary)
|
136
|
+
signature = JOSE.urlsafe_encode64(alg.sign(key, signing_input))
|
137
|
+
return signature_to_map(payload, protected_binary, header, key, signature)
|
138
|
+
end
|
139
|
+
|
140
|
+
# See https://tools.ietf.org/html/draft-ietf-jose-jws-signing-input-options-04
|
141
|
+
def signing_input(payload, protected_binary = JOSE.urlsafe_encode64(to_binary))
|
142
|
+
if b64 == true or b64.nil?
|
143
|
+
payload = JOSE.urlsafe_encode64(payload)
|
144
|
+
end
|
145
|
+
return [protected_binary, '.', payload].join
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.verify(key, signed)
|
149
|
+
if signed.is_a?(String)
|
150
|
+
signed = JOSE::JWS.expand(signed)
|
151
|
+
end
|
152
|
+
if signed.is_a?(Hash)
|
153
|
+
signed = JOSE::SignedMap.new(signed)
|
154
|
+
end
|
155
|
+
if signed.is_a?(JOSE::Map) and signed['payload'].is_a?(String) and signed['protected'].is_a?(String) and signed['signature'].is_a?(String)
|
156
|
+
jws = from_binary(JOSE.urlsafe_decode64(signed['protected']))
|
157
|
+
signature = JOSE.urlsafe_decode64(signed['signature'])
|
158
|
+
plain_text = JOSE.urlsafe_decode64(signed['payload'])
|
159
|
+
return jws.verify(key, plain_text, signature, signed['protected'])
|
160
|
+
else
|
161
|
+
raise ArgumentError, "'signed' is not a valid signed String, Hash, or JOSE::Map"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def verify(key, plain_text, signature, protected_binary = JOSE.urlsafe_encode64(to_binary))
|
166
|
+
payload = JOSE.urlsafe_encode64(plain_text)
|
167
|
+
signing_input = signing_input(plain_text, protected_binary)
|
168
|
+
return alg.verify(key, signing_input, signature), plain_text, self
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.verify_strict(key, allow, signed)
|
172
|
+
if signed.is_a?(String)
|
173
|
+
signed = JOSE::JWS.expand(signed)
|
174
|
+
end
|
175
|
+
if signed.is_a?(Hash)
|
176
|
+
signed = JOSE::SignedMap.new(signed)
|
177
|
+
end
|
178
|
+
if signed.is_a?(JOSE::Map) and signed['payload'].is_a?(String) and signed['protected'].is_a?(String) and signed['signature'].is_a?(String)
|
179
|
+
protected_map = JOSE.decode(JOSE.urlsafe_decode64(signed['protected']))
|
180
|
+
plain_text = JOSE.urlsafe_decode64(signed['payload'])
|
181
|
+
if allow.member?(protected_map['alg'])
|
182
|
+
jws = from_map(protected_map)
|
183
|
+
signature = JOSE.urlsafe_decode64(signed['signature'])
|
184
|
+
return jws.verify(key, plain_text, signature, signed['protected'])
|
185
|
+
else
|
186
|
+
return false, plain_text, protected_map
|
187
|
+
end
|
188
|
+
else
|
189
|
+
raise ArgumentError, "'signed' is not a valid signed String, Hash, or JOSE::Map"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def self.from_fields(jws, modules)
|
196
|
+
if jws.fields.has_key?('b64')
|
197
|
+
jws.b64 = jws.fields['b64']
|
198
|
+
jws.fields = jws.fields.delete('b64')
|
199
|
+
return from_fields(jws, modules)
|
200
|
+
elsif jws.fields.has_key?('alg') and jws.fields['alg'].is_a?(String)
|
201
|
+
alg = modules[:alg] || case
|
202
|
+
when jws.fields['alg'].start_with?('ES')
|
203
|
+
JOSE::JWS::ALG_ECDSA
|
204
|
+
when jws.fields['alg'].start_with?('HS')
|
205
|
+
JOSE::JWS::ALG_HMAC
|
206
|
+
when jws.fields['alg'].start_with?('PS')
|
207
|
+
JOSE::JWS::ALG_RSA_PSS
|
208
|
+
when jws.fields['alg'].start_with?('RS')
|
209
|
+
JOSE::JWS::ALG_RSA_PKCS1_V1_5
|
210
|
+
when jws.fields['alg'] == 'none'
|
211
|
+
JOSE::JWS::ALG_none
|
212
|
+
else
|
213
|
+
raise ArgumentError, "unknown 'alg': #{jws.fields['alg'].inspect}"
|
214
|
+
end
|
215
|
+
jws.alg, jws.fields = alg.from_map(jws.fields)
|
216
|
+
return from_fields(jws, modules)
|
217
|
+
elsif jws.alg.nil?
|
218
|
+
raise ArgumentError, "missing required keys: 'alg'"
|
219
|
+
else
|
220
|
+
return jws
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def signature_to_map(payload, protected_binary, header, key, signature)
|
225
|
+
if header and header.is_a?(Hash)
|
226
|
+
header = JOSE::Map.new(header)
|
227
|
+
end
|
228
|
+
header ||= JOSE::Map[]
|
229
|
+
if key.is_a?(JOSE::JWK) and key.fields['kid'].is_a?(String)
|
230
|
+
header = header.put('kid', key.fields['kid'])
|
231
|
+
end
|
232
|
+
if header.size == 0
|
233
|
+
return JOSE::SignedMap['payload' => payload, 'protected' => protected_binary, 'signature' => signature]
|
234
|
+
else
|
235
|
+
return JOSE::SignedMap['header' => header.to_hash, 'payload' => payload, 'protected' => protected_binary, 'signature' => signature]
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
require 'jose/jws/alg'
|