Using Warden Supreme from Java
Warden Supreme is written in Kotlin, but its server-side JVM modules can also be used from Java. The regular data model and configuration classes are directly accessible; small Java-facing adapters bridge the places where the Kotlin API uses suspending functions, receiver lambdas, or Kotlin time types.
Java-only API
Java-only adapters are deliberately marked internal in Kotlin source so they do not clutter the Kotlin API.
Kotlin/JVM still emits them as public JVM declarations, and @JvmName gives their methods stable, idiomatic Java
names. The examples on this page are compiled and exercised from the Java test source set.
Integrated Attestation
For a new integration, use JavaAttestationVerifier. It wraps the Kotlin-first AttestationVerifier and exposes:
- regular methods instead of suspending functions
- an ordinary
Callbacksinterface instead of Kotlin receiver lambdas andFunctionNtypes CompletableFutureresults for challenge issuance and attestation verification
The following complete example is compiled as part of the JVM test suite:
public static JavaAttestationVerifier newVerifier() throws AttestationException.Configuration {
IosAttestationConfiguration ios = new IosAttestationConfiguration(
new IosAttestationConfiguration.AppData(
/*(1)!*/"9CYHJNG644",
"at.asitplus.attestation-client"
)
);
SupremeConfiguration configuration = /*(2)!*/newConfiguration(ios, null);
return new JavaAttestationVerifier(configuration);
}
public static CompletableFuture<AttestationChallenge> issueChallenge(
JavaAttestationVerifier verifier,
String endpoint
) {
return verifier.issueChallenge(endpoint);
}
public static CompletableFuture<AttestationResponse> verify(
JavaAttestationVerifier verifier,
AttestationProof proof
) {
return verifier.verifyAttestation(proof, new JavaAttestationVerifier.Callbacks() {
@Override
public void onChallengeValidated(
/*(3)!*/AttestationChallenge challenge,
AttestationProof receivedProof
) {
// Log or record the validated challenge here.
}
@Override
public String onPreAttestationError(PreAttestationError error) {
return null;
}
@Override
public String onAttestationError(
AttestationResult.Error error,
WardenDebugAttestationStatement debugInfo
) {
return null;
}
@Override
public void onAttestationSuccess(
/*(4)!*/AttestationResult.Verified verified,
CryptoPublicKey publicKey
) {
// Record successful attestation metrics here.
}
@Override
public AttestationResponse.Failure additionalVerifications(
AttestationChallenge challenge,
AttestationProof receivedProof,
AttestationResult.Verified verified
) {
/*(5)!*/return null;
}
@Override
public List<X509Certificate> certificateIssuer(
AttestationResult.Verified verified,
AttestationProof receivedProof
) {
// Replace with the certificate chain issued by the application.
/*(6)!*/return Collections.emptyList();
}
});
}
public static void applicationFlow(AttestationProof proof) throws AttestationException.Configuration {
/*(7)!*/JavaAttestationVerifier verifier = newVerifier();
issueChallenge(verifier, "https://example.test/attest").thenAccept(challenge -> {
// Send challenge to the mobile client, then receive its proof over HTTPS.
});
verify(verifier, proof).thenAccept(response -> {
// Return the response to the client or handle the failure.
});
}
- Configure the iOS application using the regular Java constructor.
- Create the platform and integrated policy through the Java-friendly configuration helper shown below.
- Implement the regular Java callback interface. The challenge has already been validated when this callback runs.
- Successful verification provides both the verified platform result and the attested public key.
- Return
nullto accept the proof after application-specific checks, or return anAttestationResponse.Failureto reject it. - Return the certificate chain issued for the attested key. An empty list is useful only when exercising an error path, as this test example does.
- Keep one verifier instance and compose the returned futures at the application's asynchronous boundary.
Working with CompletableFuture
Use thenApply, thenCompose, or exceptionally to continue asynchronously. join() is convenient at a genuinely
synchronous boundary, but must not block an event-loop thread.
Configuration and Time
SupremeConfiguration provides a Java-friendly constructor with nullable iOS and Android policies followed by Java
standard-library time types:
public static SupremeConfiguration newConfiguration(
IosAttestationConfiguration ios,
AndroidAttestationConfiguration android
) throws AttestationException.Configuration {
return new SupremeConfiguration(
/*(1)!*/ios,
/*(2)!*/android,
/*(3)!*/Clock.systemUTC(),
/*(4)!*/Duration.ofMinutes(5)
);
}
- Pass the iOS policy, or
nullfor an Android-only verifier. - Pass the Android policy, or
nullfor an iOS-only verifier. At least one platform policy must be present. - Supply the clock used for verification.
Clock.systemUTC()is the default. - Set the verification-time offset used to compensate for clock drift. The default is five minutes.
The constructor uses @JvmOverloads, so trailing arguments can be omitted when their defaults are suitable. In
particular, new SupremeConfiguration(iosConfiguration, androidConfiguration) uses the system UTC clock and the default
verification-time offset.
For deterministic tests, construct a serialisable fixed clock with
new SupremeConfiguration.Clock.Fixed(java.time.Instant). An existing Java clock can also be adapted with
SupremeConfiguration.Clock.from(java.time.Clock).
Clocks in externalised configuration
Clock.Fixed and the system clock have canonical YAML and JSON representations. A clock created with Clock.from(...)
is a runtime adapter and should not be written to externalised configuration. See
Externalising Configuration for the supported formats.
Using Makoto or Roboto Directly
The integrated flow above is the recommended path. The APIs in this section are useful when the application owns its wire format and calls Warden makoto or Warden roboto directly.
Warden makoto
Makoto provides Java constructors for combined, Android-only, and iOS-only policies:
Makoto(IosAttestationConfiguration, AndroidAttestationConfiguration, java.time.Duration, java.time.Clock)Makoto(AndroidAttestationConfiguration, java.time.Duration, java.time.Clock)Makoto(IosAttestationConfiguration, java.time.Duration, java.time.Clock)
All three use @JvmOverloads; the trailing duration and clock can therefore be omitted. The combined constructor
requires both policies, while the platform-specific constructors configure only their named platform.
The most useful synchronous Java methods are:
verifyKeyAttestation(Attestation, byte[])for the unified key-attestation representationgetAndroid().verifyKeyAttestationBlocking(List<X509Certificate>, byte[])for a raw Android certificate chaingetIos().verifyAppAttestation(byte[], byte[])for an App Attest object and challengegetIos().verifyCombined(byte[], byte[], byte[], byte[])for App Attest plus assertion verification- the
collectDebugInfo(...)overload matching the failed verification call
The first method is named verifyKeyAttestationBlocking in Kotlin source, but Java sees
verifyKeyAttestation(...) through @JvmName. Overloads accepting the legacy List<byte[]> proof format are deprecated
and should only be used while migrating an existing integration.
Warden roboto
Construct Roboto with an AndroidAttestationConfiguration. A second constructor accepting a Kotlin Function2 is
visible on the JVM, but the one-argument overload provides the intended Java experience and compares challenge bytes by
content.
Java can then call:
verifyBlocking(List<X509Certificate>, java.time.Instant, byte[]), or omit the instant to use the current timecollectDebugInfo(List<X509Certificate>, byte[], java.time.Instant)collectDebugInfo(List<X509Certificate>, byte[], java.util.Date)collectDebugInfoBlocking(List<X509Certificate>, byte[], java.time.Instant), or omit the instant
The two collectDebugInfo(...) overloads are hidden from the public Kotlin API, while being exposed to Java.
The similarly named top-level Kotlin extension functions are emitted on
RobotoKt, use Kotlin time types, and are not intended as the Java entry point, but for calling from Kotlin.