jose 0.1.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 +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'
|