Polish up Passkey interface
- Fix indentation - Split awkward initializer into separate methods - Rename credentials to passkeys - Simplify authenticator registry loading - Flatten nested errors under ActionPack::WebAuthn - Set passkey current params only requests that use it - Inline anemic methods - Replace custom validation with ActiveModel::Validation - Rename identity to holder in Passkey - Rename credentials to passkeys in JS - Extract framework library out of controllers - Pass params hashes down to ActionPack - Attempt to simplify public interface - Push data decoding down to the classes representing the data - Introduce has_passkeys - Add CBOR bigint support - Rename ActionPack::WebAuthn::Passkey to ActionPack::Passkey - Add create_passkey_button helper - Rename public-key to creation-options - Add sign_in_with_passkey_button helper - Dispatch events for the whole Passkey lifecycle - Add ED25519 support - Prevent crash on missing meta tag - Validate resident key options - Don't clobber existing ActionPack config options - Validate cryptographic params - Move CurrentWebAuthnRequest into ActionPack::Passkey - Use ActiveModel::Attributes for Options objects - Implement expiring challanges - Add lifecycle events - Extract param helpers into Request - Add passkey_creation_options and passkey_request_options helpers - Add create_passkey_challenge to make it easier to override the create method if needed - Prefix all view helpers with passkey_ - Auto-include ActionPack::Passkey::Holder - Make the passkey challange url configurable - Add a reminder about Passkeys to the magic link email
This commit is contained in:
@@ -9,21 +9,20 @@
|
||||
#
|
||||
# # Look up the credential by ID
|
||||
# credential = user.credentials.find_by!(
|
||||
# credentail_id: params[:id]
|
||||
# credential_id: params[:id]
|
||||
# )
|
||||
#
|
||||
# response = ActionPack::WebAuthn::Authenticator::AssertionResponse.new(
|
||||
# client_data_json: params[:response][:clientDataJSON],
|
||||
# authenticator_data: params[:response][:authenticatorData],
|
||||
# signature: params[:response][:signature],
|
||||
# credential: credential.to_public_key_credential
|
||||
# )
|
||||
#
|
||||
# response.validate!(
|
||||
# challenge: session[:authentication_challenge],
|
||||
# credential: credential.to_public_key_credential,
|
||||
# challenge: ActionPack::WebAuthn::Current.challenge,
|
||||
# origin: "https://example.com"
|
||||
# )
|
||||
#
|
||||
# response.validate!
|
||||
#
|
||||
# == Validation
|
||||
#
|
||||
# In addition to the base Response validations, this class verifies:
|
||||
@@ -34,37 +33,43 @@
|
||||
class ActionPack::WebAuthn::Authenticator::AssertionResponse < ActionPack::WebAuthn::Authenticator::Response
|
||||
attr_reader :credential, :authenticator_data, :signature
|
||||
|
||||
validate :client_data_type_must_be_get
|
||||
validate :signature_must_be_valid
|
||||
validate :sign_count_must_increase
|
||||
|
||||
def initialize(credential:, authenticator_data:, signature:, **attributes)
|
||||
super(**attributes)
|
||||
@credential = credential
|
||||
@signature = signature
|
||||
@signature = Base64.urlsafe_decode64(@signature) unless @signature.encoding == Encoding::BINARY
|
||||
@authenticator_data = ActionPack::WebAuthn::Authenticator::Data.wrap(authenticator_data)
|
||||
end
|
||||
|
||||
def validate!(**args)
|
||||
super(**args)
|
||||
|
||||
unless client_data["type"] == "webauthn.get"
|
||||
raise InvalidResponseError, "Client data type is not webauthn.get"
|
||||
end
|
||||
|
||||
unless valid_signature?
|
||||
raise InvalidResponseError, "Invalid signature"
|
||||
end
|
||||
|
||||
unless sign_count_increased?
|
||||
raise InvalidResponseError, "Sign count did not increase"
|
||||
end
|
||||
rescue ArgumentError
|
||||
raise ActionPack::WebAuthn::InvalidResponseError, "Invalid base64 encoding in signature"
|
||||
end
|
||||
|
||||
private
|
||||
def valid_signature?
|
||||
def client_data_type_must_be_get
|
||||
unless client_data["type"] == "webauthn.get"
|
||||
errors.add(:base, "Client data type is not webauthn.get")
|
||||
end
|
||||
end
|
||||
|
||||
def signature_must_be_valid
|
||||
client_data_hash = Digest::SHA256.digest(client_data_json)
|
||||
signed_data = authenticator_data.bytes.pack("C*") + client_data_hash
|
||||
digest = credential.public_key.oid == "ED25519" ? nil : "SHA256"
|
||||
|
||||
credential.public_key.verify("SHA256", signature, signed_data)
|
||||
unless credential.public_key.verify(digest, signature, signed_data)
|
||||
errors.add(:base, "Invalid signature")
|
||||
end
|
||||
rescue OpenSSL::PKey::PKeyError
|
||||
false
|
||||
errors.add(:base, "Invalid signature")
|
||||
end
|
||||
|
||||
def sign_count_must_increase
|
||||
unless sign_count_increased?
|
||||
errors.add(:base, "Sign count did not increase")
|
||||
end
|
||||
end
|
||||
|
||||
def sign_count_increased?
|
||||
|
||||
Reference in New Issue
Block a user