basecamp/fizzy · error · ActionPack::WebAuthn::InvalidKeyError
RSA key must be at least #{MINIMUM_RSA_KEY_BITS} bits
Error message
RSA key must be at least #{MINIMUM_RSA_KEY_BITS} bits What it means
ActionPack::WebAuthn::CoseKey enforces a 2048-bit floor on RS256 keys: it raises InvalidKeyError when the modulus (label -1) is shorter than 256 bytes. A genuine RSA-2048 modulus always encodes as exactly 256 big-endian bytes with the top bit set, so a shorter modulus means the authenticator or your fixture produced a key below the WebAuthn RS256 profile's minimum strength. This is a deliberate security policy, not a parse error.
Source
Thrown at lib/action_pack/web_authn/cose_key.rb:161
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
)
])
OpenSSL::PKey::RSA.new(asn1.to_der)View on GitHub (pinned to 7aabe74580)
Solutions
- Check the modulus length: cose.parameters[-1].bytesize must be >= 256 for RSA-2048; log it in the error handler to confirm
- If this comes from a fixture, generate a real key with OpenSSL::PKey::RSA.new(2048) and export n/e via .to_s(2) instead of hand-written constants
- If a real authenticator emits keys under 2048 bits, register a different credential (ES256 or EdDSA) or use a conformant security key; do not lower MINIMUM_RSA_KEY_BITS, it is a security floor
- Verify n was not truncated or re-encoded upstream (base64url padding, hex) which can shorten the byte string
Example fix
# before: fixture modulus shorter than 256 bytes, fails the 2048-bit floor
n_bytes = (0x80..0xff).to_a.pack('C*') * 4 # 128 bytes
# after: derive from a real RSA-2048 key
rsa = OpenSSL::PKey::RSA.new(2048)
n_bytes = rsa.n.to_s(2) # exactly 256 bytes
e_bytes = rsa.e.to_s(2) Defensive patterns
Strategy: validation
Validate before calling
n = cose.parameters[ActionPack::WebAuthn::CoseKey::RSA_N_LABEL] return if n.nil? || n.bytesize * 8 < ActionPack::WebAuthn::CoseKey::MINIMUM_RSA_KEY_BITS # mirrors the 2048-bit floor key = cose.to_openssl_key
Type guard
def rsa_modulus_at_least_2048_bits?(cose) n = cose.parameters[-1] n.is_a?(String) && n.bytesize * 8 >= 2048 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
- Generate fixture RSA keys with OpenSSL::PKey::RSA.new(2048) and export n/e via .to_s(2)
- Treat MINIMUM_RSA_KEY_BITS as a security contract: never lower it to make a fixture pass
- Log modulus bytesize on registration failure to spot truncation quickly
- Prefer ES256 or EdDSA authenticators in CI to avoid RSA fixture pitfalls
When it happens
Trigger: Calling to_openssl_key on an RS256 COSE key whose -1 modulus is under 256 bytes: a test fixture with a short dummy modulus, a sub-2048-bit key from a non-conformant authenticator, or modulus bytes truncated or incorrectly re-encoded upstream so the byte string shrank.
Common situations: Adopting real security keys that use RS256 after developing only against toy fixtures; CI fixtures with hand-written small constants for n; rare authenticators emitting RSA-1024 CTAP2 credentials; hex/base64 round-trips that drop leading bytes of the modulus.
Related errors
- Missing RSA key parameters
- Invalid RSA key: #{error.message}
- Unsupported COSE key type/algorithm: #{key_type}/#{algorithm
- Unsupported EC curve: #{curve}
- Missing EC2 key coordinates
AI-assisted analysis of basecamp/fizzy@7aabe74580 (2026-08-21).
Data as JSON: /api/errors/65c3ca1180b4d0d6.
Report an issue: GitHub.