Skip to content

Commit 91bcd26

Browse files
committed
Convert points to ec pkeys
1 parent d9266a2 commit 91bcd26

File tree

5 files changed

+49
-25
lines changed

5 files changed

+49
-25
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
- Add support for x5t header parameter for X.509 certificate thumbprint verification [#669](https://github.com/jwt/ruby-jwt/pull/669) ([@hieuk09](https://github.com/hieuk09))
1010
- Raise an error if the ECDSA signing or verification key is not an instance of `OpenSSL::PKey::EC` [#688](https://github.com/jwt/ruby-jwt/pull/688) ([@anakinj](https://github.com/anakinj))
11+
- Allow `OpenSSL::PKey::EC::Point` to be used as the verification key in ECDSA [#689](https://github.com/jwt/ruby-jwt/pull/689) ([@anakinj](https://github.com/anakinj))
1112
- Your contribution here
1213

1314
**Fixes and enhancements:**

lib/jwt/jwa/ecdsa.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ def initialize(alg, digest)
1212
end
1313

1414
def sign(data:, signing_key:)
15-
raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::EC instance.") unless signing_key.is_a?(::OpenSSL::PKey::EC)
15+
raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::EC instance") unless signing_key.is_a?(::OpenSSL::PKey::EC)
16+
raise_sign_error!('The given key is not a private key') unless signing_key.private?
1617

1718
curve_definition = curve_by_name(signing_key.group.curve_name)
1819
key_algorithm = curve_definition[:algorithm]
@@ -23,7 +24,9 @@ def sign(data:, signing_key:)
2324
end
2425

2526
def verify(data:, signature:, verification_key:)
26-
raise_verify_error!("The given key is a #{verification_key.class}. It has to be an OpenSSL::PKey::EC instance.") unless verification_key.is_a?(::OpenSSL::PKey::EC)
27+
verification_key = self.class.create_public_key_from_point(verification_key) if verification_key.is_a?(::OpenSSL::PKey::EC::Point)
28+
29+
raise_verify_error!("The given key is a #{verification_key.class}. It has to be an OpenSSL::PKey::EC instance") unless verification_key.is_a?(::OpenSSL::PKey::EC)
2730

2831
curve_definition = curve_by_name(verification_key.group.curve_name)
2932
key_algorithm = curve_definition[:algorithm]
@@ -67,6 +70,14 @@ def self.curve_by_name(name)
6770
end
6871
end
6972

73+
def self.create_public_key_from_point(point)
74+
sequence = OpenSSL::ASN1::Sequence([
75+
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(point.group.curve_name)]),
76+
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
77+
])
78+
OpenSSL::PKey::EC.new(sequence.to_der)
79+
end
80+
7081
private
7182

7283
attr_reader :digest

lib/jwt/jwk/ec.rb

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def parse_ec_key(key)
137137
end
138138

139139
if ::JWT.openssl_3?
140-
def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d) # rubocop:disable Metrics/MethodLength
140+
def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d)
141141
curve = EC.to_openssl_curve(jwk_crv)
142142
x_octets = decode_octets(jwk_x)
143143
y_octets = decode_octets(jwk_y)
@@ -147,29 +147,25 @@ def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d) # rubocop:disable Metrics/Method
147147
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
148148
)
149149

150-
sequence = if jwk_d
151-
# https://datatracker.ietf.org/doc/html/rfc5915.html
152-
# ECPrivateKey ::= SEQUENCE {
153-
# version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
154-
# privateKey OCTET STRING,
155-
# parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
156-
# publicKey [1] BIT STRING OPTIONAL
157-
# }
150+
if jwk_d
151+
# https://datatracker.ietf.org/doc/html/rfc5915.html
152+
# ECPrivateKey ::= SEQUENCE {
153+
# version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
154+
# privateKey OCTET STRING,
155+
# parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
156+
# publicKey [1] BIT STRING OPTIONAL
157+
# }
158158

159-
OpenSSL::ASN1::Sequence([
159+
sequence = OpenSSL::ASN1::Sequence([
160160
OpenSSL::ASN1::Integer(1),
161161
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
162162
OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
163163
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
164164
])
165-
else
166-
OpenSSL::ASN1::Sequence([
167-
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
168-
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
169-
])
170-
end
171-
172-
OpenSSL::PKey::EC.new(sequence.to_der)
165+
OpenSSL::PKey::EC.new(sequence.to_der)
166+
else
167+
::JWT::JWA::Ecdsa.create_public_key_from_point(point)
168+
end
173169
end
174170
else
175171
def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d)

spec/jwt/jwa/ecdsa_spec.rb

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,19 @@
3535
let(:ecdsa_key) { test_pkey('ec256-private.pem') }
3636
let(:data) { 'test data' }
3737
let(:instance) { described_class.new('ES256', 'sha256') }
38+
let(:signature) { instance.sign(data: data, signing_key: ecdsa_key) }
3839

3940
describe '#verify' do
4041
context 'when the verification key is valid' do
4142
it 'returns true for a valid signature' do
42-
signature = instance.sign(data: data, signing_key: ecdsa_key)
4343
expect(instance.verify(data: data, signature: signature, verification_key: ecdsa_key)).to be true
4444
end
4545

4646
it 'returns false for an invalid signature' do
4747
expect(instance.verify(data: data, signature: 'invalid_signature', verification_key: ecdsa_key)).to be false
4848
end
4949
end
50+
5051
context 'when verification results in a OpenSSL::PKey::PKeyError error' do
5152
it 'raises a JWT::VerificationError' do
5253
allow(ecdsa_key).to receive(:dsa_verify_asn1).and_raise(OpenSSL::PKey::PKeyError.new('Error'))
@@ -60,25 +61,40 @@
6061
it 'raises a JWT::DecodeError' do
6162
expect do
6263
instance.verify(data: data, signature: '', verification_key: 'not_a_key')
63-
end.to raise_error(JWT::DecodeError, 'The given key is a String. It has to be an OpenSSL::PKey::EC instance.')
64+
end.to raise_error(JWT::DecodeError, 'The given key is a String. It has to be an OpenSSL::PKey::EC instance')
65+
end
66+
end
67+
68+
context 'when the verification key is a point' do
69+
it 'verifies the signature' do
70+
expect(ecdsa_key.public_key).to be_a(OpenSSL::PKey::EC::Point)
71+
expect(instance.verify(data: data, signature: signature, verification_key: ecdsa_key.public_key)).to be(true)
6472
end
6573
end
6674
end
6775

6876
describe '#sign' do
6977
context 'when the signing key is valid' do
7078
it 'returns a valid signature' do
71-
signature = instance.sign(data: data, signing_key: ecdsa_key)
7279
expect(signature).to be_a(String)
7380
expect(signature.length).to be > 0
7481
end
7582
end
7683

84+
context 'when the signing key is a public key' do
85+
it 'raises a JWT::DecodeError' do
86+
public_key = test_pkey('ec256-public.pem')
87+
expect do
88+
instance.sign(data: data, signing_key: public_key)
89+
end.to raise_error(JWT::EncodeError, 'The given key is not a private key')
90+
end
91+
end
92+
7793
context 'when the signing key is not an OpenSSL::PKey::EC instance' do
7894
it 'raises a JWT::DecodeError' do
7995
expect do
8096
instance.sign(data: data, signing_key: 'not_a_key')
81-
end.to raise_error(JWT::EncodeError, 'The given key is a String. It has to be an OpenSSL::PKey::EC instance.')
97+
end.to raise_error(JWT::EncodeError, 'The given key is a String. It has to be an OpenSSL::PKey::EC instance')
8298
end
8399
end
84100

spec/jwt/jwk/decode_with_jwk_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@
169169

170170
it 'fails in some way' do
171171
expect { described_class.decode(signed_token, nil, true, algorithms: ['ES384'], jwks: jwks) }.to(
172-
raise_error(JWT::DecodeError, 'The given key is a String. It has to be an OpenSSL::PKey::EC instance.')
172+
raise_error(JWT::DecodeError, 'The given key is a String. It has to be an OpenSSL::PKey::EC instance')
173173
)
174174
end
175175
end

0 commit comments

Comments
 (0)