feat(license-minter): --verify round-trips before shipping
Adds --verify (requires --public-key) to LicenseMinterCli. After writing the output file the CLI parses the freshly-minted token through LicenseValidator against the supplied public key. On verify failure the output file is deleted (so the bad token is not accidentally shipped) and the CLI exits 3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,23 @@ public final class LicenseMinterCli {
|
||||
} else {
|
||||
out.println(token);
|
||||
}
|
||||
if (bool.contains("--verify")) {
|
||||
String pubPath = flags.get("--public-key");
|
||||
if (pubPath == null) {
|
||||
err.println("--verify requires --public-key");
|
||||
if (outPath != null) Files.deleteIfExists(Path.of(outPath));
|
||||
return 2;
|
||||
}
|
||||
try {
|
||||
String pubB64 = Files.readString(Path.of(pubPath)).trim();
|
||||
new com.cameleer.server.core.license.LicenseValidator(pubB64, tenant).validate(token);
|
||||
out.println("verified ok");
|
||||
} catch (Exception ve) {
|
||||
err.println("VERIFY FAILED: " + ve.getMessage());
|
||||
if (outPath != null) Files.deleteIfExists(Path.of(outPath));
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} catch (Exception e) {
|
||||
err.println("ERROR: " + e.getMessage());
|
||||
|
||||
@@ -48,4 +48,65 @@ class LicenseMinterCliTest {
|
||||
int code = LicenseMinterCli.run(new String[]{"--frobnicate=yes"});
|
||||
assertThat(code).isNotZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void verify_happyPath_succeeds() throws Exception {
|
||||
KeyPair kp = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
|
||||
Path priv = tmp.resolve("priv.b64");
|
||||
Path pub = tmp.resolve("pub.b64");
|
||||
Files.writeString(priv, Base64.getEncoder().encodeToString(kp.getPrivate().getEncoded()));
|
||||
Files.writeString(pub, Base64.getEncoder().encodeToString(kp.getPublic().getEncoded()));
|
||||
Path out = tmp.resolve("license.tok");
|
||||
|
||||
int code = LicenseMinterCli.run(new String[]{
|
||||
"--private-key=" + priv,
|
||||
"--public-key=" + pub,
|
||||
"--tenant=acme",
|
||||
"--expires=2099-12-31",
|
||||
"--output=" + out,
|
||||
"--verify"
|
||||
});
|
||||
|
||||
assertThat(code).isEqualTo(0);
|
||||
assertThat(out).exists();
|
||||
}
|
||||
|
||||
@Test
|
||||
void verify_wrongPublicKey_deletesOutputAndExitsNonZero() throws Exception {
|
||||
KeyPair signing = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
|
||||
KeyPair other = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
|
||||
Path priv = tmp.resolve("priv.b64");
|
||||
Path pub = tmp.resolve("pub.b64");
|
||||
Files.writeString(priv, Base64.getEncoder().encodeToString(signing.getPrivate().getEncoded()));
|
||||
Files.writeString(pub, Base64.getEncoder().encodeToString(other.getPublic().getEncoded()));
|
||||
Path out = tmp.resolve("license.tok");
|
||||
|
||||
int code = LicenseMinterCli.run(new String[]{
|
||||
"--private-key=" + priv,
|
||||
"--public-key=" + pub,
|
||||
"--tenant=acme",
|
||||
"--expires=2099-12-31",
|
||||
"--output=" + out,
|
||||
"--verify"
|
||||
});
|
||||
|
||||
assertThat(code).isNotZero();
|
||||
assertThat(out).doesNotExist();
|
||||
}
|
||||
|
||||
@Test
|
||||
void verify_withoutPublicKey_fails() throws Exception {
|
||||
KeyPair kp = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
|
||||
Path priv = tmp.resolve("priv.b64");
|
||||
Files.writeString(priv, Base64.getEncoder().encodeToString(kp.getPrivate().getEncoded()));
|
||||
|
||||
int code = LicenseMinterCli.run(new String[]{
|
||||
"--private-key=" + priv,
|
||||
"--tenant=acme",
|
||||
"--expires=2099-12-31",
|
||||
"--verify"
|
||||
});
|
||||
|
||||
assertThat(code).isNotZero();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user