diff --git a/pom.xml b/pom.xml index 19745da..1882300 100644 --- a/pom.xml +++ b/pom.xml @@ -119,6 +119,13 @@ jakarta.el 4.0.2 + + + dev.sigstore + sigstore-java + 0.10.0 + + diff --git a/src/main/java/io/github/intoto/dsse/helpers/SimpleSigstoreSigner.java b/src/main/java/io/github/intoto/dsse/helpers/SimpleSigstoreSigner.java new file mode 100644 index 0000000..6670f2d --- /dev/null +++ b/src/main/java/io/github/intoto/dsse/helpers/SimpleSigstoreSigner.java @@ -0,0 +1,90 @@ +package io.github.intoto.dsse.helpers; + +import dev.sigstore.KeylessSigner; +import dev.sigstore.KeylessSignerException; +import dev.sigstore.bundle.Bundle; +import io.github.intoto.dsse.models.Signer; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.util.Optional; + +public class SimpleSigstoreSigner implements Signer { + private String keyId; + Optional messageSignature; + Bundle result; + + public byte[] sign(byte[] payload) throws InvalidAlgorithmParameterException, CertificateException, IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, KeylessSignerException { + if (payload == null || payload.length == 0) { + throw new RuntimeException("payload cannot be null or empty"); + } + + // convert payload to SHA-256 Digest + MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + byte[] payloadDigest = messageDigest.digest(payload); + + KeylessSigner functionary = new KeylessSigner.Builder().sigstorePublicDefaults().build(); + this.result = functionary.sign(payloadDigest); + + this.keyId = setKeyId(this.result); + + this.messageSignature = this.result.getMessageSignature(); + if (this.messageSignature.isPresent()) { + return this.messageSignature + .get() + .getSignature(); + } + throw new RuntimeException("Cannot retrieve Message Signature"); + } + + private String setKeyId(Bundle bundle) throws CertificateParsingException { + if (!bundle.getCertPath().getCertificates().isEmpty()) { + X509Certificate certificate = (X509Certificate) (bundle.getCertPath().getCertificates().get(0)); + String oid = "1.3.6.1.4.1.57264.1.8"; + byte[] extensionValue = certificate.getExtensionValue(oid); + String issuer = new String(extensionValue, StandardCharsets.UTF_8); + String header = "https://"; + String provider = issuer.substring(issuer.lastIndexOf("/") + 1); + issuer = header + provider; + + this.keyId = "<" + issuer + ">:"; + Object sanArray = certificate.getSubjectAlternativeNames().toArray()[0]; + String san = sanArray.toString(); + san = san.substring(4, san.length() - 1); + this.keyId = keyId.concat("<" + san + ">"); + return this.keyId; + } + throw new RuntimeException("Cannot extract certificates from empty bundle"); + } + + @Override + public String getKeyId() { + if (this.keyId.isEmpty()) { + throw new RuntimeException("Sign the artifact to initialize keyId"); + } + return this.keyId; + } + + public byte[] getPayloadDigest() { + if (this.messageSignature.isEmpty()) { + throw new RuntimeException("Cannot retrieve and unsigned payload"); + } + if (this.messageSignature.get().getMessageDigest().isPresent()) { + return this.messageSignature + .get() + .getMessageDigest() + .get() + .getDigest(); + } + throw new RuntimeException("Cannot retrieve SHA-256 Message Digest"); + } + + public byte[] getBundleJsonBytes() { + return this.result.toJson().getBytes(); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/intoto/dsse/models/Signer.java b/src/main/java/io/github/intoto/dsse/models/Signer.java index 4928d9e..4566c0d 100644 --- a/src/main/java/io/github/intoto/dsse/models/Signer.java +++ b/src/main/java/io/github/intoto/dsse/models/Signer.java @@ -1,8 +1,14 @@ package io.github.intoto.dsse.models; +import dev.sigstore.KeylessSignerException; + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; /** Interface for a DSSE Signer. */ public interface Signer { @@ -13,7 +19,7 @@ public interface Signer { * @param payload the message that you want to sign. */ byte[] sign(byte[] payload) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException; + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, InvalidAlgorithmParameterException, CertificateException, IOException, InvalidKeySpecException, KeylessSignerException; /** Returns the ID of this key, or null if not supported. */ String getKeyId(); diff --git a/src/main/java/io/github/intoto/helpers/IntotoHelper.java b/src/main/java/io/github/intoto/helpers/IntotoHelper.java index f51487e..9f48313 100644 --- a/src/main/java/io/github/intoto/helpers/IntotoHelper.java +++ b/src/main/java/io/github/intoto/helpers/IntotoHelper.java @@ -4,15 +4,21 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; +import dev.sigstore.KeylessSignerException; import io.github.intoto.dsse.models.IntotoEnvelope; import io.github.intoto.dsse.models.Signature; import io.github.intoto.dsse.models.Signer; import io.github.intoto.exceptions.InvalidModelException; import io.github.intoto.models.Statement; + +import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; import java.util.Base64; import java.util.List; import java.util.Set; @@ -49,8 +55,8 @@ public class IntotoHelper { */ public static String produceIntotoEnvelopeAsJson( Statement statement, Signer signer, boolean prettyPrint) - throws InvalidModelException, JsonProcessingException, NoSuchAlgorithmException, - SignatureException, InvalidKeyException { + throws InvalidModelException, IOException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException, InvalidAlgorithmParameterException, CertificateException, InvalidKeySpecException, KeylessSignerException { IntotoEnvelope envelope = produceIntotoEnvelope(statement, signer); if (prettyPrint) { return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(envelope); @@ -74,8 +80,8 @@ public static String produceIntotoEnvelopeAsJson( * algorithm */ public static IntotoEnvelope produceIntotoEnvelope(Statement statement, Signer signer) - throws InvalidModelException, JsonProcessingException, NoSuchAlgorithmException, - SignatureException, InvalidKeyException { + throws InvalidModelException, IOException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException, InvalidAlgorithmParameterException, CertificateException, InvalidKeySpecException, KeylessSignerException { // Get the Base64 encoded Statement to use as the payload String jsonStatement = validateAndTransformToJson(statement, false); String base64EncodedStatement = Base64.getEncoder().encodeToString(jsonStatement.getBytes()); diff --git a/src/test/java/io/github/intoto/helpers/provenancev01/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/provenancev01/IntotoHelperTest.java index f2a97bd..1e3c260 100644 --- a/src/test/java/io/github/intoto/helpers/provenancev01/IntotoHelperTest.java +++ b/src/test/java/io/github/intoto/helpers/provenancev01/IntotoHelperTest.java @@ -9,6 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.fasterxml.jackson.core.JsonProcessingException; +import dev.sigstore.KeylessSignerException; import io.github.intoto.dsse.helpers.SimpleECDSASigner; import io.github.intoto.dsse.helpers.SimpleECDSAVerifier; import io.github.intoto.dsse.models.IntotoEnvelope; @@ -25,14 +26,11 @@ import io.github.intoto.slsa.models.v01.Recipe; import io.github.intoto.utilities.provenancev01.IntotoStubFactory; import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Security; -import java.security.SignatureException; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; import java.util.Collections; import java.util.List; import java.util.Map; @@ -566,8 +564,8 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact @DisplayName("Test creating envelope from Statement") public void produceIntotoEnvelopeAsJson_shouldCorrectlyCreateAnEnvelope_whenCompleteStatementIsPassed() - throws InvalidModelException, JsonProcessingException, NoSuchAlgorithmException, - SignatureException, InvalidKeyException { + throws InvalidModelException, IOException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException, InvalidAlgorithmParameterException, CertificateException, InvalidKeySpecException, KeylessSignerException { // ** The subject ** Subject subject = new Subject(); subject.setName("curl-7.72.0.tar.bz2"); diff --git a/src/test/java/io/github/intoto/helpers/provenancev02/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/provenancev02/IntotoHelperTest.java index 2b32dbb..22d08d7 100644 --- a/src/test/java/io/github/intoto/helpers/provenancev02/IntotoHelperTest.java +++ b/src/test/java/io/github/intoto/helpers/provenancev02/IntotoHelperTest.java @@ -9,6 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.fasterxml.jackson.core.JsonProcessingException; +import dev.sigstore.KeylessSignerException; import io.github.intoto.dsse.helpers.SimpleECDSASigner; import io.github.intoto.dsse.helpers.SimpleECDSAVerifier; import io.github.intoto.dsse.models.IntotoEnvelope; @@ -22,14 +23,11 @@ import io.github.intoto.slsa.models.v02.*; import io.github.intoto.utilities.provenancev02.IntotoStubFactory; import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Security; -import java.security.SignatureException; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; import java.util.Collections; import java.util.List; import java.util.Map; @@ -577,8 +575,8 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact @DisplayName("Test creating envelope from Statement") public void produceIntotoEnvelopeAsJson_shouldCorrectlyCreateAnEnvelope_whenCompleteStatementIsPassed() - throws InvalidModelException, JsonProcessingException, NoSuchAlgorithmException, - SignatureException, InvalidKeyException { + throws InvalidModelException, IOException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException, InvalidAlgorithmParameterException, CertificateException, InvalidKeySpecException, KeylessSignerException { // ** The subject ** Subject subject = new Subject(); subject.setName("curl-7.72.0.tar.bz2"); diff --git a/src/test/java/io/github/intoto/helpers/provenancev1/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/provenancev1/IntotoHelperTest.java index 0252c48..0b10974 100644 --- a/src/test/java/io/github/intoto/helpers/provenancev1/IntotoHelperTest.java +++ b/src/test/java/io/github/intoto/helpers/provenancev1/IntotoHelperTest.java @@ -9,6 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.fasterxml.jackson.core.JsonProcessingException; +import dev.sigstore.KeylessSignerException; import io.github.intoto.dsse.helpers.SimpleECDSASigner; import io.github.intoto.dsse.helpers.SimpleECDSAVerifier; import io.github.intoto.dsse.models.IntotoEnvelope; @@ -27,14 +28,11 @@ import io.github.intoto.slsa.models.v1.RunDetails; import io.github.intoto.utilities.provenancev1.IntotoStubFactory; import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Security; -import java.security.SignatureException; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Collections; @@ -600,8 +598,8 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact @DisplayName("Test creating envelope from Statement") public void produceIntotoEnvelopeAsJson_shouldCorrectlyCreateAnEnvelope_whenCompleteStatementIsPassed() - throws InvalidModelException, JsonProcessingException, NoSuchAlgorithmException, - SignatureException, InvalidKeyException { + throws InvalidModelException, IOException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException, InvalidAlgorithmParameterException, CertificateException, InvalidKeySpecException, KeylessSignerException { // ** The subject ** Subject subject = new Subject(); subject.setName("curl-7.72.0.tar.bz2");