{"record":{"id":"5e217e2f4d9447a0","repo":"basecamp/fizzy","slug":"missing-ec2-key-coordinates","errorCode":null,"errorMessage":"Missing EC2 key coordinates","messagePattern":"Missing EC2 key coordinates","errorType":"exception","errorClass":"ActionPack::WebAuthn::InvalidKeyError","httpStatus":null,"severity":"error","filePath":"lib/action_pack/web_authn/cose_key.rb","lineNumber":119,"sourceCode":"  # Raises +UnsupportedKeyTypeError+ if the key type, algorithm, or curve\n  # is not supported.\n  def to_openssl_key\n    case [ key_type, algorithm ]\n    when [ EC2, ES256 ] then build_ec2_es256_key\n    when [ OKP, EDDSA ] then build_okp_eddsa_key\n    when [ RSA, RS256 ] then build_rsa_rs256_key\n    else raise ActionPack::WebAuthn::UnsupportedKeyTypeError, \"Unsupported COSE key type/algorithm: #{key_type}/#{algorithm}\"\n    end\n  end\n\n  private\n    def build_ec2_es256_key\n      curve = parameters[EC2_CURVE_LABEL]\n      raise ActionPack::WebAuthn::UnsupportedKeyTypeError, \"Unsupported EC curve: #{curve}\" unless curve == P256\n\n      x = parameters[EC2_X_LABEL]\n      y = parameters[EC2_Y_LABEL]\n      raise ActionPack::WebAuthn::InvalidKeyError, \"Missing EC2 key coordinates\" if x.nil? || y.nil?\n      raise ActionPack::WebAuthn::InvalidKeyError, \"Invalid EC2 coordinate length\" unless x.bytesize == P256_COORDINATE_LENGTH && y.bytesize == P256_COORDINATE_LENGTH\n\n      # Uncompressed point format: 0x04 || x || y\n      public_key_bytes = [ UNCOMPRESSED_POINT_MARKER, *x.bytes, *y.bytes ].pack(\"C*\")\n\n      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","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/basecamp/fizzy/blob/7aabe7458060d8a1759a53b7ede39e74e6c0b20d/lib/action_pack/web_authn/cose_key.rb#L101-L137","documentation":"Building an EC2/ES256 key requires the x (-2) and y (-3) coordinate byte strings from the COSE map. If either label is missing (nil), InvalidKeyError 'Missing EC2 key coordinates' is raised before any OpenSSL work happens. In practice the map itself was malformed or mis-parsed, since real authenticators always include both coordinates.","triggerScenarios":"CoseKey.decode over a CBOR map that lacks label -2 or -3 — e.g. the wrong byte slice was decoded as the key (offset shifted past the map), a truncated attested credential data block, or a fixture map copied incompletely.","commonSituations":"Custom code that extracts public_key_bytes from authenticator data with wrong offsets; fixtures hand-built with only kty/alg/crv; CBOR maps whose labels were encoded as strings ('-2') instead of integers during relay through JSON.","solutions":["Confirm the input to CoseKey.decode is exactly public_key_bytes from Authenticator::Data (bytes after the credential ID) — not the whole attested block.","Inspect the decoded map: it must contain integer keys 1, 3, -1, -2, -3 with byte-string values.","If coordinates crossed a JSON layer, re-encode binary values as base64url and decode back to ASCII-8BIT before decoding COSE.","Rescue InvalidKeyError and fail the registration with a clear message; never substitute a default key."],"exampleFix":"# before\n# decoding from the wrong offset (includes credential-id bytes)\ncose_key = ActionPack::WebAuthn::CoseKey.decode(attestation_bytes[id_offset..])\n\n# after — use the parsed authenticator data's key slice\nauth_data = ActionPack::WebAuthn::Authenticator::Data.wrap(params[:authenticator_data])\ncose_key = ActionPack::WebAuthn::CoseKey.decode(auth_data.public_key_bytes)","handlingStrategy":"validation","validationCode":"params_map = cose_key.parameters\nmissing = [ -1, -2, -3 ].reject { |label| params_map[label].is_a?(String) }\nreturn render(json: { error: 'credential key incomplete' }, status: :bad_request) if missing.any? && cose_key.key_type == ActionPack::WebAuthn::CoseKey::EC2","typeGuard":"def complete_ec2_cose_key?(cose_key)\n  !cose_key.parameters[ActionPack::WebAuthn::CoseKey::EC2_X_LABEL].nil? &&\n    !cose_key.parameters[ActionPack::WebAuthn::CoseKey::EC2_Y_LABEL].nil?\nend","tryCatchPattern":"begin\n  key = cose_key.to_openssl_key\nrescue ActionPack::WebAuthn::InvalidKeyError => e\n  render json: { error: 'credential key invalid' }, status: :bad_request\nend","preventionTips":["Always extract the key as authenticator_data.public_key_bytes — never slice raw attestation bytes yourself.","Keep COSE maps in CBOR end-to-end; JSON relays must encode byte strings as base64url, not drop them.","Fail registration on missing coordinates — there is no safe default."],"tags":["webauthn","cose","public-key","malformed-data"],"backgroundTag":"malformed-public-key","analyzedSha":"7aabe7458060d8a1759a53b7ede39e74e6c0b20d","analyzedAt":"2026-08-21T18:33:25.349Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}