{"record":{"id":"9e22e5dc1d9ee4b9","repo":"basecamp/fizzy","slug":"missing-okp-key-coordinate","errorCode":null,"errorMessage":"Missing OKP key coordinate","messagePattern":"Missing OKP key coordinate","errorType":"exception","errorClass":"ActionPack::WebAuthn::InvalidKeyError","httpStatus":null,"severity":"error","filePath":"lib/action_pack/web_authn/cose_key.rb","lineNumber":143,"sourceCode":"      asn1 = OpenSSL::ASN1::Sequence([\n        OpenSSL::ASN1::Sequence([\n          OpenSSL::ASN1::ObjectId(\"id-ecPublicKey\"),\n          OpenSSL::ASN1::ObjectId(\"prime256v1\")\n        ]),\n        OpenSSL::ASN1::BitString(public_key_bytes)\n      ])\n\n      OpenSSL::PKey::EC.new(asn1.to_der)\n    rescue OpenSSL::PKey::PKeyError => error\n      raise ActionPack::WebAuthn::InvalidKeyError, \"Invalid EC2 key: #{error.message}\"\n    end\n\n    def build_okp_eddsa_key\n      curve = parameters[OKP_CURVE_LABEL]\n      raise ActionPack::WebAuthn::UnsupportedKeyTypeError, \"Unsupported OKP curve: #{curve}\" unless curve == ED25519\n\n      x = parameters[OKP_X_LABEL]\n      raise ActionPack::WebAuthn::InvalidKeyError, \"Missing OKP key coordinate\" if x.nil?\n\n      asn1 = OpenSSL::ASN1::Sequence([\n        OpenSSL::ASN1::Sequence([\n          OpenSSL::ASN1::ObjectId(\"ED25519\")\n        ]),\n        OpenSSL::ASN1::BitString(x)\n      ])\n\n      OpenSSL::PKey.read(asn1.to_der)\n    rescue OpenSSL::PKey::PKeyError => error\n      raise ActionPack::WebAuthn::InvalidKeyError, \"Invalid OKP key: #{error.message}\"\n    end\n\n    def build_rsa_rs256_key\n      n_bytes = parameters[RSA_N_LABEL]\n      e_bytes = parameters[RSA_E_LABEL]\n      raise ActionPack::WebAuthn::InvalidKeyError, \"Missing RSA key parameters\" if n_bytes.nil? || e_bytes.nil?\n      raise ActionPack::WebAuthn::InvalidKeyError, \"RSA key must be at least #{MINIMUM_RSA_KEY_BITS} bits\" if n_bytes.bytesize * 8 < MINIMUM_RSA_KEY_BITS","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/basecamp/fizzy/blob/7aabe7458060d8a1759a53b7ede39e74e6c0b20d/lib/action_pack/web_authn/cose_key.rb#L125-L161","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before (test fixture): OKP map without the x-coordinate\nparams = { 1 => 1, 3 => -8, -1 => 6 }\n\n# after: include the 32-byte Ed25519 x-coordinate under label -2\nparams = { 1 => 1, 3 => -8, -1 => 6, -2 => ed25519_public_bytes }","handlingStrategy":"validation","validationCode":"cose = ActionPack::WebAuthn::CoseKey.decode(public_key_bytes)\nx = cose.parameters[ActionPack::WebAuthn::CoseKey::OKP_X_LABEL]\nreturn if x.nil? || !x.is_a?(String) || x.bytesize != 32 # reject before conversion\nkey = cose.to_openssl_key","typeGuard":"def ed25519_cose_key?(cose)\n  [cose.key_type, cose.algorithm] == [1, -8] &&\n    cose.parameters[-1] == 6 &&\n    cose.parameters[-2].is_a?(String) && cose.parameters[-2].bytesize == 32\nend","tryCatchPattern":"begin\n  openssl_key = cose_key.to_openssl_key\nrescue ActionPack::WebAuthn::InvalidKeyError, ActionPack::WebAuthn::UnsupportedKeyTypeError => e\n  # client-supplied key is unusable: fail the registration ceremony, not a 500\n  render json: { error: e.message }, status: :unprocessable_entity\nend","preventionTips":["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"],"tags":["webauthn","cose","ed25519","okp","invalid-key"],"backgroundTag":"cose-key-missing-parameter","analyzedSha":"7aabe7458060d8a1759a53b7ede39e74e6c0b20d","analyzedAt":"2026-08-21T18:33:25.349Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}