jwt 2.2.2 → 2.4.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,35 +1,53 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Ecdsa
4
6
  module_function
5
7
 
6
- SUPPORTED = %w[ES256 ES384 ES512].freeze
7
8
  NAMED_CURVES = {
8
9
  'prime256v1' => 'ES256',
10
+ 'secp256r1' => 'ES256', # alias for prime256v1
9
11
  'secp384r1' => 'ES384',
10
12
  'secp521r1' => 'ES512'
11
13
  }.freeze
12
14
 
15
+ SUPPORTED = NAMED_CURVES.values.uniq.freeze
16
+
13
17
  def sign(to_sign)
14
18
  algorithm, msg, key = to_sign.values
15
- key_algorithm = NAMED_CURVES[key.group.curve_name]
19
+ curve_definition = curve_by_name(key.group.curve_name)
20
+ key_algorithm = curve_definition[:algorithm]
16
21
  if algorithm != key_algorithm
17
22
  raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
18
23
  end
19
24
 
20
- digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
25
+ digest = OpenSSL::Digest.new(curve_definition[:digest])
21
26
  SecurityUtils.asn1_to_raw(key.dsa_sign_asn1(digest.digest(msg)), key)
22
27
  end
23
28
 
24
29
  def verify(to_verify)
25
30
  algorithm, public_key, signing_input, signature = to_verify.values
26
- key_algorithm = NAMED_CURVES[public_key.group.curve_name]
31
+ curve_definition = curve_by_name(public_key.group.curve_name)
32
+ key_algorithm = curve_definition[:algorithm]
27
33
  if algorithm != key_algorithm
28
34
  raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
29
35
  end
30
- digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
36
+
37
+ digest = OpenSSL::Digest.new(curve_definition[:digest])
31
38
  public_key.dsa_verify_asn1(digest.digest(signing_input), SecurityUtils.raw_to_asn1(signature, public_key))
32
39
  end
40
+
41
+ def curve_by_name(name)
42
+ algorithm = NAMED_CURVES.fetch(name) do
43
+ raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
44
+ end
45
+
46
+ {
47
+ algorithm: algorithm,
48
+ digest: algorithm.sub('ES', 'sha')
49
+ }
50
+ end
33
51
  end
34
52
  end
35
53
  end
@@ -1,21 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Eddsa
4
6
  module_function
5
7
 
6
- SUPPORTED = %w[ED25519].freeze
8
+ SUPPORTED = %w[ED25519 EdDSA].freeze
7
9
 
8
10
  def sign(to_sign)
9
11
  algorithm, msg, key = to_sign.values
10
- raise EncodeError, "Key given is a #{key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey" if key.class != RbNaCl::Signatures::Ed25519::SigningKey
11
- raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided" if algorithm.downcase.to_sym != key.primitive
12
+ if key.class != RbNaCl::Signatures::Ed25519::SigningKey
13
+ raise EncodeError, "Key given is a #{key.class} but has to be an RbNaCl::Signatures::Ed25519::SigningKey"
14
+ end
15
+ unless SUPPORTED.map(&:downcase).map(&:to_sym).include?(algorithm.downcase.to_sym)
16
+ raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided"
17
+ end
18
+
12
19
  key.sign(msg)
13
20
  end
14
21
 
15
22
  def verify(to_verify)
16
23
  algorithm, public_key, signing_input, signature = to_verify.values
17
- raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{public_key.primitive} verification key was provided" if algorithm.downcase.to_sym != public_key.primitive
24
+ unless SUPPORTED.map(&:downcase).map(&:to_sym).include?(algorithm.downcase.to_sym)
25
+ raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key.primitive} signing key was provided"
26
+ end
18
27
  raise DecodeError, "key given is a #{public_key.class} but has to be a RbNaCl::Signatures::Ed25519::VerifyKey" if public_key.class != RbNaCl::Signatures::Ed25519::VerifyKey
28
+
19
29
  public_key.verify(signature, signing_input)
20
30
  end
21
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Hmac
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module Algos
5
+ module None
6
+ module_function
7
+
8
+ SUPPORTED = %w[none].freeze
9
+
10
+ def sign(*); end
11
+
12
+ def verify(*)
13
+ true
14
+ end
15
+ end
16
+ end
17
+ end
data/lib/jwt/algos/ps.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Ps
@@ -29,9 +31,7 @@ module JWT
29
31
 
30
32
  def require_openssl!
31
33
  if Object.const_defined?('OpenSSL')
32
- major, minor = OpenSSL::VERSION.split('.').first(2)
33
-
34
- unless major.to_i >= 2 && minor.to_i >= 1
34
+ if ::Gem::Version.new(OpenSSL::VERSION) < ::Gem::Version.new('2.1')
35
35
  raise JWT::RequiredDependencyError, "You currently have OpenSSL #{OpenSSL::VERSION}. PS support requires >= 2.1"
36
36
  end
37
37
  else
data/lib/jwt/algos/rsa.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Rsa
@@ -7,7 +9,8 @@ module JWT
7
9
 
8
10
  def sign(to_sign)
9
11
  algorithm, msg, key = to_sign.values
10
- raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance." if key.class == String
12
+ raise EncodeError, "The given key is a #{key.class}. It has to be an OpenSSL::PKey::RSA instance." if key.instance_of?(String)
13
+
11
14
  key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
12
15
  end
13
16
 
@@ -1,16 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module Algos
3
5
  module Unsupported
4
6
  module_function
5
7
 
6
- SUPPORTED = Object.new.tap { |object| object.define_singleton_method(:include?) { |*| true } }
7
- def verify(*)
8
- raise JWT::VerificationError, 'Algorithm not supported'
9
- end
8
+ SUPPORTED = [].freeze
10
9
 
11
10
  def sign(*)
12
11
  raise NotImplementedError, 'Unsupported signing method'
13
12
  end
13
+
14
+ def verify(*)
15
+ raise JWT::VerificationError, 'Algorithm not supported'
16
+ end
14
17
  end
15
18
  end
16
19
  end
data/lib/jwt/algos.rb ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt/algos/hmac'
4
+ require 'jwt/algos/eddsa'
5
+ require 'jwt/algos/ecdsa'
6
+ require 'jwt/algos/rsa'
7
+ require 'jwt/algos/ps'
8
+ require 'jwt/algos/none'
9
+ require 'jwt/algos/unsupported'
10
+
11
+ # JWT::Signature module
12
+ module JWT
13
+ # Signature logic for JWT
14
+ module Algos
15
+ extend self
16
+
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
26
+
27
+ def find(algorithm)
28
+ indexed[algorithm && algorithm.downcase]
29
+ end
30
+
31
+ private
32
+
33
+ def indexed
34
+ @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]
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,33 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative './error'
2
4
 
3
5
  module JWT
4
6
  class ClaimsValidator
5
- INTEGER_CLAIMS = %i[
7
+ NUMERIC_CLAIMS = %i[
6
8
  exp
7
9
  iat
8
10
  nbf
9
11
  ].freeze
10
12
 
11
13
  def initialize(payload)
12
- @payload = payload.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
14
+ @payload = payload.transform_keys(&:to_sym)
13
15
  end
14
16
 
15
17
  def validate!
16
- validate_int_claims
18
+ validate_numeric_claims
17
19
 
18
20
  true
19
21
  end
20
22
 
21
23
  private
22
24
 
23
- def validate_int_claims
24
- INTEGER_CLAIMS.each do |claim|
25
- validate_is_int(claim) if @payload.key?(claim)
25
+ def validate_numeric_claims
26
+ NUMERIC_CLAIMS.each do |claim|
27
+ validate_is_numeric(claim) if @payload.key?(claim)
26
28
  end
27
29
  end
28
30
 
29
- def validate_is_int(claim)
30
- raise InvalidPayload, "#{claim} claim must be an Integer but it is a #{@payload[claim].class}" unless @payload[claim].is_a?(Integer)
31
+ def validate_is_numeric(claim)
32
+ return if @payload[claim].is_a?(Numeric)
33
+
34
+ raise InvalidPayload, "#{claim} claim must be a Numeric value but it is a #{@payload[claim].class}"
31
35
  end
32
36
  end
33
37
  end
data/lib/jwt/decode.rb CHANGED
@@ -4,12 +4,14 @@ require 'json'
4
4
 
5
5
  require 'jwt/signature'
6
6
  require 'jwt/verify'
7
+ require 'jwt/x5c_key_finder'
7
8
  # JWT::Decode module
8
9
  module JWT
9
10
  # Decoding logic for JWT
10
11
  class Decode
11
12
  def initialize(jwt, key, verify, options, &keyfinder)
12
13
  raise(JWT::DecodeError, 'Nil JSON web token') unless jwt
14
+
13
15
  @jwt = jwt
14
16
  @key = key
15
17
  @options = options
@@ -23,57 +25,85 @@ module JWT
23
25
  validate_segment_count!
24
26
  if @verify
25
27
  decode_crypto
28
+ verify_algo
29
+ set_key
26
30
  verify_signature
27
31
  verify_claims
28
32
  end
29
33
  raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
34
+
30
35
  [payload, header]
31
36
  end
32
37
 
33
38
  private
34
39
 
35
40
  def verify_signature
41
+ return unless @key || @verify
42
+
43
+ return if none_algorithm?
44
+
45
+ raise JWT::DecodeError, 'No verification key available' unless @key
46
+
47
+ return if Array(@key).any? { |key| verify_signature_for?(key) }
48
+
49
+ raise(JWT::VerificationError, 'Signature verification failed')
50
+ end
51
+
52
+ def verify_algo
36
53
  raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') if allowed_algorithms.empty?
54
+ raise(JWT::IncorrectAlgorithm, 'Token is missing alg header') unless algorithm
37
55
  raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless options_includes_algo_in_header?
56
+ end
38
57
 
58
+ def set_key
39
59
  @key = find_key(&@keyfinder) if @keyfinder
40
60
  @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks]).key_for(header['kid']) if @options[:jwks]
61
+ if (x5c_options = @options[:x5c])
62
+ @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
63
+ end
64
+ end
41
65
 
42
- Signature.verify(header['alg'], @key, signing_input, @signature)
66
+ def verify_signature_for?(key)
67
+ Signature.verify(algorithm, key, signing_input, @signature)
43
68
  end
44
69
 
45
70
  def options_includes_algo_in_header?
46
- allowed_algorithms.include? header['alg']
71
+ allowed_algorithms.any? { |alg| alg.casecmp(algorithm).zero? }
47
72
  end
48
73
 
49
74
  def allowed_algorithms
50
75
  # Order is very important - first check for string keys, next for symbols
51
- if @options.key?('algorithm')
52
- [@options['algorithm']]
76
+ algos = if @options.key?('algorithm')
77
+ @options['algorithm']
53
78
  elsif @options.key?(:algorithm)
54
- [@options[:algorithm]]
79
+ @options[:algorithm]
55
80
  elsif @options.key?('algorithms')
56
- @options['algorithms'] || []
81
+ @options['algorithms']
57
82
  elsif @options.key?(:algorithms)
58
- @options[:algorithms] || []
83
+ @options[:algorithms]
59
84
  else
60
85
  []
61
86
  end
87
+ Array(algos)
62
88
  end
63
89
 
64
90
  def find_key(&keyfinder)
65
91
  key = (keyfinder.arity == 2 ? yield(header, payload) : yield(header))
66
- raise JWT::DecodeError, 'No verification key available' unless key
67
- key
92
+ # key can be of type [string, nil, OpenSSL::PKey, Array]
93
+ return key if key && !Array(key).empty?
94
+
95
+ raise JWT::DecodeError, 'No verification key available'
68
96
  end
69
97
 
70
98
  def verify_claims
71
99
  Verify.verify_claims(payload, @options)
100
+ Verify.verify_required_claims(payload, @options)
72
101
  end
73
102
 
74
103
  def validate_segment_count!
75
104
  return if segment_length == 3
76
105
  return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
106
+ return if segment_length == 2 && none_algorithm?
77
107
 
78
108
  raise(JWT::DecodeError, 'Not enough or too many segments')
79
109
  end
@@ -82,8 +112,16 @@ module JWT
82
112
  @segments.count
83
113
  end
84
114
 
115
+ def none_algorithm?
116
+ algorithm.casecmp('none').zero?
117
+ end
118
+
85
119
  def decode_crypto
86
- @signature = JWT::Base64.url_decode(@segments[2])
120
+ @signature = Base64.urlsafe_decode64(@segments[2] || '')
121
+ end
122
+
123
+ def algorithm
124
+ header['alg']
87
125
  end
88
126
 
89
127
  def header
@@ -99,8 +137,8 @@ module JWT
99
137
  end
100
138
 
101
139
  def parse_and_decode(segment)
102
- JWT::JSON.parse(JWT::Base64.url_decode(segment))
103
- rescue ::JSON::ParserError
140
+ JWT::JSON.parse(Base64.urlsafe_decode64(segment))
141
+ rescue ::JSON::ParserError, ArgumentError
104
142
  raise JWT::DecodeError, 'Invalid segment encoding'
105
143
  end
106
144
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  module DefaultOptions
3
5
  DEFAULT_OPTIONS = {
@@ -9,7 +11,8 @@ module JWT
9
11
  verify_aud: false,
10
12
  verify_sub: false,
11
13
  leeway: 0,
12
- algorithms: ['HS256']
14
+ algorithms: ['HS256'],
15
+ required_claims: []
13
16
  }.freeze
14
17
  end
15
18
  end
data/lib/jwt/encode.rb CHANGED
@@ -1,19 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './algos'
3
4
  require_relative './claims_validator'
4
5
 
5
6
  # JWT::Encode module
6
7
  module JWT
7
8
  # Encoding logic for JWT
8
9
  class Encode
9
- ALG_NONE = 'none'.freeze
10
- ALG_KEY = 'alg'.freeze
10
+ ALG_NONE = 'none'
11
+ ALG_KEY = 'alg'
11
12
 
12
13
  def initialize(options)
13
- @payload = options[:payload]
14
- @key = options[:key]
15
- @algorithm = options[:algorithm]
16
- @headers = options[:headers].each_with_object({}) { |(key, value), headers| headers[key.to_s] = value }
14
+ @payload = options[:payload]
15
+ @key = options[:key]
16
+ _, @algorithm = Algos.find(options[:algorithm])
17
+ @headers = options[:headers].transform_keys(&:to_s)
17
18
  end
18
19
 
19
20
  def segments
@@ -44,7 +45,7 @@ module JWT
44
45
  end
45
46
 
46
47
  def encode_payload
47
- if @payload && @payload.is_a?(Hash)
48
+ if @payload.is_a?(Hash)
48
49
  ClaimsValidator.new(@payload).validate!
49
50
  end
50
51
 
@@ -54,11 +55,11 @@ module JWT
54
55
  def encode_signature
55
56
  return '' if @algorithm == ALG_NONE
56
57
 
57
- JWT::Base64.url_encode(JWT::Signature.sign(@algorithm, encoded_header_and_payload, @key))
58
+ Base64.urlsafe_encode64(JWT::Signature.sign(@algorithm, encoded_header_and_payload, @key), padding: false)
58
59
  end
59
60
 
60
61
  def encode(data)
61
- JWT::Base64.url_encode(JWT::JSON.generate(data))
62
+ Base64.urlsafe_encode64(JWT::JSON.generate(data), padding: false)
62
63
  end
63
64
 
64
65
  def combine(*parts)
data/lib/jwt/error.rb CHANGED
@@ -10,11 +10,13 @@ module JWT
10
10
  class IncorrectAlgorithm < DecodeError; end
11
11
  class ImmatureSignature < DecodeError; end
12
12
  class InvalidIssuerError < DecodeError; end
13
+ class UnsupportedEcdsaCurve < IncorrectAlgorithm; end
13
14
  class InvalidIatError < DecodeError; end
14
15
  class InvalidAudError < DecodeError; end
15
16
  class InvalidSubError < DecodeError; end
16
17
  class InvalidJtiError < DecodeError; end
17
18
  class InvalidPayload < DecodeError; end
19
+ class MissingRequiredClaim < DecodeError; end
18
20
 
19
21
  class JWKError < DecodeError; end
20
22
  end
data/lib/jwt/jwk/ec.rb ADDED
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module JWT
6
+ module JWK
7
+ class EC < KeyBase
8
+ extend Forwardable
9
+ def_delegators :@keypair, :public_key
10
+
11
+ KTY = 'EC'
12
+ KTYS = [KTY, OpenSSL::PKey::EC].freeze
13
+ BINARY = 2
14
+
15
+ def initialize(keypair, kid = nil)
16
+ raise ArgumentError, 'keypair must be of type OpenSSL::PKey::EC' unless keypair.is_a?(OpenSSL::PKey::EC)
17
+
18
+ kid ||= generate_kid(keypair)
19
+ super(keypair, kid)
20
+ end
21
+
22
+ def private?
23
+ @keypair.private_key?
24
+ end
25
+
26
+ def export(options = {})
27
+ crv, x_octets, y_octets = keypair_components(keypair)
28
+ exported_hash = {
29
+ kty: KTY,
30
+ crv: crv,
31
+ x: encode_octets(x_octets),
32
+ y: encode_octets(y_octets),
33
+ kid: kid
34
+ }
35
+ return exported_hash unless private? && options[:include_private] == true
36
+
37
+ append_private_parts(exported_hash)
38
+ end
39
+
40
+ private
41
+
42
+ def append_private_parts(the_hash)
43
+ octets = keypair.private_key.to_bn.to_s(BINARY)
44
+ the_hash.merge(
45
+ d: encode_octets(octets)
46
+ )
47
+ end
48
+
49
+ def generate_kid(ec_keypair)
50
+ _crv, x_octets, y_octets = keypair_components(ec_keypair)
51
+ sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
52
+ OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
53
+ OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
54
+ end
55
+
56
+ def keypair_components(ec_keypair)
57
+ encoded_point = ec_keypair.public_key.to_bn.to_s(BINARY)
58
+ case ec_keypair.group.curve_name
59
+ when 'prime256v1'
60
+ crv = 'P-256'
61
+ x_octets, y_octets = encoded_point.unpack('xa32a32')
62
+ when 'secp384r1'
63
+ crv = 'P-384'
64
+ x_octets, y_octets = encoded_point.unpack('xa48a48')
65
+ when 'secp521r1'
66
+ crv = 'P-521'
67
+ x_octets, y_octets = encoded_point.unpack('xa66a66')
68
+ else
69
+ raise JWT::JWKError, "Unsupported curve '#{ec_keypair.group.curve_name}'"
70
+ end
71
+ [crv, x_octets, y_octets]
72
+ end
73
+
74
+ def encode_octets(octets)
75
+ Base64.urlsafe_encode64(octets, padding: false)
76
+ end
77
+
78
+ def encode_open_ssl_bn(key_part)
79
+ Base64.urlsafe_encode64(key_part.to_s(BINARY), padding: false)
80
+ end
81
+
82
+ class << self
83
+ def import(jwk_data)
84
+ # See https://tools.ietf.org/html/rfc7518#section-6.2.1 for an
85
+ # explanation of the relevant parameters.
86
+
87
+ jwk_crv, jwk_x, jwk_y, jwk_d, jwk_kid = jwk_attrs(jwk_data, %i[crv x y d kid])
88
+ raise JWT::JWKError, 'Key format is invalid for EC' unless jwk_crv && jwk_x && jwk_y
89
+
90
+ new(ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d), jwk_kid)
91
+ end
92
+
93
+ def to_openssl_curve(crv)
94
+ # The JWK specs and OpenSSL use different names for the same curves.
95
+ # See https://tools.ietf.org/html/rfc5480#section-2.1.1.1 for some
96
+ # pointers on different names for common curves.
97
+ case crv
98
+ when 'P-256' then 'prime256v1'
99
+ when 'P-384' then 'secp384r1'
100
+ when 'P-521' then 'secp521r1'
101
+ else raise JWT::JWKError, 'Invalid curve provided'
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def jwk_attrs(jwk_data, attrs)
108
+ attrs.map do |attr|
109
+ jwk_data[attr] || jwk_data[attr.to_s]
110
+ end
111
+ end
112
+
113
+ def ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d)
114
+ curve = to_openssl_curve(jwk_crv)
115
+
116
+ x_octets = decode_octets(jwk_x)
117
+ y_octets = decode_octets(jwk_y)
118
+
119
+ key = OpenSSL::PKey::EC.new(curve)
120
+
121
+ # The details of the `Point` instantiation are covered in:
122
+ # - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
123
+ # - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
124
+ # - https://tools.ietf.org/html/rfc5480#section-2.2
125
+ # - https://www.secg.org/SEC1-Ver-1.0.pdf
126
+ # Section 2.3.3 of the last of these references specifies that the
127
+ # encoding of an uncompressed point consists of the byte `0x04` followed
128
+ # by the x value then the y value.
129
+ point = OpenSSL::PKey::EC::Point.new(
130
+ OpenSSL::PKey::EC::Group.new(curve),
131
+ OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
132
+ )
133
+
134
+ key.public_key = point
135
+ key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
136
+
137
+ key
138
+ end
139
+
140
+ def decode_octets(jwk_data)
141
+ Base64.urlsafe_decode64(jwk_data)
142
+ end
143
+
144
+ def decode_open_ssl_bn(jwk_data)
145
+ OpenSSL::BN.new(Base64.urlsafe_decode64(jwk_data), BINARY)
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end