Introduction to Sigstore Rekor
Sigstore Rekor is a transparency log for software supply chain metadata that provides an immutable, tamper-proof ledger of software artifacts, signatures, and attestations. It enables verifiable tracking of software provenance and integrity.
Dependencies and Configuration
Maven Configuration
<properties> <sigstore.version>0.6.0</sigstore.version> <rekor-client.version>1.0.0</rekor-client.version> <fulcio-client.version>1.0.0</fulcio-client.version> </properties> <dependencies> <!-- Sigstore Java SDK --> <dependency> <groupId>dev.sigstore</groupId> <artifactId>sigstore-java</artifactId> <version>${sigstore.version}</version> </dependency> <!-- Rekor Java Client --> <dependency> <groupId>dev.sigstore.rekor</groupId> <artifactId>rekor-client</artifactId> <version>${rekor-client.version}</version> </dependency> <!-- Fulcio Client for code signing --> <dependency> <groupId>dev.sigstore.fulcio</groupId> <artifactId>fulcio-client</artifactId> <version>${fulcio-client.version}</version> </dependency> <!-- JSON Web Token Support --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <!-- Cryptographic Libraries --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk18on</artifactId> <version>1.77</version> </dependency> <!-- HTTP Client --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> Application Configuration
# application.yml sigstore: enabled: true # Rekor configuration rekor: server-url: ${REKOR_SERVER_URL:https://rekor.sigstore.dev} api-version: v1 timeout: 30 retry: max-attempts: 3 backoff-delay: 1000 # Local cache configuration cache: enabled: true directory: ${user.home}/.sigstore/rekor max-age-hours: 24 # Fulcio configuration (for code signing) fulcio: server-url: ${FULCIO_SERVER_URL:https://fulcio.sigstore.dev} oidc: issuer: https://oauth2.sigstore.dev/auth client-id: sigstore # Signing configuration signing: enabled: true identity-provider: github identity-token: ${GITHUB_TOKEN} private-key-store: ${SIGSTORE_KEY_STORE:file://${user.home}/.sigstore/keys} # Verification configuration verification: enabled: true require-proof-of-possession: true require-timestamp-authority: true policy: allowed-identities: - "https://github.com/.*" max-age-days: 90 # Monitoring configuration monitoring: enabled: true metrics-prefix: sigstore health-check-interval: 60 # Logging configuration for Sigstore logging: level: dev.sigstore: INFO org.apache.http: WARN Core Rekor Client
Rekor Service Implementation
/** * Rekor Service for interacting with Sigstore transparency log */ @Service @Slf4j public class RekorService { private final RekorClient rekorClient; private final FulcioClient fulcioClient; private final SigningService signingService; private final VerificationService verificationService; private final ObjectMapper objectMapper; @Value("${sigstore.rekor.server-url}") private String rekorServerUrl; @Value("${sigstore.rekor.api-version}") private String apiVersion; public RekorService(RestTemplateBuilder restTemplateBuilder, SigningService signingService, VerificationService verificationService) { this.signingService = signingService; this.verificationService = verificationService; this.objectMapper = new ObjectMapper(); configureObjectMapper(); // Initialize Rekor client this.rekorClient = createRekorClient(restTemplateBuilder); // Initialize Fulcio client this.fulcioClient = createFulcioClient(); } /** * Enters artifact metadata into Rekor transparency log */ public RekorEntry enterArtifact(ArtifactMetadata artifact, byte[] signature, String publicKey, String signatureFormat) { try { log.info("Entering artifact into Rekor: {}", artifact.getArtifactHash()); // Create Rekor entry RekorEntryRequest request = createRekorEntryRequest( artifact, signature, publicKey, signatureFormat); // Submit to Rekor RekorEntry entry = rekorClient.createEntry(request); log.info("Artifact entered into Rekor. UUID: {}, LogIndex: {}", entry.getUuid(), entry.getLogIndex()); // Verify the entry was properly recorded verifyRekorEntry(entry.getUuid()); return entry; } catch (Exception e) { log.error("Failed to enter artifact into Rekor: {}", artifact.getArtifactHash(), e); throw new RekorException("Failed to enter artifact into Rekor", e); } } /** * Signs and enters an artifact into Rekor */ public SignedArtifact signAndEnterArtifact(ArtifactMetadata artifact, PrivateKey privateKey) { try { // Sign the artifact SignatureResult signature = signingService.signArtifact( artifact, privateKey); // Get public key String publicKey = signingService.getPublicKeyPem(privateKey); // Enter into Rekor RekorEntry entry = enterArtifact( artifact, signature.getSignatureBytes(), publicKey, signature.getSignatureFormat() ); return new SignedArtifact(artifact, signature, entry); } catch (Exception e) { log.error("Failed to sign and enter artifact: {}", artifact.getArtifactHash(), e); throw new RekorException("Failed to sign and enter artifact", e); } } /** * Retrieves an entry from Rekor by UUID */ public RekorEntry getEntry(String uuid) { try { log.debug("Retrieving Rekor entry: {}", uuid); return rekorClient.getEntry(uuid); } catch (Exception e) { log.error("Failed to retrieve Rekor entry: {}", uuid, e); throw new RekorException("Failed to retrieve Rekor entry: " + uuid, e); } } /** * Retrieves entries by artifact hash */ public List<RekorEntry> getEntriesByArtifactHash(String artifactHash, String hashAlgorithm) { try { log.debug("Searching Rekor entries for artifact hash: {}", artifactHash); SearchQuery query = new SearchQuery() .hash(artifactHash) .hashAlgorithm(hashAlgorithm); return rekorClient.searchLog(query); } catch (Exception e) { log.error("Failed to search Rekor entries for hash: {}", artifactHash, e); throw new RekorException("Failed to search Rekor entries", e); } } /** * Retrieves entries by public key */ public List<RekorEntry> getEntriesByPublicKey(String publicKey) { try { log.debug("Searching Rekor entries for public key"); SearchQuery query = new SearchQuery().publicKey(publicKey); return rekorClient.searchLog(query); } catch (Exception e) { log.error("Failed to search Rekor entries by public key", e); throw new RekorException("Failed to search Rekor entries by public key", e); } } /** * Verifies a Rekor entry */ public VerificationResult verifyEntry(String uuid) { try { log.debug("Verifying Rekor entry: {}", uuid); // Get the entry RekorEntry entry = getEntry(uuid); // Verify inclusion proof boolean inclusionVerified = verifyInclusionProof(entry); // Verify signature boolean signatureVerified = verificationService.verifySignature( entry.getBody(), entry.getSignature(), entry.getPublicKey() ); // Verify timestamp boolean timestampVerified = verifyTimestamp(entry); VerificationResult result = new VerificationResult(); result.setUuid(uuid); result.setInclusionVerified(inclusionVerified); result.setSignatureVerified(signatureVerified); result.setTimestampVerified(timestampVerified); result.setFullyVerified(inclusionVerified && signatureVerified && timestampVerified); result.setVerificationTime(Instant.now()); return result; } catch (Exception e) { log.error("Failed to verify Rekor entry: {}", uuid, e); throw new RekorException("Failed to verify Rekor entry", e); } } /** * Gets Rekor log info */ public LogInfo getLogInfo() { try { return rekorClient.getLogInfo(); } catch (Exception e) { log.error("Failed to get Rekor log info", e); throw new RekorException("Failed to get Rekor log info", e); } } /** * Gets Rekor public key */ public String getRekorPublicKey() { try { return rekorClient.getPublicKey(); } catch (Exception e) { log.error("Failed to get Rekor public key", e); throw new RekorException("Failed to get Rekor public key", e); } } /** * Monitors Rekor log for new entries */ public List<RekorEntry> monitorLog(long sinceLogIndex, int maxEntries) { try { LogInfo logInfo = getLogInfo(); long currentSize = logInfo.getTreeSize(); if (sinceLogIndex >= currentSize) { return Collections.emptyList(); } long endIndex = Math.min(sinceLogIndex + maxEntries, currentSize); List<RekorEntry> newEntries = new ArrayList<>(); for (long i = sinceLogIndex; i < endIndex; i++) { try { // Note: Rekor API might have limits on batch retrieval // This is a simplified implementation RekorEntry entry = rekorClient.getEntryByIndex(i); newEntries.add(entry); } catch (Exception e) { log.warn("Failed to retrieve entry at index {}", i, e); } } return newEntries; } catch (Exception e) { log.error("Failed to monitor Rekor log", e); throw new RekorException("Failed to monitor Rekor log", e); } } /** * Creates a signed attestation and enters it into Rekor */ public AttestationResult createAndEnterAttestation(Attestation attestation, PrivateKey privateKey) { try { // Serialize attestation byte[] attestationBytes = objectMapper.writeValueAsBytes(attestation); // Create artifact metadata ArtifactMetadata metadata = ArtifactMetadata.builder() .artifactHash(calculateHash(attestationBytes, "SHA-256")) .hashAlgorithm("sha256") .artifactType("attestation") .contentType("application/json") .created(Instant.now()) .build(); // Sign and enter SignedArtifact signedArtifact = signAndEnterArtifact(metadata, privateKey); return new AttestationResult(attestation, signedArtifact); } catch (Exception e) { log.error("Failed to create and enter attestation", e); throw new RekorException("Failed to create attestation", e); } } private RekorClient createRekorClient(RestTemplateBuilder restTemplateBuilder) { return new DefaultRekorClient.Builder() .serverUrl(rekorServerUrl) .apiVersion(apiVersion) .restTemplate(restTemplateBuilder.build()) .build(); } private FulcioClient createFulcioClient() { return new DefaultFulcioClient.Builder() .serverUrl("https://fulcio.sigstore.dev") .build(); } private RekorEntryRequest createRekorEntryRequest(ArtifactMetadata artifact, byte[] signature, String publicKey, String signatureFormat) { RekorEntryRequest request = new RekorEntryRequest(); // Set artifact information request.setApiVersion("0.0.1"); request.setKind("hashedrekord"); // Build spec HashedRekordSpec spec = new HashedRekordSpec(); spec.setData(HashedRekordData.builder() .hash(Hash.builder() .algorithm(artifact.getHashAlgorithm()) .value(artifact.getArtifactHash()) .build()) .build()); spec.setSignature(HashedRekordSignature.builder() .content(Base64.getEncoder().encodeToString(signature)) .publicKey(HashedRekordPublicKey.builder() .content(publicKey) .build()) .format(signatureFormat) .build()); request.setSpec(spec); return request; } private void verifyRekorEntry(String uuid) { try { // Wait a moment for the entry to be processed Thread.sleep(1000); VerificationResult result = verifyEntry(uuid); if (!result.isFullyVerified()) { throw new RekorException("Rekor entry verification failed for UUID: " + uuid); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RekorException("Verification interrupted", e); } } private boolean verifyInclusionProof(RekorEntry entry) { try { // Verify Merkle tree inclusion proof InclusionProof proof = entry.getInclusionProof(); if (proof == null) { log.warn("No inclusion proof found for entry: {}", entry.getUuid()); return false; } // Verify the proof return rekorClient.verifyInclusionProof( entry.getLogIndex(), entry.getBodyHash(), proof.getRootHash(), proof.getHashes(), proof.getTreeSize() ); } catch (Exception e) { log.error("Failed to verify inclusion proof for entry: {}", entry.getUuid(), e); return false; } } private boolean verifyTimestamp(RekorEntry entry) { try { // Verify integrated timestamp signature if (entry.getIntegratedTime() == null) { log.warn("No timestamp found for entry: {}", entry.getUuid()); return false; } // Check timestamp is not too far in the future Instant entryTime = Instant.ofEpochSecond(entry.getIntegratedTime()); Instant now = Instant.now(); if (entryTime.isAfter(now.plus(Duration.ofDays(1)))) { log.warn("Entry timestamp is in the future: {}", entryTime); return false; } // Verify timestamp signature if available if (entry.getTimestampSignature() != null) { return verificationService.verifyTimestamp( entry.getBodyHash(), entry.getTimestampSignature(), entry.getIntegratedTime() ); } return true; // Some entries might not have timestamp signatures } catch (Exception e) { log.error("Failed to verify timestamp for entry: {}", entry.getUuid(), e); return false; } } private String calculateHash(byte[] data, String algorithm) throws NoSuchAlgorithmException { MessageDigest digest = MessageDigest.getInstance(algorithm); byte[] hash = digest.digest(data); return Hex.encodeHexString(hash); } private void configureObjectMapper() { objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); objectMapper.registerModule(new JavaTimeModule()); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } } /** * Rekor data models */ @Data @Builder public class ArtifactMetadata { private String artifactHash; private String hashAlgorithm; private String artifactType; private String contentType; private String description; private Map<String, String> annotations; private Instant created; public static class ArtifactMetadataBuilder { public ArtifactMetadataBuilder annotation(String key, String value) { if (this.annotations == null) { this.annotations = new HashMap<>(); } this.annotations.put(key, value); return this; } } } @Data public class SignedArtifact { private ArtifactMetadata artifact; private SignatureResult signature; private RekorEntry rekorEntry; private Instant signedAt; public SignedArtifact(ArtifactMetadata artifact, SignatureResult signature, RekorEntry rekorEntry) { this.artifact = artifact; this.signature = signature; this.rekorEntry = rekorEntry; this.signedAt = Instant.now(); } } @Data public class Attestation { private String predicateType; private Map<String, Object> predicate; private Map<String, Object> subject; private Map<String, String> headers; private Instant generatedAt; } @Data public class AttestationResult { private Attestation attestation; private SignedArtifact signedArtifact; public AttestationResult(Attestation attestation, SignedArtifact signedArtifact) { this.attestation = attestation; this.signedArtifact = signedArtifact; } } @Data public class VerificationResult { private String uuid; private boolean inclusionVerified; private boolean signatureVerified; private boolean timestampVerified; private boolean fullyVerified; private Instant verificationTime; private String verificationMessage; } @Data public class LogInfo { private Long treeSize; private String rootHash; private String signedTreeHead; private String treeId; private Instant timestamp; } @Data @Builder public class HashedRekordSpec { private HashedRekordData data; private HashedRekordSignature signature; @Data @Builder public static class HashedRekordData { private Hash hash; @Data @Builder public static class Hash { private String algorithm; private String value; } } @Data @Builder public static class HashedRekordSignature { private String content; private HashedRekordPublicKey publicKey; private String format; @Data @Builder public static class HashedRekordPublicKey { private String content; } } } public class RekorException extends RuntimeException { public RekorException(String message) { super(message); } public RekorException(String message, Throwable cause) { super(message, cause); } } Signing Service
Artifact Signing Service
/** * Service for signing artifacts with cryptographic signatures */ @Service @Slf4j public class SigningService { private final KeyPairGenerator keyPairGenerator; private final Signature signatureAlgorithm; private final SecureRandom secureRandom; @Value("${sigstore.signing.signature-algorithm:RSA}") private String signatureAlgorithmName; @Value("${sigstore.signing.key-size:2048}") private int keySize; @Value("${sigstore.signing.hash-algorithm:SHA256withRSA}") private String hashAlgorithm; public SigningService() throws NoSuchAlgorithmException { this.keyPairGenerator = KeyPairGenerator.getInstance(signatureAlgorithmName); this.keyPairGenerator.initialize(keySize); this.signatureAlgorithm = Signature.getInstance(hashAlgorithm); this.secureRandom = new SecureRandom(); } /** * Generates a new key pair for signing */ public KeyPair generateKeyPair() { try { log.info("Generating new key pair for signing"); return keyPairGenerator.generateKeyPair(); } catch (Exception e) { log.error("Failed to generate key pair", e); throw new SigningException("Failed to generate key pair", e); } } /** * Signs an artifact with the given private key */ public SignatureResult signArtifact(ArtifactMetadata artifact, PrivateKey privateKey) { try { log.debug("Signing artifact: {}", artifact.getArtifactHash()); // Create signing data byte[] dataToSign = createSigningData(artifact); // Initialize signature signatureAlgorithm.initSign(privateKey, secureRandom); signatureAlgorithm.update(dataToSign); // Generate signature byte[] signatureBytes = signatureAlgorithm.sign(); // Create signature result SignatureResult result = new SignatureResult(); result.setSignatureBytes(signatureBytes); result.setSignatureFormat(getSignatureFormat()); result.setHashAlgorithm(artifact.getHashAlgorithm()); result.setArtifactHash(artifact.getArtifactHash()); result.setSigningTime(Instant.now()); log.info("Artifact signed successfully: {}", artifact.getArtifactHash()); return result; } catch (Exception e) { log.error("Failed to sign artifact: {}", artifact.getArtifactHash(), e); throw new SigningException("Failed to sign artifact", e); } } /** * Signs an artifact with OIDC token (for Sigstore Fulcio) */ public SignatureResult signWithOIDC(ArtifactMetadata artifact, String oidcToken) { try { // This would integrate with Fulcio for code signing certificates // For now, we'll use a simplified approach log.info("Signing artifact with OIDC token: {}", artifact.getArtifactHash()); // Generate ephemeral key pair KeyPair keyPair = generateKeyPair(); // Get signing certificate from Fulcio (simplified) X509Certificate signingCertificate = getSigningCertificate( keyPair.getPublic(), oidcToken); // Sign the artifact SignatureResult signature = signArtifact(artifact, keyPair.getPrivate()); // Add certificate to signature signature.setCertificate(signingCertificate); signature.setSigningMethod("fulcio-oidc"); return signature; } catch (Exception e) { log.error("Failed to sign with OIDC: {}", artifact.getArtifactHash(), e); throw new SigningException("Failed to sign with OIDC", e); } } /** * Gets public key in PEM format */ public String getPublicKeyPem(PrivateKey privateKey) { try { // In a real implementation, we'd extract public key from private key // For RSA keys RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey; RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec( rsaPrivateKey.getModulus(), rsaPrivateKey.getPrivateExponent()); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); return convertToPem(publicKey); } catch (Exception e) { log.error("Failed to get public key PEM", e); throw new SigningException("Failed to get public key PEM", e); } } /** * Creates a detached signature for an artifact */ public DetachedSignature createDetachedSignature(byte[] artifactData, PrivateKey privateKey) { try { log.debug("Creating detached signature for artifact data"); // Calculate hash String hash = calculateHash(artifactData, "SHA-256"); // Create artifact metadata ArtifactMetadata metadata = ArtifactMetadata.builder() .artifactHash(hash) .hashAlgorithm("sha256") .artifactType("binary") .contentType("application/octet-stream") .created(Instant.now()) .build(); // Sign the metadata SignatureResult signature = signArtifact(metadata, privateKey); return new DetachedSignature(artifactData, signature); } catch (Exception e) { log.error("Failed to create detached signature", e); throw new SigningException("Failed to create detached signature", e); } } /** * Signs multiple artifacts in batch */ public Map<String, SignatureResult> signBatch(List<ArtifactMetadata> artifacts, PrivateKey privateKey) { Map<String, SignatureResult> results = new HashMap<>(); for (ArtifactMetadata artifact : artifacts) { try { SignatureResult signature = signArtifact(artifact, privateKey); results.put(artifact.getArtifactHash(), signature); } catch (Exception e) { log.error("Failed to sign artifact in batch: {}", artifact.getArtifactHash(), e); // Continue with other artifacts } } return results; } /** * Creates a timestamped signature */ public TimestampedSignature createTimestampedSignature(ArtifactMetadata artifact, PrivateKey privateKey) { try { // Create standard signature SignatureResult signature = signArtifact(artifact, privateKey); // Get timestamp from timestamp authority Timestamp timestamp = getTimestampFromAuthority( signature.getSignatureBytes()); return new TimestampedSignature(signature, timestamp); } catch (Exception e) { log.error("Failed to create timestamped signature", e); throw new SigningException("Failed to create timestamped signature", e); } } private byte[] createSigningData(ArtifactMetadata artifact) throws JsonProcessingException { // Create canonical JSON representation for signing Map<String, Object> signingData = new LinkedHashMap<>(); signingData.put("artifactHash", artifact.getArtifactHash()); signingData.put("hashAlgorithm", artifact.getHashAlgorithm()); signingData.put("artifactType", artifact.getArtifactType()); signingData.put("contentType", artifact.getContentType()); signingData.put("created", artifact.getCreated().toString()); if (artifact.getAnnotations() != null) { signingData.put("annotations", artifact.getAnnotations()); } ObjectMapper mapper = new ObjectMapper(); return mapper.writeValueAsBytes(signingData); } private String getSignatureFormat() { return "x509"; } private X509Certificate getSigningCertificate(PublicKey publicKey, String oidcToken) { // This would make a request to Fulcio to get a signing certificate // For now, return a mock certificate try { // Create a self-signed certificate for demonstration return createSelfSignedCertificate(publicKey); } catch (Exception e) { log.error("Failed to get signing certificate", e); throw new SigningException("Failed to get signing certificate", e); } } private X509Certificate createSelfSignedCertificate(PublicKey publicKey) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException { // Create certificate builder X500Name issuer = new X500Name("CN=Sigstore Demo, O=Demo Org"); X500Name subject = new X500Name("CN=Sigstore Demo, O=Demo Org"); BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); Date notBefore = new Date(); Date notAfter = new Date(notBefore.getTime() + 365L * 24 * 60 * 60 * 1000); // 1 year X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder( issuer, serial, notBefore, notAfter, subject, SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); // Sign the certificate ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA") .build(generateKeyPair().getPrivate()); X509CertificateHolder certHolder = certBuilder.build(signer); return new JcaX509CertificateConverter() .getCertificate(certHolder); } private String convertToPem(PublicKey publicKey) throws IOException { StringWriter writer = new StringWriter(); try (PemWriter pemWriter = new PemWriter(writer)) { PemObject pemObject = new PemObject("PUBLIC KEY", publicKey.getEncoded()); pemWriter.writeObject(pemObject); } return writer.toString(); } private String calculateHash(byte[] data, String algorithm) throws NoSuchAlgorithmException { MessageDigest digest = MessageDigest.getInstance(algorithm); byte[] hash = digest.digest(data); return Hex.encodeHexString(hash); } private Timestamp getTimestampFromAuthority(byte[] signature) { // This would request a timestamp from a timestamp authority // For now, create a mock timestamp return new Timestamp(Instant.now(), "mock-authority", "mock-token"); } } /** * Signing data models */ @Data public class SignatureResult { private byte[] signatureBytes; private String signatureFormat; private String hashAlgorithm; private String artifactHash; private Instant signingTime; private X509Certificate certificate; private String signingMethod; private Map<String, String> metadata; } @Data @AllArgsConstructor public class DetachedSignature { private byte[] artifactData; private SignatureResult signature; private Instant created; public DetachedSignature(byte[] artifactData, SignatureResult signature) { this.artifactData = artifactData; this.signature = signature; this.created = Instant.now(); } } @Data public class TimestampedSignature { private SignatureResult signature; private Timestamp timestamp; private Instant timestampedAt; public TimestampedSignature(SignatureResult signature, Timestamp timestamp) { this.signature = signature; this.timestamp = timestamp; this.timestampedAt = Instant.now(); } } @Data public class Timestamp { private Instant timestamp; private String authority; private String token; private byte[] signature; public Timestamp(Instant timestamp, String authority, String token) { this.timestamp = timestamp; this.authority = authority; this.token = token; } } public class SigningException extends RuntimeException { public SigningException(String message) { super(message); } public SigningException(String message, Throwable cause) { super(message, cause); } } Verification Service
Signature Verification Service
```java
/**
- Service for verifying signatures and Rekor entries
*/
@Service
@Slf4j
public class VerificationService { private final Signature signatureVerifier;
private final CertificateFactory certificateFactory;
private final TrustManagerFactory trustManagerFactory; @Value("${sigstore.verification.require-proof-of-possession:true}")
private boolean requireProofOfPossession; @Value("${sigstore.verification.allowed-signature-algorithms:RSA,ECDSA}")
private List allowedSignatureAlgorithms; public VerificationService() throws NoSuchAlgorithmException, CertificateException,
KeyStoreException, IOException {this.signatureVerifier = Signature.getInstance("SHA256withRSA"); this.certificateFactory = CertificateFactory.getInstance("X.509"); // Initialize trust store with Sigstore roots this.trustManagerFactory = initializeTrustManagerFactory();} /**- Verifies a signature against public key
*/
public boolean verifySignature(byte[] data, byte[] signature, String publicKeyPem) {
try {
log.debug("Verifying signature with public key");// Parse public key from PEM PublicKey publicKey = parsePublicKey(publicKeyPem); // Initialize verifier signatureVerifier.initVerify(publicKey); signatureVerifier.update(data); // Verify signature return signatureVerifier.verify(signature);} catch (Exception e) {
log.error("Signature verification failed", e);
return false;
}
}
- Verifies a signature with X.509 certificate
*/
public CertificateVerificationResult verifySignatureWithCertificate(
byte[] data, byte[] signature, X509Certificate certificate) { try {
log.debug("Verifying signature with X.509 certificate");CertificateVerificationResult result = new CertificateVerificationResult(); // Verify certificate chain boolean certificateValid = verifyCertificateChain(certificate); result.setCertificateValid(certificateValid); if (!certificateValid) { result.setVerificationMessage("Certificate validation failed"); return result; } // Verify certificate is not expired boolean certificateNotExpired = verifyCertificateNotExpired(certificate); result.setCertificateNotExpired(certificateNotExpired); if (!certificateNotExpired) { result.setVerificationMessage("Certificate has expired"); return result; } // Verify signature signatureVerifier.initVerify(certificate.getPublicKey()); signatureVerifier.update(data); boolean signatureValid = signatureVerifier.verify(signature); result.setSignatureValid(signatureValid); if (!signatureValid) { result.setVerificationMessage("Signature verification failed"); return result; } // Verify proof of possession if required if (requireProofOfPossession) { boolean popVerified = verifyProofOfPossession(certificate, data); result.setProofOfPossessionVerified(popVerified);if (!popVerified) { result.setVerificationMessage("Proof of possession verification failed"); return result; }} // Extract and verify identity IdentityVerification identity = verifyCertificateIdentity(certificate); result.setIdentityVerified(identity.isVerified()); result.setIdentity(identity.getIdentity()); result.setFullyVerified(true); result.setVerificationMessage("Signature and certificate verified successfully"); return result;} catch (Exception e) {
log.error("Certificate signature verification failed", e);
CertificateVerificationResult result = new CertificateVerificationResult();
result.setVerificationMessage("Verification failed: " + e.getMessage());
return result;
}
}
- Verifies timestamp signature
*/
public boolean verifyTimestamp(String dataHash, byte[] timestampSignature, long timestamp) {
try {
log.debug("Verifying timestamp signature");// Create timestamp verification data String verificationData = String.format("%s:%d", dataHash, timestamp); // This would verify against timestamp authority's public key // For now, return true for demonstration return true;} catch (Exception e) {
log.error("Timestamp verification failed", e);
return false;
}
}
- Verifies a complete Rekor entry chain
*/
public ChainVerificationResult verifyEntryChain(RekorEntry entry) {
try {
log.debug("Verifying Rekor entry chain: {}", entry.getUuid());ChainVerificationResult result = new ChainVerificationResult(); result.setEntryUuid(entry.getUuid()); // Verify entry signature boolean entrySignatureVerified = verifySignature( entry.getBody().getBytes(StandardCharsets.UTF_8), Base64.getDecoder().decode(entry.getSignature()), entry.getPublicKey() ); result.setEntrySignatureVerified(entrySignatureVerified); // Verify timestamp boolean timestampVerified = verifyTimestamp( entry.getBodyHash(), entry.getTimestampSignature(), entry.getIntegratedTime() ); result.setTimestampVerified(timestampVerified); // Verify inclusion proof if available if (entry.getInclusionProof() != null) { // This would verify Merkle tree inclusion // Implementation depends on Rekor client capabilities result.setInclusionVerified(true); // Simplified } else { result.setInclusionVerified(false); result.addWarning("No inclusion proof available"); } // Verify certificate chain if certificate is present if (entry.getCertificate() != null) { CertificateVerificationResult certResult = verifySignatureWithCertificate( entry.getBody().getBytes(StandardCharsets.UTF_8), Base64.getDecoder().decode(entry.getSignature()), entry.getCertificate() ); result.setCertificateVerified(certResult.isFullyVerified()); result.setCertificateIdentity(certResult.getIdentity()); } // Check log consistency boolean logConsistent = verifyLog - 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.
- Verifies a signature against public key