Skip to content

Commit

Permalink
refactor: create a class to deserialize stored public keys
Browse files Browse the repository at this point in the history
  * this will allow to keep the code isolated and more maintainable
  * at the same time, the WebAuthn::PublicKey.deserialize interface
  will be available for public use
  • Loading branch information
ssuttner committed Nov 29, 2019
1 parent f561c56 commit 474f7f3
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 0 deletions.
45 changes: 45 additions & 0 deletions lib/webauthn/public_key.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

require "webauthn/attestation_statement/fido_u2f/public_key"
require "cose/key"
require "cose/key/ec2"
require "cose/algorithm"

module WebAuthn
class PublicKey
def self.deserialize(public_key)
cose_key = if WebAuthn::AttestationStatement::FidoU2f::PublicKey.uncompressed_point?(public_key)
# Gem version v1.11.0 and lower, used to behave so that Credential#public_key
# returned an EC P-256 uncompressed point.
#
# Because of https://github.com/cedarcode/webauthn-ruby/issues/137 this was changed
# and Credential#public_key started returning the unchanged COSE_Key formatted
# credentialPublicKey (as in https://www.w3.org/TR/webauthn/#credentialpublickey).
#
# Given that the credential public key is expected to be stored long-term by the gem
# user and later be passed as the public_key argument in the
# AuthenticatorAssertionResponse.verify call, we then need to support the two formats.
COSE::Key::EC2.new(
alg: COSE::Algorithm.by_name("ES256").id,
crv: 1,
x: public_key[1..32],
y: public_key[33..-1]
)
else
COSE::Key.deserialize(public_key)
end

new(cose_key: cose_key)
end

attr_reader :cose_key

def initialize(cose_key:)
@cose_key = cose_key
end

def pkey
@cose_key.to_pkey
end
end
end
70 changes: 70 additions & 0 deletions spec/webauthn/public_key_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

require "spec_helper"

require "webauthn/public_key"
require "support/seeds"
require "cose"
require "openssl"

RSpec.describe "PublicKey" do
let(:uncompressed_point_public_key) do
Base64.strict_decode64(seeds[:u2f_migration][:stored_credential][:public_key])
end
let(:cose_public_key) do
Base64.urlsafe_decode64(
"pQECAyYgASFYIPJKd_-Rl0QtQwbLggjGC_EbUFIMriCkdc2yuaukkBuNIlggaBsBjCwnMzFL7OUGJNm4b-HVpFNUa_NbsHGARuYKHfU"
)
end
let(:webauthn_public_key) { WebAuthn::PublicKey.deserialize(public_key) }

describe ".deserialize" do
context "when invalid public key" do
let(:public_key) { 'invalidinvalid' }

it "should fail" do
expect { webauthn_public_key }.to raise_error(CBOR::MalformedFormatError)
end
end
end

describe "#pkey" do
let(:pkey) { webauthn_public_key.pkey }

context "when public key stored in uncompressed point format" do
let(:public_key) { uncompressed_point_public_key }

it "should return ssl pkey" do
expect(pkey).to be_instance_of(OpenSSL::PKey::EC)
end
end

context "when public key stored in cose format" do
let(:public_key) { cose_public_key }

it "should return ssl pkey" do
expect(pkey).to be_instance_of(OpenSSL::PKey::EC)
end
end
end

describe "#cose_key" do
let(:cose_key) { webauthn_public_key.cose_key }

context "when public key stored in uncompressed point format" do
let(:public_key) { uncompressed_point_public_key }

it "should return EC2 cose key" do
expect(cose_key).to be_instance_of(COSE::Key::EC2)
end
end

context "when public key stored in cose format" do
let(:public_key) { cose_public_key }

it "should return cose key" do
expect(cose_key).to be_a(COSE::Key::Base)
end
end
end
end

0 comments on commit 474f7f3

Please sign in to comment.