{"record":{"id":"d1ba3eb89be6bbe4","repo":"signalapp/Signal-Server","slug":"invalid-combined-unidentified-sender-access-keys","errorCode":null,"errorMessage":"Invalid combined unidentified sender access keys","messagePattern":"Invalid combined unidentified sender access keys","errorType":"http","errorClass":"WebApplicationException","httpStatus":401,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/auth/CombinedUnidentifiedSenderAccessKeys.java","lineNumber":20,"sourceCode":" * Copyright 2021 Signal Messenger, LLC\n * SPDX-License-Identifier: AGPL-3.0-only\n */\n\npackage org.whispersystems.textsecuregcm.auth;\n\nimport jakarta.ws.rs.WebApplicationException;\nimport jakarta.ws.rs.core.Response;\nimport jakarta.ws.rs.core.Response.Status;\nimport java.util.Base64;\n\npublic class CombinedUnidentifiedSenderAccessKeys {\n  private final byte[] combinedUnidentifiedSenderAccessKeys;\n\n  public CombinedUnidentifiedSenderAccessKeys(String header) {\n    try {\n      this.combinedUnidentifiedSenderAccessKeys = Base64.getDecoder().decode(header);\n      if (this.combinedUnidentifiedSenderAccessKeys == null || this.combinedUnidentifiedSenderAccessKeys.length != UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH) {\n        throw new WebApplicationException(\"Invalid combined unidentified sender access keys\", Status.UNAUTHORIZED);\n      }\n    } catch (IllegalArgumentException e) {\n      throw new WebApplicationException(e, Response.Status.UNAUTHORIZED);\n    }\n  }\n\n  public byte[] getAccessKeys() {\n    return combinedUnidentifiedSenderAccessKeys;\n  }\n}\n","sourceCodeStart":2,"sourceCodeEnd":31,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/auth/CombinedUnidentifiedSenderAccessKeys.java#L2-L31","documentation":"CombinedUnidentifiedSenderAccessKeys parses a header of Base64-encoded combined unidentified sender access keys and throws WebApplicationException(401) if decoding fails (IllegalArgumentException) or the decoded array is null / not 16 bytes. The combined keys value must be exactly 16 bytes like a single access key (the XOR combination).","triggerScenarios":"Supplying a header whose Base64 decodes to a non-16-byte array; malformed Base64 (caught as IllegalArgumentException and wrapped with the same message context); sending per-recipient multi-byte concatenations instead of the 16-byte combined value.","commonSituations":"Client computes the combined key incorrectly (e.g. concatenates both 16-byte keys yielding 32 bytes); wrong Base64 alphabet or padding; stale key material after re-registration.","solutions":["Compute the combined key as the protocol-specified 16-byte combination (XOR) of the sender's key and the recipient's unrestricted access key, not a concatenation","Verify the decoded length is 16 before sending","Re-derive from current identity keys if accounts re-registered"],"exampleFix":"// before\nbyte[] combined = concat(senderAccessKey, recipientAccessKey); // 32 bytes\n// after\nbyte[] combined = new byte[16];\nfor (int i = 0; i < 16; i++) combined[i] = (byte)(senderAccessKey[i] ^ recipientAccessKey[i]);","handlingStrategy":"validation","validationCode":"byte[] combined = Base64.getDecoder().decode(header);\nif (combined == null || combined.length != 16) throw new IllegalArgumentException(\"combined access keys must decode to 16 bytes\");","typeGuard":null,"tryCatchPattern":"try { authFilter.call(); } catch (WebApplicationException e) { if (e.getResponse().getStatus() == 401) { /* recompute combined key */ } }","preventionTips":["XOR-combine to 16 bytes; never concatenate access keys","Recompute after either party re-registers","Test decode length before sending"],"tags":["http-401","base64","access-key","auth"],"backgroundTag":"invalid-argument-value","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"}