Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ab1e729
feat(signing): add core L1 signing SPI interfaces (AdcpUse, SigningCo…
Jun 17, 2026
3b3ad10
feat(signing): add RFC 9421 canonicalizer, signer, verifier, and conf…
Jun 17, 2026
2f4e923
feat(signing): add InProcessSigningProvider, WebhookSigner seam, and …
Jun 17, 2026
8bfda3b
feat(signing): add KMS provider module skeletons and BouncyCastle stub
Jun 17, 2026
19ed167
docs(signing): add RFC 9421 canonicalizer design spec
Jun 17, 2026
28697e5
feat(signing): implement AWS KMS SigningProvider with lazy-init, trip…
Jun 17, 2026
3578adf
feat(signing): implement GCP KMS SigningProvider with lazy-init, trip…
Jun 17, 2026
57fbd1e
feat(signing): add JWKS VerificationKeyResolver with caching, SSRF va…
Jun 17, 2026
689d122
feat(signing): add InMemoryReplayStore and InMemoryRevocationStore fo…
Jun 17, 2026
82802fa
feat(signing): add pre-deploy KMS probe CLI command
Jun 17, 2026
3ce0955
feat(signing): add SigningKeyGenerator for Ed25519/ES256/ES384 keypai…
Jun 17, 2026
d98cf3e
feat(signing): add IDNA hostname canonicalization to Rfc9421Canonical…
Jun 17, 2026
15ebe32
feat(signing): add key origin consistency check and eTLD+1 utility
Jun 17, 2026
7147c61
feat(signing): add JWS verification for revocation lists
Jun 17, 2026
fada93f
feat(signing): add CachingRevocationChecker with JWS verification
Jun 17, 2026
9d4f17d
feat(signing): add legacy HMAC-SHA256 webhook verification (deprecated)
Jun 17, 2026
f3d9c7b
feat(signing): add Standard Webhooks v1 interop verifier
Jun 17, 2026
5ee26c1
feat(signing): add webhook challenge proof-of-control
Jun 17, 2026
7165a40
chore: add gitguardian config to ignore test key fixtures
Jun 17, 2026
a15e3f6
chore: add gitguardian config to ignore test key fixtures
Jun 17, 2026
125b8a5
chore: remove gitguardian config (paths-ignore requires default branc…
Jun 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .gitguardian.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# GitGuardian configuration for adcp-sdk-java
#
# Test key material in the compliance vectors is intentionally public.
# The _private_d_for_test_only fields are for signer/verifier round-trip
# conformance testing and MUST NOT be used in production. The keys.json
# files carry explicit warnings in their _WARNING and $comment fields.
#
# See: adcp-server/src/test/resources/compliance/webhook-signing/README.md
# See: adcp-server/src/test/resources/compliance/request-signing/README.md

paths-ignore:
- "adcp-server/src/test/resources/compliance/**/keys.json"
- "adcp-server/src/test/resources/compliance/**/*hmac*"
118 changes: 113 additions & 5 deletions adcp-cli/src/main/java/org/adcontextprotocol/adcp/cli/Main.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package org.adcontextprotocol.adcp.cli;

import org.adcontextprotocol.adcp.signing.AdcpUse;

import java.util.ArrayList;
import java.util.List;

/**
* Entry point for the {@code adcp} CLI. Commands land here as the CLI track
* is implemented; today this is a placeholder that prints a usage stub.
* Entry point for the {@code adcp} CLI.
*
* <p>Subcommands:
* <ul>
* <li>{@code signing-probe} — pre-deploy KMS connectivity and key usability check</li>
* </ul>
*/
public final class Main {

Expand All @@ -11,7 +20,106 @@ private Main() {
}

public static void main(String[] args) {
System.out.println("adcp <not yet implemented>");
System.out.println("see ROADMAP.md track 13 (cli) for the planned surface");
if (args.length == 0) {
printUsage();
System.exit(1);
}

String command = args[0];
String[] subArgs = new String[args.length - 1];
System.arraycopy(args, 1, subArgs, 0, subArgs.length);

switch (command) {
case "signing-probe" -> runSigningProbe(subArgs);
default -> {
System.err.println("Unknown command: " + command);
printUsage();
System.exit(1);
}
}
}

private static void runSigningProbe(String[] args) {
SigningProbeCommand probe = new SigningProbeCommand();
List<String> errors = new ArrayList<>();

int i = 0;
while (i < args.length) {
String arg = args[i];
switch (arg) {
case "--aws-key" -> {
if (i + 3 >= args.length) {
errors.add("--aws-key requires <use> <key-arn> <region>");
i = args.length;
} else {
try {
AdcpUse use = AdcpUse.fromWireName(args[i + 1]);
probe.addAwsKey(use, args[i + 2], args[i + 3]);
} catch (IllegalArgumentException e) {
errors.add("Invalid adcp_use for --aws-key: " + args[i + 1]
+ ". Valid values: adcp_req, adcp_whk");
}
i += 4;
}
}
case "--gcp-key" -> {
if (i + 3 >= args.length) {
errors.add("--gcp-key requires <use> <key-version-path> <credentials-path>");
i = args.length;
} else {
try {
AdcpUse use = AdcpUse.fromWireName(args[i + 1]);
probe.addGcpKey(use, args[i + 2], args[i + 3]);
} catch (IllegalArgumentException e) {
errors.add("Invalid adcp_use for --gcp-key: " + args[i + 1]
+ ". Valid values: adcp_req, adcp_whk");
}
i += 4;
}
}
default -> {
errors.add("Unknown option: " + arg);
i++;
}
}
}

if (!errors.isEmpty()) {
for (String error : errors) {
System.err.println("Error: " + error);
}
System.err.println();
printSigningProbeUsage();
System.exit(1);
}

try {
int exitCode = probe.call();
System.exit(exitCode);
} catch (Exception e) {
System.err.println("Probe failed: " + e.getMessage());
System.exit(1);
}
}

private static void printUsage() {
System.out.println("Usage: adcp <command> [options]");
System.out.println();
System.out.println("Commands:");
System.out.println(" signing-probe Pre-deploy KMS connectivity and key usability check");
System.out.println();
System.out.println("Run 'adcp <command> --help' for command details.");
}

private static void printSigningProbeUsage() {
System.out.println("Usage: adcp signing-probe [options]");
System.out.println();
System.out.println("Options:");
System.out.println(" --aws-key <use> <key-arn> <region>");
System.out.println(" Probe an AWS KMS key. <use> is adcp_req or adcp_whk.");
System.out.println(" --gcp-key <use> <key-version-path> <credentials-path>");
System.out.println(" Probe a GCP KMS key. <use> is adcp_req or adcp_whk.");
System.out.println();
System.out.println("Exit code: 0 if all probes pass, 1 if any fail.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package org.adcontextprotocol.adcp.cli;

import org.adcontextprotocol.adcp.signing.AdcpUse;
import org.adcontextprotocol.adcp.signing.SigningException;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

/**
* Pre-deploy KMS probe CLI command.
*
* <p>Verifies KMS connectivity and key usability for each configured KMS key.
* Per the ROADMAP: "separate CLI command, not part of boot critical path."
*
* <p>Supports AWS and GCP KMS providers. Outputs structured JSON results
* with exit code 0 (all pass) or 1 (any fail).
*/
public final class SigningProbeCommand implements Callable<Integer> {

private final List<KeyProbe> probes = new ArrayList<>();

public SigningProbeCommand addAwsKey(AdcpUse use, String keyArn, String region) {
probes.add(new AwsKeyProbe(use, keyArn, region));
return this;
}

public SigningProbeCommand addGcpKey(AdcpUse use, String keyVersionPath, String credentialsPath) {
probes.add(new GcpKeyProbe(use, keyVersionPath, credentialsPath));
return this;
}

@Override
public Integer call() {
List<ProbeResult> results = new ArrayList<>();
boolean allPassed = true;

for (KeyProbe probe : probes) {
ProbeResult result = probe.run();
results.add(result);
if (!result.passed()) {
allPassed = false;
}
}

System.out.println(formatResults(results));
return allPassed ? 0 : 1;
}

String formatResults(List<ProbeResult> results) {
StringBuilder sb = new StringBuilder();
sb.append("{\n \"probe_results\": [\n");
for (int i = 0; i < results.size(); i++) {
ProbeResult r = results.get(i);
if (i > 0) sb.append(",\n");
sb.append(" {\n");
sb.append(" \"provider\": \"").append(escapeJson(r.provider())).append("\",\n");
sb.append(" \"use\": \"").append(escapeJson(r.use().wireName())).append("\",\n");
sb.append(" \"key_id\": \"").append(escapeJson(r.keyId())).append("\",\n");
sb.append(" \"connectivity\": ").append(r.connectivityOk() ? "\"ok\"" : "\"failed: " + escapeJson(r.connectivityError()) + "\"").append(",\n");
sb.append(" \"signing\": ").append(r.signingOk() ? "\"ok\"" : "\"failed: " + escapeJson(r.signingError()) + "\"").append(",\n");
sb.append(" \"purpose\": ").append(r.purposeOk() ? "\"ok\"" : "\"failed: " + escapeJson(r.purposeError()) + "\"").append(",\n");
sb.append(" \"passed\": ").append(r.passed());
sb.append("\n }");
}
sb.append("\n ]\n}");
return sb.toString();
}

private static String escapeJson(String s) {
if (s == null) return "";
return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");
}

record ProbeResult(
String provider,
AdcpUse use,
String keyId,
boolean connectivityOk,
String connectivityError,
boolean signingOk,
String signingError,
boolean purposeOk,
String purposeError,
boolean passed
) {}

interface KeyProbe {
ProbeResult run();
}

static final class AwsKeyProbe implements KeyProbe {
private final AdcpUse use;
private final String keyArn;
private final String region;

AwsKeyProbe(AdcpUse use, String keyArn, String region) {
this.use = use;
this.keyArn = keyArn;
this.region = region;
}

@Override
public ProbeResult run() {
boolean connectivityOk = false;
String connectivityError = null;
boolean signingOk = false;
String signingError = null;
boolean purposeOk = false;
String purposeError = null;

try {
Class<?> providerClass = Class.forName(
"org.adcontextprotocol.adcp.signing.aws.AwsKmsSigningProvider");
Object provider = providerClass.getMethod("builder").invoke(null);
provider.getClass().getMethod("keyArn", AdcpUse.class, String.class)
.invoke(provider, use, keyArn);
provider.getClass().getMethod("region", String.class)
.invoke(provider, region);

Object built = provider.getClass().getMethod("build").invoke(provider);

connectivityOk = true;

try {
built.getClass().getMethod("initialize").invoke(built);
signingOk = true;
} catch (Exception e) {
Throwable cause = e.getCause() != null ? e.getCause() : e;
if (cause instanceof SigningException) {
signingError = cause.getMessage();
} else {
signingError = cause.getMessage();
}
}

try {
@SuppressWarnings("unchecked")
Map<AdcpUse, ?> algorithms = (Map<AdcpUse, ?>) built.getClass()
.getMethod("getAlgorithms").invoke(built);
purposeOk = algorithms.containsKey(use);
if (!purposeOk) {
purposeError = "No algorithm resolved for use: " + use;
}
} catch (Exception e) {
purposeError = e.getMessage();
}

} catch (ClassNotFoundException e) {
connectivityError = "AWS KMS provider not on classpath";
} catch (Exception e) {
connectivityError = e.getMessage();
}

boolean passed = connectivityOk && signingOk && purposeOk;
return new ProbeResult("aws", use, keyArn, connectivityOk, connectivityError,
signingOk, signingError, purposeOk, purposeError, passed);
}
}

static final class GcpKeyProbe implements KeyProbe {
private final AdcpUse use;
private final String keyVersionPath;
private final String credentialsPath;

GcpKeyProbe(AdcpUse use, String keyVersionPath, String credentialsPath) {
this.use = use;
this.keyVersionPath = keyVersionPath;
this.credentialsPath = credentialsPath;
}

@Override
public ProbeResult run() {
boolean connectivityOk = false;
String connectivityError = null;
boolean signingOk = false;
String signingError = null;
boolean purposeOk = false;
String purposeError = null;

try {
Class<?> providerClass = Class.forName(
"org.adcontextprotocol.adcp.signing.gcp.GcpKmsSigningProvider");
Object provider = providerClass.getMethod("builder").invoke(null);
provider.getClass().getMethod("keyVersionPath", AdcpUse.class, String.class)
.invoke(provider, use, keyVersionPath);
if (credentialsPath != null) {
provider.getClass().getMethod("credentialsPath", String.class)
.invoke(provider, credentialsPath);
}

Object built = provider.getClass().getMethod("build").invoke(provider);

connectivityOk = true;

try {
built.getClass().getMethod("initialize").invoke(built);
signingOk = true;
} catch (Exception e) {
Throwable cause = e.getCause() != null ? e.getCause() : e;
if (cause instanceof SigningException) {
signingError = cause.getMessage();
} else {
signingError = cause.getMessage();
}
}

try {
@SuppressWarnings("unchecked")
Map<AdcpUse, ?> algorithms = (Map<AdcpUse, ?>) built.getClass()
.getMethod("getAlgorithms").invoke(built);
purposeOk = algorithms.containsKey(use);
if (!purposeOk) {
purposeError = "No algorithm resolved for use: " + use;
}
} catch (Exception e) {
purposeError = e.getMessage();
}

} catch (ClassNotFoundException e) {
connectivityError = "GCP KMS provider not on classpath";
} catch (Exception e) {
connectivityError = e.getMessage();
}

boolean passed = connectivityOk && signingOk && purposeOk;
return new ProbeResult("gcp", use, keyVersionPath, connectivityOk, connectivityError,
signingOk, signingError, purposeOk, purposeError, passed);
}
}
}
Loading
Loading