{"record":{"id":"2664b7e79d5ee111","repo":"signalapp/Signal-Server","slug":"backup-auth-credential-presentation-verification-f","errorCode":null,"errorMessage":"backup auth credential presentation verification failed","messagePattern":"backup auth credential presentation verification failed","errorType":"exception","errorClass":"BackupFailedZkAuthenticationException","httpStatus":401,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java","lineNumber":752,"sourceCode":"    Pair<BackupCredentialType, BackupLevel> verifySignature(byte[] signature, ECPublicKey publicKey) throws BackupFailedZkAuthenticationException;\n  }\n\n  /**\n   * Verify the presentation was issued by us, which should be done before checking the stored public key\n   *\n   * @param presentation A ZK credential presentation that encodes the backupId and the receipt level of the requester\n   * @return A function that can be used to verify a signature provided with the presentation\n   */\n  private PresentationSignatureVerifier verifyPresentation(final BackupAuthCredentialPresentation presentation)\n      throws BackupFailedZkAuthenticationException {\n    try {\n      presentation.verify(clock.instant(), serverSecretParams);\n    } catch (VerificationFailedException e) {\n      Metrics.counter(ZK_AUTHN_COUNTER_NAME,\n              SUCCESS_TAG_NAME, String.valueOf(false),\n              FAILURE_REASON_TAG_NAME, \"presentation_verification\")\n          .increment();\n      throw new BackupFailedZkAuthenticationException(\"backup auth credential presentation verification failed\");\n    }\n    return (signature, publicKey) -> {\n      if (!publicKey.verifySignature(presentation.serialize(), signature)) {\n        Metrics.counter(ZK_AUTHN_COUNTER_NAME,\n                SUCCESS_TAG_NAME, String.valueOf(false),\n                FAILURE_REASON_TAG_NAME, \"signature_validation\")\n            .increment();\n        throw new BackupFailedZkAuthenticationException(\"backup auth credential presentation signature verification failed\");\n      }\n      return new Pair<>(presentation.getType(), presentation.getBackupLevel());\n    };\n  }\n\n  /**\n   * Check that the authenticated backup user is authorized to use the provided backupLevel\n   *\n   * @param backupUser  The backup user to check\n   * @param backupLevel The authorization level to verify the backupUser has access to","sourceCodeStart":734,"sourceCodeEnd":770,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java#L734-L770","documentation":"BackupFailedZkAuthenticationException thrown during backup authentication when presentation.verify(clock.instant(), serverSecretParams) raises VerificationFailedException for the backup auth credential presentation. The zero-knowledge proof embedded in the presentation does not verify against the server's secret parameters, so the request is rejected before any signature or authorization checks. A failure metric tagged presentation_verification is emitted for every occurrence.","triggerScenarios":"Presenting a backup auth credential whose ZK proof is malformed, was created against different serverSecretParams (staging vs production keys), or was truncated/tampered in transit; also credentials generated by an incompatible client ZK library version.","commonSituations":"Pointing a dev client at production (or vice versa) so credentials are minted under the wrong server params; serializing the presentation incorrectly (re-encoding after partial parsing); replaying a presentation after the server rotated secret params; fuzzed or corrupted request bodies.","solutions":["Regenerate the backup auth credential and presentation with the same serverSecretParams the server uses","Confirm client and server are in the same environment (matching public/secret parameter set)","Serialize the presentation exactly once, as produced by the ZK library, without intermediate re-parsing","Check client ZK credential library version compatibility with the server; upgrade if parameters changed"],"exampleFix":"// before\nbyte[] body = partiallyReparsedPresentation(); // proof bytes corrupted\nauthenticateBackup(body); // presentation verification failed\n// after\nbyte[] body = presentation.serialize(); // single canonical serialization from ZK lib\nauthenticateBackup(body);","handlingStrategy":"validation","validationCode":"if (!paramsEnv.equals(serverEnv)) {\n  throw new IllegalStateException(\"credential minted with wrong serverSecretParams environment\");\n}\n// verify locally with the server's public parameters before sending\npresentation.verify(Instant.now(), serverPublicParams);","typeGuard":null,"tryCatchPattern":"try { authenticateBackup(presentation, signature); }\ncatch (BackupFailedZkAuthenticationException e) {\n  regenerateCredentialAndPresentation(); // proof unverifiable: mint a fresh credential\n}","preventionTips":["Verify the presentation locally against server public params before sending","Never re-serialize or transform the presentation after the ZK library produces it","Keep client/server ZK parameter environments matched (no staging/prod mixing)","Track server secret-param rotations and re-mint credentials after them"],"tags":["zk-authentication","backup","zero-knowledge"],"backgroundTag":"credential-verification-failed","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}