antiwork/gumroad · warning
bad_cert
bad_cert
Error message
Attestation rejected.
What it means
WalksAppAttestVerifier.attest never raises on bad input — it returns a Result with valid? false and a stable error symbol. The :bad_cert code maps OpenSSL::X509::CertificateError, OpenSSL::PKey::PKeyError, and ArgumentError from parsing the attestation's x5c chain or key material: structurally invalid certificates, wrong key type, or base64 that decoded to garbage bytes. Earlier shape guards (x5c must be a non-empty array of non-empty strings) already passed, so the DER content itself failed OpenSSL.
Source
Thrown at app/services/walks_app_attest_verifier.rb:97
return fail_result(:bad_counter) unless parsed[:counter].zero?
environment = ENVIRONMENT_BY_AAGUID[parsed[:aaguid]]
return fail_result(:bad_aaguid) unless environment
return fail_result(:wrong_env_for_rails) unless aaguid_allowed_in_env?(environment)
return fail_result(:credential_id_mismatch) unless parsed[:credential_id] == credential_id
key = WalksAppAttestKey.create!(
key_id: key_id,
public_key: cred_cert.public_key.to_der,
environment: environment,
attested_at: Time.current,
)
Result.new(valid?: true, key: key)
rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
fail_result(:duplicate_key)
rescue OpenSSL::X509::CertificateError, OpenSSL::PKey::PKeyError, ArgumentError => e
Rails.logger.warn("WalksAppAttestVerifier.attest failed: #{e.class} #{e.message}")
fail_result(:bad_cert)
rescue CBOR::MalformedFormatError, EOFError => e
Rails.logger.warn("WalksAppAttestVerifier.attest CBOR error: #{e.class} #{e.message}")
fail_result(:bad_cbor)
end
def assert(key_id:, assertion_b64:, challenge:, request_body:)
return fail_result(:missing_key_id) if key_id.blank?
return fail_result(:missing_assertion) if assertion_b64.blank?
return fail_result(:missing_challenge) if challenge.blank?
key = WalksAppAttestKey.find_by(key_id: key_id)
return fail_result(:unknown_key) unless key
# Consume the challenge only after the keyId is known to be real.
# A scraper hitting the endpoint with a random keyId would otherwise
# be able to burn challenges issued to legitimate devices (single-use
# Redis nonces). Since keyIds are SHA256(pubkey) — unguessable —
# gating consume on `find_by` closes the DoS without changing theView on GitHub (pinned to afeacbd394)
Solutions
- On the client, send standard-alphabet base64 (e.g. base64EncodedString()) of the DCAppAttestService.attestKey object untouched — no re-encoding of the CBOR.
- Check the Rails warn line "WalksAppAttestVerifier.attest failed: <class> <message>" to see which OpenSSL parse failed.
- Compare the received blob byte-for-byte with what the device produced and re-attest with a fresh challenge.
- Third-party clients: forward Apple's attestation object as-is; any transformation produces exactly this error.
Example fix
// before: urlsafe base64 / re-encoded payload let b64 = attestationObject.base64URLEncodedString() // after: standard base64 of the untouched object let b64 = attestationObject.base64EncodedString()
Defensive patterns
Strategy: validation
Validate before calling
# Caller: branch on the Result symbol, never on an exception result = WalksAppAttestVerifier.attest(key_id:, attestation_b64:, challenge:) return head :unprocessable_entity unless result.valid? # result.error == :bad_cert means the x5c chain/key failed OpenSSL parsing — client must re-attest with a fresh challenge
Type guard
# Ruby: narrow on the stable error symbol before deciding a response
BAD_REQUEST_ERRORS = %i[missing_key_id missing_attestation invalid_challenge bad_cbor bad_cert wrong_fmt].freeze
attest_rejected_as_bad_request = ->(r) { !r.valid? && BAD_REQUEST_ERRORS.include?(r.error) } Try / catch
# Not needed: attest/assert never raise on bad input; every failure path returns # WalksAppAttestVerifier::Result (valid?: false, error: <symbol>) that the caller logs and maps to 4xx
Prevention
- Forward Apple's attestation object byte-for-byte: standard base64, no CBOR re-encoding, no urlsafe alphabet.
- Always request a fresh server challenge before attestKey; challenges are single-use.
- Log the error symbol plus the warn line's exception class to separate device bugs from tampering.
When it happens
Trigger: An iOS client sends an attestation blob whose x5c entries are truncated or corrupted DER, base64 decoded to garbage (wrong alphabet such as urlsafe vs standard, bad padding), a certificate whose public key is not EC P-256, or a proxy mangled the request body between device and server.
Common situations: Client-side base64/CBOR encoding bugs (double encoding, re-encoding the CBOR instead of forwarding Apple's object), tampered attestations from non-genuine clients, test payloads captured from a different environment, or request-body corruption in transit.
Related errors
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/33347c98042173ca.
Report an issue: GitHub.