jwt 1.5.6 → 2.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +74 -0
- data/.gitignore +1 -1
- data/.rspec +1 -0
- data/.rubocop.yml +95 -0
- data/.rubocop_todo.yml +191 -0
- data/.sourcelevel.yml +18 -0
- data/AUTHORS +101 -0
- data/Appraisals +10 -0
- data/CHANGELOG.md +349 -8
- data/Gemfile +2 -1
- data/README.md +225 -68
- data/Rakefile +4 -1
- data/lib/jwt.rb +14 -176
- data/lib/jwt/algos.rb +44 -0
- data/lib/jwt/algos/ecdsa.rb +35 -0
- data/lib/jwt/algos/eddsa.rb +23 -0
- data/lib/jwt/algos/hmac.rb +34 -0
- data/lib/jwt/algos/none.rb +15 -0
- data/lib/jwt/algos/ps.rb +43 -0
- data/lib/jwt/algos/rsa.rb +19 -0
- data/lib/jwt/algos/unsupported.rb +17 -0
- data/lib/jwt/base64.rb +19 -0
- data/lib/jwt/claims_validator.rb +35 -0
- data/lib/jwt/decode.rb +83 -31
- data/lib/jwt/default_options.rb +15 -0
- data/lib/jwt/encode.rb +69 -0
- data/lib/jwt/error.rb +6 -0
- data/lib/jwt/json.rb +10 -9
- data/lib/jwt/jwk.rb +51 -0
- data/lib/jwt/jwk/ec.rb +150 -0
- data/lib/jwt/jwk/hmac.rb +58 -0
- data/lib/jwt/jwk/key_base.rb +18 -0
- data/lib/jwt/jwk/key_finder.rb +62 -0
- data/lib/jwt/jwk/rsa.rb +115 -0
- data/lib/jwt/security_utils.rb +57 -0
- data/lib/jwt/signature.rb +39 -0
- data/lib/jwt/verify.rb +45 -53
- data/lib/jwt/version.rb +3 -3
- data/ruby-jwt.gemspec +6 -8
- metadata +39 -95
- data/.codeclimate.yml +0 -20
- data/.travis.yml +0 -13
- data/Manifest +0 -8
- data/spec/fixtures/certs/ec256-private.pem +0 -8
- data/spec/fixtures/certs/ec256-public.pem +0 -4
- data/spec/fixtures/certs/ec256-wrong-private.pem +0 -8
- data/spec/fixtures/certs/ec256-wrong-public.pem +0 -4
- data/spec/fixtures/certs/ec384-private.pem +0 -9
- data/spec/fixtures/certs/ec384-public.pem +0 -5
- data/spec/fixtures/certs/ec384-wrong-private.pem +0 -9
- data/spec/fixtures/certs/ec384-wrong-public.pem +0 -5
- data/spec/fixtures/certs/ec512-private.pem +0 -10
- data/spec/fixtures/certs/ec512-public.pem +0 -6
- data/spec/fixtures/certs/ec512-wrong-private.pem +0 -10
- data/spec/fixtures/certs/ec512-wrong-public.pem +0 -6
- data/spec/fixtures/certs/rsa-1024-private.pem +0 -15
- data/spec/fixtures/certs/rsa-1024-public.pem +0 -6
- data/spec/fixtures/certs/rsa-2048-private.pem +0 -27
- data/spec/fixtures/certs/rsa-2048-public.pem +0 -9
- data/spec/fixtures/certs/rsa-2048-wrong-private.pem +0 -27
- data/spec/fixtures/certs/rsa-2048-wrong-public.pem +0 -9
- data/spec/fixtures/certs/rsa-4096-private.pem +0 -51
- data/spec/fixtures/certs/rsa-4096-public.pem +0 -14
- data/spec/integration/readme_examples_spec.rb +0 -190
- data/spec/jwt/verify_spec.rb +0 -197
- data/spec/jwt_spec.rb +0 -240
- data/spec/spec_helper.rb +0 -31
@@ -0,0 +1,17 @@
|
|
1
|
+
module JWT
|
2
|
+
module Algos
|
3
|
+
module Unsupported
|
4
|
+
module_function
|
5
|
+
|
6
|
+
SUPPORTED = [].freeze
|
7
|
+
|
8
|
+
def sign(*)
|
9
|
+
raise NotImplementedError, 'Unsupported signing method'
|
10
|
+
end
|
11
|
+
|
12
|
+
def verify(*)
|
13
|
+
raise JWT::VerificationError, 'Algorithm not supported'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/jwt/base64.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module JWT
|
6
|
+
# Base64 helpers
|
7
|
+
class Base64
|
8
|
+
class << self
|
9
|
+
def url_encode(str)
|
10
|
+
::Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
|
11
|
+
end
|
12
|
+
|
13
|
+
def url_decode(str)
|
14
|
+
str += '=' * (4 - str.length.modulo(4))
|
15
|
+
::Base64.decode64(str.tr('-_', '+/'))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative './error'
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
class ClaimsValidator
|
5
|
+
NUMERIC_CLAIMS = %i[
|
6
|
+
exp
|
7
|
+
iat
|
8
|
+
nbf
|
9
|
+
].freeze
|
10
|
+
|
11
|
+
def initialize(payload)
|
12
|
+
@payload = payload.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate!
|
16
|
+
validate_numeric_claims
|
17
|
+
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def validate_numeric_claims
|
24
|
+
NUMERIC_CLAIMS.each do |claim|
|
25
|
+
validate_is_numeric(claim) if @payload.key?(claim)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_is_numeric(claim)
|
30
|
+
return if @payload[claim].is_a?(Numeric)
|
31
|
+
|
32
|
+
raise InvalidPayload, "#{claim} claim must be a Numeric value but it is a #{@payload[claim].class}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/jwt/decode.rb
CHANGED
@@ -1,57 +1,109 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'jwt/json'
|
3
|
-
require 'jwt/verify'
|
4
2
|
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'jwt/signature'
|
6
|
+
require 'jwt/verify'
|
5
7
|
# JWT::Decode module
|
6
8
|
module JWT
|
7
|
-
extend JWT::Json
|
8
|
-
|
9
9
|
# Decoding logic for JWT
|
10
10
|
class Decode
|
11
|
-
attr_reader :header, :payload, :signature
|
12
|
-
|
13
11
|
def initialize(jwt, key, verify, options, &keyfinder)
|
12
|
+
raise(JWT::DecodeError, 'Nil JSON web token') unless jwt
|
14
13
|
@jwt = jwt
|
15
14
|
@key = key
|
16
|
-
@verify = verify
|
17
15
|
@options = options
|
16
|
+
@segments = jwt.split('.')
|
17
|
+
@verify = verify
|
18
|
+
@signature = ''
|
18
19
|
@keyfinder = keyfinder
|
19
20
|
end
|
20
21
|
|
21
22
|
def decode_segments
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
validate_segment_count!
|
24
|
+
if @verify
|
25
|
+
decode_crypto
|
26
|
+
verify_signature
|
27
|
+
verify_claims
|
28
|
+
end
|
29
|
+
raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
|
30
|
+
[payload, header]
|
27
31
|
end
|
28
32
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
raise(JWT::
|
33
|
-
|
33
|
+
private
|
34
|
+
|
35
|
+
def verify_signature
|
36
|
+
raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') if allowed_algorithms.empty?
|
37
|
+
raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless options_includes_algo_in_header?
|
38
|
+
|
39
|
+
@key = find_key(&@keyfinder) if @keyfinder
|
40
|
+
@key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks]).key_for(header['kid']) if @options[:jwks]
|
41
|
+
|
42
|
+
Signature.verify(header['alg'], @key, signing_input, @signature)
|
34
43
|
end
|
35
|
-
private :raw_segments
|
36
44
|
|
37
|
-
def
|
38
|
-
|
39
|
-
payload = JWT.decode_json(Decode.base64url_decode(payload_segment))
|
40
|
-
[header, payload]
|
45
|
+
def options_includes_algo_in_header?
|
46
|
+
allowed_algorithms.any? { |alg| alg.casecmp(header['alg']).zero? }
|
41
47
|
end
|
42
|
-
private :decode_header_and_payload
|
43
48
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
49
|
+
def allowed_algorithms
|
50
|
+
# Order is very important - first check for string keys, next for symbols
|
51
|
+
algos = if @options.key?('algorithm')
|
52
|
+
@options['algorithm']
|
53
|
+
elsif @options.key?(:algorithm)
|
54
|
+
@options[:algorithm]
|
55
|
+
elsif @options.key?('algorithms')
|
56
|
+
@options['algorithms']
|
57
|
+
elsif @options.key?(:algorithms)
|
58
|
+
@options[:algorithms]
|
59
|
+
else
|
60
|
+
[]
|
61
|
+
end
|
62
|
+
Array(algos)
|
47
63
|
end
|
48
64
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
65
|
+
def find_key(&keyfinder)
|
66
|
+
key = (keyfinder.arity == 2 ? yield(header, payload) : yield(header))
|
67
|
+
raise JWT::DecodeError, 'No verification key available' unless key
|
68
|
+
key
|
69
|
+
end
|
52
70
|
|
53
|
-
|
54
|
-
|
71
|
+
def verify_claims
|
72
|
+
Verify.verify_claims(payload, @options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def validate_segment_count!
|
76
|
+
return if segment_length == 3
|
77
|
+
return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
|
78
|
+
return if segment_length == 2 && header['alg'] == 'none'
|
79
|
+
|
80
|
+
raise(JWT::DecodeError, 'Not enough or too many segments')
|
81
|
+
end
|
82
|
+
|
83
|
+
def segment_length
|
84
|
+
@segments.count
|
85
|
+
end
|
86
|
+
|
87
|
+
def decode_crypto
|
88
|
+
@signature = JWT::Base64.url_decode(@segments[2] || '')
|
89
|
+
end
|
90
|
+
|
91
|
+
def header
|
92
|
+
@header ||= parse_and_decode @segments[0]
|
93
|
+
end
|
94
|
+
|
95
|
+
def payload
|
96
|
+
@payload ||= parse_and_decode @segments[1]
|
97
|
+
end
|
98
|
+
|
99
|
+
def signing_input
|
100
|
+
@segments.first(2).join('.')
|
101
|
+
end
|
102
|
+
|
103
|
+
def parse_and_decode(segment)
|
104
|
+
JWT::JSON.parse(JWT::Base64.url_decode(segment))
|
105
|
+
rescue ::JSON::ParserError
|
106
|
+
raise JWT::DecodeError, 'Invalid segment encoding'
|
55
107
|
end
|
56
108
|
end
|
57
109
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module JWT
|
2
|
+
module DefaultOptions
|
3
|
+
DEFAULT_OPTIONS = {
|
4
|
+
verify_expiration: true,
|
5
|
+
verify_not_before: true,
|
6
|
+
verify_iss: false,
|
7
|
+
verify_iat: false,
|
8
|
+
verify_jti: false,
|
9
|
+
verify_aud: false,
|
10
|
+
verify_sub: false,
|
11
|
+
leeway: 0,
|
12
|
+
algorithms: ['HS256']
|
13
|
+
}.freeze
|
14
|
+
end
|
15
|
+
end
|
data/lib/jwt/encode.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './algos'
|
4
|
+
require_relative './claims_validator'
|
5
|
+
|
6
|
+
# JWT::Encode module
|
7
|
+
module JWT
|
8
|
+
# Encoding logic for JWT
|
9
|
+
class Encode
|
10
|
+
ALG_NONE = 'none'.freeze
|
11
|
+
ALG_KEY = 'alg'.freeze
|
12
|
+
|
13
|
+
def initialize(options)
|
14
|
+
@payload = options[:payload]
|
15
|
+
@key = options[:key]
|
16
|
+
_, @algorithm = Algos.find(options[:algorithm])
|
17
|
+
@headers = options[:headers].each_with_object({}) { |(key, value), headers| headers[key.to_s] = value }
|
18
|
+
end
|
19
|
+
|
20
|
+
def segments
|
21
|
+
@segments ||= combine(encoded_header_and_payload, encoded_signature)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def encoded_header
|
27
|
+
@encoded_header ||= encode_header
|
28
|
+
end
|
29
|
+
|
30
|
+
def encoded_payload
|
31
|
+
@encoded_payload ||= encode_payload
|
32
|
+
end
|
33
|
+
|
34
|
+
def encoded_signature
|
35
|
+
@encoded_signature ||= encode_signature
|
36
|
+
end
|
37
|
+
|
38
|
+
def encoded_header_and_payload
|
39
|
+
@encoded_header_and_payload ||= combine(encoded_header, encoded_payload)
|
40
|
+
end
|
41
|
+
|
42
|
+
def encode_header
|
43
|
+
@headers[ALG_KEY] = @algorithm
|
44
|
+
encode(@headers)
|
45
|
+
end
|
46
|
+
|
47
|
+
def encode_payload
|
48
|
+
if @payload && @payload.is_a?(Hash)
|
49
|
+
ClaimsValidator.new(@payload).validate!
|
50
|
+
end
|
51
|
+
|
52
|
+
encode(@payload)
|
53
|
+
end
|
54
|
+
|
55
|
+
def encode_signature
|
56
|
+
return '' if @algorithm == ALG_NONE
|
57
|
+
|
58
|
+
JWT::Base64.url_encode(JWT::Signature.sign(@algorithm, encoded_header_and_payload, @key))
|
59
|
+
end
|
60
|
+
|
61
|
+
def encode(data)
|
62
|
+
JWT::Base64.url_encode(JWT::JSON.generate(data))
|
63
|
+
end
|
64
|
+
|
65
|
+
def combine(*parts)
|
66
|
+
parts.join('.')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/jwt/error.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module JWT
|
4
|
+
class EncodeError < StandardError; end
|
3
5
|
class DecodeError < StandardError; end
|
6
|
+
class RequiredDependencyError < StandardError; end
|
7
|
+
|
4
8
|
class VerificationError < DecodeError; end
|
5
9
|
class ExpiredSignature < DecodeError; end
|
6
10
|
class IncorrectAlgorithm < DecodeError; end
|
@@ -11,4 +15,6 @@ module JWT
|
|
11
15
|
class InvalidSubError < DecodeError; end
|
12
16
|
class InvalidJtiError < DecodeError; end
|
13
17
|
class InvalidPayload < DecodeError; end
|
18
|
+
|
19
|
+
class JWKError < DecodeError; end
|
14
20
|
end
|
data/lib/jwt/json.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'json'
|
3
4
|
|
4
5
|
module JWT
|
5
|
-
# JSON
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
6
|
+
# JSON wrapper
|
7
|
+
class JSON
|
8
|
+
class << self
|
9
|
+
def generate(data)
|
10
|
+
::JSON.generate(data)
|
11
|
+
end
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
def parse(data)
|
14
|
+
::JSON.parse(data)
|
15
|
+
end
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
data/lib/jwt/jwk.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'jwk/key_finder'
|
4
|
+
|
5
|
+
module JWT
|
6
|
+
module JWK
|
7
|
+
class << self
|
8
|
+
def import(jwk_data)
|
9
|
+
jwk_kty = jwk_data[:kty] || jwk_data['kty']
|
10
|
+
raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_kty
|
11
|
+
|
12
|
+
mappings.fetch(jwk_kty.to_s) do |kty|
|
13
|
+
raise JWT::JWKError, "Key type #{kty} not supported"
|
14
|
+
end.import(jwk_data)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_from(keypair)
|
18
|
+
mappings.fetch(keypair.class) do |klass|
|
19
|
+
raise JWT::JWKError, "Cannot create JWK from a #{klass.name}"
|
20
|
+
end.new(keypair)
|
21
|
+
end
|
22
|
+
|
23
|
+
def classes
|
24
|
+
@mappings = nil # reset the cached mappings
|
25
|
+
@classes ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
alias new create_from
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def mappings
|
33
|
+
@mappings ||= generate_mappings
|
34
|
+
end
|
35
|
+
|
36
|
+
def generate_mappings
|
37
|
+
classes.each_with_object({}) do |klass, hash|
|
38
|
+
next unless klass.const_defined?('KTYS')
|
39
|
+
Array(klass::KTYS).each do |kty|
|
40
|
+
hash[kty] = klass
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
require_relative 'jwk/key_base'
|
49
|
+
require_relative 'jwk/ec'
|
50
|
+
require_relative 'jwk/rsa'
|
51
|
+
require_relative 'jwk/hmac'
|
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'.freeze
|
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
|
+
::JWT::Base64.url_encode(octets)
|
76
|
+
end
|
77
|
+
|
78
|
+
def encode_open_ssl_bn(key_part)
|
79
|
+
::JWT::Base64.url_encode(key_part.to_s(BINARY))
|
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
|
+
::JWT::Base64.url_decode(jwk_data)
|
142
|
+
end
|
143
|
+
|
144
|
+
def decode_open_ssl_bn(jwk_data)
|
145
|
+
OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|