{"record":{"id":"2edaaaccef376222","repo":"basecamp/fizzy","slug":"missing-rsa-key-parameters","errorCode":null,"errorMessage":"Missing RSA key parameters","messagePattern":"Missing RSA key parameters","errorType":"exception","errorClass":"ActionPack::WebAuthn::InvalidKeyError","httpStatus":null,"severity":"error","filePath":"lib/action_pack/web_authn/cose_key.rb","lineNumber":160,"sourceCode":"      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\n\n      n = OpenSSL::BN.new(n_bytes, 2)\n      e = OpenSSL::BN.new(e_bytes, 2)\n\n      asn1 = OpenSSL::ASN1::Sequence([\n        OpenSSL::ASN1::Sequence([\n          OpenSSL::ASN1::ObjectId(\"rsaEncryption\"),\n          OpenSSL::ASN1::Null.new(nil)\n        ]),\n        OpenSSL::ASN1::BitString(\n          OpenSSL::ASN1::Sequence([\n            OpenSSL::ASN1::Integer(n),\n            OpenSSL::ASN1::Integer(e)\n          ]).to_der\n        )\n      ])\n","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/basecamp/fizzy/blob/7aabe7458060d8a1759a53b7ede39e74e6c0b20d/lib/action_pack/web_authn/cose_key.rb#L142-L178","documentation":"ActionPack::WebAuthn::CoseKey raised InvalidKeyError because a COSE RSA key (key type 3, algorithm -257 RS256) was missing label -1 (the modulus n) or label -2 (the public exponent e). RFC 9053 requires both integer parameters for an RSA public key, and the library refuses to guess or default before constructing the OpenSSL key. It means the decoded CBOR map was structurally incomplete.","triggerScenarios":"Calling to_openssl_key on a COSE map with {1=>3, 3=>-257} that lacks -1 or -2: typically a fixture built with only one RSA parameter, or credential-public-key bytes sliced at the wrong offset in authenticatorData (misread credential ID length) so the remaining CBOR decodes to a partial map.","commonSituations":"Switching test fixtures from EC2/OKP keys to RSA without remembering that -1/-2 carry different parameters per key type; offset drift while hand-parsing attestedCredentialData after credential ID lengths change; emulators emitting incomplete RSA COSE maps.","solutions":["Inspect the decoded parameters map and confirm which of -1 (n) or -2 (e) is missing","Fix the slice offset of the credential public key in authenticatorData (16-byte AAGUID + 2-byte credentialIdLength + credentialId) and verify against a known-good WebAuthn payload such as one from webauthn.io","Complete the fixture: { 1 => 3, 3 => -257, -1 => n_bytes, -2 => e_bytes }","Rescue ActionPack::WebAuthn::InvalidKeyError at the ceremony boundary and fail registration cleanly"],"exampleFix":"# before (fixture): RSA map without n/e\nparams = { 1 => 3, 3 => -257 }\n\n# after: carry both RSA parameters as raw byte strings\nrsa = OpenSSL::PKey::RSA.new(2048)\nparams = { 1 => 3, 3 => -257, -1 => rsa.n.to_s(2), -2 => rsa.e.to_s(2) }","handlingStrategy":"validation","validationCode":"cose = ActionPack::WebAuthn::CoseKey.decode(public_key_bytes)\nn = cose.parameters[ActionPack::WebAuthn::CoseKey::RSA_N_LABEL]\ne = cose.parameters[ActionPack::WebAuthn::CoseKey::RSA_E_LABEL]\nreturn if n.nil? || e.nil? # incomplete COSE RSA map\nkey = cose.to_openssl_key","typeGuard":"def valid_rsa_cose_key?(cose)\n  [cose.key_type, cose.algorithm] == [3, -257] &&\n    cose.parameters[-1].is_a?(String) && cose.parameters[-1].bytesize >= 256 &&\n    cose.parameters[-2].is_a?(String) && !cose.parameters[-2].empty?\nend","tryCatchPattern":"begin\n  openssl_key = cose_key.to_openssl_key\nrescue ActionPack::WebAuthn::InvalidKeyError => e\n  render json: { error: e.message }, status: :unprocessable_entity\nend","preventionTips":["When switching fixtures between EC2, OKP, and RSA key types, update all labels: -1/-2 mean different parameters per key type","Re-verify authenticator-data offsets whenever credential ID lengths change","Rescue InvalidKeyError at the ceremony boundary and log parameters.keys to see which labels arrived","Use CoseKey.decode in tests the same way production code does so fixture shape gets exercised"],"tags":["webauthn","cose","rsa","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"}