{"record":{"id":"a1d059ad87815f67","repo":"signalapp/Signal-Server","slug":"backup-auth-credential-presentation-signature-veri","errorCode":null,"errorMessage":"backup auth credential presentation signature verification failed","messagePattern":"backup auth credential presentation signature verification failed","errorType":"exception","errorClass":"BackupFailedZkAuthenticationException","httpStatus":401,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java","lineNumber":760,"sourceCode":"   */\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\n   * @throws BackupPermissionException if the backupUser is not authorized to access {@code backupLevel}\n   */\n  @VisibleForTesting\n  static void checkBackupLevel(final AuthenticatedBackupUser backupUser, final BackupLevel backupLevel)\n      throws BackupPermissionException {\n    if (backupUser.backupLevel().compareTo(backupLevel) < 0) {\n      Metrics.counter(ZK_AUTHZ_FAILURE_COUNTER_NAME, Tags.of(\n              UserAgentTagUtil.getPlatformTag(backupUser.userAgent()),","sourceCodeStart":742,"sourceCodeEnd":778,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java#L742-L778","documentation":"BackupFailedZkAuthenticationException thrown after the ZK proof verifies but the signature over the presentation, checked via publicKey.verifySignature(presentation.serialize(), signature), fails. The client must prove possession of the private key corresponding to the presentation's public key; a bad signature means the requester did not, so authentication is aborted. A metric tagged signature_validation is incremented.","triggerScenarios":"Signing something other than the exact presentation.serialize() bytes, signing with the wrong private key, or the signature being mangled/truncated by transport encoding — the server re-verifies the signature over the canonical serialized presentation with the presentation's public key.","commonSituations":"Client sign helpers that canonicalize or re-serialize the payload (different byte encoding than the server's); key material mismatch after account re-registration; base64/URL-encoding differences that corrupt the signature; replay proxies altering the body.","solutions":["Sign the exact byte array returned by presentation.serialize(), without re-encoding","Use the private key pair corresponding to the credential presentation's public key","Send the signature with byte-preserving encodings (avoid transformations that alter bytes)","Regenerate the keypair and credential if account keys were reset"],"exampleFix":"// before\nsignature = sign(canonicalJson(presentation)); // wrong bytes\n// after\nsignature = sign(presentation.serialize()); // exact serialized presentation bytes","handlingStrategy":"validation","validationCode":"byte[] serialized = presentation.serialize();\nif (!publicKey.verifySignature(serialized, signature)) {\n  throw new IllegalStateException(\"local signature check failed before sending\");\n}","typeGuard":null,"tryCatchPattern":"try { authenticateBackup(presentation, signature); }\ncatch (BackupFailedZkAuthenticationException e) {\n  reSignAndRetryOnce(presentation); // recompute signature over exact serialize() bytes\n}","preventionTips":["Sign presentation.serialize() bytes exactly, once, with the matching private key","Avoid encodings that transform signature bytes in transit","Verify the signature locally with the public key before sending","Re-sign after any change to key material or serialization"],"tags":["zk-authentication","signature","backup"],"backgroundTag":"signature-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"}