Skip to main content

Java Code Signing

Code signing lets the key server decide which client builds it serves. A Java client proves which build it is by attesting a signed release manifest: the BUNDLE-MANIFEST.txt at the root of the Java SDK bundle it runs from, with the detached signature BUNDLE-MANIFEST.txt.sig and the signer's certificate BUNDLE-MANIFEST.txt.cert.pem beside it. The manifest names the release (artifact: tricryption-java-sdk-<version>-<platform>) and lists the SHA-256 of every file in the bundle, so a signature over it is a signature over the whole SDK.

The client attests on every connection, right after it signs in: it asks the key server which code-signing level is in force and, at level 1 or 2, sends the release it was started with. The key server checks the signature itself; nothing on the client side can raise its own admission.

The levels​

LevelConsole settingThe key server serves a client when
0Nonealways (the default; the client still reports its release)
1Enablethe manifest is signed by an approved publisher: a certificate with the code-signing extended key usage, issued by a CA in the key server's trusted list, not revoked under that CA's CRL
2Strictlevel 1, and this exact release -- its artifact name and the SHA-256 of its manifest -- is registered

A connection that is not admitted at the level in force may only ask for the level and attest; every other request is refused. Raising the level applies to connections that are already open, at their next request.

Before you start​

  • The Java SDK bundle for your platform, unpacked, with its signed manifest. Verify that the manifest is the release's and see who signed it:
openssl x509 -in /opt/tricryption/java-sdk/BUNDLE-MANIFEST.txt.cert.pem -noout -subject -issuer -ext extendedKeyUsage
  • The release CA certificate and its CRL, published with the release (root.pem and root.crl.der below, in /etc/pisoft/release).
  • A machine with the tricryption-console-gateway package installed: it carries the registration tool and the Node runtime it runs on.
  • The key server's CA certificate (the anchor the Console and the Gateway trust), and the password of an administrator who holds the Security Officer role. Put both in the environment of the shell you register from -- never on a command line: KS_SERVER_ANCHOR names the anchor file, KS_PASS holds the password (KS_USER names the administrator when it is not admin).

Run the client with its release manifest​

Put the bundle's lib directory on the class path, its native directory on the library path, and name the manifest with the system property te.release.manifest:

java --enable-native-access=ALL-UNNAMED -cp "/opt/tricryption/java-sdk/lib/*:myapp.jar" -Djava.library.path=/opt/tricryption/java-sdk/native -Dte.release.manifest=/opt/tricryption/java-sdk/BUNDLE-MANIFEST.txt com.example.MyApp

Nothing else in your code changes: the attestation is made by the connection itself.

Approve the release​

One command trusts the release CA, imports its CRL (with a revocation policy covering the CRL's period), registers the signer as an approved publisher and this release as an approved release, and reads both back:

/opt/pisoft/tricryption/node/bin/node /opt/pisoft/tricryption/libexec/console-gateway/release-register.js --host ks.example.com --port 8888 --manifest /opt/tricryption/java-sdk/BUNDLE-MANIFEST.txt --signer-cert /opt/tricryption/java-sdk/BUNDLE-MANIFEST.txt.cert.pem --signer-ca /etc/pisoft/release/root.pem --signer-crl /etc/pisoft/release/root.crl.der

It ends with RELEASE-REGISTER: OK tricryption-java-sdk-<version>-<platform> <digest prefix>. A row that is already registered is kept, so running it again is safe. It never changes the level.

Set the level​

In the Console, open Policy, find Java Code Signing, and set Enforce code signing to Enable (level 1) or Strict (level 2). Approve the releases your clients run before you raise the level: from then on, every connection -- the administrator's too -- must attest a release the level admits.

What a refusal looks like​

A client that is not admitted gets error 0x0301 (ERR_CLIENT_BUILD_NOT_ADMITTED) from TEAgentConnection.open, and the connection is closed. The message carries the key server's reason:

  • client build not admitted at code-signing level 2: the release is not approved -- the manifest is signed by an approved publisher, but this release (or this exact manifest) is not registered. A bundle whose files were changed and whose manifest was signed again is a different manifest and is refused here.
  • ... the signer certificate does not chain to a trusted CA or the signer is not an approved publisher -- the manifest was signed by someone else.
  • the signer certificate is refused -- the signer is revoked, or its CA's CRL is missing or out of date.
  • no release manifest is configured -- set the system property te.release.manifest ... -- the client was started without the property at level 1 or 2. The client itself refuses, with the same error code, after reading the level and before it sends any other request.

Revoke a release​

When a release signer is revoked, a new CRL is published with the release. Import it by running the registration command above again with the new root.crl.der: every client whose manifest that signer signed is then refused (the signer certificate is refused), at levels 1 and 2.

Withdraw one release​

To withdraw approval of one release without revoking its signer, remove its registration through the REST Gateway's code-signing operations, as an administrator who holds the Security Officer role. Sign in (the password is read from KS_PASS and sent on standard input, never on a command line) and list the registrations:

export TE_SESSION="$(printf '{"user":"%s","password":"%s"}' "${KS_USER:-admin}" "$KS_PASS" | curl -sf -X POST -H 'Content-Type: application/json' --data-binary @- https://gateway.example.com/api/v1/session | /opt/pisoft/tricryption/node/bin/node -pe 'JSON.parse(require("fs").readFileSync(0)).session')"
curl -sf -H "Authorization: Bearer $TE_SESSION" https://gateway.example.com/api/v1/code-signing | /opt/pisoft/tricryption/node/bin/node -e 'for (const e of JSON.parse(require("fs").readFileSync(0)).entries) console.log(e.csId, e.kind, e.codeName, e.digest || "")'

Each line is one registration: its csId, publisher or release, the name, and for a release the SHA-256 of its manifest. Set CS_ID to the csId of the release you withdraw, remove it, read the list back, and sign out:

curl -s -X DELETE -H "Authorization: Bearer $TE_SESSION" "https://gateway.example.com/api/v1/code-signing/$CS_ID"
curl -sf -H "Authorization: Bearer $TE_SESSION" https://gateway.example.com/api/v1/code-signing | /opt/pisoft/tricryption/node/bin/node -e 'for (const e of JSON.parse(require("fs").readFileSync(0)).entries) console.log(e.csId, e.kind, e.codeName, e.digest || "")'
curl -s -X DELETE -H "Authorization: Bearer $TE_SESSION" https://gateway.example.com/api/v1/session

The removal answers {"removed":"<csId>", ...} and the row is gone from the list. From the next connection on, a client running that release is refused at level 2 with the release is not approved.

The key server refuses to remove the last approved release at level 2: the Gateway answers 409 LAST_APPROVAL, because no client could be admitted after it. Lower the level first, or approve the release that replaces it before you withdraw the old one.

Qualified at engine master 9da0b6b2929d.