SLSA Provenance in Java: Complete Guide to Supply Chain Security


Article

SLSA (Supply-chain Levels for Software Artifacts) is a security framework that provides standards for software supply chain integrity. Provenance is the foundation of SLSA - it's metadata about how software artifacts were built. This guide covers how to generate, verify, and work with SLSA Provenance in Java applications.

What is SLSA Provenance?

Provenance answers: Who built what, when, where, and how? It includes:

  • Builder identity - Who created the artifact
  • Build process - How it was built
  • Source materials - What went into the build
  • Dependencies - External components used

Project Setup and Dependencies

Maven Dependencies:

<properties>
<in-toto.version>0.5.0</version>
<bouncycastle.version>1.77</bouncycastle.version>
<jackson.version>2.15.2</jackson.version>
</properties>
<dependencies>
<!-- SLSA Provenance / in-toto attestation -->
<dependency>
<groupId>io.github.intoto</groupId>
<artifactId>java-dsse</artifactId>
<version>${in-toto.version}</version>
</dependency>
<!-- JSON-LD for provenance -->
<dependency>
<groupId>com.github.jsonld-java</groupId>
<artifactId>jsonld-java</artifactId>
<version>0.13.4</version>
</dependency>
<!-- Cryptography -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>

1. SLSA Provenance Data Model

Core Provenance Classes:

package com.example.slsa.provenance;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.time.Instant;
import java.util.*;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SLSAProvenance {
@JsonProperty("_type")
private final String type = "https://in-toto.io/Statement/v1";
@JsonProperty("subject")
private List<Subject> subject;
@JsonProperty("predicateType")
private final String predicateType = "https://slsa.dev/provenance/v1";
@JsonProperty("predicate")
private Predicate predicate;
// Builder class for fluent creation
public static class Builder {
private List<Subject> subject = new ArrayList<>();
private Predicate predicate;
public Builder addSubject(String name, Map<String, String> digest) {
subject.add(new Subject(name, digest));
return this;
}
public Builder withPredicate(Predicate predicate) {
this.predicate = predicate;
return this;
}
public SLSAProvenance build() {
SLSAProvenance provenance = new SLSAProvenance();
provenance.subject = new ArrayList<>(this.subject);
provenance.predicate = this.predicate;
return provenance;
}
}
// Getters
public String getType() { return type; }
public List<Subject> getSubject() { return subject; }
public String getPredicateType() { return predicateType; }
public Predicate getPredicate() { return predicate; }
}
@JsonInclude(JsonInclude.Include.NON_NULL)
class Subject {
private String name;
private Map<String, String> digest;
public Subject() {}
public Subject(String name, Map<String, String> digest) {
this.name = name;
this.digest = new HashMap<>(digest);
}
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Map<String, String> getDigest() { return digest; }
public void setDigest(Map<String, String> digest) { 
this.digest = new HashMap<>(digest); 
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
class Predicate {
@JsonProperty("buildDefinition")
private BuildDefinition buildDefinition;
@JsonProperty("runDetails")
private RunDetails runDetails;
// Getters and setters
public BuildDefinition getBuildDefinition() { return buildDefinition; }
public void setBuildDefinition(BuildDefinition buildDefinition) { 
this.buildDefinition = buildDefinition; 
}
public RunDetails getRunDetails() { return runDetails; }
public void setRunDetails(RunDetails runDetails) { 
this.runDetails = runDetails; 
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
class BuildDefinition {
@JsonProperty("buildType")
private String buildType;
@JsonProperty("externalParameters")
private Map<String, Object> externalParameters;
@JsonProperty("internalParameters")
private Map<String, Object> internalParameters;
@JsonProperty("resolvedDependencies")
private List<ResourceDescriptor> resolvedDependencies;
// Builder pattern
public static class Builder {
private String buildType;
private Map<String, Object> externalParameters = new HashMap<>();
private Map<String, Object> internalParameters = new HashMap<>();
private List<ResourceDescriptor> resolvedDependencies = new ArrayList<>();
public Builder withBuildType(String buildType) {
this.buildType = buildType;
return this;
}
public Builder addExternalParameter(String key, Object value) {
this.externalParameters.put(key, value);
return this;
}
public Builder addInternalParameter(String key, Object value) {
this.internalParameters.put(key, value);
return this;
}
public Builder addResolvedDependency(ResourceDescriptor dependency) {
this.resolvedDependencies.add(dependency);
return this;
}
public BuildDefinition build() {
BuildDefinition definition = new BuildDefinition();
definition.buildType = this.buildType;
definition.externalParameters = new HashMap<>(this.externalParameters);
definition.internalParameters = new HashMap<>(this.internalParameters);
definition.resolvedDependencies = new ArrayList<>(this.resolvedDependencies);
return definition;
}
}
// Getters
public String getBuildType() { return buildType; }
public Map<String, Object> getExternalParameters() { return externalParameters; }
public Map<String, Object> getInternalParameters() { return internalParameters; }
public List<ResourceDescriptor> getResolvedDependencies() { return resolvedDependencies; }
}
@JsonInclude(JsonInclude.Include.NON_NULL)
class ResourceDescriptor {
private String uri;
private Map<String, String> digest;
private String name;
private String downloadLocation;
private String mediaType;
private Map<String, Object> annotations;
// Builder pattern
public static class Builder {
private String uri;
private Map<String, String> digest = new HashMap<>();
private String name;
private String downloadLocation;
private String mediaType;
private Map<String, Object> annotations = new HashMap<>();
public Builder withUri(String uri) {
this.uri = uri;
return this;
}
public Builder addDigest(String algorithm, String hash) {
this.digest.put(algorithm, hash);
return this;
}
public Builder withName(String name) {
this.name = name;
return this;
}
public ResourceDescriptor build() {
ResourceDescriptor descriptor = new ResourceDescriptor();
descriptor.uri = this.uri;
descriptor.digest = new HashMap<>(this.digest);
descriptor.name = this.name;
descriptor.downloadLocation = this.downloadLocation;
descriptor.mediaType = this.mediaType;
descriptor.annotations = new HashMap<>(this.annotations);
return descriptor;
}
}
// Getters and setters
public String getUri() { return uri; }
public void setUri(String uri) { this.uri = uri; }
public Map<String, String> getDigest() { return digest; }
public void setDigest(Map<String, String> digest) { 
this.digest = new HashMap<>(digest); 
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
class RunDetails {
private Builder builder;
private BuildMetadata metadata;
private List<ResourceDescriptor> byproducts;
// Getters and setters
public Builder getBuilder() { return builder; }
public void setBuilder(Builder builder) { this.builder = builder; }
public BuildMetadata getMetadata() { return metadata; }
public void setMetadata(BuildMetadata metadata) { this.metadata = metadata; }
public List<ResourceDescriptor> getByproducts() { return byproducts; }
public void setByproducts(List<ResourceDescriptor> byproducts) { 
this.byproducts = new ArrayList<>(byproducts); 
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
class Builder {
private String id;
private Map<String, Object> builderDependencies;
private String version;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public Map<String, Object> getBuilderDependencies() { return builderDependencies; }
public void setBuilderDependencies(Map<String, Object> builderDependencies) { 
this.builderDependencies = new HashMap<>(builderDependencies); 
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
class BuildMetadata {
private String invokedBy;
private Instant startedOn;
private Instant finishedOn;
// Getters and setters
public String getInvokedBy() { return invokedBy; }
public void setInvokedBy(String invokedBy) { this.invokedBy = invokedBy; }
public Instant getStartedOn() { return startedOn; }
public void setStartedOn(Instant startedOn) { this.startedOn = startedOn; }
public Instant getFinishedOn() { return finishedOn; }
public void setFinishedOn(Instant finishedOn) { this.finishedOn = finishedOn; }
}

2. Provenance Generator for Maven/Gradle Builds

Maven Build Provenance Generator:

package com.example.slsa.generator;
import com.example.slsa.provenance.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
public class MavenProvenanceGenerator {
private final ObjectMapper objectMapper;
private final String buildId;
private final String builderId;
public MavenProvenanceGenerator(String buildId, String builderId) {
this.objectMapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.enable(SerializationFeature.INDENT_OUTPUT);
this.buildId = buildId;
this.builderId = builderId;
}
public SLSAProvenance generateProvenance(
File artifact, 
File pomFile,
Map<String, String> environment,
List<File> sourceFiles) throws IOException {
// Calculate artifact digest
Map<String, String> artifactDigest = calculateDigests(artifact);
// Build provenance
SLSAProvenance provenance = new SLSAProvenance.Builder()
.addSubject(artifact.getName(), artifactDigest)
.withPredicate(buildPredicate(pomFile, environment, sourceFiles))
.build();
return provenance;
}
private Predicate buildPredicate(
File pomFile, 
Map<String, String> environment,
List<File> sourceFiles) throws IOException {
Predicate predicate = new Predicate();
// Build Definition
BuildDefinition buildDefinition = new BuildDefinition.Builder()
.withBuildType("https://slsa.dev/maven/v0.2?draft")
.addExternalParameter("pom_file", pomFile.getAbsolutePath())
.addExternalParameter("maven_version", System.getProperty("maven.version"))
.addExternalParameter("java_version", System.getProperty("java.version"))
.addInternalParameter("environment", environment)
.build();
// Add resolved dependencies (from Maven)
List<ResourceDescriptor> dependencies = extractMavenDependencies(pomFile);
dependencies.forEach(buildDefinition::addResolvedDependency);
// Add source files as dependencies
sourceFiles.stream()
.map(this::fileToResourceDescriptor)
.forEach(buildDefinition::addResolvedDependency);
predicate.setBuildDefinition(buildDefinition);
// Run Details
RunDetails runDetails = new RunDetails();
Builder builder = new Builder();
builder.setId(builderId);
builder.setBuilderDependencies(Map.of(
"maven", getMavenVersion(),
"java", System.getProperty("java.version")
));
BuildMetadata metadata = new BuildMetadata();
metadata.setInvokedBy(System.getProperty("user.name"));
metadata.setStartedOn(Instant.now().minusSeconds(300)); // 5 minutes ago
metadata.setFinishedOn(Instant.now());
runDetails.setBuilder(builder);
runDetails.setMetadata(metadata);
// Add build logs as byproducts
List<ResourceDescriptor> byproducts = new ArrayList<>();
byproducts.add(createLogDescriptor("build.log", "text/plain"));
runDetails.setByproducts(byproducts);
predicate.setRunDetails(runDetails);
return predicate;
}
private List<ResourceDescriptor> extractMavenDependencies(File pomFile) throws IOException {
List<ResourceDescriptor> dependencies = new ArrayList<>();
// Parse pom.xml to extract dependencies
// In production, use Maven's API or Aether
String pomContent = Files.readString(pomFile.toPath());
// Simple regex-based extraction (simplified)
// Use DOM parser in real implementation
dependencies.add(new ResourceDescriptor.Builder()
.withUri("pkg:maven/org.springframework/[email protected]")
.addDigest("sha256", "abc123...")
.withName("spring-core")
.build());
return dependencies;
}
private ResourceDescriptor fileToResourceDescriptor(File file) {
try {
Map<String, String> digest = calculateDigests(file);
return new ResourceDescriptor.Builder()
.withUri("file://" + file.getAbsolutePath())
.withName(file.getName())
.digest(digest)
.build();
} catch (IOException e) {
throw new RuntimeException("Failed to calculate digest for: " + file, e);
}
}
private Map<String, String> calculateDigests(File file) throws IOException {
Map<String, String> digests = new HashMap<>();
byte[] fileContent = Files.readAllBytes(file.toPath());
// SHA-256
digests.put("sha256", calculateHash(fileContent, "SHA-256"));
// SHA-512 for stronger security
digests.put("sha512", calculateHash(fileContent, "SHA-512"));
return digests;
}
private String calculateHash(byte[] data, String algorithm) {
try {
MessageDigest digest = MessageDigest.getInstance(algorithm);
byte[] hashBytes = digest.digest(data);
return bytesToHex(hashBytes);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Unsupported algorithm: " + algorithm, e);
}
}
private String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
private ResourceDescriptor createLogDescriptor(String logFileName, String mediaType) {
return new ResourceDescriptor.Builder()
.withUri("file://" + logFileName)
.withName(logFileName)
.withMediaType(mediaType)
.build();
}
private String getMavenVersion() {
try {
Process process = Runtime.getRuntime().exec("mvn --version");
try (var reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()))) {
return reader.lines().findFirst().orElse("unknown");
}
} catch (IOException e) {
return "unknown";
}
}
public void writeProvenance(SLSAProvenance provenance, File outputFile) throws IOException {
objectMapper.writeValue(outputFile, provenance);
}
public void writeProvenanceAsAttestation(
SLSAProvenance provenance, 
File privateKeyFile,
File outputFile) throws Exception {
String provenanceJson = objectMapper.writeValueAsString(provenance);
String signature = signProvenance(provenanceJson, privateKeyFile);
Map<String, Object> attestation = new LinkedHashMap<>();
attestation.put("payload", Base64.getEncoder().encodeToString(provenanceJson.getBytes()));
attestation.put("payloadType", "application/vnd.in-toto+json");
attestation.put("signatures", List.of(Map.of(
"sig", signature,
"keyid", "key1"
)));
objectMapper.writeValue(outputFile, attestation);
}
private String signProvenance(String payload, File privateKeyFile) throws Exception {
// Implementation using Bouncy Castle
// Load private key and sign
return "signature-placeholder";
}
}

3. DSSE (Dead Simple Signing Envelope) Implementation

package com.example.slsa.dsse;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Base64;
import java.util.List;
import java.util.Map;
public class DSSEEnvelope {
@JsonProperty("payload")
private String payload;
@JsonProperty("payloadType")
private String payloadType;
@JsonProperty("signatures")
private List<Signature> signatures;
public static class Builder {
private String payload;
private String payloadType;
private List<Signature> signatures;
public Builder withPayload(String payload) {
this.payload = payload;
return this;
}
public Builder withPayloadType(String payloadType) {
this.payloadType = payloadType;
return this;
}
public Builder withSignatures(List<Signature> signatures) {
this.signatures = signatures;
return this;
}
public DSSEEnvelope build() {
DSSEEnvelope envelope = new DSSEEnvelope();
envelope.payload = this.payload;
envelope.payloadType = this.payloadType;
envelope.signatures = this.signatures;
return envelope;
}
}
public static DSSEEnvelope createForProvenance(
String provenanceJson, 
List<Signature> signatures) {
String encodedPayload = Base64.getEncoder()
.encodeToString(provenanceJson.getBytes());
return new DSSEEnvelope.Builder()
.withPayload(encodedPayload)
.withPayloadType("application/vnd.in-toto+json")
.withSignatures(signatures)
.build();
}
// Getters
public String getPayload() { return payload; }
public String getPayloadType() { return payloadType; }
public List<Signature> getSignatures() { return signatures; }
public String decodePayload() {
byte[] decoded = Base64.getDecoder().decode(payload);
return new String(decoded);
}
}
class Signature {
@JsonProperty("keyid")
private String keyId;
@JsonProperty("sig")
private String signature;
@JsonProperty("cert")
private String certificate;
public Signature(String keyId, String signature) {
this.keyId = keyId;
this.signature = signature;
}
// Getters and setters
public String getKeyId() { return keyId; }
public void setKeyId(String keyId) { this.keyId = keyId; }
public String getSignature() { return signature; }
public void setSignature(String signature) { this.signature = signature; }
public String getCertificate() { return certificate; }
public void setCertificate(String certificate) { this.certificate = certificate; }
}
public class DSSEVerifier {
private final ObjectMapper objectMapper;
public DSSEVerifier() {
this.objectMapper = new ObjectMapper();
}
public boolean verifyEnvelope(DSSEEnvelope envelope, String publicKey) throws Exception {
// Verify each signature
for (Signature signature : envelope.getSignatures()) {
if (!verifySignature(envelope.getPayload(), signature, publicKey)) {
return false;
}
}
return true;
}
public SLSAProvenance extractProvenance(DSSEEnvelope envelope) throws Exception {
String provenanceJson = envelope.decodePayload();
return objectMapper.readValue(provenanceJson, SLSAProvenance.class);
}
private boolean verifySignature(String payload, Signature signature, String publicKey) {
// Implement signature verification using Bouncy Castle
// This is a simplified version
return true; // Placeholder
}
}

4. Provenance Verification Service

package com.example.slsa.verifier;
import com.example.slsa.provenance.SLSAProvenance;
import com.example.slsa.provenance.Subject;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.util.*;
public class ProvenanceVerifier {
private final ObjectMapper objectMapper;
private final Set<String> trustedBuilders;
private final Map<String, String> trustedPublicKeys;
public ProvenanceVerifier(Set<String> trustedBuilders) {
this.objectMapper = new ObjectMapper();
this.trustedBuilders = new HashSet<>(trustedBuilders);
this.trustedPublicKeys = new HashMap<>();
}
public void addTrustedPublicKey(String builderId, String publicKey) {
trustedPublicKeys.put(builderId, publicKey);
}
public VerificationResult verifyProvenance(
File artifact, 
File provenanceFile,
SLSAProvenance provenance) throws IOException {
List<String> errors = new ArrayList<>();
List<String> warnings = new ArrayList<>();
// 1. Verify artifact matches subject
if (!verifyArtifactMatchesSubject(artifact, provenance.getSubject())) {
errors.add("Artifact digest does not match provenance subject");
}
// 2. Verify builder is trusted
if (!isBuilderTrusted(provenance)) {
errors.add("Builder is not in trusted list: " + 
provenance.getPredicate().getRunDetails().getBuilder().getId());
}
// 3. Verify build type is allowed
if (!isBuildTypeAllowed(provenance)) {
warnings.add("Build type may not be standard: " + 
provenance.getPredicate().getBuildDefinition().getBuildType());
}
// 4. Verify timestamps are reasonable
verifyTimestamps(provenance, errors, warnings);
// 5. Verify dependencies (if policy requires)
verifyDependencies(provenance, errors, warnings);
// 6. Check for required parameters
verifyRequiredParameters(provenance, errors);
return new VerificationResult(
errors.isEmpty(),
errors,
warnings,
calculateSLSALevel(provenance, errors)
);
}
private boolean verifyArtifactMatchesSubject(File artifact, List<Subject> subjects) 
throws IOException {
byte[] artifactBytes = Files.readAllBytes(artifact.toPath());
Map<String, String> actualDigests = calculateDigests(artifactBytes);
for (Subject subject : subjects) {
if (subject.getName().equals(artifact.getName())) {
return digestsMatch(actualDigests, subject.getDigest());
}
}
return false;
}
private boolean digestsMatch(Map<String, String> actual, Map<String, String> expected) {
for (Map.Entry<String, String> entry : expected.entrySet()) {
String algorithm = entry.getKey();
String expectedHash = entry.getValue();
String actualHash = actual.get(algorithm);
if (actualHash == null || !actualHash.equalsIgnoreCase(expectedHash)) {
return false;
}
}
return true;
}
private Map<String, String> calculateDigests(byte[] data) throws IOException {
Map<String, String> digests = new HashMap<>();
try {
// SHA-256
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
digests.put("sha256", bytesToHex(sha256.digest(data)));
// SHA-512
MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
digests.put("sha512", bytesToHex(sha512.digest(data)));
} catch (Exception e) {
throw new IOException("Failed to calculate digests", e);
}
return digests;
}
private String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
private boolean isBuilderTrusted(SLSAProvenance provenance) {
String builderId = provenance.getPredicate()
.getRunDetails()
.getBuilder()
.getId();
return trustedBuilders.contains(builderId);
}
private boolean isBuildTypeAllowed(SLSAProvenance provenance) {
String buildType = provenance.getPredicate()
.getBuildDefinition()
.getBuildType();
// Allow list of known build types
Set<String> allowedTypes = Set.of(
"https://slsa.dev/maven/v0.2",
"https://slsa.dev/gradle/v0.2",
"https://slsa.dev/generic/v0.2"
);
return allowedTypes.contains(buildType);
}
private void verifyTimestamps(SLSAProvenance provenance, 
List<String> errors, 
List<String> warnings) {
var metadata = provenance.getPredicate().getRunDetails().getMetadata();
if (metadata.getStartedOn() == null || metadata.getFinishedOn() == null) {
warnings.add("Missing timestamp information");
return;
}
// Check if build happened in the future
if (metadata.getStartedOn().isAfter(java.time.Instant.now())) {
errors.add("Build started in the future");
}
// Check if build duration is reasonable
long duration = java.time.Duration.between(
metadata.getStartedOn(), 
metadata.getFinishedOn()
).toMinutes();
if (duration > 120) { // 2 hours
warnings.add("Build duration unusually long: " + duration + " minutes");
}
}
private void verifyDependencies(SLSAProvenance provenance,
List<String> errors,
List<String> warnings) {
var dependencies = provenance.getPredicate()
.getBuildDefinition()
.getResolvedDependencies();
if (dependencies == null || dependencies.isEmpty()) {
warnings.add("No dependencies listed in provenance");
return;
}
// Check for known vulnerable dependencies
// This would integrate with vulnerability databases
for (var dependency : dependencies) {
if (dependency.getUri() != null && 
dependency.getUri().contains("log4j:log4j")) {
warnings.add("Uses Log4j dependency: " + dependency.getUri());
}
}
}
private void verifyRequiredParameters(SLSAProvenance provenance, 
List<String> errors) {
var externalParams = provenance.getPredicate()
.getBuildDefinition()
.getExternalParameters();
// Check for required parameters based on policy
if (externalParams == null || !externalParams.containsKey("source_repository")) {
errors.add("Missing required parameter: source_repository");
}
}
private int calculateSLSALevel(SLSAProvenance provenance, List<String> errors) {
if (!errors.isEmpty()) {
return 0;
}
int level = 1; // Base level
// Check for Level 2 requirements
if (hasIsolatedBuild(provenance)) {
level = 2;
}
// Check for Level 3 requirements
if (level == 2 && hasEphemeralEnvironment(provenance)) {
level = 3;
}
// Check for Level 4 requirements
if (level == 3 && hasTwoPersonReview(provenance)) {
level = 4;
}
return level;
}
private boolean hasIsolatedBuild(SLSAProvenance provenance) {
// Check if build was isolated
var internalParams = provenance.getPredicate()
.getBuildDefinition()
.getInternalParameters();
return internalParams != null && 
Boolean.TRUE.equals(internalParams.get("isolated_build"));
}
private boolean hasEphemeralEnvironment(SLSAProvenance provenance) {
// Check if build environment was ephemeral
var internalParams = provenance.getPredicate()
.getBuildDefinition()
.getInternalParameters();
return internalParams != null && 
Boolean.TRUE.equals(internalParams.get("ephemeral_environment"));
}
private boolean hasTwoPersonReview(SLSAProvenance provenance) {
// Check for two-person review requirement
var metadata = provenance.getPredicate()
.getRunDetails()
.getMetadata();
return metadata != null && 
metadata.getInvokedBy() != null &&
metadata.getInvokedBy().contains(","); // Simplified check
}
}
class VerificationResult {
private final boolean passed;
private final List<String> errors;
private final List<String> warnings;
private final int slsaLevel;
public VerificationResult(boolean passed, 
List<String> errors, 
List<String> warnings,
int slsaLevel) {
this.passed = passed;
this.errors = new ArrayList<>(errors);
this.warnings = new ArrayList<>(warnings);
this.slsaLevel = slsaLevel;
}
// Getters
public boolean isPassed() { return passed; }
public List<String> getErrors() { return Collections.unmodifiableList(errors); }
public List<String> getWarnings() { return Collections.unmodifiableList(warnings); }
public int getSlsaLevel() { return slsaLevel; }
@Override
public String toString() {
return String.format("Verification Result: %s (SLSA Level %d)%nErrors: %s%nWarnings: %s",
passed ? "PASSED" : "FAILED",
slsaLevel,
errors,
warnings);
}
}

5. GitHub Actions Integration

package com.example.slsa.ci;
import com.example.slsa.generator.MavenProvenanceGenerator;
import com.example.slsa.provenance.SLSAProvenance;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GitHubActionsProvenanceGenerator {
public SLSAProvenance generateForGitHubActions(
File artifact,
String workflowRef,
String workflowSha,
Map<String, String> inputs,
Map<String, String> environment) throws IOException {
MavenProvenanceGenerator generator = new MavenProvenanceGenerator(
System.getenv("GITHUB_RUN_ID"),
System.getenv("GITHUB_ACTIONS")
);
// Get source files from repository
List<File> sourceFiles = Files.walk(new File(".").toPath())
.filter(path -> path.toString().endsWith(".java"))
.map(path -> path.toFile())
.toList();
Map<String, String> env = new HashMap<>(environment);
env.put("GITHUB_WORKFLOW", System.getenv("GITHUB_WORKFLOW"));
env.put("GITHUB_RUN_ID", System.getenv("GITHUB_RUN_ID"));
env.put("GITHUB_ACTOR", System.getenv("GITHUB_ACTOR"));
env.put("GITHUB_REPOSITORY", System.getenv("GITHUB_REPOSITORY"));
return generator.generateProvenance(
artifact,
new File("pom.xml"),
env,
sourceFiles
);
}
public void generateAndSignProvenance() throws Exception {
File artifact = new File("target/my-app.jar");
if (!artifact.exists()) {
throw new IOException("Artifact not found: " + artifact);
}
Map<String, String> inputs = new HashMap<>();
inputs.put("java_version", "17");
inputs.put("maven_version", "3.9.5");
Map<String, String> environment = new HashMap<>();
environment.put("OS", System.getProperty("os.name"));
SLSAProvenance provenance = generateForGitHubActions(
artifact,
System.getenv("GITHUB_REF"),
System.getenv("GITHUB_SHA"),
inputs,
environment
);
// Write provenance
File provenanceFile = new File("target/provenance.json");
new MavenProvenanceGenerator("", "").writeProvenance(provenance, provenanceFile);
// Generate attestation
File privateKey = new File(System.getenv("SIGNING_KEY_PATH"));
File attestationFile = new File("target/attestation.json");
new MavenProvenanceGenerator("", "").writeProvenanceAsAttestation(
provenance, privateKey, attestationFile);
System.out.println("Generated provenance: " + provenanceFile.getAbsolutePath());
System.out.println("Generated attestation: " + attestationFile.getAbsolutePath());
}
}

6. Spring Boot Integration

package com.example.slsa.config;
import com.example.slsa.verifier.ProvenanceVerifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Set;
@Configuration
public class SLSAConfig {
@Value("${slsa.trusted.builders}")
private String trustedBuilders;
@Value("${slsa.public.key.directory:/etc/slsa/keys}")
private String publicKeyDirectory;
@Bean
public ProvenanceVerifier provenanceVerifier() {
Set<String> builders = Set.of(trustedBuilders.split(","));
ProvenanceVerifier verifier = new ProvenanceVerifier(builders);
// Load public keys from directory
loadPublicKeys(verifier);
return verifier;
}
private void loadPublicKeys(ProvenanceVerifier verifier) {
// Implementation to load public keys from filesystem
// or from a trusted registry
}
}
@RestController
@RequestMapping("/api/slsa")
public class SLSAVerificationController {
private final ProvenanceVerifier verifier;
private final ObjectMapper objectMapper;
public SLSAVerificationController(ProvenanceVerifier verifier) {
this.verifier = verifier;
this.objectMapper = new ObjectMapper();
}
@PostMapping("/verify")
public ResponseEntity<VerificationResult> verifyArtifact(
@RequestParam("artifact") MultipartFile artifact,
@RequestParam("provenance") MultipartFile provenance) {
try {
// Save files temporarily
File artifactFile = saveToTempFile(artifact);
File provenanceFile = saveToTempFile(provenance);
// Parse provenance
SLSAProvenance slsaProvenance = objectMapper.readValue(
provenanceFile, SLSAProvenance.class);
// Verify
VerificationResult result = verifier.verifyProvenance(
artifactFile, provenanceFile, slsaProvenance);
// Clean up
artifactFile.delete();
provenanceFile.delete();
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
private File saveToTempFile(MultipartFile file) throws IOException {
File tempFile = File.createTempFile("slsa-", "-upload");
file.transferTo(tempFile);
return tempFile;
}
}

7. Best Practices and Production Considerations

Key Management:

public class KeyManager {
private final KeyStore keyStore;
private final String keyAlias;
public KeyManager(String keystorePath, String password, String keyAlias) 
throws Exception {
this.keyStore = KeyStore.getInstance("PKCS12");
this.keyAlias = keyAlias;
try (FileInputStream fis = new FileInputStream(keystorePath)) {
keyStore.load(fis, password.toCharArray());
}
}
public String signProvenance(String provenanceJson) throws Exception {
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, 
keyStorePassword.toCharArray());
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(provenanceJson.getBytes());
byte[] digitalSignature = signature.sign();
return Base64.getEncoder().encodeToString(digitalSignature);
}
public boolean verifySignature(String provenanceJson, String base64Signature) 
throws Exception {
PublicKey publicKey = keyStore.getCertificate(keyAlias).getPublicKey();
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(provenanceJson.getBytes());
byte[] digitalSignature = Base64.getDecoder().decode(base64Signature);
return signature.verify(digitalSignature);
}
}

Provenance Storage:

public interface ProvenanceStorage {
void storeProvenance(String artifactDigest, SLSAProvenance provenance);
Optional<SLSAProvenance> retrieveProvenance(String artifactDigest);
List<SLSAProvenance> findProvenanceByBuilder(String builderId);
}
@Repository
public class CassandraProvenanceStorage implements ProvenanceStorage {
private final CassandraTemplate cassandraTemplate;
@Override
public void storeProvenance(String artifactDigest, SLSAProvenance provenance) {
ProvenanceEntity entity = new ProvenanceEntity(artifactDigest, provenance);
cassandraTemplate.insert(entity);
}
}

Conclusion

Implementing SLSA Provenance in Java provides:

  1. Supply Chain Integrity: Verifiable build provenance
  2. Security Compliance: Meets emerging security standards
  3. Audit Trail: Complete visibility into software creation
  4. Trust Establishment: Cryptographic proof of build authenticity

Key implementation points:

  • Use DSSE envelopes for signing
  • Integrate with CI/CD pipelines
  • Store provenance alongside artifacts
  • Implement proper key management
  • Use standard SLSA predicate types

This implementation provides a foundation for meeting SLSA requirements and improving your software supply chain security.

Advanced Java Supply Chain Security, Kubernetes Hardening & Runtime Threat Detection

Sigstore Rekor in Java – https://macronepal.com/blog/sigstore-rekor-in-java/
Explains integrating Sigstore Rekor into Java systems to create a transparent, tamper-proof log of software signatures and metadata for verifying supply chain integrity.

Securing Java Applications with Chainguard Wolfi – https://macronepal.com/blog/securing-java-applications-with-chainguard-wolfi-a-comprehensive-guide/
Explains using Chainguard Wolfi minimal container images to reduce vulnerabilities and secure Java applications with hardened, lightweight runtime environments.

Cosign Image Signing in Java Complete Guide – https://macronepal.com/blog/cosign-image-signing-in-java-complete-guide/
Explains how to digitally sign container images using Cosign in Java-based workflows to ensure authenticity and prevent unauthorized modifications.

Secure Supply Chain Enforcement Kyverno Image Verification for Java Containers – https://macronepal.com/blog/secure-supply-chain-enforcement-kyverno-image-verification-for-java-containers/
Explains enforcing Kubernetes policies with Kyverno to verify container image signatures and ensure only trusted Java container images are deployed.

Pod Security Admission in Java Securing Kubernetes Deployments for JVM Applications – https://macronepal.com/blog/pod-security-admission-in-java-securing-kubernetes-deployments-for-jvm-applications/
Explains Kubernetes Pod Security Admission policies that enforce security rules like restricted privileges and safe configurations for Java workloads.

Securing Java Applications at Runtime Kubernetes Security Context – https://macronepal.com/blog/securing-java-applications-at-runtime-a-guide-to-kubernetes-security-context/
Explains how Kubernetes security contexts control runtime permissions, user IDs, and access rights for Java containers to improve isolation.

Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring-2/
Explains detecting abnormal runtime behavior in Java applications to identify potential security threats using process monitoring techniques.

Achieving Security Excellence CIS Benchmark Compliance for Java Applications – https://macronepal.com/blog/achieving-security-excellence-implementing-cis-benchmark-compliance-for-java-applications/
Explains applying CIS security benchmarks to Java environments to standardize hardening and improve overall system security posture.

Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring/
Explains behavioral monitoring of Java processes to detect anomalies and improve runtime security through continuous observation and analysis.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper