Skip to content

Commit

Permalink
Merge 8ceefc7 into ee12a25
Browse files Browse the repository at this point in the history
  • Loading branch information
nscott committed Jan 17, 2023
2 parents ee12a25 + 8ceefc7 commit bd50dae
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
health_cards (1.1.0)
health_cards (1.1.1)
fhir_models (>= 4.0.0)
rqrcode
rqrcode_core (>= 1.2.0)
Expand Down
61 changes: 61 additions & 0 deletions lib/health_cards/key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,63 @@ def self.enforce_valid_key_type!(obj, allow_nil: false)
# @param jwk_key [Hash] The JWK represented by a Hash
# @return [HealthCards::Key] The key represented by the JWK
def self.from_jwk(jwk_key)
return Key.from_jwk_openssl3(jwk_key) if Key.openssl_3?

Key.from_jwk_openssl1(jwk_key)
end

def self.from_jwk_openssl3(jwk_key)
# Largely taken, then slightly modified from
# https://github.com/jwt/ruby-jwt/blob/main/lib/jwt/jwk/ec.rb#L131 on 2022-01-17
jwk_key = jwk_key.transform_keys(&:to_sym)
curve = 'prime256v1'

x_octets = Base64.urlsafe_decode64(jwk_key[:x])
y_octets = Base64.urlsafe_decode64(jwk_key[:y])

point = OpenSSL::PKey::EC::Point.new(
OpenSSL::PKey::EC::Group.new(curve),
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
)

sequence = if jwk_key.key?(:d)
d_octets = Base64.urlsafe_decode64(jwk_key[:d])
Key.jwk_ec_asn1_seq(curve, point, d_octets)
else
Key.jwk_ec_asn1_seq(curve, point)
end

key = OpenSSL::PKey::EC.new(sequence.to_der)
key.private_key? ? HealthCards::PrivateKey.new(key) : HealthCards::PublicKey.new(key)
end

def self.jwk_ec_asn1_seq(curve, point, d_octets = nil)
if d_octets.nil?
# Public key
OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'),
OpenSSL::ASN1::ObjectId(curve)]),
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
])
else
# https://datatracker.ietf.org/doc/html/rfc5915.html
# ECPrivateKey ::= SEQUENCE {
# version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
# privateKey OCTET STRING,
# parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
# publicKey [1] BIT STRING OPTIONAL
# }
OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Integer(1),
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(d_octets, 2).to_s(2)),
OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1,
:EXPLICIT)
])
end
end

def self.from_jwk_openssl1(jwk_key)
jwk_key = jwk_key.transform_keys(&:to_sym)
group = OpenSSL::PKey::EC::Group.new('prime256v1')
key = OpenSSL::PKey::EC.new(group)
Expand All @@ -30,6 +87,10 @@ def self.from_jwk(jwk_key)
key.private_key? ? HealthCards::PrivateKey.new(key) : HealthCards::PublicKey.new(key)
end

def self.openssl_3?
OpenSSL::OPENSSL_VERSION_NUMBER >= 3 * 0x10000000
end

def initialize(ec_key)
@key = ec_key
end
Expand Down
28 changes: 25 additions & 3 deletions lib/health_cards/private_key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,35 @@ def sign(payload)
def public_key
return @public_key if @public_key

pub = OpenSSL::PKey::EC.new('prime256v1')
pub.public_key = @key.public_key
@public_key = PublicKey.new(pub)
@public_key = if Key.openssl_3?
public_key_openssl3
else
public_key_openssl1
end
end

private

def public_key_openssl3
# Largely taken, then slightly modified from
# https://github.com/jwt/ruby-jwt/blob/main/lib/jwt/jwk/ec.rb#L131 on 2022-01-17
curve = 'prime256v1'
point = @key.public_key
sequence = OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'),
OpenSSL::ASN1::ObjectId(curve)]),
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
])
pub = OpenSSL::PKey::EC.new(sequence.to_der)
PublicKey.new(pub)
end

def public_key_openssl1
pub = OpenSSL::PKey::EC.new('prime256v1')
pub.public_key = @key.public_key
PublicKey.new(pub)
end

# Convert the ASN.1 Representation into the raw signature
#
# Adapted from ruby-jwt and json-jwt gems. More info here:
Expand Down
2 changes: 1 addition & 1 deletion lib/health_cards/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module HealthCards
VERSION = '1.1.0'
VERSION = '1.1.1'
end

0 comments on commit bd50dae

Please sign in to comment.