{"record":{"id":"33347c98042173ca","repo":"antiwork/gumroad","slug":"bad-cert","errorCode":"bad_cert","errorMessage":"Attestation rejected.","messagePattern":"Attestation rejected\\.","errorType":"http","errorClass":null,"httpStatus":422,"severity":"warning","filePath":"app/services/walks_app_attest_verifier.rb","lineNumber":97,"sourceCode":"      return fail_result(:bad_counter) unless parsed[:counter].zero?\n\n      environment = ENVIRONMENT_BY_AAGUID[parsed[:aaguid]]\n      return fail_result(:bad_aaguid) unless environment\n      return fail_result(:wrong_env_for_rails) unless aaguid_allowed_in_env?(environment)\n      return fail_result(:credential_id_mismatch) unless parsed[:credential_id] == credential_id\n\n      key = WalksAppAttestKey.create!(\n        key_id: key_id,\n        public_key: cred_cert.public_key.to_der,\n        environment: environment,\n        attested_at: Time.current,\n      )\n      Result.new(valid?: true, key: key)\n    rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid\n      fail_result(:duplicate_key)\n    rescue OpenSSL::X509::CertificateError, OpenSSL::PKey::PKeyError, ArgumentError => e\n      Rails.logger.warn(\"WalksAppAttestVerifier.attest failed: #{e.class} #{e.message}\")\n      fail_result(:bad_cert)\n    rescue CBOR::MalformedFormatError, EOFError => e\n      Rails.logger.warn(\"WalksAppAttestVerifier.attest CBOR error: #{e.class} #{e.message}\")\n      fail_result(:bad_cbor)\n    end\n\n    def assert(key_id:, assertion_b64:, challenge:, request_body:)\n      return fail_result(:missing_key_id) if key_id.blank?\n      return fail_result(:missing_assertion) if assertion_b64.blank?\n      return fail_result(:missing_challenge) if challenge.blank?\n\n      key = WalksAppAttestKey.find_by(key_id: key_id)\n      return fail_result(:unknown_key) unless key\n\n      # Consume the challenge only after the keyId is known to be real.\n      # A scraper hitting the endpoint with a random keyId would otherwise\n      # be able to burn challenges issued to legitimate devices (single-use\n      # Redis nonces). Since keyIds are SHA256(pubkey) — unguessable —\n      # gating consume on `find_by` closes the DoS without changing the","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/services/walks_app_attest_verifier.rb#L79-L115","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: urlsafe base64 / re-encoded payload\nlet b64 = attestationObject.base64URLEncodedString()\n\n// after: standard base64 of the untouched object\nlet b64 = attestationObject.base64EncodedString()","handlingStrategy":"validation","validationCode":"# Caller: branch on the Result symbol, never on an exception\nresult = WalksAppAttestVerifier.attest(key_id:, attestation_b64:, challenge:)\nreturn head :unprocessable_entity unless result.valid?\n# result.error == :bad_cert means the x5c chain/key failed OpenSSL parsing — client must re-attest with a fresh challenge","typeGuard":"# Ruby: narrow on the stable error symbol before deciding a response\nBAD_REQUEST_ERRORS = %i[missing_key_id missing_attestation invalid_challenge bad_cbor bad_cert wrong_fmt].freeze\n\n attest_rejected_as_bad_request = ->(r) { !r.valid? && BAD_REQUEST_ERRORS.include?(r.error) }","tryCatchPattern":"# Not needed: attest/assert never raise on bad input; every failure path returns\n# WalksAppAttestVerifier::Result (valid?: false, error: <symbol>) that the caller logs and maps to 4xx","preventionTips":["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."],"tags":["app-attest","ios","security","openssl","certificate","devicecheck"],"backgroundTag":"app-attest-verification-failed","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}