Introduction to Cosign Image Signing
Cosign is a tool developed by the Sigstore project for signing and verifying container images and other artifacts. It uses keyless signing with OpenID Connect (OIDC) or traditional key-based approaches, providing a secure way to verify the provenance of container images.
System Architecture Overview
Cosign Signing Process ├── Key-based Signing │ ├ - Generate Key Pair │ ├ - Sign Image │ └ - Verify Signature ├── Keyless Signing (OIDC) │ ├ - OIDC Token Acquisition │ ├ - Fulcio Certificate │ └ - Rekor Transparency Log ├── Artifact Types │ ├ - Container Images (OCI) │ ├ - SBOMs (Software Bill of Materials) │ ├ - Binary Files │ └ - Java Archives (JAR) └── Verification ├ - Signature Check ├ - Certificate Validation └ - Rekor Entry Verification
Core Implementation
1. Maven Dependencies
<properties> <cosign.version>2.1.1</cosign.version> <sigstore.version>0.8.0</sigstore.version> <bouncycastle.version>1.75</bouncycastle.version> <jib.version>3.3.2</jib.version> </properties> <dependencies> <!-- Cosign Java Client --> <dependency> <groupId>dev.sigstore</groupId> <artifactId>sigstore-java</artifactId> <version>${sigstore.version}</version> </dependency> <!-- Cosign REST Client --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.14</version> </dependency> <!-- JGit for Git operations --> <dependency> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit</artifactId> <version>6.6.0.202305301015-r</version> </dependency> <!-- Docker Registry Client --> <dependency> <groupId>com.github.docker-java</groupId> <artifactId>docker-java</artifactId> <version>3.3.0</version> </dependency> <!-- OCI Registry Client --> <dependency> <groupId>com.google.cloud.tools</groupId> <artifactId>jib-core</artifactId> <version>${jib.version}</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>2.15.2</version> </dependency> <!-- Base64 & Utilities --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.16.0</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency> </dependencies> 2. Core Cosign Service
import dev.sigstore.KeylessSignature; import dev.sigstore.KeylessSigner; import dev.sigstore.bundle.Bundle; import dev.sigstore.encryption.signers.Signers; import dev.sigstore.rekor.client.RekorClient; import dev.sigstore.trustroot.SigstoreTrustedRoot; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.stereotype.Service; import java.nio.file.Files; import java.nio.file.Path; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.HashMap; import java.util.Map; @Service public class CosignService { static { Security.addProvider(new BouncyCastleProvider()); } private final RekorClient rekorClient; private final KeyPair keyPair; private final SigstoreTrustedRoot trustedRoot; public CosignService() throws Exception { // Initialize Rekor client (transparency log) this.rekorClient = RekorClient.builder() .setServerUrl("https://rekor.sigstore.dev") .build(); // Initialize trusted root this.trustedRoot = SigstoreTrustedRoot.from( getClass().getResourceAsStream("/trustroot.json") ); // Generate or load key pair this.keyPair = loadOrGenerateKeyPair(); } /** * Key-based Image Signing */ public String signImage(String imageReference, Map<String, String> annotations) throws Exception { // Generate signature payload String payload = generateSignaturePayload(imageReference, annotations); byte[] payloadBytes = payload.getBytes(); // Sign the payload byte[] signature = signPayload(payloadBytes); // Upload to Rekor transparency log String rekorEntry = uploadToRekor(payloadBytes, signature); // Create and push signature to registry pushSignatureToRegistry(imageReference, signature, annotations); return rekorEntry; } /** * Keyless Signing with OIDC */ public Bundle keylessSignImage(String imageReference, Map<String, String> annotations, String oidcToken) throws Exception { KeylessSigner signer = KeylessSigner.builder() .sigstorePublicDefaults() .oidcToken(oidcToken) .build(); // Generate signature byte[] payload = generateSignaturePayload(imageReference, annotations).getBytes(); KeylessSignature keylessSignature = signer.sign(payload); // Create bundle Bundle bundle = Bundle.builder() .withMessage(payload) .withSignatures(keylessSignature) .build(); // Push signature pushBundleToRegistry(imageReference, bundle); return bundle; } /** * Verify Image Signature */ public boolean verifyImage(String imageReference) throws Exception { // Pull signature from registry byte[] signature = pullSignatureFromRegistry(imageReference); // Pull certificate (if keyless) byte[] certificate = pullCertificateFromRegistry(imageReference); // Verify against Rekor boolean rekorVerified = verifyRekorEntry(imageReference, signature); // Cryptographic verification boolean cryptoVerified = verifyCryptographic(signature, certificate, imageReference); return rekorVerified && cryptoVerified; } /** * Generate Key Pair */ public KeyPair generateKeyPair() throws NoSuchAlgorithmException { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(2048); return keyGen.generateKeyPair(); } /** * Save Key Pair to Files */ public void saveKeyPair(KeyPair keyPair, Path privateKeyPath, Path publicKeyPath) throws Exception { // Save private key Files.write(privateKeyPath, keyPair.getPrivate().getEncoded()); // Save public key Files.write(publicKeyPath, keyPair.getPublic().getEncoded()); } /** * Load Key Pair from Files */ public KeyPair loadKeyPair(Path privateKeyPath, Path publicKeyPath) throws Exception { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); // Load private key byte[] privateKeyBytes = Files.readAllBytes(privateKeyPath); PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes); PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); // Load public key byte[] publicKeyBytes = Files.readAllBytes(publicKeyPath); X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes); PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); return new KeyPair(publicKey, privateKey); } /** * Sign SBOM (Software Bill of Materials) */ public String signSBOM(String imageReference, String sbomContent) throws Exception { Map<String, String> annotations = new HashMap<>(); annotations.put("sbom.type", "spdx"); annotations.put("sbom.format", "json"); String payload = generateSBOMSignaturePayload(imageReference, sbomContent); byte[] signature = signPayload(payload.getBytes()); // Upload SBOM to registry pushSBOMToRegistry(imageReference, sbomContent, signature); return Base64.getEncoder().encodeToString(signature); } /** * Verify Multiple Signatures */ public VerificationResult verifyAllSignatures(String imageReference) throws Exception { VerificationResult result = new VerificationResult(); result.setImageReference(imageReference); // Check for cosign signature result.setCosignSignatureVerified(verifyCosignSignature(imageReference)); // Check for SBOM signature result.setSbomVerified(verifySBOMSignature(imageReference)); // Check for attestations result.setAttestationsVerified(verifyAttestations(imageReference)); // Check transparency log result.setTransparencyLogVerified(verifyTransparencyLog(imageReference)); return result; } private KeyPair loadOrGenerateKeyPair() throws Exception { Path privateKeyPath = Path.of("cosign.key"); Path publicKeyPath = Path.of("cosign.pub"); if (Files.exists(privateKeyPath) && Files.exists(publicKeyPath)) { return loadKeyPair(privateKeyPath, publicKeyPath); } else { KeyPair newKeyPair = generateKeyPair(); saveKeyPair(newKeyPair, privateKeyPath, publicKeyPath); return newKeyPair; } } private byte[] signPayload(byte[] payload) throws Exception { Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(keyPair.getPrivate()); signature.update(payload); return signature.sign(); } private String generateSignaturePayload(String imageReference, Map<String, String> annotations) { StringBuilder payload = new StringBuilder(); payload.append("image:").append(imageReference).append("\n"); if (annotations != null) { annotations.forEach((key, value) -> { payload.append(key).append(":").append(value).append("\n"); }); } return payload.toString(); } private String uploadToRekor(byte[] payload, byte[] signature) throws Exception { // Implementation for uploading to Rekor transparency log // This would use the RekorClient to create an entry return "rekor-entry-id"; } private void pushSignatureToRegistry(String imageReference, byte[] signature, Map<String, String> annotations) { // Implementation for pushing signature to container registry } private byte[] pullSignatureFromRegistry(String imageReference) { // Implementation for pulling signature from registry return new byte[0]; } private boolean verifyRekorEntry(String imageReference, byte[] signature) { // Implementation for verifying Rekor entry return true; } private boolean verifyCryptographic(byte[] signature, byte[] certificate, String imageReference) { // Implementation for cryptographic verification return true; } // Additional helper methods... } 3. Container Registry Integration
import com.google.cloud.tools.jib.api.*; import com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath; import org.springframework.stereotype.Service; import java.nio.file.Path; import java.util.Map; @Service public class ContainerRegistryService { /** * Push Image with Signature */ public void pushImageWithSignature(Path dockerfilePath, String targetImage, byte[] signature, Map<String, String> annotations) throws Exception { // Build and push container image Containerizer containerizer = Containerizer.to(RegistryImage.named(targetImage) .addCredential("username", "password")); Jib.from("base-image") .addLayer(dockerfilePath.getParent(), AbsoluteUnixPath.get("/")) .containerize(containerizer); // Create and push signature as separate layer pushSignatureLayer(targetImage, signature, annotations); } /** * Attach Signature to Existing Image */ public void attachSignatureToImage(String imageReference, byte[] signature, Map<String, String> annotations) throws Exception { // Pull the image manifest ImageReference imageRef = ImageReference.parse(imageReference); // Create signature manifest DescriptorDigest signatureDigest = createSignatureManifest(signature, annotations); // Attach signature to image attachSignatureToManifest(imageRef, signatureDigest); } /** * Verify Image in Registry */ public boolean verifyImageInRegistry(String imageReference) throws Exception { ImageReference imageRef = ImageReference.parse(imageReference); // Pull image manifest ManifestDescriptor manifest = pullManifest(imageRef); // Check for signature attachment boolean hasSignature = checkForSignature(manifest); if (!hasSignature) { return false; } // Pull and verify signature byte[] signature = pullSignature(manifest); return verifySignatureDigest(signature, manifest.getDigest()); } private DescriptorDigest createSignatureManifest(byte[] signature, Map<String, String> annotations) { // Create a manifest for the signature layer // This would include annotations and the signature content return DescriptorDigest.fromHash("sha256:abc123"); } private void attachSignatureToManifest(ImageReference imageRef, DescriptorDigest signatureDigest) { // Attach signature to image manifest as a referrer } private ManifestDescriptor pullManifest(ImageReference imageRef) throws Exception { // Pull manifest from registry return null; } private boolean checkForSignature(ManifestDescriptor manifest) { // Check if manifest has signature referrers return false; } private byte[] pullSignature(ManifestDescriptor manifest) { // Pull signature from registry return new byte[0]; } private boolean verifySignatureDigest(byte[] signature, DescriptorDigest expectedDigest) { // Verify signature matches expected digest return true; } } 4. OIDC Integration for Keyless Signing
import org.springframework.http.*; import org.springframework.web.client.RestTemplate; import org.springframework.stereotype.Service; import java.util.Map; @Service public class OIDCService { private final RestTemplate restTemplate; public OIDCService() { this.restTemplate = new RestTemplate(); } /** * Get OIDC Token from GitHub Actions */ public String getGitHubActionsToken() { // In GitHub Actions, the token is available via environment variable String token = System.getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN"); String url = System.getenv("ACTIONS_ID_TOKEN_REQUEST_URL"); if (token == null || url == null) { throw new IllegalStateException("Not running in GitHub Actions"); } HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Bearer " + token); HttpEntity<String> entity = new HttpEntity<>(headers); ResponseEntity<Map> response = restTemplate.exchange( url, HttpMethod.GET, entity, Map.class ); return (String) response.getBody().get("value"); } /** * Get OIDC Token from Google Cloud */ public String getGoogleCloudToken() throws Exception { // Use Google Cloud metadata server String metadataUrl = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=sigstore"; HttpHeaders headers = new HttpHeaders(); headers.set("Metadata-Flavor", "Google"); HttpEntity<String> entity = new HttpEntity<>(headers); ResponseEntity<String> response = restTemplate.exchange( metadataUrl, HttpMethod.GET, entity, String.class ); return response.getBody(); } /** * Exchange OIDC Token for Fulcio Certificate */ public String getFulcioCertificate(String oidcToken) { String fulcioUrl = "https://fulcio.sigstore.dev/api/v1/signingCert"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(oidcToken); Map<String, Object> requestBody = Map.of( "publicKey", Map.of( "algorithm", "ECDSA", "content", "base64-encoded-public-key" ), "signedEmailAddress", "base64-signed-email" ); HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers); ResponseEntity<Map> response = restTemplate.exchange( fulcioUrl, HttpMethod.POST, entity, Map.class ); return (String) response.getBody().get("certificate"); } /** * Validate OIDC Token */ public boolean validateToken(String token, String issuer, String audience) { // Implement JWT validation // Check issuer, audience, expiration, etc. return true; } /** * Extract Claims from OIDC Token */ public Map<String, Object> extractClaims(String token) { // Parse JWT and extract claims // This would use a JWT library like jjwt return Map.of(); } } 5. Rekor Transparency Log Integration
import org.springframework.http.*; import org.springframework.web.client.RestTemplate; import org.springframework.stereotype.Service; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Map; @Service public class RekorService { private final RestTemplate restTemplate; private final ObjectMapper objectMapper; private final String rekorUrl = "https://rekor.sigstore.dev"; public RekorService() { this.restTemplate = new RestTemplate(); this.objectMapper = new ObjectMapper(); } /** * Create Rekor Entry for Signature */ public String createEntry(String signature, String certificate, String artifactHash) throws Exception { String entryUrl = rekorUrl + "/api/v1/log/entries"; Map<String, Object> requestBody = Map.of( "apiVersion", "0.0.1", "spec", Map.of( "signature", Map.of( "format", "x509", "content", signature, "publicKey", Map.of( "content", certificate ) ), "data", Map.of( "hash", Map.of( "algorithm", "sha256", "value", artifactHash ) ) ) ); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers); ResponseEntity<Map> response = restTemplate.exchange( entryUrl, HttpMethod.POST, entity, Map.class ); Map<String, Object> responseBody = response.getBody(); return (String) responseBody.get("uuid"); } /** * Get Rekor Entry by UUID */ public Map<String, Object> getEntry(String uuid) { String entryUrl = rekorUrl + "/api/v1/log/entries/" + uuid; ResponseEntity<Map> response = restTemplate.getForEntity(entryUrl, Map.class); return response.getBody(); } /** * Verify Rekor Entry */ public boolean verifyEntry(String uuid, String expectedHash) throws Exception { Map<String, Object> entry = getEntry(uuid); // Extract hash from entry Map<String, Object> body = (Map<String, Object>) entry.get("body"); Map<String, Object> spec = (Map<String, Object>) body.get("spec"); Map<String, Object> data = (Map<String, Object>) spec.get("data"); Map<String, Object> hash = (Map<String, Object>) data.get("hash"); String actualHash = (String) hash.get("value"); // Compare with expected hash return expectedHash.equals(actualHash); } /** * Search Rekor Entries */ public Map<String, Object> searchEntries(String hash, String email, String publicKey) throws Exception { String searchUrl = rekorUrl + "/api/v1/index/retrieve"; Map<String, Object> requestBody = Map.of( "hash", hash, "email", email, "publicKey", publicKey ); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers); ResponseEntity<Map> response = restTemplate.exchange( searchUrl, HttpMethod.POST, entity, Map.class ); return response.getBody(); } /** * Get Rekor Public Key */ public String getPublicKey() { String publicKeyUrl = rekorUrl + "/api/v1/log/publicKey"; return restTemplate.getForObject(publicKeyUrl, String.class); } } 6. REST API Controllers
import org.springframework.http.*; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.util.Map; @RestController @RequestMapping("/api/cosign") public class CosignController { private final CosignService cosignService; private final OIDCService oidcService; private final ContainerRegistryService registryService; public CosignController(CosignService cosignService, OIDCService oidcService, ContainerRegistryService registryService) { this.cosignService = cosignService; this.oidcService = oidcService; this.registryService = registryService; } @PostMapping("/sign/key-based") public ResponseEntity<SignResponse> signImageKeyBased( @RequestBody SignRequest request) { try { String rekorEntry = cosignService.signImage( request.getImageReference(), request.getAnnotations() ); return ResponseEntity.ok(new SignResponse( "Image signed successfully", rekorEntry )); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new SignResponse("Signing failed: " + e.getMessage(), null)); } } @PostMapping("/sign/keyless") public ResponseEntity<SignResponse> signImageKeyless( @RequestBody KeylessSignRequest request) { try { // Get OIDC token based on provider String oidcToken; switch (request.getIdentityProvider()) { case "github": oidcToken = oidcService.getGitHubActionsToken(); break; case "google": oidcToken = oidcService.getGoogleCloudToken(); break; default: oidcToken = request.getOidcToken(); } Bundle bundle = cosignService.keylessSignImage( request.getImageReference(), request.getAnnotations(), oidcToken ); return ResponseEntity.ok(new SignResponse( "Image signed keylessly", bundle.getSignatures().get(0).getCertPath().toString() )); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new SignResponse("Keyless signing failed: " + e.getMessage(), null)); } } @PostMapping("/verify") public ResponseEntity<VerifyResponse> verifyImage( @RequestBody VerifyRequest request) { try { boolean verified = cosignService.verifyImage(request.getImageReference()); if (verified) { return ResponseEntity.ok(new VerifyResponse( "Signature verified successfully", true )); } else { return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(new VerifyResponse( "Signature verification failed", false )); } } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new VerifyResponse( "Verification error: " + e.getMessage(), false )); } } @PostMapping("/sbom/sign") public ResponseEntity<SignResponse> signSBOM( @RequestParam String imageReference, @RequestParam MultipartFile sbomFile) { try { String sbomContent = new String(sbomFile.getBytes()); String signature = cosignService.signSBOM(imageReference, sbomContent); return ResponseEntity.ok(new SignResponse( "SBOM signed successfully", signature )); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new SignResponse("SBOM signing failed: " + e.getMessage(), null)); } } @GetMapping("/keys/generate") public ResponseEntity<KeyPairResponse> generateKeys() { try { KeyPair keyPair = cosignService.generateKeyPair(); return ResponseEntity.ok(new KeyPairResponse( Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()), Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()) )); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new KeyPairResponse(null, null)); } } @PostMapping("/attestations/create") public ResponseEntity<AttestationResponse> createAttestation( @RequestBody AttestationRequest request) { try { // Create in-toto attestation String attestation = createAttestationDocument( request.getImageReference(), request.getPredicateType(), request.getPredicate() ); // Sign attestation String signature = signAttestation(attestation); return ResponseEntity.ok(new AttestationResponse( "Attestation created and signed", attestation, signature )); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new AttestationResponse( "Attestation creation failed: " + e.getMessage(), null, null )); } } // Request/Response classes public static class SignRequest { private String imageReference; private Map<String, String> annotations; // getters and setters } public static class KeylessSignRequest { private String imageReference; private Map<String, String> annotations; private String identityProvider; private String oidcToken; // getters and setters } public static class SignResponse { private String message; private String reference; // constructor, getters and setters } public static class VerifyRequest { private String imageReference; // getters and setters } public static class VerifyResponse { private String message; private boolean verified; // constructor, getters and setters } public static class KeyPairResponse { private String publicKey; private String privateKey; // constructor, getters and setters } private String createAttestationDocument(String imageReference, String predicateType, Map<String, Object> predicate) { // Create in-toto attestation document return ""; } private String signAttestation(String attestation) { // Sign attestation document return ""; } } 7. GitHub Actions Integration
# .github/workflows/build-and-sign.yaml name: Build, Sign and Push on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build-and-sign: runs-on: ubuntu-latest permissions: id-token: write # Needed for keyless signing contents: read steps: - uses: actions/checkout@v3 - name: Set up Java uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' - name: Build with Maven run: mvn clean package - name: Build Docker image run: | docker build -t myapp:${{ github.sha }} . - name: Set up Cosign uses: sigstore/cosign-installer@main - name: Login to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - name: Keyless Sign with Cosign run: | cosign sign --yes \ myapp:${{ github.sha }} \ --registry-username ${{ secrets.DOCKER_USERNAME }} \ --registry-password ${{ secrets.DOCKER_TOKEN }} - name: Generate SBOM run: | syft myapp:${{ github.sha }} --scope all-layers -o spdx-json > sbom.json - name: Sign SBOM run: | cosign attach sbom --yes \ --sbom sbom.json \ myapp:${{ github.sha }} 8. Verification in CI/CD Pipeline
import org.springframework.stereotype.Service; @Service public class CICDVerificationService { private final CosignService cosignService; private final RekorService rekorService; public CICDVerificationService(CosignService cosignService, RekorService rekorService) { this.cosignService = cosignService; this.rekorService = rekorService; } /** * Verify Image Before Deployment */ public boolean verifyImageForDeployment(String imageReference, VerificationPolicy policy) throws Exception { VerificationResult result = cosignService.verifyAllSignatures(imageReference); // Apply policy checks if (!result.isCosignSignatureVerified() && policy.isSignatureRequired()) { throw new SecurityException("Signature verification failed"); } if (!result.isSbomVerified() && policy.isSbomRequired()) { throw new SecurityException("SBOM verification failed"); } if (!result.isAttestationsVerified() && policy.areAttestationsRequired()) { throw new SecurityException("Attestations verification failed"); } // Check for known vulnerabilities if (policy.isVulnerabilityScanRequired()) { boolean hasVulnerabilities = checkVulnerabilities(imageReference); if (hasVulnerabilities && policy.isBlockOnVulnerabilities()) { throw new SecurityException("Image contains vulnerabilities"); } } return true; } /** * Verify Supply Chain */ public SupplyChainVerification verifySupplyChain(String imageReference) throws Exception { SupplyChainVerification verification = new SupplyChainVerification(); // Verify build provenance verification.setBuildProvenanceVerified( verifyBuildProvenance(imageReference) ); // Verify source code verification.setSourceVerified( verifySourceCode(imageReference) ); // Verify dependencies verification.setDependenciesVerified( verifyDependencies(imageReference) ); // Verify deployment environment verification.setEnvironmentVerified( verifyDeploymentEnvironment(imageReference) ); return verification; } private boolean verifyBuildProvenance(String imageReference) { // Verify SLSA provenance return true; } private boolean verifySourceCode(String imageReference) { // Verify source matches built artifact return true; } private boolean verifyDependencies(String imageReference) { // Verify dependencies are from trusted sources return true; } private boolean verifyDeploymentEnvironment(String imageReference) { // Verify deployment environment compliance return true; } private boolean checkVulnerabilities(String imageReference) { // Integrate with vulnerability scanner like Trivy return false; } public static class VerificationPolicy { private boolean signatureRequired = true; private boolean sbomRequired = true; private boolean attestationsRequired = false; private boolean vulnerabilityScanRequired = true; private boolean blockOnVulnerabilities = true; private String[] allowedSigners; private String[] trustedRegistries; // getters and setters } public static class SupplyChainVerification { private boolean buildProvenanceVerified; private boolean sourceVerified; private boolean dependenciesVerified; private boolean environmentVerified; // getters and setters } } 9. Security Configuration
import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/api/cosign/verify").permitAll() .antMatchers("/api/cosign/sign/**").hasRole("SIGNER") .antMatchers("/api/cosign/keys/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .httpBasic() .and() .csrf().disable(); } } 10. Monitoring and Auditing
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.time.Instant; import java.util.concurrent.ConcurrentHashMap; @Service public class CosignAuditService { private static final Logger logger = LoggerFactory.getLogger(CosignAuditService.class); private final ConcurrentHashMap<String, AuditEntry> auditLog = new ConcurrentHashMap<>(); public void logSigningEvent(String imageReference, String signer, String method, boolean success) { AuditEntry entry = new AuditEntry( Instant.now(), "SIGN", imageReference, signer, method, success ); String id = generateId(); auditLog.put(id, entry); logger.info("Signing event: {} - {} - {} - {}", imageReference, signer, method, success ? "SUCCESS" : "FAILURE"); } public void logVerificationEvent(String imageReference, String verifier, boolean success, String details) { AuditEntry entry = new AuditEntry( Instant.now(), "VERIFY", imageReference, verifier, details, success ); String id = generateId(); auditLog.put(id, entry); logger.info("Verification event: {} - {} - {} - {}", imageReference, verifier, success ? "PASS" : "FAIL", details); } public AuditReport generateReport(Instant from, Instant to) { AuditReport report = new AuditReport(); auditLog.forEach((id, entry) -> { if (!entry.getTimestamp().isBefore(from) && !entry.getTimestamp().isAfter(to)) { report.addEntry(entry); } }); return report; } private String generateId() { return Instant.now().toString() + "-" + System.currentTimeMillis(); } public static class AuditEntry { private final Instant timestamp; private final String operation; private final String imageReference; private final String actor; private final String details; private final boolean success; // constructor, getters } public static class AuditReport { private final List<AuditEntry> entries = new ArrayList<>(); private int totalSignings = 0; private int totalVerifications = 0; private int failedSignings = 0; private int failedVerifications = 0; // methods to add entries and calculate statistics } } Best Practices
1. Key Management
// Store keys securely, never commit to version control public class SecureKeyStorage { public void storeKeyInVault(String keyId, byte[] keyMaterial) { // Use HashiCorp Vault, AWS KMS, or Azure Key Vault } public byte[] retrieveKeyFromVault(String keyId) { // Retrieve key from secure storage return new byte[0]; } } 2. Policy Enforcement
// Enforce signing policies public class SigningPolicyEnforcer { public boolean canSign(String imageReference, String signer) { // Check if signer is authorized for this image // Check if image is from allowed registry // Check rate limits return true; } } 3. Error Handling
public class CosignException extends RuntimeException { private final ErrorCode errorCode; public CosignException(ErrorCode errorCode, String message) { super(message); this.errorCode = errorCode; } public enum ErrorCode { SIGNATURE_INVALID, CERTIFICATE_EXPIRED, REKOR_UNAVAILABLE, KEY_NOT_FOUND } } Conclusion
This comprehensive Cosign Image Signing implementation provides:
- Key-based signing with RSA/ECDSA keys
- Keyless signing with OIDC integration
- SBOM signing and verification
- Container registry integration
- Rekor transparency log operations
- CI/CD pipeline integration
- Comprehensive audit logging
- Security policy enforcement
Key benefits:
- Supply chain security for container images
- Non-repudiation through cryptographic signatures
- Transparency via Rekor public log
- Automation-friendly for CI/CD pipelines
- Vendor-neutral implementation
This setup ensures that your container images are securely signed and verified throughout the software supply chain, providing confidence in the integrity and provenance of deployed artifacts.
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.