basecamp/fizzy · error · ActionPack::WebAuthn::InvalidKeyError

Missing RSA key parameters

Error message

Missing RSA key parameters

What it means

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.

Source

Thrown at lib/action_pack/web_authn/cose_key.rb:160

      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_BITS

      n = OpenSSL::BN.new(n_bytes, 2)
      e = OpenSSL::BN.new(e_bytes, 2)

      asn1 = OpenSSL::ASN1::Sequence([
        OpenSSL::ASN1::Sequence([
          OpenSSL::ASN1::ObjectId("rsaEncryption"),
          OpenSSL::ASN1::Null.new(nil)
        ]),
        OpenSSL::ASN1::BitString(
          OpenSSL::ASN1::Sequence([
            OpenSSL::ASN1::Integer(n),
            OpenSSL::ASN1::Integer(e)
          ]).to_der
        )
      ])

View on GitHub (pinned to 7aabe74580)

Solutions

  1. Inspect the decoded parameters map and confirm which of -1 (n) or -2 (e) is missing
  2. 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
  3. Complete the fixture: { 1 => 3, 3 => -257, -1 => n_bytes, -2 => e_bytes }
  4. Rescue ActionPack::WebAuthn::InvalidKeyError at the ceremony boundary and fail registration cleanly

Example fix

# before (fixture): RSA map without n/e
params = { 1 => 3, 3 => -257 }

# after: carry both RSA parameters as raw byte strings
rsa = OpenSSL::PKey::RSA.new(2048)
params = { 1 => 3, 3 => -257, -1 => rsa.n.to_s(2), -2 => rsa.e.to_s(2) }
Defensive patterns

Strategy: validation

Validate before calling

cose = ActionPack::WebAuthn::CoseKey.decode(public_key_bytes)
n = cose.parameters[ActionPack::WebAuthn::CoseKey::RSA_N_LABEL]
e = cose.parameters[ActionPack::WebAuthn::CoseKey::RSA_E_LABEL]
return if n.nil? || e.nil? # incomplete COSE RSA map
key = cose.to_openssl_key

Type guard

def valid_rsa_cose_key?(cose)
  [cose.key_type, cose.algorithm] == [3, -257] &&
    cose.parameters[-1].is_a?(String) && cose.parameters[-1].bytesize >= 256 &&
    cose.parameters[-2].is_a?(String) && !cose.parameters[-2].empty?
end

Try / catch

begin
  openssl_key = cose_key.to_openssl_key
rescue ActionPack::WebAuthn::InvalidKeyError => e
  render json: { error: e.message }, status: :unprocessable_entity
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of basecamp/fizzy@7aabe74580 (2026-08-21). Data as JSON: /api/errors/2edaaaccef376222. Report an issue: GitHub.