basecamp/fizzy · error · ActionPack::WebAuthn::InvalidKeyError
Missing OKP key coordinate
Error message
Missing OKP key coordinate
What it means
ActionPack::WebAuthn::CoseKey raised InvalidKeyError because a COSE OKP key (key type 1, algorithm -8 EdDSA, curve 6 Ed25519) had no entry under label -2, the x-coordinate. WebAuthn authenticators deliver the credential public key as a CBOR-encoded COSE map, and RFC 9053 requires every OKP key to carry its 32-byte x-coordinate under label -2. The library validates the map's shape before handing anything to OpenSSL, so this error means the decoded CBOR was structurally incomplete, not that OpenSSL rejected the key.
Solutions
- Inspect the decoded map (CoseKey.decode(bytes).parameters) and confirm label -2 is actually absent, to distinguish a malformed authenticator payload from your own slicing offset being wrong
- Re-check how you slice the credential public key out of attestedCredentialData (16-byte AAGUID + 2-byte big-endian credentialIdLength + credentialId + CBOR map); off-by-one slicing can decode to a map that lost trailing parameters
- If you build the COSE map yourself (tests or firmware), include the Ed25519 coordinate: { 1 => 1, 3 => -8, -1 => 6, -2 => x }
- Rescue ActionPack::WebAuthn::InvalidKeyError around to_openssl_key at registration and fail the ceremony with a client-visible error instead of a 500
Example fix
# before (test fixture): OKP map without the x-coordinate
params = { 1 => 1, 3 => -8, -1 => 6 }
# after: include the 32-byte Ed25519 x-coordinate under label -2
params = { 1 => 1, 3 => -8, -1 => 6, -2 => ed25519_public_bytes } Defensive patterns
Strategy: validation
Validate before calling
cose = ActionPack::WebAuthn::CoseKey.decode(public_key_bytes) x = cose.parameters[ActionPack::WebAuthn::CoseKey::OKP_X_LABEL] return if x.nil? || !x.is_a?(String) || x.bytesize != 32 # reject before conversion key = cose.to_openssl_key
Type guard
def ed25519_cose_key?(cose)
[cose.key_type, cose.algorithm] == [1, -8] &&
cose.parameters[-1] == 6 &&
cose.parameters[-2].is_a?(String) && cose.parameters[-2].bytesize == 32
end Try / catch
begin
openssl_key = cose_key.to_openssl_key
rescue ActionPack::WebAuthn::InvalidKeyError, ActionPack::WebAuthn::UnsupportedKeyTypeError => e
# client-supplied key is unusable: fail the registration ceremony, not a 500
render json: { error: e.message }, status: :unprocessable_entity
end Prevention
- Slice the credential public key using the documented layout (16-byte AAGUID, 2-byte big-endian credential ID length, credential ID, then the CBOR map) instead of fixed offsets
- Build test COSE keys from real authenticator captures or exported OpenSSL keys, not hand-typed maps
- Rescue InvalidKeyError at the registration ceremony boundary so bad keys become a 4xx response
- Log the decoded label keys (parameters.keys) whenever a key fails to convert
When it happens
Trigger: Calling ActionPack::WebAuthn::CoseKey.decode(cbor).to_openssl_key where the decoded map contains {1=>1, 3=>-8, -1=>6} but no -2 entry. Typical sources: a hand-built test COSE key, a truncated or wrongly sliced publicKey field of attestedCredentialData, or a buggy CTAP implementation that omits the coordinate.
Common situations: Writing WebAuthn registration tests with manually constructed COSE maps; parsing authenticatorData with home-grown offset math (misreading the 2-byte credentialIdLength) so the key slice starts mid-CBOR; firmware or emulator authenticators that emit incomplete OKP maps.
Related errors
- Invalid OKP key: #
- Invalid EC2 key: #
- Missing RSA key parameters
- Unsupported OKP curve: #
- Invalid EC2 coordinate length
AI-assisted analysis of basecamp/fizzy@7aabe74580 (2026-08-21).
Data as JSON: /api/errors/9e22e5dc1d9ee4b9.
Report an issue: GitHub.
Appendix: source
Thrown at lib/action_pack/web_authn/cose_key.rb:143
asn1 = OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::ObjectId("id-ecPublicKey"),
OpenSSL::ASN1::ObjectId("prime256v1")
]),
OpenSSL::ASN1::BitString(public_key_bytes)
])
OpenSSL::PKey::EC.new(asn1.to_der)
rescue OpenSSL::PKey::PKeyError => error
raise ActionPack::WebAuthn::InvalidKeyError, "Invalid EC2 key: #{error.message}"
end
def build_okp_eddsa_key
curve = parameters[OKP_CURVE_LABEL]
raise ActionPack::WebAuthn::UnsupportedKeyTypeError, "Unsupported OKP curve: #{curve}" unless curve == ED25519
x = parameters[OKP_X_LABEL]
raise ActionPack::WebAuthn::InvalidKeyError, "Missing OKP key coordinate" if x.nil?
asn1 = OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::ObjectId("ED25519")
]),
OpenSSL::ASN1::BitString(x)
])
OpenSSL::PKey.read(asn1.to_der)
rescue OpenSSL::PKey::PKeyError => error
raise ActionPack::WebAuthn::InvalidKeyError, "Invalid OKP key: #{error.message}"
end
def build_rsa_rs256_key
n_bytes = parameters[RSA_N_LABEL]
e_bytes = parameters[RSA_E_LABEL]
raise ActionPack::WebAuthn::InvalidKeyError, "Missing RSA key parameters" if n_bytes.nil? || e_bytes.nil?
raise ActionPack::WebAuthn::InvalidKeyError, "RSA key must be at least #{MINIMUM_RSA_KEY_BITS} bits" if n_bytes.bytesize * 8 < MINIMUM_RSA_KEY_BITSView on GitHub (pinned to 7aabe74580)