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

Invalid OKP key: #{error.message}

Error message

Invalid OKP key: #{error.message}

What it means

ActionPack::WebAuthn::CoseKey wrapped an OpenSSL::PKey::PKeyError raised while reading an Ed25519 public key: the COSE map did carry an x-coordinate (label -2), but the DER SubjectPublicKeyInfo built from it failed OpenSSL::PKey.read. The usual causes are that the coordinate is not exactly 32 raw bytes (wrong CBOR type, hex text, truncation) or that the linked OpenSSL/LibreSSL lacks Ed25519 support (needs OpenSSL 1.1.1 or later). The library re-raises as InvalidKeyError with the original OpenSSL message appended, e.g. 'Invalid OKP key: ...'.

Source

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

    end

    def build_okp_eddsa_key
      curve = parameters[OKP_CURVE_LABEL]
      raise ActionPack::WebAuthn::UnsupportedKeyTypeError, "Unsupported OKP curve: #{curve}" unless curve == ED25519

      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([

View on GitHub (pinned to 7aabe74580)

Solutions

  1. Check what -2 actually contains after decode: it must be a 32-byte binary String; convert hex or base64url input with pack('H*') or Base64.urlsafe_decode64 before building the COSE map
  2. Verify your Ruby's OpenSSL supports Ed25519: ruby -ropenssl -e 'puts OpenSSL::OPENSSL_LIBRARY_VERSION' (needs OpenSSL >= 1.1.1); on macOS system Ruby or old Linux, switch to a Ruby build linked against modern OpenSSL
  3. Rescue ActionPack::WebAuthn::InvalidKeyError at the registration boundary and reject the credential when Ed25519 conversion fails
  4. For fixtures, take the x-coordinate from a real Ed25519 public key rather than random bytes

Example fix

# before: coordinate ends up as hex text, so OpenSSL::PKey.read fails
x = '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'

# after: pack to the raw 32 bytes the COSE label expects
x = ['9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'].pack('H*')
Defensive patterns

Strategy: try-catch

Validate before calling

x = cose.parameters[-2]
raise ArgumentError, 'bad Ed25519 coordinate' unless x.is_a?(String) && x.bytesize == 32
# Ed25519 requires OpenSSL >= 1.1.1
version = (OpenSSL::OPENSSL_LIBRARY_VERSION rescue OpenSSL::OPENSSL_VERSION)
raise LoadError, 'OpenSSL lacks Ed25519 support' unless version =~ /OpenSSL (1\.1\.1|3\.)/

Try / catch

begin
  openssl_key = cose_key.to_openssl_key
rescue ActionPack::WebAuthn::InvalidKeyError => e
  # error.message carries the underlying OpenSSL reason; fail the ceremony cleanly
  render json: { error: e.message }, status: :unprocessable_entity
end

Prevention

When it happens

Trigger: Calling to_openssl_key on an OKP/EdDSA key whose -2 value is present but not a 32-byte binary String (an Integer, hex text, or wrong length), so OpenSSL::PKey.read raises; or running on a Ruby linked against OpenSSL < 1.1.1 or LibreSSL without Ed25519, where any Ed25519 SPKI read fails regardless of the coordinate.

Common situations: Enabling EdDSA passkey support on a host with an old OpenSSL (CentOS 7 OpenSSL 1.0.2, macOS system Ruby with LibreSSL); CBOR or JSON layers returning the coordinate as a hex string instead of bytes; base64url mis-decoding (lost padding) truncating the key bytes in transit.

Related errors


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