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

Missing EC2 key coordinates

Error message

Missing EC2 key coordinates

What it means

Building an EC2/ES256 key requires the x (-2) and y (-3) coordinate byte strings from the COSE map. If either label is missing (nil), InvalidKeyError 'Missing EC2 key coordinates' is raised before any OpenSSL work happens. In practice the map itself was malformed or mis-parsed, since real authenticators always include both coordinates.

Source

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

  # Raises +UnsupportedKeyTypeError+ if the key type, algorithm, or curve
  # is not supported.
  def to_openssl_key
    case [ key_type, algorithm ]
    when [ EC2, ES256 ] then build_ec2_es256_key
    when [ OKP, EDDSA ] then build_okp_eddsa_key
    when [ RSA, RS256 ] then build_rsa_rs256_key
    else raise ActionPack::WebAuthn::UnsupportedKeyTypeError, "Unsupported COSE key type/algorithm: #{key_type}/#{algorithm}"
    end
  end

  private
    def build_ec2_es256_key
      curve = parameters[EC2_CURVE_LABEL]
      raise ActionPack::WebAuthn::UnsupportedKeyTypeError, "Unsupported EC curve: #{curve}" unless curve == P256

      x = parameters[EC2_X_LABEL]
      y = parameters[EC2_Y_LABEL]
      raise ActionPack::WebAuthn::InvalidKeyError, "Missing EC2 key coordinates" if x.nil? || y.nil?
      raise ActionPack::WebAuthn::InvalidKeyError, "Invalid EC2 coordinate length" unless x.bytesize == P256_COORDINATE_LENGTH && y.bytesize == P256_COORDINATE_LENGTH

      # Uncompressed point format: 0x04 || x || y
      public_key_bytes = [ UNCOMPRESSED_POINT_MARKER, *x.bytes, *y.bytes ].pack("C*")

      asn1 = OpenSSL::ASN1::Sequence([
        OpenSSL::ASN1::Sequence([
          OpenSSL::ASN1::ObjectId("id-ecPublicKey"),
          OpenSSL::ASN1::ObjectId("prime256v1")
        ]),
        OpenSSL::ASN1::BitString(public_key_bytes)
      ])

      OpenSSL::PKey::EC.new(asn1.to_der)
    rescue OpenSSL::PKey::PKeyError => error
      raise ActionPack::WebAuthn::InvalidKeyError, "Invalid EC2 key: #{error.message}"
    end

View on GitHub (pinned to 7aabe74580)

Solutions

  1. Confirm the input to CoseKey.decode is exactly public_key_bytes from Authenticator::Data (bytes after the credential ID) — not the whole attested block.
  2. Inspect the decoded map: it must contain integer keys 1, 3, -1, -2, -3 with byte-string values.
  3. If coordinates crossed a JSON layer, re-encode binary values as base64url and decode back to ASCII-8BIT before decoding COSE.
  4. Rescue InvalidKeyError and fail the registration with a clear message; never substitute a default key.

Example fix

# before
# decoding from the wrong offset (includes credential-id bytes)
cose_key = ActionPack::WebAuthn::CoseKey.decode(attestation_bytes[id_offset..])

# after — use the parsed authenticator data's key slice
auth_data = ActionPack::WebAuthn::Authenticator::Data.wrap(params[:authenticator_data])
cose_key = ActionPack::WebAuthn::CoseKey.decode(auth_data.public_key_bytes)
Defensive patterns

Strategy: validation

Validate before calling

params_map = cose_key.parameters
missing = [ -1, -2, -3 ].reject { |label| params_map[label].is_a?(String) }
return render(json: { error: 'credential key incomplete' }, status: :bad_request) if missing.any? && cose_key.key_type == ActionPack::WebAuthn::CoseKey::EC2

Type guard

def complete_ec2_cose_key?(cose_key)
  !cose_key.parameters[ActionPack::WebAuthn::CoseKey::EC2_X_LABEL].nil? &&
    !cose_key.parameters[ActionPack::WebAuthn::CoseKey::EC2_Y_LABEL].nil?
end

Try / catch

begin
  key = cose_key.to_openssl_key
rescue ActionPack::WebAuthn::InvalidKeyError => e
  render json: { error: 'credential key invalid' }, status: :bad_request
end

Prevention

When it happens

Trigger: CoseKey.decode over a CBOR map that lacks label -2 or -3 — e.g. the wrong byte slice was decoded as the key (offset shifted past the map), a truncated attested credential data block, or a fixture map copied incompletely.

Common situations: Custom code that extracts public_key_bytes from authenticator data with wrong offsets; fixtures hand-built with only kty/alg/crv; CBOR maps whose labels were encoded as strings ('-2') instead of integers during relay through JSON.

Related errors


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