signalapp/Signal-Server · error · IllegalArgumentException
Key ID %08x has been reserved or revoked and may not be…
Error message
Key ID %08x has been reserved or revoked and may not be used in new certificates.
What it means
CertificateCommand refuses to generate a new server certificate signing key certificate when the operator-supplied --keyId is in the RESERVED_CERTIFICATE_IDS set. Those IDs belong to previously issued or revoked certificate keys and reusing them would let new certificates masquerade as ones chained to a revoked key. The command fails fast with an IllegalArgumentException naming the offending ID.
Solutions
- Pick a fresh key ID not present in RESERVED_CERTIFICATE_IDS in CertificateCommand.java
- Check the RESERVED_CERTIFICATE_IDS set in the source to see which values are blocked
- Re-run the certificate command with the new --keyId value
Example fix
// before .//bin/textsecure-server certificate --keyId 2 --key <base64> // after ./bin/textsecure-server certificate --keyId 7 --key <base64>
Defensive patterns
Strategy: validation
Validate before calling
int keyId = namespace.getInt("keyId");
if (RESERVED_CERTIFICATE_IDS.contains(keyId)) {
throw new IllegalArgumentException("keyId " + keyId + " is reserved; choose another");
} Try / catch
try { runCertificateCommand(...); } catch (IllegalArgumentException e) { logger.error("keyId rejected: {}", e.getMessage()); } Prevention
- Consult RESERVED_CERTIFICATE_IDS before choosing a keyId
- Use high, non-colliding key IDs for new certificates
- Document key ID allocations per environment
When it happens
Trigger: Running the `certificate` command (via Dropwizard's `run` → `runCertificateCommand`) with a `--keyId` numeric flag whose value appears in RESERVED_CERTIFICATE_IDS.
Common situations: Re-issuing certificates after a key rotation and picking the old key ID; copying a deployment script from an older environment that used a since-reserved ID; fat-fingering a small keyId like 1 or 2 that collides with reserved entries.
Understand the failure class
Background: "Unknown argument", "Invalid value", and "must be one of": invalid CLI argument errors explained — this error's family across 35 libraries.
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Response body was below minimum
- Got a non-200 reply from source URI:
- target-queue-size-bytes must be positive
- target-queue-size-bytes must be less than…
- range-split-chunk-size-bytes must be positive
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/c28e03699ee5edaf.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java:80
System.out.println("Private key: " + Base64.getEncoder().encodeToString(keyPair.getPrivateKey().serialize()));
}
private void runCertificateCommand(Namespace namespace) throws InvalidKeyException, org.signal.libsignal.protocol.InvalidKeyException {
if (namespace.getString("key") == null) {
System.out.println("No key specified!");
return;
}
if (namespace.getInt("keyId") == null) {
System.out.print("No key id specified!");
return;
}
ECPrivateKey key = new ECPrivateKey(Base64.getDecoder().decode(namespace.getString("key")));
int keyId = namespace.getInt("keyId");
if (RESERVED_CERTIFICATE_IDS.contains(keyId)) {
throw new IllegalArgumentException(
String.format("Key ID %08x has been reserved or revoked and may not be used in new certificates.", keyId));
}
ECKeyPair keyPair = ECKeyPair.generate();
byte[] certificate = MessageProtos.ServerCertificate.Certificate.newBuilder()
.setId(keyId)
.setKey(ByteString.copyFrom(keyPair.getPublicKey().serialize()))
.build()
.toByteArray();
byte[] signature;
signature = key.calculateSignature(certificate);
byte[] signedCertificate = MessageProtos.ServerCertificate.newBuilder()
.setCertificate(ByteString.copyFrom(certificate))
.setSignature(ByteString.copyFrom(signature))
.build()View on GitHub (pinned to 100ab61c82)