In-Toto is a framework that protects software supply chain integrity by providing a cryptographically verifiable way to document and validate each step in your software production process.
In-Toto Architecture Overview
Key Concepts:
- Layout: Defines the supply chain steps and authorization rules
- Link Metadata: Attestations from each step in the supply chain
- Attestations: Signed statements about artifacts and processes
- Verification: Cryptographic validation of the entire supply chain
Supply Chain Steps:
- Source Code â Build â Test â Package â Deploy
- Each step generates signed link metadata
- Final verification ensures no tampering occurred
Dependencies and Setup
Maven Dependencies
<properties>
<in-toto.version>0.5.0</in-toto.version>
<bouncycastle.version>1.75</bouncycastle.version>
<jackson.version>2.15.2</jackson.version>
<spring-boot.version>3.1.0</spring-boot.version>
</properties>
<dependencies>
<!-- In-Toto Java (if available) or custom implementation -->
<dependency>
<groupId>io.github.in-toto</groupId>
<artifactId>in-toto-java</artifactId>
<version>${in-toto.version}</version>
</dependency>
<!-- Cryptography -->
<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.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Utilities -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
Cryptography Setup
@Configuration
public class CryptoConfig {
@Bean
public KeyPairGenerator keyPairGenerator() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
return keyGen;
}
@Bean
public SignatureService signatureService() {
return new SignatureService();
}
}
Core In-Toto Models
1. Layout Metadata
@Data
@Builder
@Jacksonized
public class LayoutMetadata {
@JsonProperty("_type")
private final String type = "layout";
private String version;
private Instant expires;
private Map<String, PublicKey> keys;
private List<Step> steps;
private List<Inspection> inspections;
@Data
@Builder
@Jacksonized
public static class Step {
private String name;
private List<String> pubkeys;
private Map<String, String> expectedCommand;
private Map<String, ArtifactRule> expectedMaterials;
private Map<String, ArtifactRule> expectedProducts;
private Threshold threshold;
@Data
@Builder
@Jacksonized
public static class Threshold {
private int threshold;
}
}
@Data
@Builder
@Jacksonized
public static class Inspection {
private String name;
private Map<String, ArtifactRule> expectedMaterials;
private Map<String, ArtifactRule> expectedProducts;
private List<String> run;
}
@Data
@Builder
@Jacksonized
public static class ArtifactRule {
private List<String> include;
private List<String> exclude;
private boolean allowModification;
}
}
2. Link Metadata
@Data
@Builder
@Jacksonized
public class LinkMetadata {
@JsonProperty("_type")
private final String type = "link";
private String name;
private Map<String, String> materials;
private Map<String, String> products;
private String command;
private List<String> byproducts;
private Map<String, String> environment;
private Instant timestamp;
public static LinkMetadata create(String stepName, Map<String, String> materials,
Map<String, String> products, String command) {
return LinkMetadata.builder()
.name(stepName)
.materials(materials)
.products(products)
.command(command)
.byproducts(new ArrayList<>())
.environment(new HashMap<>())
.timestamp(Instant.now())
.build();
}
}
3. Public Key Models
@Data
@Builder
@Jacksonized
public class PublicKey {
@JsonProperty("keyid")
private String keyId;
private String keytype;
private String scheme;
private String keyval;
public static PublicKey fromKeyPair(KeyPair keyPair) throws Exception {
String publicKeyPem = KeyUtils.toPemString(keyPair.getPublic());
String keyId = KeyUtils.generateKeyId(publicKeyPem);
return PublicKey.builder()
.keyId(keyId)
.keytype("rsa")
.scheme("rsassa-pss-sha256")
.keyval(publicKeyPem)
.build();
}
}
@Data
@Builder
@Jacksonized
public class Signature {
@JsonProperty("keyid")
private String keyId;
private String sig;
}
4. Attestation Models
@Data
@Builder
@Jacksonized
public class Attestation {
@JsonProperty("_type")
private final String type = "https://in-toto.io/Statement/v1";
private List<String> subject;
private String predicateType;
private Object predicate;
@Data
@Builder
@Jacksonized
public static class Subject {
private String name;
private Map<String, String> digest;
}
@Data
@Builder
@Jacksonized
public static class ProvenancePredicate {
private String builder;
private Map<String, Object> metadata;
private List<ResourceDescriptor> materials;
private List<BuildStep> buildSteps;
@Data
@Builder
@Jacksonized
public static class ResourceDescriptor {
private String uri;
private Map<String, String> digest;
private Map<String, Object> annotations;
}
@Data
@Builder
@Jacksonized
public static class BuildStep {
private String name;
private String command;
private Map<String, String> environment;
private Map<String, String> inputs;
private Map<String, String> outputs;
}
}
@Data
@Builder
@Jacksonized
public static class SlsaProvenancePredicate {
private String buildType;
private Map<String, Object> builder;
private Map<String, Object> metadata;
private List<ResourceDescriptor> materials;
private Map<String, Object> recipe;
private Map<String, Object> runDetails;
}
}
Cryptography Services
1. Key Management Service
@Service
@Slf4j
public class KeyManagementService {
private final Map<String, KeyPair> keyStore = new ConcurrentHashMap<>();
private final Map<String, PublicKey> publicKeyStore = new ConcurrentHashMap<>();
public KeyPair generateKeyPair(String keyId) throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.generateKeyPair();
keyStore.put(keyId, keyPair);
// Generate public key metadata
PublicKey publicKey = PublicKey.fromKeyPair(keyPair);
publicKeyStore.put(keyId, publicKey);
log.info("Generated key pair for keyId: {}", keyId);
return keyPair;
}
public String signData(String keyId, byte[] data) throws Exception {
KeyPair keyPair = keyStore.get(keyId);
if (keyPair == null) {
throw new KeyNotFoundException("Key not found: " + keyId);
}
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(keyPair.getPrivate());
signature.update(data);
byte[] digitalSignature = signature.sign();
return Base64.getEncoder().encodeToString(digitalSignature);
}
public boolean verifySignature(String keyId, byte[] data, String signatureBase64) throws Exception {
PublicKey publicKey = publicKeyStore.get(keyId);
if (publicKey == null) {
throw new KeyNotFoundException("Public key not found: " + keyId);
}
java.security.PublicKey cryptoPublicKey = KeyUtils.fromPemString(publicKey.getKeyval());
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(cryptoPublicKey);
signature.update(data);
byte[] digitalSignature = Base64.getDecoder().decode(signatureBase64);
return signature.verify(digitalSignature);
}
public PublicKey getPublicKey(String keyId) {
return publicKeyStore.get(keyId);
}
public void importKeyPair(String keyId, KeyPair keyPair) throws Exception {
keyStore.put(keyId, keyPair);
PublicKey publicKey = PublicKey.fromKeyPair(keyPair);
publicKeyStore.put(keyId, publicKey);
log.info("Imported key pair for keyId: {}", keyId);
}
}
@Component
public class KeyUtils {
public static String toPemString(java.security.PublicKey publicKey) {
return "-----BEGIN PUBLIC KEY-----\n" +
Base64.getEncoder().encodeToString(publicKey.getEncoded()) +
"\n-----END PUBLIC KEY-----";
}
public static java.security.PublicKey fromPemString(String pem) throws Exception {
String publicKeyPEM = pem
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
return keyFactory.generatePublic(keySpec);
}
public static String generateKeyId(String publicKeyPem) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(publicKeyPem.getBytes(StandardCharsets.UTF_8));
return Hex.encodeHexString(hash).substring(0, 16);
} catch (Exception e) {
throw new RuntimeException("Failed to generate key ID", e);
}
}
public static String calculateDigest(File file) throws IOException {
try (FileInputStream fis = new FileInputStream(file)) {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
byte[] hash = digest.digest();
return "sha256:" + Hex.encodeHexString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 algorithm not available", e);
}
}
public static String calculateDigest(byte[] data) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data);
return "sha256:" + Hex.encodeHexString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 algorithm not available", e);
}
}
}
2. Signature Service
@Service
@Slf4j
public class SignatureService {
private final KeyManagementService keyManagementService;
private final ObjectMapper objectMapper;
public SignatureService(KeyManagementService keyManagementService) {
this.keyManagementService = keyManagementService;
this.objectMapper = new ObjectMapper();
this.objectMapper.registerModule(new JavaTimeModule());
this.objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
public <T> SignedMetadata<T> signMetadata(T metadata, String keyId) throws Exception {
String json = objectMapper.writeValueAsString(metadata);
byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
String signature = keyManagementService.signData(keyId, jsonBytes);
PublicKey publicKey = keyManagementService.getPublicKey(keyId);
Signature sigObj = Signature.builder()
.keyId(publicKey.getKeyId())
.sig(signature)
.build();
return SignedMetadata.<T>builder()
.signed(metadata)
.signatures(List.of(sigObj))
.build();
}
public <T> boolean verifySignature(SignedMetadata<T> signedMetadata) throws Exception {
T metadata = signedMetadata.getSigned();
String json = objectMapper.writeValueAsString(metadata);
byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
for (Signature signature : signedMetadata.getSignatures()) {
boolean valid = keyManagementService.verifySignature(
signature.getKeyId(), jsonBytes, signature.getSig());
if (!valid) {
log.warn("Invalid signature for keyId: {}", signature.getKeyId());
return false;
}
}
return true;
}
public <T> String toJson(SignedMetadata<T> signedMetadata) throws Exception {
return objectMapper.writeValueAsString(signedMetadata);
}
public <T> SignedMetadata<T> fromJson(String json, Class<T> metadataType) throws Exception {
return objectMapper.readValue(json,
objectMapper.getTypeFactory().constructParametricType(SignedMetadata.class, metadataType));
}
}
@Data
@Builder
@Jacksonized
class SignedMetadata<T> {
private T signed;
private List<Signature> signatures;
}
In-Toto Core Services
1. Layout Service
@Service
@Slf4j
public class LayoutService {
private final SignatureService signatureService;
private final KeyManagementService keyManagementService;
public LayoutService(SignatureService signatureService, KeyManagementService keyManagementService) {
this.signatureService = signatureService;
this.keyManagementService = keyManagementService;
}
public SignedMetadata<LayoutMetadata> createBuildLayout(String projectName,
Map<String, KeyPair> stepKeys) throws Exception {
// Generate keys for each step if not provided
Map<String, PublicKey> publicKeys = new HashMap<>();
for (Map.Entry<String, KeyPair> entry : stepKeys.entrySet()) {
PublicKey publicKey = PublicKey.fromKeyPair(entry.getValue());
publicKeys.put(publicKey.getKeyId(), publicKey);
}
// Define the supply chain steps
List<LayoutMetadata.Step> steps = Arrays.asList(
createStep("clone",
List.of(getKeyId(stepKeys.get("clone"))),
Map.of("expectedCommand", List.of("git", "clone")),
Map.of("include", List.of("*.git"), "exclude", List.of(), "allowModification", false),
Map.of("include", List.of("source/*"), "exclude", List.of(), "allowModification", false)),
createStep("compile",
List.of(getKeyId(stepKeys.get("compile"))),
Map.of("expectedCommand", List.of("mvn", "compile")),
Map.of("include", List.of("source/*"), "exclude", List.of(), "allowModification", false),
Map.of("include", List.of("target/*.jar"), "exclude", List.of(), "allowModification", false)),
createStep("test",
List.of(getKeyId(stepKeys.get("test"))),
Map.of("expectedCommand", List.of("mvn", "test")),
Map.of("include", List.of("target/*.jar"), "exclude", List.of(), "allowModification", false),
Map.of("include", List.of("target/surefire-reports/*"), "exclude", List.of(), "allowModification", false)),
createStep("package",
List.of(getKeyId(stepKeys.get("package"))),
Map.of("expectedCommand", List.of("mvn", "package")),
Map.of("include", List.of("target/*.jar"), "exclude", List.of(), "allowModification", false),
Map.of("include", List.of("target/*.war"), "exclude", List.of(), "allowModification", false))
);
// Create inspections
List<LayoutMetadata.Inspection> inspections = List.of(
createInspection("verify-artifacts",
Map.of("include", List.of("target/*.war"), "exclude", List.of(), "allowModification", false),
Map.of("include", List.of("verification/*"), "exclude", List.of(), "allowModification", false),
List.of("./verify_artifacts.sh"))
);
LayoutMetadata layout = LayoutMetadata.builder()
.version("1.0.0")
.expires(Instant.now().plus(30, ChronoUnit.DAYS))
.keys(publicKeys)
.steps(steps)
.inspections(inspections)
.build();
// Sign the layout with a root key
String rootKeyId = "root";
if (!stepKeys.containsKey(rootKeyId)) {
stepKeys.put(rootKeyId, keyManagementService.generateKeyPair(rootKeyId));
}
return signatureService.signMetadata(layout, rootKeyId);
}
private LayoutMetadata.Step createStep(String name, List<String> pubkeys,
Map<String, List<String>> expectedCommand,
Map<String, Object> expectedMaterials,
Map<String, Object> expectedProducts) {
return LayoutMetadata.Step.builder()
.name(name)
.pubkeys(pubkeys)
.expectedCommand(expectedCommand)
.expectedMaterials(createArtifactRule(expectedMaterials))
.expectedProducts(createArtifactRule(expectedProducts))
.threshold(LayoutMetadata.Step.Threshold.builder().threshold(1).build())
.build();
}
private LayoutMetadata.Inspection createInspection(String name,
Map<String, Object> expectedMaterials,
Map<String, Object> expectedProducts,
List<String> run) {
return LayoutMetadata.Inspection.builder()
.name(name)
.expectedMaterials(createArtifactRule(expectedMaterials))
.expectedProducts(createArtifactRule(expectedProducts))
.run(run)
.build();
}
private Map<String, LayoutMetadata.ArtifactRule> createArtifactRule(Map<String, Object> ruleConfig) {
Map<String, LayoutMetadata.ArtifactRule> rules = new HashMap<>();
@SuppressWarnings("unchecked")
List<String> include = (List<String>) ruleConfig.get("include");
@SuppressWarnings("unchecked")
List<String> exclude = (List<String>) ruleConfig.get("exclude");
boolean allowModification = (Boolean) ruleConfig.get("allowModification");
LayoutMetadata.ArtifactRule rule = LayoutMetadata.ArtifactRule.builder()
.include(include != null ? include : List.of())
.exclude(exclude != null ? exclude : List.of())
.allowModification(allowModification)
.build();
rules.put("rule1", rule);
return rules;
}
private String getKeyId(KeyPair keyPair) throws Exception {
return KeyUtils.generateKeyId(KeyUtils.toPemString(keyPair.getPublic()));
}
}
2. Link Metadata Service
@Service
@Slf4j
public class LinkService {
private final SignatureService signatureService;
private final KeyManagementService keyManagementService;
public LinkService(SignatureService signatureService, KeyManagementService keyManagementService) {
this.signatureService = signatureService;
this.keyManagementService = keyManagementService;
}
public SignedMetadata<LinkMetadata> createLinkMetadata(String stepName,
Map<String, String> materials,
Map<String, String> products,
String command,
String keyId) throws Exception {
LinkMetadata link = LinkMetadata.create(stepName, materials, products, command);
return signatureService.signMetadata(link, keyId);
}
public Map<String, String> captureArtifacts(File directory, List<String> patterns) throws IOException {
Map<String, String> artifacts = new HashMap<>();
for (String pattern : patterns) {
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
Files.walk(directory.toPath())
.filter(Files::isRegularFile)
.filter(path -> matcher.matches(path.getFileName()))
.forEach(path -> {
try {
String digest = KeyUtils.calculateDigest(path.toFile());
String relativePath = directory.toPath().relativize(path).toString();
artifacts.put(relativePath, digest);
} catch (IOException e) {
log.error("Failed to calculate digest for: {}", path, e);
}
});
}
return artifacts;
}
public boolean verifyLinkMetadata(SignedMetadata<LinkMetadata> signedLink,
LayoutMetadata.Step stepDefinition) throws Exception {
// Verify signature
if (!signatureService.verifySignature(signedLink)) {
log.error("Link metadata signature verification failed");
return false;
}
LinkMetadata link = signedLink.getSigned();
// Verify step name matches
if (!link.getName().equals(stepDefinition.getName())) {
log.error("Step name mismatch: {} != {}", link.getName(), stepDefinition.getName());
return false;
}
// Verify materials against expected patterns
if (!verifyArtifacts(link.getMaterials(), stepDefinition.getExpectedMaterials())) {
log.error("Materials verification failed for step: {}", link.getName());
return false;
}
// Verify products against expected patterns
if (!verifyArtifacts(link.getProducts(), stepDefinition.getExpectedProducts())) {
log.error("Products verification failed for step: {}", link.getName());
return false;
}
log.info("Link metadata verification passed for step: {}", link.getName());
return true;
}
private boolean verifyArtifacts(Map<String, String> artifacts,
Map<String, LayoutMetadata.ArtifactRule> expectedRules) {
for (LayoutMetadata.ArtifactRule rule : expectedRules.values()) {
for (String pattern : rule.getInclude()) {
boolean found = artifacts.keySet().stream()
.anyMatch(artifact -> matchesPattern(artifact, pattern));
if (!found && !rule.isAllowModification()) {
log.error("Required artifact pattern not found: {}", pattern);
return false;
}
}
for (String pattern : rule.getExclude()) {
boolean found = artifacts.keySet().stream()
.anyMatch(artifact -> matchesPattern(artifact, pattern));
if (found) {
log.error("Excluded artifact pattern found: {}", pattern);
return false;
}
}
}
return true;
}
private boolean matchesPattern(String artifact, String pattern) {
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
return matcher.matches(Paths.get(artifact));
}
}
3. Attestation Service
@Service
@Slf4j
public class AttestationService {
private final SignatureService signatureService;
private final KeyManagementService keyManagementService;
public AttestationService(SignatureService signatureService, KeyManagementService keyManagementService) {
this.signatureService = signatureService;
this.keyManagementService = keyManagementService;
}
public SignedMetadata<Attestation> createProvenanceAttestation(String subjectName,
Map<String, String> subjectDigest,
String builderId,
List<Attestation.ResourceDescriptor> materials,
List<Attestation.BuildStep> buildSteps,
String keyId) throws Exception {
Attestation.Subject subject = Attestation.Subject.builder()
.name(subjectName)
.digest(subjectDigest)
.build();
Attestation.ProvenancePredicate predicate = Attestation.ProvenancePredicate.builder()
.builder(builderId)
.metadata(Map.of(
"buildInvocationId", UUID.randomUUID().toString(),
"buildStartedOn", Instant.now().toString(),
"completeness", Map.of(
"parameters", true,
"environment", true,
"materials", true
)
))
.materials(materials)
.buildSteps(buildSteps)
.build();
Attestation attestation = Attestation.builder()
.subject(List.of(subject))
.predicateType("https://slsa.dev/provenance/v1")
.predicate(predicate)
.build();
return signatureService.signMetadata(attestation, keyId);
}
public SignedMetadata<Attestation> createSlsaProvenanceAttestation(String subjectName,
Map<String, String> subjectDigest,
String buildType,
Map<String, Object> builder,
List<Attestation.ResourceDescriptor> materials,
String keyId) throws Exception {
Attestation.Subject subject = Attestation.Subject.builder()
.name(subjectName)
.digest(subjectDigest)
.build();
Attestation.SlsaProvenancePredicate predicate = Attestation.SlsaProvenancePredicate.builder()
.buildType(buildType)
.builder(builder)
.metadata(Map.of(
"buildInvocationId", UUID.randomUUID().toString(),
"buildStartedOn", Instant.now().toString()
))
.materials(materials)
.recipe(Map.of(
"type", "https://example.com/java-build",
"definedInMaterial", 0,
"entryPoint", "mvn package"
))
.runDetails(Map.of(
"builder", builder,
"metadata", Map.of(
"startedOn", Instant.now().toString()
)
))
.build();
Attestation attestation = Attestation.builder()
.subject(List.of(subject))
.predicateType("https://slsa.dev/provenance/v0.2")
.predicate(predicate)
.build();
return signatureService.signMetadata(attestation, keyId);
}
public boolean verifyAttestation(SignedMetadata<Attestation> signedAttestation) throws Exception {
// Verify signature
if (!signatureService.verifySignature(signedAttestation)) {
log.error("Attestation signature verification failed");
return false;
}
Attestation attestation = signedAttestation.getSigned();
// Verify subject exists
if (attestation.getSubject() == null || attestation.getSubject().isEmpty()) {
log.error("Attestation has no subject");
return false;
}
// Verify predicate type is supported
List<String> supportedPredicateTypes = List.of(
"https://slsa.dev/provenance/v1",
"https://slsa.dev/provenance/v0.2",
"https://in-toto.io/Provenance/v1"
);
if (!supportedPredicateTypes.contains(attestation.getPredicateType())) {
log.error("Unsupported predicate type: {}", attestation.getPredicateType());
return false;
}
log.info("Attestation verification passed");
return true;
}
}
4. Verification Service
@Service
@Slf4j
public class VerificationService {
private final SignatureService signatureService;
private final LinkService linkService;
public VerificationService(SignatureService signatureService, LinkService linkService) {
this.signatureService = signatureService;
this.linkService = linkService;
}
public VerificationResult verifySupplyChain(SignedMetadata<LayoutMetadata> signedLayout,
Map<String, SignedMetadata<LinkMetadata>> stepLinks) throws Exception {
VerificationResult result = new VerificationResult();
// Verify layout signature
if (!signatureService.verifySignature(signedLayout)) {
result.addError("Layout signature verification failed");
return result;
}
LayoutMetadata layout = signedLayout.getSigned();
// Check layout expiration
if (layout.getExpires().isBefore(Instant.now())) {
result.addError("Layout has expired");
return result;
}
// Verify each step
for (LayoutMetadata.Step step : layout.getSteps()) {
SignedMetadata<LinkMetadata> stepLink = stepLinks.get(step.getName());
if (stepLink == null) {
result.addError("Missing link metadata for step: " + step.getName());
continue;
}
if (!linkService.verifyLinkMetadata(stepLink, step)) {
result.addError("Link verification failed for step: " + step.getName());
} else {
result.addVerifiedStep(step.getName());
}
}
// Run inspections
for (LayoutMetadata.Inspection inspection : layout.getInspections()) {
if (!runInspection(inspection, stepLinks)) {
result.addError("Inspection failed: " + inspection.getName());
} else {
result.addVerifiedInspection(inspection.getName());
}
}
if (result.isSuccess()) {
log.info("Supply chain verification completed successfully");
} else {
log.warn("Supply chain verification failed with {} errors", result.getErrors().size());
}
return result;
}
private boolean runInspection(LayoutMetadata.Inspection inspection,
Map<String, SignedMetadata<LinkMetadata>> stepLinks) {
// In a real implementation, this would execute the inspection commands
// For this example, we'll simulate the inspection
log.info("Running inspection: {}", inspection.getName());
// Verify that expected materials and products exist
// This would involve checking the actual filesystem
return true; // Simulation
}
public boolean verifyFinalArtifact(File artifact,
SignedMetadata<LayoutMetadata> signedLayout,
Map<String, SignedMetadata<LinkMetadata>> stepLinks) throws Exception {
// First verify the supply chain
VerificationResult chainResult = verifySupplyChain(signedLayout, stepLinks);
if (!chainResult.isSuccess()) {
log.error("Supply chain verification failed, cannot verify artifact");
return false;
}
// Verify the artifact matches the final product from the last step
String artifactDigest = KeyUtils.calculateDigest(artifact);
// Get the last step's products
LayoutMetadata layout = signedLayout.getSigned();
if (layout.getSteps().isEmpty()) {
log.error("No steps defined in layout");
return false;
}
LayoutMetadata.Step lastStep = layout.getSteps().get(layout.getSteps().size() - 1);
SignedMetadata<LinkMetadata> lastLink = stepLinks.get(lastStep.getName());
if (lastLink == null) {
log.error("No link metadata for final step: {}", lastStep.getName());
return false;
}
LinkMetadata lastLinkMetadata = lastLink.getSigned();
// Check if artifact digest matches any product
boolean digestMatches = lastLinkMetadata.getProducts().values().stream()
.anyMatch(digest -> digest.equals(artifactDigest));
if (!digestMatches) {
log.error("Artifact digest does not match any product from final step");
return false;
}
log.info("Final artifact verification passed");
return true;
}
}
@Data
class VerificationResult {
private boolean success;
private List<String> verifiedSteps = new ArrayList<>();
private List<String> verifiedInspections = new ArrayList<>();
private List<String> errors = new ArrayList<>();
public void addVerifiedStep(String step) {
verifiedSteps.add(step);
}
public void addVerifiedInspection(String inspection) {
verifiedInspections.add(inspection);
}
public void addError(String error) {
errors.add(error);
success = false;
}
public boolean isSuccess() {
return errors.isEmpty();
}
}
Build System Integration
1. Maven Plugin Integration
@Component
@Slf4j
public class MavenBuildService {
private final LinkService linkService;
private final KeyManagementService keyManagementService;
private final AttestationService attestationService;
public MavenBuildService(LinkService linkService, KeyManagementService keyManagementService,
AttestationService attestationService) {
this.linkService = linkService;
this.keyManagementService = keyManagementService;
this.attestationService = attestationService;
}
public BuildResult executeBuildWithAttestation(File projectDir, String keyId) throws Exception {
BuildResult result = new BuildResult();
// Capture materials (source code)
Map<String, String> materials = linkService.captureArtifacts(projectDir,
List.of("src/**/*", "pom.xml", "*.java"));
// Execute Maven compile
ProcessBuilder compileProcess = new ProcessBuilder("mvn", "compile", "-q");
compileProcess.directory(projectDir);
Process compile = compileProcess.start();
int compileExitCode = compile.waitFor();
if (compileExitCode != 0) {
throw new BuildException("Maven compile failed");
}
// Capture products (compiled classes)
Map<String, String> products = linkService.captureArtifacts(projectDir,
List.of("target/classes/**/*", "target/generated-sources/**/*"));
// Create link metadata for compile step
SignedMetadata<LinkMetadata> compileLink = linkService.createLinkMetadata(
"compile", materials, products, "mvn compile", keyId);
result.addStepLink("compile", compileLink);
// Execute Maven test
ProcessBuilder testProcess = new ProcessBuilder("mvn", "test", "-q");
testProcess.directory(projectDir);
Process test = testProcess.start();
int testExitCode = test.waitFor();
if (testExitCode != 0) {
throw new BuildException("Maven test failed");
}
// Capture test products
Map<String, String> testProducts = linkService.captureArtifacts(projectDir,
List.of("target/surefire-reports/**/*", "target/test-classes/**/*"));
// Create link metadata for test step
SignedMetadata<LinkMetadata> testLink = linkService.createLinkMetadata(
"test", products, testProducts, "mvn test", keyId);
result.addStepLink("test", testLink);
// Execute Maven package
ProcessBuilder packageProcess = new ProcessBuilder("mvn", "package", "-q");
packageProcess.directory(projectDir);
Process packageProc = packageProcess.start();
int packageExitCode = packageProc.waitFor();
if (packageExitCode != 0) {
throw new BuildException("Maven package failed");
}
// Capture final products
Map<String, String> finalProducts = linkService.captureArtifacts(projectDir,
List.of("target/*.jar", "target/*.war"));
// Create link metadata for package step
SignedMetadata<LinkMetadata> packageLink = linkService.createLinkMetadata(
"package", testProducts, finalProducts, "mvn package", keyId);
result.addStepLink("package", packageLink);
// Create provenance attestation
File finalArtifact = findFinalArtifact(projectDir);
if (finalArtifact != null) {
String artifactDigest = KeyUtils.calculateDigest(finalArtifact);
List<Attestation.ResourceDescriptor> buildMaterials = materials.entrySet().stream()
.map(entry -> Attestation.ResourceDescriptor.builder()
.uri("file://" + projectDir.getAbsolutePath() + "/" + entry.getKey())
.digest(Map.of("sha256", entry.getValue().split(":")[1]))
.build())
.collect(Collectors.toList());
List<Attestation.BuildStep> buildSteps = List.of(
Attestation.BuildStep.builder()
.name("compile")
.command("mvn compile")
.inputs(materials)
.outputs(products)
.build(),
Attestation.BuildStep.builder()
.name("test")
.command("mvn test")
.inputs(products)
.outputs(testProducts)
.build(),
Attestation.BuildStep.builder()
.name("package")
.command("mvn package")
.inputs(testProducts)
.outputs(finalProducts)
.build()
);
SignedMetadata<Attestation> provenance = attestationService.createProvenanceAttestation(
finalArtifact.getName(),
Map.of("sha256", artifactDigest.split(":")[1]),
"maven-build-service",
buildMaterials,
buildSteps,
keyId
);
result.setProvenanceAttestation(provenance);
result.setFinalArtifact(finalArtifact);
}
log.info("Build completed with {} steps attested", result.getStepLinks().size());
return result;
}
private File findFinalArtifact(File projectDir) {
File targetDir = new File(projectDir, "target");
if (!targetDir.exists()) return null;
File[] jarFiles = targetDir.listFiles((dir, name) ->
name.endsWith(".jar") && !name.endsWith("-sources.jar"));
return jarFiles != null && jarFiles.length > 0 ? jarFiles[0] : null;
}
}
@Data
class BuildResult {
private File finalArtifact;
private Map<String, SignedMetadata<LinkMetadata>> stepLinks = new HashMap<>();
private SignedMetadata<Attestation> provenanceAttestation;
public void addStepLink(String stepName, SignedMetadata<LinkMetadata> link) {
stepLinks.put(stepName, link);
}
}
2. Jenkins Pipeline Integration
@Component
@Slf4j
public class JenkinsIntegrationService {
private final LinkService linkService;
private final AttestationService attestationService;
private final VerificationService verificationService;
public JenkinsIntegrationService(LinkService linkService, AttestationService attestationService,
VerificationService verificationService) {
this.linkService = linkService;
this.attestationService = attestationService;
this.verificationService = verificationService;
}
public JenkinsBuildResult executeJenkinsPipelineWithAttestation(String jobName,
Map<String, String> parameters,
String workspacePath,
String keyId) throws Exception {
JenkinsBuildResult result = new JenkinsBuildResult();
File workspace = new File(workspacePath);
// Simulate Jenkins pipeline steps with attestation
// Step 1: Checkout
Map<String, String> checkoutMaterials = linkService.captureArtifacts(workspace,
List.of("**/.git/**", "**/*.java", "**/pom.xml"));
// Simulate git checkout
ProcessBuilder gitProcess = new ProcessBuilder("git", "clone", parameters.get("repository"), ".");
gitProcess.directory(workspace);
Process git = gitProcess.start();
git.waitFor();
Map<String, String> checkoutProducts = linkService.captureArtifacts(workspace,
List.of("src/**/*", "pom.xml"));
SignedMetadata<LinkMetadata> checkoutLink = linkService.createLinkMetadata(
"checkout", checkoutMaterials, checkoutProducts,
"git clone " + parameters.get("repository"), keyId);
result.addStepLink("checkout", checkoutLink);
// Step 2: Build
Map<String, String> buildProducts = linkService.captureArtifacts(workspace,
List.of("target/classes/**/*", "target/*.jar"));
// Simulate Maven build
ProcessBuilder mvnProcess = new ProcessBuilder("mvn", "clean", "package", "-q");
mvnProcess.directory(workspace);
Process mvn = mvnProcess.start();
mvn.waitFor();
SignedMetadata<LinkMetadata> buildLink = linkService.createLinkMetadata(
"build", checkoutProducts, buildProducts, "mvn clean package", keyId);
result.addStepLink("build", buildLink);
// Step 3: Test
Map<String, String> testProducts = linkService.captureArtifacts(workspace,
List.of("target/surefire-reports/**/*", "target/jacoco-reports/**/*"));
ProcessBuilder testProcess = new ProcessBuilder("mvn", "test", "-q");
testProcess.directory(workspace);
Process test = testProcess.start();
test.waitFor();
SignedMetadata<LinkMetadata> testLink = linkService.createLinkMetadata(
"test", buildProducts, testProducts, "mvn test", keyId);
result.addStepLink("test", testLink);
// Create final attestation
File finalArtifact = findFinalArtifact(workspace);
if (finalArtifact != null) {
String artifactDigest = KeyUtils.calculateDigest(finalArtifact);
SignedMetadata<Attestation> provenance = attestationService.createSlsaProvenanceAttestation(
finalArtifact.getName(),
Map.of("sha256", artifactDigest.split(":")[1]),
"https://jenkins.io/job/" + jobName,
Map.of(
"id", "jenkins-" + jobName,
"version", "2.4.0"
),
List.of(
Attestation.ResourceDescriptor.builder()
.uri(parameters.get("repository"))
.digest(Map.of("gitCommit", parameters.get("commitId")))
.build()
),
keyId
);
result.setProvenanceAttestation(provenance);
result.setFinalArtifact(finalArtifact);
}
log.info("Jenkins pipeline {} completed with attestations", jobName);
return result;
}
private File findFinalArtifact(File workspace) {
File targetDir = new File(workspace, "target");
File[] jars = targetDir.listFiles((dir, name) -> name.endsWith(".jar"));
return jars != null && jars.length > 0 ? jars[0] : null;
}
}
@Data
class JenkinsBuildResult {
private File finalArtifact;
private Map<String, SignedMetadata<LinkMetadata>> stepLinks = new HashMap<>();
private SignedMetadata<Attestation> provenanceAttestation;
private String buildUrl;
private Instant buildTimestamp;
public void addStepLink(String stepName, SignedMetadata<LinkMetadata> link) {
stepLinks.put(stepName, link);
}
}
REST API Controllers
1. Attestation Controller
@RestController
@RequestMapping("/api/attestations")
@Slf4j
public class AttestationController {
private final LayoutService layoutService;
private final LinkService linkService;
private final AttestationService attestationService;
private final VerificationService verificationService;
private final MavenBuildService mavenBuildService;
public AttestationController(LayoutService layoutService, LinkService linkService,
AttestationService attestationService, VerificationService verificationService,
MavenBuildService mavenBuildService) {
this.layoutService = layoutService;
this.linkService = linkService;
this.attestationService = attestationService;
this.verificationService = verificationService;
this.mavenBuildService = mavenBuildService;
}
@PostMapping("/layout")
public ResponseEntity<ApiResponse<SignedMetadata<LayoutMetadata>>> createLayout(
@RequestBody CreateLayoutRequest request) {
try {
Map<String, KeyPair> stepKeys = new HashMap<>();
for (String step : request.getSteps()) {
stepKeys.put(step, generateKeyPairForStep(step));
}
SignedMetadata<LayoutMetadata> layout = layoutService.createBuildLayout(
request.getProjectName(), stepKeys);
return ResponseEntity.ok(ApiResponse.success(layout));
} catch (Exception e) {
log.error("Failed to create layout", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Failed to create layout: " + e.getMessage()));
}
}
@PostMapping("/link")
public ResponseEntity<ApiResponse<SignedMetadata<LinkMetadata>>> createLink(
@RequestBody CreateLinkRequest request) {
try {
SignedMetadata<LinkMetadata> link = linkService.createLinkMetadata(
request.getStepName(),
request.getMaterials(),
request.getProducts(),
request.getCommand(),
request.getKeyId());
return ResponseEntity.ok(ApiResponse.success(link));
} catch (Exception e) {
log.error("Failed to create link metadata", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Failed to create link: " + e.getMessage()));
}
}
@PostMapping("/provenance")
public ResponseEntity<ApiResponse<SignedMetadata<Attestation>>> createProvenance(
@RequestBody CreateProvenanceRequest request) {
try {
SignedMetadata<Attestation> provenance = attestationService.createProvenanceAttestation(
request.getSubjectName(),
request.getSubjectDigest(),
request.getBuilderId(),
request.getMaterials(),
request.getBuildSteps(),
request.getKeyId());
return ResponseEntity.ok(ApiResponse.success(provenance));
} catch (Exception e) {
log.error("Failed to create provenance attestation", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Failed to create provenance: " + e.getMessage()));
}
}
@PostMapping("/verify/supply-chain")
public ResponseEntity<ApiResponse<VerificationResult>> verifySupplyChain(
@RequestBody VerifySupplyChainRequest request) {
try {
VerificationResult result = verificationService.verifySupplyChain(
request.getLayout(),
request.getStepLinks());
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("Supply chain verification failed", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Verification failed: " + e.getMessage()));
}
}
@PostMapping("/build/maven")
public ResponseEntity<ApiResponse<BuildResult>> executeAttestedBuild(
@RequestBody MavenBuildRequest request) {
try {
BuildResult result = mavenBuildService.executeBuildWithAttestation(
new File(request.getProjectDir()),
request.getKeyId());
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("Attested build failed", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Build failed: " + e.getMessage()));
}
}
private KeyPair generateKeyPairForStep(String step) throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
return keyGen.generateKeyPair();
}
}
@Data
class CreateLayoutRequest {
private String projectName;
private List<String> steps;
}
@Data
class CreateLinkRequest {
private String stepName;
private Map<String, String> materials;
private Map<String, String> products;
private String command;
private String keyId;
}
@Data
class CreateProvenanceRequest {
private String subjectName;
private Map<String, String> subjectDigest;
private String builderId;
private List<Attestation.ResourceDescriptor> materials;
private List<Attestation.BuildStep> buildSteps;
private String keyId;
}
@Data
class VerifySupplyChainRequest {
private SignedMetadata<LayoutMetadata> layout;
private Map<String, SignedMetadata<LinkMetadata>> stepLinks;
}
@Data
class MavenBuildRequest {
private String projectDir;
private String keyId;
}
@Data
@AllArgsConstructor
class ApiResponse<T> {
private boolean success;
private String message;
private T data;
private Instant timestamp;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, "Success", data, Instant.now());
}
public static <T> ApiResponse<T> error(String message) {
return new ApiResponse<>(false, message, null, Instant.now());
}
}
2. Key Management Controller
@RestController
@RequestMapping("/api/keys")
@Slf4j
public class KeyManagementController {
private final KeyManagementService keyManagementService;
public KeyManagementController(KeyManagementService keyManagementService) {
this.keyManagementService = keyManagementService;
}
@PostMapping("/generate")
public ResponseEntity<ApiResponse<String>> generateKeyPair(@RequestParam String keyId) {
try {
keyManagementService.generateKeyPair(keyId);
return ResponseEntity.ok(ApiResponse.success("Key pair generated for: " + keyId));
} catch (Exception e) {
log.error("Failed to generate key pair", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Failed to generate key pair: " + e.getMessage()));
}
}
@GetMapping("/{keyId}/public")
public ResponseEntity<ApiResponse<PublicKey>> getPublicKey(@PathVariable String keyId) {
try {
PublicKey publicKey = keyManagementService.getPublicKey(keyId);
if (publicKey == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("Key not found: " + keyId));
}
return ResponseEntity.ok(ApiResponse.success(publicKey));
} catch (Exception e) {
log.error("Failed to get public key", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Failed to get public key: " + e.getMessage()));
}
}
@PostMapping("/verify")
public ResponseEntity<ApiResponse<Boolean>> verifySignature(
@RequestBody VerifySignatureRequest request) {
try {
boolean isValid = keyManagementService.verifySignature(
request.getKeyId(),
request.getData().getBytes(StandardCharsets.UTF_8),
request.getSignature());
return ResponseEntity.ok(ApiResponse.success(isValid));
} catch (Exception e) {
log.error("Signature verification failed", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Verification failed: " + e.getMessage()));
}
}
}
@Data
class VerifySignatureRequest {
private String keyId;
private String data;
private String signature;
}
Configuration and Exception Handling
1. Application Configuration
# application.yaml spring: application: name: in-toto-attestation-service in-toto: key-storage: path: ./keys algorithm: RSA key-size: 2048 attestation: default-predicate-type: https://slsa.dev/provenance/v1 expiration-days: 30 verification: strict-mode: true require-timestamps: true logging: level: com.example.intoto: DEBUG
2. Custom Exceptions
public class KeyNotFoundException extends RuntimeException {
public KeyNotFoundException(String message) {
super(message);
}
}
public class SignatureVerificationException extends RuntimeException {
public SignatureVerificationException(String message) {
super(message);
}
public SignatureVerificationException(String message, Throwable cause) {
super(message, cause);
}
}
public class BuildException extends RuntimeException {
public BuildException(String message) {
super(message);
}
public BuildException(String message, Throwable cause) {
super(message, cause);
}
}
@ControllerAdvice
public class InTotoExceptionHandler {
@ExceptionHandler(KeyNotFoundException.class)
public ResponseEntity<ApiResponse<?>> handleKeyNotFoundException(KeyNotFoundException e) {
log.error("Key not found", e);
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error(e.getMessage()));
}
@ExceptionHandler(SignatureVerificationException.class)
public ResponseEntity<ApiResponse<?>> handleSignatureVerificationException(SignatureVerificationException e) {
log.error("Signature verification failed", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.error(e.getMessage()));
}
@ExceptionHandler(BuildException.class)
public ResponseEntity<ApiResponse<?>> handleBuildException(BuildException e) {
log.error("Build failed", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error(e.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<?>> handleGenericException(Exception e) {
log.error("Unexpected error", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("An unexpected error occurred"));
}
}
Testing
1. Unit Tests
@ExtendWith(MockitoExtension.class)
class LinkServiceTest {
@Mock
private SignatureService signatureService;
@Mock
private KeyManagementService keyManagementService;
@InjectMocks
private LinkService linkService;
@Test
void shouldCreateLinkMetadata() throws Exception {
// Given
String stepName = "compile";
Map<String, String> materials = Map.of("src/Main.java", "sha256:abc123");
Map<String, String> products = Map.of("target/Main.class", "sha256:def456");
String command = "mvn compile";
String keyId = "test-key";
when(signatureService.signMetadata(any(LinkMetadata.class), eq(keyId)))
.thenReturn(createMockSignedLink());
// When
SignedMetadata<LinkMetadata> result = linkService.createLinkMetadata(
stepName, materials, products, command, keyId);
// Then
assertThat(result).isNotNull();
assertThat(result.getSigned().getName()).isEqualTo(stepName);
}
private SignedMetadata<LinkMetadata> createMockSignedLink() {
LinkMetadata link = LinkMetadata.builder()
.name("compile")
.materials(Map.of())
.products(Map.of())
.command("mvn compile")
.build();
return SignedMetadata.<LinkMetadata>builder()
.signed(link)
.signatures(List.of())
.build();
}
}
@SpringBootTest
class InTotoIntegrationTest {
@Autowired
private LayoutService layoutService;
@Autowired
private VerificationService verificationService;
@Test
void shouldVerifyCompleteSupplyChain() throws Exception {
// Integration test for complete supply chain verification
// This would test the entire attestation flow
}
}
Best Practices
- Key Management: Store private keys securely, use HSM where possible
- Timestamping: Use trusted timestamping services for long-term verification
- Key Rotation: Implement regular key rotation policies
- Audit Logging: Maintain comprehensive audit logs of all attestations
- Policy Enforcement: Define and enforce supply chain security policies
- Integration: Integrate with CI/CD systems for automated attestation
// Example of secure key storage integration
@Component
@Slf4j
public class HSMKeyService {
public KeyPair getKeyFromHSM(String keyId) {
// Integration with Hardware Security Module
// This would use JCE providers like PKCS#11
log.info("Retrieving key from HSM: {}", keyId);
// Implementation would depend on specific HSM
return null;
}
public String signWithHSM(String keyId, byte[] data) {
// Sign data using HSM
log.info("Signing data with HSM key: {}", keyId);
// Implementation would depend on specific HSM
return null;
}
}
Conclusion
In-Toto for Java provides:
- Supply Chain Integrity: Cryptographically verified software provenance
- Step Attestation: Signed metadata for each build step
- Policy Enforcement: Defined expectations for supply chain execution
- Verification: Automated validation of software artifacts
- Transparency: Complete visibility into software production process
By implementing the patterns shown above, you can build robust software supply chain security that prevents tampering, ensures accountability, and provides verifiable proof of your software's integrity from source code to final artifact.
Java Logistics, Shipping Integration & Enterprise Inventory Automation (Tracking, ERP, RFID & Billing Systems)
https://macronepal.com/blog/aftership-tracking-in-java-enterprise-package-visibility/
Explains how to integrate AfterShip tracking services into Java applications to provide real-time shipment visibility, delivery status updates, and centralized tracking across multiple courier services.
https://macronepal.com/blog/shipping-integration-using-fedex-api-with-java-for-logistics-automation/
Explains how to integrate the FedEx API into Java systems to automate shipping tasks such as creating shipments, calculating delivery costs, generating shipping labels, and tracking packages.
https://macronepal.com/blog/shipping-and-logistics-integrating-ups-apis-with-java-applications/
Explains UPS API integration in Java to enable automated shipping operations including rate calculation, shipment scheduling, tracking, and delivery confirmation management.
https://macronepal.com/blog/generating-and-reading-qr-codes-for-products-in-java/
Explains how Java applications generate and read QR codes for product identification, tracking, and authentication, supporting faster inventory handling and product verification processes.
https://macronepal.com/blog/designing-a-robust-pick-and-pack-workflow-in-java/
Explains how to design an efficient pick-and-pack workflow in Java warehouse systems, covering order processing, item selection, packaging steps, and logistics preparation to improve fulfillment efficiency.
https://macronepal.com/blog/rfid-inventory-management-system-in-java-a-complete-guide/
Explains how RFID technology integrates with Java applications to automate inventory tracking, reduce manual errors, and enable real-time stock monitoring in warehouses and retail environments.
https://macronepal.com/blog/erp-integration-with-odoo-in-java/
Explains how Java applications connect with Odoo ERP systems to synchronize inventory, orders, customer records, and financial data across enterprise systems.
https://macronepal.com/blog/automated-invoice-generation-creating-professional-excel-invoices-with-apache-poi-in-java/
Explains how to automatically generate professional Excel invoices in Java using Apache POI, enabling structured billing documents and automated financial record creation.
https://macronepal.com/blog/enterprise-financial-integration-using-quickbooks-api-in-java-applications/
Explains QuickBooks API integration in Java to automate financial workflows such as invoice management, payment tracking, accounting synchronization, and financial reporting.