{"record":{"id":"aea8fc55a9b48062","repo":"basecamp/fizzy","slug":"invalid-ec2-coordinate-length","errorCode":null,"errorMessage":"Invalid EC2 coordinate length","messagePattern":"Invalid EC2 coordinate length","errorType":"exception","errorClass":"ActionPack::WebAuthn::InvalidKeyError","httpStatus":null,"severity":"error","filePath":"lib/action_pack/web_authn/cose_key.rb","lineNumber":120,"sourceCode":"  # 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\n    def build_okp_eddsa_key","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/basecamp/fizzy/blob/7aabe7458060d8a1759a53b7ede39e74e6c0b20d/lib/action_pack/web_authn/cose_key.rb#L102-L138","documentation":"For P-256, both the x and y coordinates must be exactly 32 bytes (P256_COORDINATE_LENGTH); an ASN.1 EC point is 0x04 || x || y with fixed-width fields. If either coordinate has a different byte size, InvalidKeyError 'Invalid EC2 coordinate length' is raised before OpenSSL sees the key.","triggerScenarios":"Coordinates shorter than 32 bytes because leading zero bytes were stripped by a minimally-encoding relay (JSON integer round-trip, some CBOR encoders), or 48-byte coordinates from a P-384 key mislabeled as P-256.","commonSituations":"COSE maps transported through systems that treat byte strings as integers or bigints and normalize leading zeros; keys generated by non-conformant authenticators or test tools; coordinates hex-decoded to 31 bytes because the hex string lost a leading 00.","solutions":["Check both coordinates' bytesize before conversion and pad with zero bytes on the left (\"\\x00\" * (32 - x.bytesize) + x) only when you control and trust the encoding pipeline.","If the sizes are 48/66 bytes, the credential is really P-384/P-521 — reject it and fix registration options instead of padding.","Keep COSE keys as binary (CBOR) end-to-end; never round-trip byte strings through JSON numbers or hex without re-padding.","Rescue InvalidKeyError and fail registration — a wrong-length coordinate cannot be verified."],"exampleFix":"# before\nkey = cose_key.to_openssl_key # raises: x is 31 bytes after a JSON round-trip stripped \\x00\n\n# after — normalize coordinate width before conversion (trusted pipelines only)\nx = cose_key.parameters[CoseKey::EC2_X_LABEL].rjust(32, \"\\x00\")\ny = cose_key.parameters[CoseKey::EC2_Y_LABEL].rjust(32, \"\\x00\")\nnormalized = ActionPack::WebAuthn::CoseKey.new(key_type: 2, algorithm: -7, parameters: { -1 => 1, -2 => x, -3 => y })\nkey = normalized.to_openssl_key","handlingStrategy":"validation","validationCode":"x = cose_key.parameters[ActionPack::WebAuthn::CoseKey::EC2_X_LABEL]\ny = cose_key.parameters[ActionPack::WebAuthn::CoseKey::EC2_Y_LABEL]\nreturn render(json: { error: 'credential coordinates have wrong width' }, status: :bad_request) unless x.bytesize == 32 && y.bytesize == 32","typeGuard":"def valid_p256_coordinate_width?(cose_key)\n  x = cose_key.parameters[ActionPack::WebAuthn::CoseKey::EC2_X_LABEL]\n  y = cose_key.parameters[ActionPack::WebAuthn::CoseKey::EC2_Y_LABEL]\n  x.bytesize == 32 && y.bytesize == 32\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":["Keep COSE keys binary (CBOR) — never round-trip byte strings through integers/JSON/bigints that strip leading zeros.","Left-pad coordinates to 32 bytes only on trusted, self-controlled pipelines.","48-byte coordinates mean P-384: fix registration options instead of padding."],"tags":["webauthn","cose","elliptic-curve","coordinate-length","encoding"],"backgroundTag":"malformed-public-key","analyzedSha":"7aabe7458060d8a1759a53b7ede39e74e6c0b20d","analyzedAt":"2026-08-21T18:33:25.349Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}