Skip to content

Commit

Permalink
Add support for kid in JWT::JWK::EC
Browse files Browse the repository at this point in the history
Updates the implementation of JWT:JWK::EC so it will import and
export custom "kid" values.

See also jwt#320, which proposes
doing the same for JWT::JWK::RSA.
  • Loading branch information
richardlarocque committed Nov 30, 2020
1 parent 6672386 commit 5a629a6
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 12 deletions.
28 changes: 17 additions & 11 deletions lib/jwt/jwk/ec.rb
Expand Up @@ -7,21 +7,20 @@ class EC
def_delegators :@keypair, :private?, :public_key

attr_reader :keypair
attr_reader :kid

KTY = 'EC'.freeze
BINARY = 2

def initialize(keypair)
def initialize(keypair, kid = nil)
raise ArgumentError, 'keypair must be of type OpenSSL::PKey::EC' unless keypair.is_a?(OpenSSL::PKey::EC)

@keypair = keypair
@kid = kid || generate_kid(@keypair)
end

def export
crv, x_octets, y_octets = keypair_components
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
kid = OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
crv, x_octets, y_octets = keypair_components(keypair)
{
kty: KTY,
crv: crv,
Expand All @@ -33,9 +32,16 @@ def export

private

def keypair_components
encoded_point = keypair.public_key.to_bn.to_s(BINARY)
case keypair.group.curve_name
def generate_kid(ec_keypair)
_crv, x_octets, y_octets = keypair_components(ec_keypair)
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
end

def keypair_components(ec_keypair)
encoded_point = ec_keypair.public_key.to_bn.to_s(BINARY)
case ec_keypair.group.curve_name
when 'prime256v1'
crv = 'P-256'
x_octets, y_octets = encoded_point.unpack('xa32a32')
Expand All @@ -46,7 +52,7 @@ def keypair_components
crv = 'P-521'
x_octets, y_octets = encoded_point.unpack('xa66a66')
else
raise "Unsupported curve '#{keypair.group.curve_name}'"
raise "Unsupported curve '#{ec_keypair.group.curve_name}'"
end
[crv, x_octets, y_octets]
end
Expand All @@ -64,10 +70,10 @@ def import(jwk_data)
# See https://tools.ietf.org/html/rfc7518#section-6.2.1 for an
# explanation of the relevant parameters.

jwk_crv, jwk_x, jwk_y = jwk_attrs(jwk_data, %i[crv x y])
jwk_crv, jwk_x, jwk_y, jwk_kid = jwk_attrs(jwk_data, %i[crv x y kid])
raise Jwt::JWKError, 'Key format is invalid for EC' unless jwk_crv && jwk_x && jwk_y

new(ec_pkey(jwk_crv, jwk_x, jwk_y))
new(ec_pkey(jwk_crv, jwk_x, jwk_y), jwk_kid)
end

def to_openssl_curve(crv)
Expand Down
19 changes: 18 additions & 1 deletion spec/jwk/ec_spec.rb
Expand Up @@ -27,7 +27,8 @@
end

describe '#export' do
subject { described_class.new(keypair).export }
let(:kid) { nil }
subject { described_class.new(keypair, kid).export }

context 'when keypair with private key is exported' do
let(:keypair) { ec_key }
Expand All @@ -49,6 +50,13 @@
expect(subject).to include(:kty, :kid, :x, :y)
expect(subject).not_to include(:d)
end

context 'when a custom "kid" is provided' do
let(:kid) { 'custom_key_identifier' }
it 'exports it' do
expect(subject[:kid]).to eq 'custom_key_identifier'
end
end
end
end

Expand All @@ -74,6 +82,15 @@
expect(subject).to be_a described_class
expect(subject.export).to eq(exported_key)
end

context 'with a custom "kid" value' do
let(:exported_key) {
super().merge(kid: 'custom_key_identifier')
}
it 'imports that "kid" value' do
expect(subject.kid).to eq('custom_key_identifier')
end
end
end

context 'returns a public key' do
Expand Down

0 comments on commit 5a629a6

Please sign in to comment.