jwt 2.5.0 → 2.7.1

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.
data/lib/jwt/algos/ps.rb CHANGED
@@ -9,24 +9,22 @@ module JWT
9
9
 
10
10
  SUPPORTED = %w[PS256 PS384 PS512].freeze
11
11
 
12
- def sign(to_sign)
12
+ def sign(algorithm, msg, key)
13
13
  require_openssl!
14
14
 
15
- algorithm, msg, key = to_sign.values
16
-
17
- key_class = key.class
18
-
19
- raise EncodeError, "The given key is a #{key_class}. It has to be an OpenSSL::PKey::RSA instance." if key_class == String
15
+ raise EncodeError, "The given key is a #{key_class}. It has to be an OpenSSL::PKey::RSA instance." if key.is_a?(String)
20
16
 
21
17
  translated_algorithm = algorithm.sub('PS', 'sha')
22
18
 
23
19
  key.sign_pss(translated_algorithm, msg, salt_length: :digest, mgf1_hash: translated_algorithm)
24
20
  end
25
21
 
26
- def verify(to_verify)
22
+ def verify(algorithm, public_key, signing_input, signature)
27
23
  require_openssl!
28
-
29
- SecurityUtils.verify_ps(to_verify.algorithm, to_verify.public_key, to_verify.signing_input, to_verify.signature)
24
+ translated_algorithm = algorithm.sub('PS', 'sha')
25
+ public_key.verify_pss(translated_algorithm, signature, signing_input, salt_length: :auto, mgf1_hash: translated_algorithm)
26
+ rescue OpenSSL::PKey::PKeyError
27
+ raise JWT::VerificationError, 'Signature verification raised'
30
28
  end
31
29
 
32
30
  def require_openssl!
data/lib/jwt/algos/rsa.rb CHANGED
@@ -7,15 +7,16 @@ module JWT
7
7
 
8
8
  SUPPORTED = %w[RS256 RS384 RS512].freeze
9
9
 
10
- def sign(to_sign)
11
- algorithm, msg, key = to_sign.values
10
+ def sign(algorithm, msg, key)
12
11
  raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance." if key.instance_of?(String)
13
12
 
14
13
  key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
15
14
  end
16
15
 
17
- def verify(to_verify)
18
- SecurityUtils.verify_rsa(to_verify.algorithm, to_verify.public_key, to_verify.signing_input, to_verify.signature)
16
+ def verify(algorithm, public_key, signing_input, signature)
17
+ public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
18
+ rescue OpenSSL::PKey::PKeyError
19
+ raise JWT::VerificationError, 'Signature verification raised'
19
20
  end
20
21
  end
21
22
  end
data/lib/jwt/algos.rb CHANGED
@@ -1,5 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ begin
4
+ require 'rbnacl'
5
+ rescue LoadError
6
+ raise if defined?(RbNaCl)
7
+ end
8
+ require 'openssl'
9
+
3
10
  require 'jwt/algos/hmac'
4
11
  require 'jwt/algos/eddsa'
5
12
  require 'jwt/algos/ecdsa'
@@ -7,35 +14,50 @@ require 'jwt/algos/rsa'
7
14
  require 'jwt/algos/ps'
8
15
  require 'jwt/algos/none'
9
16
  require 'jwt/algos/unsupported'
17
+ require 'jwt/algos/algo_wrapper'
10
18
 
11
- # JWT::Signature module
12
19
  module JWT
13
- # Signature logic for JWT
14
20
  module Algos
15
21
  extend self
16
22
 
17
- ALGOS = [
18
- Algos::Hmac,
19
- Algos::Ecdsa,
20
- Algos::Rsa,
21
- Algos::Eddsa,
22
- Algos::Ps,
23
- Algos::None,
24
- Algos::Unsupported
25
- ].freeze
23
+ ALGOS = [Algos::Ecdsa,
24
+ Algos::Rsa,
25
+ Algos::Eddsa,
26
+ Algos::Ps,
27
+ Algos::None,
28
+ Algos::Unsupported].tap do |l|
29
+ if ::JWT.rbnacl_6_or_greater?
30
+ require_relative 'algos/hmac_rbnacl'
31
+ l.unshift(Algos::HmacRbNaCl)
32
+ elsif ::JWT.rbnacl?
33
+ require_relative 'algos/hmac_rbnacl_fixed'
34
+ l.unshift(Algos::HmacRbNaClFixed)
35
+ else
36
+ l.unshift(Algos::Hmac)
37
+ end
38
+ end.freeze
26
39
 
27
40
  def find(algorithm)
28
41
  indexed[algorithm && algorithm.downcase]
29
42
  end
30
43
 
44
+ def create(algorithm)
45
+ Algos::AlgoWrapper.new(*find(algorithm))
46
+ end
47
+
48
+ def implementation?(algorithm)
49
+ (algorithm.respond_to?(:valid_alg?) && algorithm.respond_to?(:verify)) ||
50
+ (algorithm.respond_to?(:alg) && algorithm.respond_to?(:sign))
51
+ end
52
+
31
53
  private
32
54
 
33
55
  def indexed
34
56
  @indexed ||= begin
35
- fallback = [Algos::Unsupported, nil]
36
- ALGOS.each_with_object(Hash.new(fallback)) do |alg, hash|
37
- alg.const_get(:SUPPORTED).each do |code|
38
- hash[code.downcase] = [alg, code]
57
+ fallback = [nil, Algos::Unsupported]
58
+ ALGOS.each_with_object(Hash.new(fallback)) do |cls, hash|
59
+ cls.const_get(:SUPPORTED).each do |alg|
60
+ hash[alg.downcase] = [alg, cls]
39
61
  end
40
62
  end
41
63
  end
data/lib/jwt/decode.rb CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  require 'json'
4
4
 
5
- require 'jwt/signature'
6
5
  require 'jwt/verify'
7
6
  require 'jwt/x5c_key_finder'
7
+
8
8
  # JWT::Decode module
9
9
  module JWT
10
10
  # Decoding logic for JWT
@@ -24,7 +24,7 @@ module JWT
24
24
  def decode_segments
25
25
  validate_segment_count!
26
26
  if @verify
27
- decode_crypto
27
+ decode_signature
28
28
  verify_algo
29
29
  set_key
30
30
  verify_signature
@@ -51,40 +51,63 @@ module JWT
51
51
 
52
52
  def verify_algo
53
53
  raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') if allowed_algorithms.empty?
54
- raise(JWT::IncorrectAlgorithm, 'Token is missing alg header') unless algorithm
55
- raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless options_includes_algo_in_header?
54
+ raise(JWT::IncorrectAlgorithm, 'Token is missing alg header') unless alg_in_header
55
+ raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') if allowed_and_valid_algorithms.empty?
56
56
  end
57
57
 
58
58
  def set_key
59
59
  @key = find_key(&@keyfinder) if @keyfinder
60
- @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks]).key_for(header['kid']) if @options[:jwks]
60
+ @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks], allow_nil_kid: @options[:allow_nil_kid]).key_for(header['kid']) if @options[:jwks]
61
61
  if (x5c_options = @options[:x5c])
62
62
  @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
63
63
  end
64
64
  end
65
65
 
66
66
  def verify_signature_for?(key)
67
- Signature.verify(algorithm, key, signing_input, @signature)
67
+ allowed_and_valid_algorithms.any? do |alg|
68
+ alg.verify(data: signing_input, signature: @signature, verification_key: key)
69
+ end
70
+ end
71
+
72
+ def allowed_and_valid_algorithms
73
+ @allowed_and_valid_algorithms ||= allowed_algorithms.select { |alg| alg.valid_alg?(alg_in_header) }
68
74
  end
69
75
 
70
- def options_includes_algo_in_header?
71
- allowed_algorithms.any? { |alg| alg.casecmp(algorithm).zero? }
76
+ # Order is very important - first check for string keys, next for symbols
77
+ ALGORITHM_KEYS = ['algorithm',
78
+ :algorithm,
79
+ 'algorithms',
80
+ :algorithms].freeze
81
+
82
+ def given_algorithms
83
+ ALGORITHM_KEYS.each do |alg_key|
84
+ alg = @options[alg_key]
85
+ return Array(alg) if alg
86
+ end
87
+ []
72
88
  end
73
89
 
74
90
  def allowed_algorithms
75
- # Order is very important - first check for string keys, next for symbols
76
- algos = if @options.key?('algorithm')
77
- @options['algorithm']
78
- elsif @options.key?(:algorithm)
79
- @options[:algorithm]
80
- elsif @options.key?('algorithms')
81
- @options['algorithms']
82
- elsif @options.key?(:algorithms)
83
- @options[:algorithms]
84
- else
85
- []
91
+ @allowed_algorithms ||= resolve_allowed_algorithms
92
+ end
93
+
94
+ def resolve_allowed_algorithms
95
+ algs = given_algorithms.map do |alg|
96
+ if Algos.implementation?(alg)
97
+ alg
98
+ else
99
+ Algos.create(alg)
100
+ end
86
101
  end
87
- Array(algos)
102
+
103
+ sort_by_alg_header(algs)
104
+ end
105
+
106
+ # Move algorithms matching the JWT alg header to the beginning of the list
107
+ def sort_by_alg_header(algs)
108
+ return algs if algs.size <= 1
109
+
110
+ algs.partition { |alg| alg.valid_alg?(alg_in_header) }.flatten
88
111
  end
89
112
 
90
113
  def find_key(&keyfinder)
@@ -113,14 +136,14 @@ module JWT
113
136
  end
114
137
 
115
138
  def none_algorithm?
116
- algorithm == 'none'
139
+ alg_in_header == 'none'
117
140
  end
118
141
 
119
- def decode_crypto
142
+ def decode_signature
120
143
  @signature = ::JWT::Base64.url_decode(@segments[2] || '')
121
144
  end
122
145
 
123
- def algorithm
146
+ def alg_in_header
124
147
  header['alg']
125
148
  end
126
149
 
data/lib/jwt/encode.rb CHANGED
@@ -1,28 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './algos'
4
- require_relative './claims_validator'
3
+ require_relative 'algos'
4
+ require_relative 'claims_validator'
5
5
 
6
6
  # JWT::Encode module
7
7
  module JWT
8
8
  # Encoding logic for JWT
9
9
  class Encode
10
- ALG_NONE = 'none'
11
- ALG_KEY = 'alg'
10
+ ALG_KEY = 'alg'
12
11
 
13
12
  def initialize(options)
14
- @payload = options[:payload]
15
- @key = options[:key]
16
- _, @algorithm = Algos.find(options[:algorithm])
17
- @headers = options[:headers].transform_keys(&:to_s)
13
+ @payload = options[:payload]
14
+ @key = options[:key]
15
+ @algorithm = resolve_algorithm(options[:algorithm])
16
+ @headers = options[:headers].transform_keys(&:to_s)
17
+ @headers[ALG_KEY] = @algorithm.alg
18
18
  end
19
19
 
20
20
  def segments
21
- @segments ||= combine(encoded_header_and_payload, encoded_signature)
21
+ validate_claims!
22
+ combine(encoded_header_and_payload, encoded_signature)
22
23
  end
23
24
 
24
25
  private
25
26
 
27
+ def resolve_algorithm(algorithm)
28
+ return algorithm if Algos.implementation?(algorithm)
29
+
30
+ Algos.create(algorithm)
31
+ end
32
+
26
33
  def encoded_header
27
34
  @encoded_header ||= encode_header
28
35
  end
@@ -40,25 +47,28 @@ module JWT
40
47
  end
41
48
 
42
49
  def encode_header
43
- @headers[ALG_KEY] = @algorithm
44
- encode(@headers)
50
+ encode_data(@headers)
45
51
  end
46
52
 
47
53
  def encode_payload
48
- if @payload.is_a?(Hash)
49
- ClaimsValidator.new(@payload).validate!
50
- end
54
+ encode_data(@payload)
55
+ end
51
56
 
52
- encode(@payload)
57
+ def signature
58
+ @algorithm.sign(data: encoded_header_and_payload, signing_key: @key)
53
59
  end
54
60
 
55
- def encode_signature
56
- return '' if @algorithm == ALG_NONE
61
+ def validate_claims!
62
+ return unless @payload.is_a?(Hash)
63
+
64
+ ClaimsValidator.new(@payload).validate!
65
+ end
57
66
 
58
- ::JWT::Base64.url_encode(JWT::Signature.sign(@algorithm, encoded_header_and_payload, @key))
67
+ def encode_signature
68
+ ::JWT::Base64.url_encode(signature)
59
69
  end
60
70
 
61
- def encode(data)
71
+ def encode_data(data)
62
72
  ::JWT::Base64.url_encode(JWT::JSON.generate(data))
63
73
  end
64
74
 
data/lib/jwt/jwk/ec.rb CHANGED
@@ -5,59 +5,96 @@ require 'forwardable'
5
5
  module JWT
6
6
  module JWK
7
7
  class EC < KeyBase # rubocop:disable Metrics/ClassLength
8
- extend Forwardable
9
- def_delegators :keypair, :public_key
10
-
11
8
  KTY = 'EC'
12
- KTYS = [KTY, OpenSSL::PKey::EC].freeze
9
+ KTYS = [KTY, OpenSSL::PKey::EC, JWT::JWK::EC].freeze
13
10
  BINARY = 2
11
+ EC_PUBLIC_KEY_ELEMENTS = %i[kty crv x y].freeze
12
+ EC_PRIVATE_KEY_ELEMENTS = %i[d].freeze
13
+ EC_KEY_ELEMENTS = (EC_PRIVATE_KEY_ELEMENTS + EC_PUBLIC_KEY_ELEMENTS).freeze
14
+
15
+ def initialize(key, params = nil, options = {})
16
+ params ||= {}
14
17
 
15
- attr_reader :keypair
18
+ # For backwards compatibility when kid was a String
19
+ params = { kid: params } if params.is_a?(String)
16
20
 
17
- def initialize(keypair, options = {})
18
- raise ArgumentError, 'keypair must be of type OpenSSL::PKey::EC' unless keypair.is_a?(OpenSSL::PKey::EC)
21
+ key_params = extract_key_params(key)
19
22
 
20
- @keypair = keypair
23
+ params = params.transform_keys(&:to_sym)
24
+ check_jwk_params!(key_params, params)
25
+
26
+ super(options, key_params.merge(params))
27
+ end
21
28
 
22
- super(options)
29
+ def keypair
30
+ ec_key
23
31
  end
24
32
 
25
33
  def private?
26
- @keypair.private_key?
34
+ ec_key.private_key?
27
35
  end
28
36
 
29
- def members
30
- crv, x_octets, y_octets = keypair_components(keypair)
31
- {
32
- kty: KTY,
33
- crv: crv,
34
- x: encode_octets(x_octets),
35
- y: encode_octets(y_octets)
36
- }
37
+ def signing_key
38
+ ec_key
37
39
  end
38
40
 
39
- def export(options = {})
40
- exported_hash = members.merge(kid: kid)
41
+ def verify_key
42
+ ec_key
43
+ end
41
44
 
42
- return exported_hash unless private? && options[:include_private] == true
45
+ def public_key
46
+ ec_key
47
+ end
48
+
49
+ def members
50
+ EC_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
51
+ end
43
52
 
44
- append_private_parts(exported_hash)
53
+ def export(options = {})
54
+ exported = parameters.clone
55
+ exported.reject! { |k, _| EC_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
56
+ exported
45
57
  end
46
58
 
47
59
  def key_digest
48
- _crv, x_octets, y_octets = keypair_components(keypair)
60
+ _crv, x_octets, y_octets = keypair_components(ec_key)
49
61
  sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
50
62
  OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
51
63
  OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
52
64
  end
53
65
 
66
+ def []=(key, value)
67
+ if EC_KEY_ELEMENTS.include?(key.to_sym)
68
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes'
69
+ end
70
+
71
+ super(key, value)
72
+ end
73
+
54
74
  private
55
75
 
56
- def append_private_parts(the_hash)
57
- octets = keypair.private_key.to_bn.to_s(BINARY)
58
- the_hash.merge(
59
- d: encode_octets(octets)
60
- )
76
+ def ec_key
77
+ @ec_key ||= create_ec_key(self[:crv], self[:x], self[:y], self[:d])
78
+ end
79
+
80
+ def extract_key_params(key)
81
+ case key
82
+ when JWT::JWK::EC
83
+ key.export(include_private: true)
84
+ when OpenSSL::PKey::EC # Accept OpenSSL key as input
85
+ @ec_key = key # Preserve the object to avoid recreation
86
+ parse_ec_key(key)
87
+ when Hash
88
+ key.transform_keys(&:to_sym)
89
+ else
90
+ raise ArgumentError, 'key must be of type OpenSSL::PKey::EC or Hash with key parameters'
91
+ end
92
+ end
93
+
94
+ def check_jwk_params!(key_params, params)
95
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (EC_KEY_ELEMENTS & params.keys).empty?
96
+ raise JWT::JWKError, "Incorrect 'kty' value: #{key_params[:kty]}, expected #{KTY}" unless key_params[:kty] == KTY
97
+ raise JWT::JWKError, 'Key format is invalid for EC' unless key_params[:crv] && key_params[:x] && key_params[:y]
61
98
  end
62
99
 
63
100
  def keypair_components(ec_keypair)
@@ -82,6 +119,8 @@ module JWT
82
119
  end
83
120
 
84
121
  def encode_octets(octets)
122
+ return unless octets
123
+
85
124
  ::JWT::Base64.url_encode(octets)
86
125
  end
87
126
 
@@ -89,15 +128,94 @@ module JWT
89
128
  ::JWT::Base64.url_encode(key_part.to_s(BINARY))
90
129
  end
91
130
 
92
- class << self
93
- def import(jwk_data)
94
- # See https://tools.ietf.org/html/rfc7518#section-6.2.1 for an
95
- # explanation of the relevant parameters.
131
+ def parse_ec_key(key)
132
+ crv, x_octets, y_octets = keypair_components(key)
133
+ octets = key.private_key&.to_bn&.to_s(BINARY)
134
+ {
135
+ kty: KTY,
136
+ crv: crv,
137
+ x: encode_octets(x_octets),
138
+ y: encode_octets(y_octets),
139
+ d: encode_octets(octets)
140
+ }.compact
141
+ end
142
+
143
+ if ::JWT.openssl_3?
144
+ def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d) # rubocop:disable Metrics/MethodLength
145
+ curve = EC.to_openssl_curve(jwk_crv)
146
+
147
+ x_octets = decode_octets(jwk_x)
148
+ y_octets = decode_octets(jwk_y)
149
+
150
+ point = OpenSSL::PKey::EC::Point.new(
151
+ OpenSSL::PKey::EC::Group.new(curve),
152
+ OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
153
+ )
154
+
155
+ sequence = if jwk_d
156
+ # https://datatracker.ietf.org/doc/html/rfc5915.html
157
+ # ECPrivateKey ::= SEQUENCE {
158
+ # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
159
+ # privateKey OCTET STRING,
160
+ # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
161
+ # publicKey [1] BIT STRING OPTIONAL
162
+ # }
163
+
164
+ OpenSSL::ASN1::Sequence([
165
+ OpenSSL::ASN1::Integer(1),
166
+ OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
167
+ OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
168
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
169
+ ])
170
+ else
171
+ OpenSSL::ASN1::Sequence([
172
+ OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
173
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
174
+ ])
175
+ end
96
176
 
97
- jwk_crv, jwk_x, jwk_y, jwk_d, jwk_kid = jwk_attrs(jwk_data, %i[crv x y d kid])
98
- raise JWT::JWKError, 'Key format is invalid for EC' unless jwk_crv && jwk_x && jwk_y
177
+ OpenSSL::PKey::EC.new(sequence.to_der)
178
+ end
179
+ else
180
+ def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d)
181
+ curve = EC.to_openssl_curve(jwk_crv)
182
+
183
+ x_octets = decode_octets(jwk_x)
184
+ y_octets = decode_octets(jwk_y)
185
+
186
+ key = OpenSSL::PKey::EC.new(curve)
187
+
188
+ # The details of the `Point` instantiation are covered in:
189
+ # - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
190
+ # - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
191
+ # - https://tools.ietf.org/html/rfc5480#section-2.2
192
+ # - https://www.secg.org/SEC1-Ver-1.0.pdf
193
+ # Section 2.3.3 of the last of these references specifies that the
194
+ # encoding of an uncompressed point consists of the byte `0x04` followed
195
+ # by the x value then the y value.
196
+ point = OpenSSL::PKey::EC::Point.new(
197
+ OpenSSL::PKey::EC::Group.new(curve),
198
+ OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
199
+ )
200
+
201
+ key.public_key = point
202
+ key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
203
+
204
+ key
205
+ end
206
+ end
207
+
208
+ def decode_octets(jwk_data)
209
+ ::JWT::Base64.url_decode(jwk_data)
210
+ end
211
+
212
+ def decode_open_ssl_bn(jwk_data)
213
+ OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
214
+ end
99
215
 
100
- new(ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d), kid: jwk_kid)
216
+ class << self
217
+ def import(jwk_data)
218
+ new(jwk_data)
101
219
  end
102
220
 
103
221
  def to_openssl_curve(crv)
@@ -112,87 +230,6 @@ module JWT
112
230
  else raise JWT::JWKError, 'Invalid curve provided'
113
231
  end
114
232
  end
115
-
116
- private
117
-
118
- def jwk_attrs(jwk_data, attrs)
119
- attrs.map do |attr|
120
- jwk_data[attr] || jwk_data[attr.to_s]
121
- end
122
- end
123
-
124
- if ::JWT.openssl_3?
125
- def ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d) # rubocop:disable Metrics/MethodLength
126
- curve = to_openssl_curve(jwk_crv)
127
-
128
- x_octets = decode_octets(jwk_x)
129
- y_octets = decode_octets(jwk_y)
130
-
131
- point = OpenSSL::PKey::EC::Point.new(
132
- OpenSSL::PKey::EC::Group.new(curve),
133
- OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
134
- )
135
-
136
- sequence = if jwk_d
137
- # https://datatracker.ietf.org/doc/html/rfc5915.html
138
- # ECPrivateKey ::= SEQUENCE {
139
- # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
140
- # privateKey OCTET STRING,
141
- # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
142
- # publicKey [1] BIT STRING OPTIONAL
143
- # }
144
-
145
- OpenSSL::ASN1::Sequence([
146
- OpenSSL::ASN1::Integer(1),
147
- OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
148
- OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
149
- OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
150
- ])
151
- else
152
- OpenSSL::ASN1::Sequence([
153
- OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
154
- OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
155
- ])
156
- end
157
-
158
- OpenSSL::PKey::EC.new(sequence.to_der)
159
- end
160
- else
161
- def ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d)
162
- curve = to_openssl_curve(jwk_crv)
163
-
164
- x_octets = decode_octets(jwk_x)
165
- y_octets = decode_octets(jwk_y)
166
-
167
- key = OpenSSL::PKey::EC.new(curve)
168
-
169
- # The details of the `Point` instantiation are covered in:
170
- # - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
171
- # - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
172
- # - https://tools.ietf.org/html/rfc5480#section-2.2
173
- # - https://www.secg.org/SEC1-Ver-1.0.pdf
174
- # Section 2.3.3 of the last of these references specifies that the
175
- # encoding of an uncompressed point consists of the byte `0x04` followed
176
- # by the x value then the y value.
177
- point = OpenSSL::PKey::EC::Point.new(
178
- OpenSSL::PKey::EC::Group.new(curve),
179
- OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
180
- )
181
-
182
- key.public_key = point
183
- key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
184
-
185
- key
186
- end
187
- end
188
-
189
- def decode_octets(jwk_data)
190
- ::JWT::Base64.url_decode(jwk_data)
191
- end
192
-
193
- def decode_open_ssl_bn(jwk_data)
194
- OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
195
- end
196
233
  end
197
234
  end
198
235
  end