jwt 2.2.2 → 2.4.0.beta1

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