mirror of
https://github.com/discourse/discourse.git
synced 2025-02-08 12:24:55 +00:00
Added a fix to gracefully error with a Webauthn::SecurityKeyError if somehow a user provides an unkown COSE algorithm when logging in with a security key. If `COSE::Algorithm.find` returns nil we now fail gracefully and log the algorithm used along with the user ID and the security key params for debugging, as this will help us find other common algorithms to implement for webauthn
147 lines
5.1 KiB
Ruby
147 lines
5.1 KiB
Ruby
# frozen_string_literal: true
|
|
require 'rails_helper'
|
|
require 'webauthn'
|
|
require 'webauthn/security_key_registration_service'
|
|
|
|
describe Webauthn::SecurityKeyAuthenticationService do
|
|
let(:security_key_user) { current_user }
|
|
let(:security_key) do
|
|
Fabricate(
|
|
:user_security_key,
|
|
credential_id: 'mJAJ4CznTO0SuLkJbYwpgK75ao4KMNIPlU5KWM92nq39kRbXzI9mSv6GxTcsMYoiPgaouNw7b7zBiS4vsQaO6A==',
|
|
public_key: 'pQECAyYgASFYIMNgw4GCpwBUlR2SznJ1yY7B9yFvsuxhfo+C9kcA4IitIlggRdofrCezymy2B/YarX+gfB6gZKg648/cHIMjf6wWmmU=',
|
|
user: security_key_user,
|
|
last_used: nil
|
|
)
|
|
end
|
|
let(:credential_id) { security_key.credential_id }
|
|
let(:challenge) { '81d4acfbd69eafa8f02bc2ecbec5267be8c9b28c1e0ba306d52b79f0f13d' }
|
|
let(:client_data_challenge) { Base64.strict_encode64(challenge) }
|
|
let(:client_data_webauthn_type) { 'webauthn.get' }
|
|
let(:client_data_origin) { 'http://localhost:3000' }
|
|
|
|
##
|
|
# IMPORTANT: For the SHA256 hash to match the same one as was used to generate
|
|
# the values for this spec, the three keys and values must be in the same order
|
|
# (challenge, origin, type)
|
|
let(:client_data_param) {
|
|
{
|
|
challenge: client_data_challenge,
|
|
origin: client_data_origin,
|
|
type: client_data_webauthn_type
|
|
}
|
|
}
|
|
##
|
|
# These are sourced from an actual login using the UserSecurityKey credential
|
|
# defined in this spec.
|
|
let(:signature) { "MEUCIBppPyK8blxBDoktU54mI1vWEY96r1V5H1rEBtPDxwcGAiEAoi7LCmMoEAuWYu0krZpflZlULsbURCGcqOwP06amXYE=" }
|
|
let(:authenticator_data) { "SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MBAAAAVw==" }
|
|
let(:params) do
|
|
{
|
|
clientData: Base64.strict_encode64(client_data_param.to_json),
|
|
credentialId: credential_id,
|
|
authenticatorData: authenticator_data,
|
|
signature: signature
|
|
}
|
|
end
|
|
##
|
|
# The original key was generated in localhost
|
|
let(:rp_id) { 'localhost' }
|
|
let(:challenge_params) do
|
|
{
|
|
challenge: challenge,
|
|
rp_id: rp_id,
|
|
origin: 'http://localhost:3000'
|
|
}
|
|
end
|
|
let(:current_user) { Fabricate(:user) }
|
|
let(:subject) { described_class.new(current_user, params, challenge_params) }
|
|
|
|
it 'updates last_used when valid' do
|
|
subject.authenticate_security_key
|
|
expect(security_key.reload.last_used).not_to eq(nil)
|
|
end
|
|
|
|
context 'when the credential ID does not match any user security key in the database' do
|
|
let(:credential_id) { 'badid' }
|
|
|
|
it 'raises a NotFoundError' do
|
|
expect { subject.authenticate_security_key }.to raise_error(
|
|
Webauthn::NotFoundError, I18n.t('webauthn.validation.not_found_error')
|
|
)
|
|
end
|
|
end
|
|
|
|
context 'when the credential ID does exist but it is for a different user' do
|
|
let(:security_key_user) { Fabricate(:user) }
|
|
|
|
it 'raises an OwnershipError' do
|
|
expect { subject.authenticate_security_key }.to raise_error(
|
|
Webauthn::OwnershipError, I18n.t('webauthn.validation.ownership_error')
|
|
)
|
|
end
|
|
end
|
|
|
|
context 'when the client data webauthn type is not webauthn.get' do
|
|
let(:client_data_webauthn_type) { 'webauthn.explode' }
|
|
|
|
it 'raises an InvalidTypeError' do
|
|
expect { subject.authenticate_security_key }.to raise_error(
|
|
Webauthn::InvalidTypeError, I18n.t('webauthn.validation.invalid_type_error')
|
|
)
|
|
end
|
|
end
|
|
|
|
context 'when the decoded challenge does not match the original challenge provided by the server' do
|
|
let(:client_data_challenge) { Base64.strict_encode64('invalid challenge') }
|
|
|
|
it 'raises a ChallengeMismatchError' do
|
|
expect { subject.authenticate_security_key }.to raise_error(
|
|
Webauthn::ChallengeMismatchError, I18n.t('webauthn.validation.challenge_mismatch_error')
|
|
)
|
|
end
|
|
end
|
|
|
|
context 'when the origin of the client data does not match the server origin' do
|
|
let(:client_data_origin) { 'https://someothersite.com' }
|
|
|
|
it 'raises a InvalidOriginError' do
|
|
expect { subject.authenticate_security_key }.to raise_error(
|
|
Webauthn::InvalidOriginError, I18n.t('webauthn.validation.invalid_origin_error')
|
|
)
|
|
end
|
|
end
|
|
|
|
context 'when the sha256 hash of the relaying party ID does not match the one in attestation.authData' do
|
|
let(:rp_id) { 'bad_rp_id' }
|
|
|
|
it 'raises a InvalidRelyingPartyIdError' do
|
|
expect { subject.authenticate_security_key }.to raise_error(
|
|
Webauthn::InvalidRelyingPartyIdError, I18n.t('webauthn.validation.invalid_relying_party_id_error')
|
|
)
|
|
end
|
|
end
|
|
|
|
context 'when there is a problem verifying the public key (e.g. invalid signature)' do
|
|
let(:signature) { Base64.strict_encode64('badsig') }
|
|
|
|
it 'raises a PublicKeyError' do
|
|
expect { subject.authenticate_security_key }.to raise_error(
|
|
Webauthn::PublicKeyError, I18n.t('webauthn.validation.public_key_error')
|
|
)
|
|
end
|
|
end
|
|
|
|
context 'when the COSE algorithm used cannot be found' do
|
|
before do
|
|
COSE::Algorithm.expects(:find).returns(nil)
|
|
end
|
|
|
|
it 'raises a UnknownCOSEAlgorithmError' do
|
|
expect { subject.authenticate_security_key }.to raise_error(
|
|
Webauthn::UnknownCOSEAlgorithmError, I18n.t('webauthn.validation.unknown_cose_algorithm_error')
|
|
)
|
|
end
|
|
end
|
|
end
|