diff --git a/pom.xml b/pom.xml
index 4bcadf5..19745da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
io.github.in-toto
in-toto
jar
- 0.5.0
+ 0.6.0
in-toto
https://maven.apache.org
A framework to secure software supply chains.
diff --git a/src/main/java/io/github/intoto/slsa/models/v02/Provenance.java b/src/main/java/io/github/intoto/slsa/models/v02/Provenance.java
index ae2a915..c3a4b39 100644
--- a/src/main/java/io/github/intoto/slsa/models/v02/Provenance.java
+++ b/src/main/java/io/github/intoto/slsa/models/v02/Provenance.java
@@ -9,7 +9,7 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
-/** Implementation of the https://slsa.dev/provenance/v0.1 */
+/** Implementation of the https://slsa.dev/provenance/v0.2 */
public class Provenance extends Predicate {
/**
@@ -109,6 +109,6 @@ public int hashCode() {
@Override
public String getPredicateType() {
- return "https://slsa.dev/provenance/v0.1";
+ return "https://slsa.dev/provenance/v0.2";
}
}
diff --git a/src/main/java/io/github/intoto/slsa/models/v1/BuildDefinition.java b/src/main/java/io/github/intoto/slsa/models/v1/BuildDefinition.java
new file mode 100644
index 0000000..eee1757
--- /dev/null
+++ b/src/main/java/io/github/intoto/slsa/models/v1/BuildDefinition.java
@@ -0,0 +1,140 @@
+package io.github.intoto.slsa.models.v1;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * The BuildDefinition describes all of the inputs to the build. It SHOULD contain all the
+ * information necessary and sufficient to initialize the build and begin execution.
+ *
+ *
The externalParameters and internalParameters are the top-level inputs to the template,
+ * meaning inputs not derived from another input. Each is an arbitrary JSON object, though it is
+ * RECOMMENDED to keep the structure simple with string values to aid verification. The same field
+ * name SHOULD NOT be used for both externalParameters and internalParameters.
+ *
+ *
The parameters SHOULD only contain the actual values passed in through the interface to the
+ * build platform. Metadata about those parameter values, particularly digests of artifacts
+ * referenced by those parameters, SHOULD instead go in resolvedDependencies. The documentation for
+ * buildType SHOULD explain how to convert from a parameter to the dependency uri. For example:
+ *
+ *
+ * {@code }
+ *
+ */
+public class BuildDefinition {
+
+ /**
+ * Identifies the template for how to perform the build and interpret the parameters and
+ * dependencies.
+ *
+ * The URI SHOULD resolve to a human-readable specification that includes: overall description
+ * of the build type; schema for externalParameters and internalParameters; unambiguous
+ * instructions for how to initiate the build given this BuildDefinition, and a complete example.
+ * Example: https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1
+ */
+ @NotBlank(message = "buildType must not be empty or blank")
+ private String buildType;
+
+ /**
+ * The parameters that are under external control, such as those set by a user or tenant of the
+ * build platform. They MUST be complete at SLSA Build L3, meaning that there is no additional
+ * mechanism for an external party to influence the build. (At lower SLSA Build levels, the
+ * completeness MAY be best effort.)
+ *
+ *
The build platform SHOULD be designed to minimize the size and complexity of
+ * externalParameters, in order to reduce fragility and ease verification. Consumers SHOULD have
+ * an expectation of what “good” looks like; the more information that they need to check, the
+ * harder that task becomes.
+ *
+ *
Verifiers SHOULD reject unrecognized or unexpected fields within externalParameters.
+ */
+ @NotEmpty(message = "externalParameters must not be empty")
+ private Map externalParameters;
+
+ /**
+ * The parameters that are under the control of the entity represented by builder.id. The primary
+ * intention of this field is for debugging, incident response, and vulnerability management. The
+ * values here MAY be necessary for reproducing the build. There is no need to verify these
+ * parameters because the build platform is already trusted, and in many cases it is not practical
+ * to do so.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ private Map internalParameters;
+
+ /**
+ * Unordered collection of artifacts needed at build time. Completeness is best effort, at least
+ * through SLSA Build L3. For example, if the build script fetches and executes
+ * “example.com/foo.sh”, which in turn fetches “example.com/bar.tar.gz”, then both “foo.sh” and
+ * “bar.tar.gz” SHOULD be listed here.
+ */
+ private List resolvedDependencies;
+
+ public String getBuildType() {
+ return buildType;
+ }
+
+ public void setBuildType(String buildType) {
+ this.buildType = buildType;
+ }
+
+ public Map getExternalParameters() {
+ return externalParameters;
+ }
+
+ public void setExternalParameters(Map externalParameters) {
+ this.externalParameters = externalParameters;
+ }
+
+ public Map getInternalParameters() {
+ return internalParameters;
+ }
+
+ public void setInternalParameters(Map internalParameters) {
+ this.internalParameters = internalParameters;
+ }
+
+ public List getResolvedDependencies() {
+ return resolvedDependencies;
+ }
+
+ public void setResolvedDependencies(
+ List resolvedDependencies) {
+ this.resolvedDependencies = resolvedDependencies;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ io.github.intoto.slsa.models.v1.BuildDefinition buildDefinition = (io.github.intoto.slsa.models.v1.BuildDefinition) o;
+ return buildType.equals(buildDefinition.buildType) && Objects.equals(externalParameters,
+ buildDefinition.externalParameters)
+ && Objects.equals(internalParameters, buildDefinition.internalParameters) && Objects.equals(
+ resolvedDependencies, buildDefinition.resolvedDependencies);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(buildType, externalParameters, internalParameters,
+ resolvedDependencies);
+ }
+}
diff --git a/src/main/java/io/github/intoto/slsa/models/v1/BuildMetadata.java b/src/main/java/io/github/intoto/slsa/models/v1/BuildMetadata.java
new file mode 100644
index 0000000..3859ce6
--- /dev/null
+++ b/src/main/java/io/github/intoto/slsa/models/v1/BuildMetadata.java
@@ -0,0 +1,73 @@
+package io.github.intoto.slsa.models.v1;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import java.time.OffsetDateTime;
+import java.util.Objects;
+
+public class BuildMetadata {
+
+ /**
+ * Identifies this particular build invocation, which can be useful for finding associated logs or
+ * other ad-hoc analysis. The exact meaning and format is defined by builder.id; by default it is
+ * treated as opaque and case-sensitive. The value SHOULD be globally unique.
+ */
+ private String invocationId;
+
+ /**
+ * The timestamp of when the build started. A point in time, represented as a string in RFC 3339
+ * format in the UTC time zone ("Z").
+ */
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
+ private OffsetDateTime startedOn;
+
+ /**
+ * The timestamp of when the build completed.A point in time, represented as a string in RFC 3339
+ * format in the UTC time zone ("Z").
+ */
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
+ private OffsetDateTime finishedOn;
+
+ public String getInvocationId() {
+ return invocationId;
+ }
+
+ public void setInvocationId(String invocationId) {
+ this.invocationId = invocationId;
+ }
+
+ public OffsetDateTime getStartedOn() {
+ return startedOn;
+ }
+
+ public void setStartedOn(OffsetDateTime startedOn) {
+ this.startedOn = startedOn;
+ }
+
+ public OffsetDateTime getFinishedOn() {
+ return finishedOn;
+ }
+
+ public void setFinishedOn(OffsetDateTime finishedOn) {
+ this.finishedOn = finishedOn;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ BuildMetadata buildMetadata = (BuildMetadata) o;
+ return Objects.equals(invocationId, buildMetadata.invocationId)
+ && Objects.equals(startedOn, buildMetadata.startedOn)
+ && Objects.equals(finishedOn, buildMetadata.finishedOn);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ invocationId, startedOn, finishedOn);
+ }
+}
diff --git a/src/main/java/io/github/intoto/slsa/models/v1/Builder.java b/src/main/java/io/github/intoto/slsa/models/v1/Builder.java
new file mode 100644
index 0000000..c06b59f
--- /dev/null
+++ b/src/main/java/io/github/intoto/slsa/models/v1/Builder.java
@@ -0,0 +1,93 @@
+package io.github.intoto.slsa.models.v1;
+
+import jakarta.validation.constraints.NotBlank;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * The build platform, or builder for short, represents the transitive closure of all the entities
+ * that are, by necessity, trusted to faithfully run the build and record the provenance. This
+ * includes not only the software but the hardware and people involved in running the service. For
+ * example, a particular instance of Tekton could be a build platform, while Tekton itself is not.
+ * For more info, see Build model.
+ *
+ * The id MUST reflect the trust base that consumers care about. How detailed to be is a
+ * judgement call. For example, GitHub Actions supports both GitHub-hosted runners and self-hosted
+ * runners. The GitHub-hosted runner might be a single identity because it’s all GitHub from the
+ * consumer’s perspective. Meanwhile, each self-hosted runner might have its own identity because
+ * not all runners are trusted by all consumers.
+ *
+ *
Consumers MUST accept only specific signer-builder pairs. For example, “GitHub” can sign
+ * provenance for the “GitHub Actions” builder, and “Google” can sign provenance for the “Google
+ * Cloud Build” builder, but “GitHub” cannot sign for the “Google Cloud Build” builder.
+ *
+ *
Design rationale: The builder is distinct from the signer in order to support the case where
+ * one signer generates attestations for more than one builder, as in the GitHub Actions example
+ * above. The field is REQUIRED, even if it is implicit from the signer, to aid readability and
+ * debugging. It is an object to allow additional fields in the future, in case one URI is not
+ * sufficient.
+ */
+public class Builder {
+
+ /**
+ * URI indicating the builder’s identity. (TypeURI)
+ */
+ @NotBlank(message = "builder Id must not be empty or blank")
+ private String id;
+
+ /**
+ * Dependencies used by the orchestrator that are not run within the workload and that do not
+ * affect the build, but might affect the provenance generation or security guarantees.
+ */
+ private List builderDependencies;
+
+ /**
+ * Map of names of components of the build platform to their version.
+ */
+ private Map version;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public List getBuilderDependencies() {
+ return builderDependencies;
+ }
+
+ public void setBuilderDependencies(
+ List builderDependencies) {
+ this.builderDependencies = builderDependencies;
+ }
+
+ public Map getVersion() {
+ return version;
+ }
+
+ public void setVersion(Map version) {
+ this.version = version;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ io.github.intoto.slsa.models.v1.Builder builder = (io.github.intoto.slsa.models.v1.Builder) o;
+ return id.equals(builder.id) && Objects.equals(builderDependencies, builder.builderDependencies)
+ && Objects.equals(version, builder.version);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, version);
+ }
+}
diff --git a/src/main/java/io/github/intoto/slsa/models/v1/Provenance.java b/src/main/java/io/github/intoto/slsa/models/v1/Provenance.java
new file mode 100644
index 0000000..7533b3e
--- /dev/null
+++ b/src/main/java/io/github/intoto/slsa/models/v1/Provenance.java
@@ -0,0 +1,63 @@
+package io.github.intoto.slsa.models.v1;
+
+import io.github.intoto.models.Predicate;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import java.util.Objects;
+
+public class Provenance extends Predicate {
+
+ /**
+ * The input to the build. The accuracy and completeness are implied by runDetails.builder.id.
+ */
+ @Valid
+ @NotNull(message = "buildDefinition must not be null")
+ private BuildDefinition buildDefinition;
+
+ /**
+ * Details specific to this particular execution of the build.
+ */
+ @Valid
+ @NotNull(message = "runDetails must not be null")
+ private RunDetails runDetails;
+
+ public BuildDefinition getBuildDefinition() {
+ return buildDefinition;
+ }
+
+ public void setBuildDefinition(BuildDefinition buildDefinition) {
+ this.buildDefinition = buildDefinition;
+ }
+
+ public RunDetails getRunDetails() {
+ return runDetails;
+ }
+
+ public void setRunDetails(RunDetails runDetails) {
+ this.runDetails = runDetails;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Provenance provenance = (Provenance) o;
+ return Objects.equals(buildDefinition, provenance.buildDefinition)
+ && Objects.equals(runDetails, provenance.runDetails);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ buildDefinition, runDetails);
+ }
+
+ @Override
+ public String getPredicateType() {
+ return "https://slsa.dev/provenance/v1";
+ }
+}
diff --git a/src/main/java/io/github/intoto/slsa/models/v1/ResourceDescriptor.java b/src/main/java/io/github/intoto/slsa/models/v1/ResourceDescriptor.java
new file mode 100644
index 0000000..2906d3a
--- /dev/null
+++ b/src/main/java/io/github/intoto/slsa/models/v1/ResourceDescriptor.java
@@ -0,0 +1,59 @@
+package io.github.intoto.slsa.models.v1;
+
+import java.util.Map;
+import java.util.Objects;
+import org.hibernate.validator.constraints.URL;
+
+public class ResourceDescriptor {
+
+ private String name;
+
+ @URL(message = "Not a valid URI")
+ private String uri;
+
+ private Map digest;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ public Map getDigest() {
+ return digest;
+ }
+
+ public void setDigest(Map digest) {
+ this.digest = digest;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ResourceDescriptor resourceDescriptor = (ResourceDescriptor) o;
+ return Objects.equals(name, resourceDescriptor.name)
+ && Objects.equals(uri, resourceDescriptor.uri)
+ && Objects.equals(digest, resourceDescriptor.digest);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ name, uri, digest);
+ }
+}
diff --git a/src/main/java/io/github/intoto/slsa/models/v1/RunDetails.java b/src/main/java/io/github/intoto/slsa/models/v1/RunDetails.java
new file mode 100644
index 0000000..fb3d666
--- /dev/null
+++ b/src/main/java/io/github/intoto/slsa/models/v1/RunDetails.java
@@ -0,0 +1,79 @@
+package io.github.intoto.slsa.models.v1;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import jakarta.validation.constraints.NotNull;
+import java.util.List;
+import java.util.Objects;
+
+public class RunDetails {
+
+ /**
+ * Identifies the build platform that executed the invocation, which is trusted to have correctly
+ * performed the operation and populated this provenance.
+ */
+ @NotNull(message = "builder must not be null")
+ private Builder builder;
+
+ /**
+ * Metadata about this particular execution of the build.
+ */
+ private BuildMetadata metadata;
+
+ /**
+ * Additional artifacts generated during the build that are not considered the “output” of the
+ * build but that might be needed during debugging or incident response. For example, this might
+ * reference logs generated during the build and/or a digest of the fully evaluated build
+ * configuration.
+ *
+ * In most cases, this SHOULD NOT contain all intermediate files generated during the build.
+ * Instead, this SHOULD only contain files that are likely to be useful later and that cannot be
+ * easily reproduced.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ private List byproducts;
+
+ public Builder getBuilder() {
+ return builder;
+ }
+
+ public void setBuilder(Builder builder) {
+ this.builder = builder;
+ }
+
+ public BuildMetadata getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(BuildMetadata metadata) {
+ this.metadata = metadata;
+ }
+
+ public List getByproducts() {
+ return byproducts;
+ }
+
+ public void setByproducts(List byproducts) {
+ this.byproducts = byproducts;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ RunDetails runDetails = (RunDetails) o;
+ return Objects.equals(builder, runDetails.builder)
+ && Objects.equals(metadata, runDetails.metadata)
+ && Objects.equals(byproducts, runDetails.byproducts);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ builder, metadata, byproducts);
+ }
+}
diff --git a/src/test/java/io/github/intoto/helpers/provenance02/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/provenancev02/IntotoHelperTest.java
similarity index 98%
rename from src/test/java/io/github/intoto/helpers/provenance02/IntotoHelperTest.java
rename to src/test/java/io/github/intoto/helpers/provenancev02/IntotoHelperTest.java
index 6cec1bb..2b32dbb 100644
--- a/src/test/java/io/github/intoto/helpers/provenance02/IntotoHelperTest.java
+++ b/src/test/java/io/github/intoto/helpers/provenancev02/IntotoHelperTest.java
@@ -1,4 +1,4 @@
-package io.github.intoto.helpers.provenance02;
+package io.github.intoto.helpers.provenancev02;
import static io.github.intoto.utilities.KeyUtilities.readPrivateKey;
import static io.github.intoto.utilities.KeyUtilities.readPublicKey;
@@ -20,7 +20,7 @@
import io.github.intoto.models.Statement;
import io.github.intoto.models.Subject;
import io.github.intoto.slsa.models.v02.*;
-import io.github.intoto.utilities.provenance02.IntotoStubFactory;
+import io.github.intoto.utilities.provenancev02.IntotoStubFactory;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
@@ -91,7 +91,7 @@ public class IntotoHelperTest {
+ " \"sha256\" : \"d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2\"\n"
+ " }\n"
+ " } ],\n"
- + " \"predicateType\" : \"https://slsa.dev/provenance/v0.1\",\n"
+ + " \"predicateType\" : \"https://slsa.dev/provenance/v0.2\",\n"
+ " \"predicate\" : {\n"
+ " \"builder\" : {\n"
+ " \"id\" : \"mailto:person@example.com\"\n"
@@ -163,7 +163,7 @@ public void validateAndTransformToJson_shouldTransformStatementToJsonString_With
+ " \"sha256\" : \"d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2\"\n"
+ " }\n"
+ " } ],\n"
- + " \"predicateType\" : \"https://slsa.dev/provenance/v0.1\",\n"
+ + " \"predicateType\" : \"https://slsa.dev/provenance/v0.2\",\n"
+ " \"predicate\" : {\n"
+ " \"builder\" : {\n"
+ " \"id\" : \"mailto:person@example.com\"\n"
@@ -618,9 +618,9 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact
final String EXPECTED_JSON_ENVELOPE =
"{\n"
+ " \"payloadType\" : \"application/vnd.in-toto+json\",\n"
- + " \"payload\" : \"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9NYWtlZmlsZSIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMzIzZDMyM2VkdmdkIn0sImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn19LCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV19fQ==\",\n"
+ + " \"payload\" : \"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjIiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwiYnVpbGRUeXBlIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9NYWtlZmlsZSIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMzIzZDMyM2VkdmdkIn0sImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn19LCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV19fQ==\",\n"
+ " \"signatures\" : [ {\n"
- + " \"sig\" : \"RFNTRXYxIDI4IGFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24gNTYyIHsiX3R5cGUiOiJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiY3VybC03LjcyLjAudGFyLmJ6MiIsImRpZ2VzdCI6eyJzaGEyNTYiOiJkNGQ1ODk5YTM4NjhmYmI2YWUxODU2YzNlNTVhMzJjZTM1OTEzZGUzOTU2ZDE5NzNjYWNjZDM3YmQwMTc0ZmEyIn19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vc2xzYS5kZXYvcHJvdmVuYW5jZS92MC4xIiwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJtYWlsdG86cGVyc29uQGV4YW1wbGUuY29tIn0sImJ1aWxkVHlwZSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vTWFrZWZpbGUiLCJpbnZvY2F0aW9uIjp7ImNvbmZpZ1NvdXJjZSI6eyJ1cmkiOiJodHRwczovL2V4YW1wbGUuY29tL2V4YW1wbGUtMS4yLjMudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjMyM2QzMjNlZHZnZCJ9LCJlbnRyeVBvaW50Ijoic3JjOmZvbyJ9fSwibWF0ZXJpYWxzIjpbeyJ1cmkiOiJodHRwczovL2V4YW1wbGUuY29tL2V4YW1wbGUtMS4yLjMudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjEyMzQuLi4ifX1dfX0=\",\n"
+ + " \"sig\" : \"RFNTRXYxIDI4IGFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24gNTYyIHsiX3R5cGUiOiJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiY3VybC03LjcyLjAudGFyLmJ6MiIsImRpZ2VzdCI6eyJzaGEyNTYiOiJkNGQ1ODk5YTM4NjhmYmI2YWUxODU2YzNlNTVhMzJjZTM1OTEzZGUzOTU2ZDE5NzNjYWNjZDM3YmQwMTc0ZmEyIn19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vc2xzYS5kZXYvcHJvdmVuYW5jZS92MC4yIiwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJtYWlsdG86cGVyc29uQGV4YW1wbGUuY29tIn0sImJ1aWxkVHlwZSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vTWFrZWZpbGUiLCJpbnZvY2F0aW9uIjp7ImNvbmZpZ1NvdXJjZSI6eyJ1cmkiOiJodHRwczovL2V4YW1wbGUuY29tL2V4YW1wbGUtMS4yLjMudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjMyM2QzMjNlZHZnZCJ9LCJlbnRyeVBvaW50Ijoic3JjOmZvbyJ9fSwibWF0ZXJpYWxzIjpbeyJ1cmkiOiJodHRwczovL2V4YW1wbGUuY29tL2V4YW1wbGUtMS4yLjMudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjEyMzQuLi4ifX1dfX0=\",\n"
+ " \"keyid\" : \"Fake-Signer-Key-ID\"\n"
+ " } ]\n"
+ "}";
@@ -674,7 +674,7 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact
assertNotNull(intotoEnvelope);
final String EXPECTED_DSSE_PAYLOAD =
- "DSSEv1 28 application/vnd.in-toto+json 562 {\"_type\":\"https://in-toto.io/Statement/v0.1\",\"subject\":[{\"name\":\"curl-7.72.0.tar.bz2\",\"digest\":{\"sha256\":\"d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2\"}}],\"predicateType\":\"https://slsa.dev/provenance/v0.1\",\"predicate\":{\"builder\":{\"id\":\"mailto:person@example.com\"},\"buildType\":\"https://example.com/Makefile\",\"invocation\":{\"configSource\":{\"uri\":\"https://example.com/example-1.2.3.tar.gz\",\"digest\":{\"sha256\":\"323d323edvgd\"},\"entryPoint\":\"src:foo\"}},\"materials\":[{\"uri\":\"https://example.com/example-1.2.3.tar.gz\",\"digest\":{\"sha256\":\"1234...\"}}]}}";
+ "DSSEv1 28 application/vnd.in-toto+json 562 {\"_type\":\"https://in-toto.io/Statement/v0.1\",\"subject\":[{\"name\":\"curl-7.72.0.tar.bz2\",\"digest\":{\"sha256\":\"d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2\"}}],\"predicateType\":\"https://slsa.dev/provenance/v0.2\",\"predicate\":{\"builder\":{\"id\":\"mailto:person@example.com\"},\"buildType\":\"https://example.com/Makefile\",\"invocation\":{\"configSource\":{\"uri\":\"https://example.com/example-1.2.3.tar.gz\",\"digest\":{\"sha256\":\"323d323edvgd\"},\"entryPoint\":\"src:foo\"}},\"materials\":[{\"uri\":\"https://example.com/example-1.2.3.tar.gz\",\"digest\":{\"sha256\":\"1234...\"}}]}}";
SimpleECDSAVerifier verifier = new SimpleECDSAVerifier();
diff --git a/src/test/java/io/github/intoto/helpers/provenancev1/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/provenancev1/IntotoHelperTest.java
new file mode 100644
index 0000000..0252c48
--- /dev/null
+++ b/src/test/java/io/github/intoto/helpers/provenancev1/IntotoHelperTest.java
@@ -0,0 +1,755 @@
+package io.github.intoto.helpers.provenancev1;
+
+import static io.github.intoto.utilities.KeyUtilities.readPrivateKey;
+import static io.github.intoto.utilities.KeyUtilities.readPublicKey;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import io.github.intoto.dsse.helpers.SimpleECDSASigner;
+import io.github.intoto.dsse.helpers.SimpleECDSAVerifier;
+import io.github.intoto.dsse.models.IntotoEnvelope;
+import io.github.intoto.exceptions.InvalidModelException;
+import io.github.intoto.helpers.IntotoHelper;
+import io.github.intoto.implementations.FakeSigner;
+import io.github.intoto.models.DigestSetAlgorithmType;
+import io.github.intoto.models.Predicate;
+import io.github.intoto.models.Statement;
+import io.github.intoto.models.Subject;
+import io.github.intoto.slsa.models.v1.BuildDefinition;
+import io.github.intoto.slsa.models.v1.BuildMetadata;
+import io.github.intoto.slsa.models.v1.Builder;
+import io.github.intoto.slsa.models.v1.Provenance;
+import io.github.intoto.slsa.models.v1.ResourceDescriptor;
+import io.github.intoto.slsa.models.v1.RunDetails;
+import io.github.intoto.utilities.provenancev1.IntotoStubFactory;
+import java.io.File;
+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.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class IntotoHelperTest {
+
+ @Test
+ @DisplayName("Can transform a Statement with provenance to JSON")
+ public void
+ validateAndTransformToJson_shouldTransformStatementToJsonString_whenStatementContainsProvenance()
+ throws JsonProcessingException, InvalidModelException {
+ // ** The subject **
+ Subject subject = new Subject();
+ subject.setName("curl-7.72.0.tar.bz2");
+ subject.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+ // ** The predicate **
+ // Prepare BuildDefinition
+ BuildDefinition buildDefinition = new BuildDefinition();
+ buildDefinition.setBuildType("https://example.com/Makefile");
+ // Prepare ExternalParameters
+ Map externalParameters = new HashMap<>();
+ externalParameters.put("entryPoint", "src:foo");
+ externalParameters.put("source", "https://example.com/example-1.2.3.tar.gz");
+ buildDefinition.setExternalParameters(externalParameters);
+ // Prepare ResolvedDependencies
+ List resolvedDependencies = new ArrayList<>();
+ ResourceDescriptor configSourceResourceDescriptor = new ResourceDescriptor();
+ configSourceResourceDescriptor.setUri("https://example.com/example-1.2.3.tar.gz");
+ configSourceResourceDescriptor.setDigest(Map.of("sha256","323d323edvgd"));
+ resolvedDependencies.add(configSourceResourceDescriptor);
+ buildDefinition.setResolvedDependencies(resolvedDependencies);
+
+ // Prepare RunDetails
+ RunDetails runDetails = new RunDetails();
+ // Prepare Builder
+ Builder builder = new Builder();
+ builder.setId("mailto:person@example.com");
+ runDetails.setBuilder(builder);
+
+ // Putting the Provenance together
+ Provenance provenancePredicate = new Provenance();
+ provenancePredicate.setBuildDefinition(buildDefinition);
+ provenancePredicate.setRunDetails(runDetails);
+ // ** Putting the Statement together **
+ Statement statement = new Statement();
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(provenancePredicate);
+
+ String jsonStatement = IntotoHelper.validateAndTransformToJson(statement, true);
+ System.out.println(jsonStatement);
+ assertNotNull(jsonStatement);
+ String JSON_STATEMENT =
+ "{\n"
+ + " \"_type\" : \"https://in-toto.io/Statement/v0.1\",\n"
+ + " \"subject\" : [ {\n"
+ + " \"name\" : \"curl-7.72.0.tar.bz2\",\n"
+ + " \"digest\" : {\n"
+ + " \"sha256\" : \"d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2\"\n"
+ + " }\n"
+ + " } ],\n"
+ + " \"predicateType\" : \"https://slsa.dev/provenance/v1\",\n"
+ + " \"predicate\" : {\n"
+ + " \"buildDefinition\" : {\n"
+ + " \"buildType\" : \"https://example.com/Makefile\",\n"
+ + " \"externalParameters\" : {\n"
+ + " \"entryPoint\" : \"src:foo\",\n"
+ + " \"source\" : \"https://example.com/example-1.2.3.tar.gz\"\n"
+ + " },\n"
+ + " \"resolvedDependencies\" : [ {\n"
+ + " \"name\" : null,\n"
+ + " \"uri\" : \"https://example.com/example-1.2.3.tar.gz\",\n"
+ + " \"digest\" : {\n"
+ + " \"sha256\" : \"323d323edvgd\"\n"
+ + " }\n"
+ + " } ]\n"
+ + " },\n"
+ + " \"runDetails\" : {\n"
+ + " \"builder\" : {\n"
+ + " \"id\" : \"mailto:person@example.com\",\n"
+ + " \"builderDependencies\" : null,\n"
+ + " \"version\" : null\n"
+ + " },\n"
+ + " \"metadata\" : null\n"
+ + " }\n"
+ + " }\n"
+ + "}";
+
+ assertEquals(JSON_STATEMENT, jsonStatement);
+ }
+
+ @Test
+ @DisplayName("Can transform a Statement with provenance to JSON including Metadata")
+ public void validateAndTransformToJson_shouldTransformStatementToJsonString_WithMetadata()
+ throws JsonProcessingException, InvalidModelException {
+ // ** The subject **
+ Subject subject = new Subject();
+ subject.setName("curl-7.72.0.tar.bz2");
+ subject.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+ // ** The predicate **
+ // Putting the Provenance together
+ Provenance provenancePredicate = IntotoStubFactory.createProvenancePredicateWithMetadata();
+ // ** Putting the Statement together **
+ Statement statement = new Statement();
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(provenancePredicate);
+
+ String jsonStatement = IntotoHelper.validateAndTransformToJson(statement, true);
+ System.out.println(jsonStatement);
+ assertNotNull(jsonStatement);
+ String EXPECTED_JSON_STATEMENT =
+ "{\n"
+ + " \"_type\" : \"https://in-toto.io/Statement/v0.1\",\n"
+ + " \"subject\" : [ {\n"
+ + " \"name\" : \"curl-7.72.0.tar.bz2\",\n"
+ + " \"digest\" : {\n"
+ + " \"sha256\" : \"d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2\"\n"
+ + " }\n"
+ + " } ],\n"
+ + " \"predicateType\" : \"https://slsa.dev/provenance/v1\",\n"
+ + " \"predicate\" : {\n"
+ + " \"buildDefinition\" : {\n"
+ + " \"buildType\" : \"https://example.com/Makefile\",\n"
+ + " \"externalParameters\" : {\n"
+ + " \"entryPoint\" : \"src:foo\",\n"
+ + " \"source\" : \"https://example.com/example-1.2.3.tar.gz\"\n"
+ + " },\n"
+ + " \"resolvedDependencies\" : [ {\n"
+ + " \"name\" : null,\n"
+ + " \"uri\" : \"https://example.com/example-1.2.3.tar.gz\",\n"
+ + " \"digest\" : {\n"
+ + " \"sha256\" : \"323d323edvgd\"\n"
+ + " }\n"
+ + " } ]\n"
+ + " },\n"
+ + " \"runDetails\" : {\n"
+ + " \"builder\" : {\n"
+ + " \"id\" : \"mailto:person@example.com\",\n"
+ + " \"builderDependencies\" : null,\n"
+ + " \"version\" : null\n"
+ + " },\n"
+ + " \"metadata\" : {\n"
+ + " \"invocationId\" : \"SomeBuildId\",\n"
+ + " \"startedOn\" : \"1986-12-18T15:20:30+08:00\",\n"
+ + " \"finishedOn\" : \"1986-12-18T16:20:30+08:00\"\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + "}";
+
+ assertEquals(EXPECTED_JSON_STATEMENT, jsonStatement);
+ }
+
+ @Test
+ @DisplayName("Testing Statement Subject can't be null")
+ public void toJson_shouldThrowException_whenStatementSubjectIsNull() {
+ Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate();
+ Statement statement = new Statement();
+ statement.setPredicate(predicate);
+
+ InvalidModelException thrown =
+ assertThrows(
+ InvalidModelException.class,
+ () -> {
+ IntotoHelper.validateAndTransformToJson(statement, true);
+ });
+
+ assertEquals("subject may not be null or empty", thrown.getMessage());
+ }
+
+ @Test
+ @DisplayName("Testing Statement Subject can't be empty")
+ public void toJson_shouldThrowException_whenStatementSubjectIsEmpty() {
+ Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate();
+ Statement statement = new Statement();
+ statement.setSubject(Collections.emptyList());
+ statement.setPredicate(predicate);
+
+ InvalidModelException thrown =
+ assertThrows(
+ InvalidModelException.class,
+ () -> {
+ IntotoHelper.validateAndTransformToJson(statement, true);
+ });
+
+ assertEquals("subject may not be null or empty", thrown.getMessage());
+ }
+
+ @Test
+ @DisplayName("Testing Subject's name can't be null")
+ public void validateAndTransformToJson_shouldThrowException_whenSubjectNameIsNull() {
+ Subject subject = new Subject();
+ subject.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+ Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate();
+ Statement statement = new Statement();
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(predicate);
+
+ InvalidModelException thrown =
+ assertThrows(
+ InvalidModelException.class,
+ () -> {
+ IntotoHelper.validateAndTransformToJson(statement, true);
+ });
+
+ assertEquals("subject name must not be blank", thrown.getMessage());
+ }
+
+ @Test
+ @DisplayName("Testing Subject's name can't be blank")
+ public void validateAndTransformToJson_shouldThrowException_whenSubjectNameIsBlank() {
+ Subject subject = new Subject();
+ subject.setName("");
+ subject.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+ Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate();
+ Statement statement = new Statement();
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(predicate);
+
+ InvalidModelException thrown =
+ assertThrows(
+ InvalidModelException.class,
+ () -> {
+ IntotoHelper.validateAndTransformToJson(statement, true);
+ });
+
+ assertEquals("subject name must not be blank", thrown.getMessage());
+ }
+
+ @Test
+ @DisplayName("Testing Subject's digest can't be empty")
+ public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEmpty() {
+ Subject subject = new Subject();
+ subject.setName("curl-7.72.0.tar.bz2");
+ Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate();
+ Statement statement = new Statement();
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(predicate);
+
+ InvalidModelException thrown =
+ assertThrows(
+ InvalidModelException.class,
+ () -> {
+ IntotoHelper.validateAndTransformToJson(statement, true);
+ });
+
+ assertEquals("digest must not be empty", thrown.getMessage());
+ }
+
+ @Test
+ @DisplayName("Testing Subject's digest can't contain blank Strings (keys)")
+ public void
+ validateAndTransformToJson_shouldThrowException_whenSubjectDigestContainsEmptyKeyStrings() {
+ Subject subject = new Subject();
+ subject.setName("curl-7.72.0.tar.bz2");
+ subject.setDigest(
+ Map.of("", "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+ Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate();
+ Statement statement = new Statement();
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(predicate);
+
+ InvalidModelException thrown =
+ assertThrows(
+ InvalidModelException.class,
+ () -> {
+ IntotoHelper.validateAndTransformToJson(statement, true);
+ });
+
+ assertEquals("digest key contents can be empty strings", thrown.getMessage());
+ }
+
+ @Test
+ @DisplayName("Testing Subject's digest can't contain blank Strings (values)")
+ public void
+ validateAndTransformToJson_shouldThrowException_whenSubjectDigestContainsEmptyValueStrings() {
+ Subject subject = new Subject();
+ subject.setName("curl-7.72.0.tar.bz2");
+ subject.setDigest(Map.of(DigestSetAlgorithmType.SHA256.getValue(), ""));
+ Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate();
+ Statement statement = new Statement();
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(predicate);
+
+ InvalidModelException thrown =
+ assertThrows(
+ InvalidModelException.class,
+ () -> {
+ IntotoHelper.validateAndTransformToJson(statement, true);
+ });
+
+ assertEquals("digest value contents can be empty strings", thrown.getMessage());
+ }
+
+ @Test
+ @DisplayName("Testing Subject uniqueness")
+ public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreNotUnique() {
+ Subject subject = new Subject();
+ subject.setName("curl-7.72.0.tar.bz2");
+ subject.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+
+ Subject subject2 = new Subject();
+ subject2.setName("curl-7.72.0.tar.bz2");
+ subject2.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+
+ Subject subject3 = new Subject();
+ subject3.setName("curl-7.72.0.tar.bz2");
+ subject3.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+ Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate();
+ Statement statement = new Statement();
+ statement.setSubject(List.of(subject, subject2, subject3));
+ statement.setPredicate(predicate);
+
+ InvalidModelException thrown =
+ assertThrows(
+ InvalidModelException.class,
+ () -> {
+ IntotoHelper.validateAndTransformToJson(statement, true);
+ });
+
+ assertEquals("subjects must be unique", thrown.getMessage());
+ }
+
+ @Test
+ @DisplayName("Test Provenance with no Build")
+ public void
+ validateAndTransformToJson_shouldThrowException_whenStatementContainsProvenanceWithNoBuild() {
+ // ** The subject **
+ Subject subject = new Subject();
+ subject.setName("curl-7.72.0.tar.bz2");
+ subject.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+ // ** The predicate **
+ // Prepare BuildDefinition
+ BuildDefinition buildDefinition = new BuildDefinition();
+ buildDefinition.setBuildType("https://example.com/Makefile");
+ // Prepare ExternalParameters
+ Map externalParameters = new HashMap<>();
+ externalParameters.put("entryPoint", "src:foo");
+ externalParameters.put("source", "https://example.com/example-1.2.3.tar.gz");
+ buildDefinition.setExternalParameters(externalParameters);
+ // Prepare ResolvedDependencies
+ List resolvedDependencies = new ArrayList<>();
+ ResourceDescriptor configSourceResourceDescriptor = new ResourceDescriptor();
+ configSourceResourceDescriptor.setUri("https://example.com/example-1.2.3.tar.gz");
+ configSourceResourceDescriptor.setDigest(Map.of("sha256","323d323edvgd"));
+ resolvedDependencies.add(configSourceResourceDescriptor);
+ buildDefinition.setResolvedDependencies(resolvedDependencies);
+
+ // Prepare RunDetails
+ RunDetails runDetails = new RunDetails();
+
+ // Putting the Provenance together
+ Provenance provenancePredicate = new Provenance();
+ provenancePredicate.setBuildDefinition(buildDefinition);
+ provenancePredicate.setRunDetails(runDetails);
+ // ** Putting the Statement together **
+ Statement statement = new Statement();
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(provenancePredicate);
+
+ InvalidModelException thrown =
+ assertThrows(
+ InvalidModelException.class,
+ () -> {
+ IntotoHelper.validateAndTransformToJson(statement, true);
+ });
+
+ assertEquals("builder must not be null", thrown.getMessage());
+ }
+
+ @Test
+ @DisplayName("Test Provenance with no buildType")
+ public void
+ validateAndTransformToJson_shouldThrowException_whenStatementContainsProvenanceWithNoBuildType() {
+ // ** The subject **
+ Subject subject = new Subject();
+ subject.setName("curl-7.72.0.tar.bz2");
+ subject.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+ // ** The predicate **
+ // Prepare BuildDefinition
+ BuildDefinition buildDefinition = new BuildDefinition();
+ // Prepare ExternalParameters
+ Map externalParameters = new HashMap<>();
+ externalParameters.put("entryPoint", "src:foo");
+ externalParameters.put("source", "https://example.com/example-1.2.3.tar.gz");
+ buildDefinition.setExternalParameters(externalParameters);
+ // Prepare ResolvedDependencies
+ List resolvedDependencies = new ArrayList<>();
+ ResourceDescriptor configSourceResourceDescriptor = new ResourceDescriptor();
+ configSourceResourceDescriptor.setUri("https://example.com/example-1.2.3.tar.gz");
+ configSourceResourceDescriptor.setDigest(Map.of("sha256","323d323edvgd"));
+ resolvedDependencies.add(configSourceResourceDescriptor);
+ buildDefinition.setResolvedDependencies(resolvedDependencies);
+
+ // Prepare RunDetails
+ RunDetails runDetails = new RunDetails();
+ // Prepare Builder
+ Builder builder = new Builder();
+ builder.setId("mailto:person@example.com");
+ runDetails.setBuilder(builder);
+
+ // Putting the Provenance together
+ Provenance provenancePredicate = new Provenance();
+ provenancePredicate.setBuildDefinition(buildDefinition);
+ provenancePredicate.setRunDetails(runDetails);
+ // ** Putting the Statement together **
+ Statement statement = new Statement();
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(provenancePredicate);
+
+ InvalidModelException thrown =
+ assertThrows(
+ InvalidModelException.class,
+ () -> {
+ IntotoHelper.validateAndTransformToJson(statement, true);
+ });
+
+ assertEquals("buildType must not be empty or blank", thrown.getMessage());
+ }
+
+ @Test
+ @DisplayName("Test Provenance with no externalParameters")
+ public void
+ validateAndTransformToJson_shouldThrowException_whenStatementContainsProvenanceWithNoExternalParameters()
+ throws InvalidModelException, JsonProcessingException {
+ // ** The subject **
+ Subject subject = new Subject();
+ subject.setName("curl-7.72.0.tar.bz2");
+ subject.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+ // ** The predicate **
+ // Prepare BuildDefinition
+ BuildDefinition buildDefinition = new BuildDefinition();
+ buildDefinition.setBuildType("https://example.com/Makefile");
+ // Prepare ResolvedDependencies
+ List resolvedDependencies = new ArrayList<>();
+ ResourceDescriptor configSourceResourceDescriptor = new ResourceDescriptor();
+ configSourceResourceDescriptor.setUri("https://example.com/example-1.2.3.tar.gz");
+ configSourceResourceDescriptor.setDigest(Map.of("sha256","323d323edvgd"));
+ resolvedDependencies.add(configSourceResourceDescriptor);
+ buildDefinition.setResolvedDependencies(resolvedDependencies);
+
+ // Prepare RunDetails
+ RunDetails runDetails = new RunDetails();
+ // Prepare Builder
+ Builder builder = new Builder();
+ builder.setId("mailto:person@example.com");
+ runDetails.setBuilder(builder);
+
+ // Putting the Provenance together
+ Provenance provenancePredicate = new Provenance();
+ provenancePredicate.setBuildDefinition(buildDefinition);
+ provenancePredicate.setRunDetails(runDetails);
+ // ** Putting the Statement together **
+ Statement statement = new Statement();
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(provenancePredicate);
+
+ InvalidModelException thrown =
+ assertThrows(
+ InvalidModelException.class,
+ () -> {
+ IntotoHelper.validateAndTransformToJson(statement, true);
+ });
+ assertEquals("externalParameters must not be empty", thrown.getMessage());
+ }
+
+ @Test
+ @DisplayName("Test createPreAuthenticationEncoding")
+ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_whenSimpleValues() {
+ String helloWordString = "hello world";
+ byte[] paeString =
+ IntotoHelper.createPreAuthenticationEncoding(
+ "http://example.com/HelloWorld", helloWordString.getBytes(StandardCharsets.UTF_8));
+
+ System.out.println("paeString: " + new String(paeString, StandardCharsets.UTF_8));
+
+ assertArrayEquals(
+ new byte[] {
+ 68, 83, 83, 69, 118, 49, 32, 50, 57, 32, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97,
+ 109, 112, 108, 101, 46, 99, 111, 109, 47, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100,
+ 32, 49, 49, 32, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100
+ },
+ paeString);
+ }
+
+ @Test
+ @DisplayName("Test createPreAuthenticationEncoding with UTF 8 characters")
+ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharacters() {
+ String utf8String = "Entwickeln Sie mit Vergnügen";
+ byte[] paeString =
+ IntotoHelper.createPreAuthenticationEncoding(
+ "http://example.com/HelloWorld", utf8String.getBytes(StandardCharsets.UTF_8));
+
+ System.out.println("paeString: " + new String(paeString, StandardCharsets.UTF_8));
+
+ assertArrayEquals(
+ new byte[] {
+ 68, 83, 83, 69, 118, 49, 32, 50, 57, 32, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97,
+ 109, 112, 108, 101, 46, 99, 111, 109, 47, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100,
+ 32, 50, 57, 32, 69, 110, 116, 119, 105, 99, 107, 101, 108, 110, 32, 83, 105, 101, 32, 109,
+ 105, 116, 32, 86, 101, 114, 103, 110, -61, -68, 103, 101, 110
+ },
+ paeString);
+ }
+
+ @Test
+ @DisplayName("Test createPreAuthenticationEncoding with UTF 8 characters 2")
+ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharacters2() {
+ String utf8String = "ಠ";
+ byte[] paeString =
+ IntotoHelper.createPreAuthenticationEncoding(
+ "application/example", utf8String.getBytes(StandardCharsets.UTF_8));
+
+ System.out.println("paeString: " + new String(paeString, StandardCharsets.UTF_8));
+
+ assertArrayEquals(
+ new byte[] {
+ 68, 83, 83, 69, 118, 49, 32, 49, 57, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111,
+ 110, 47, 101, 120, 97, 109, 112, 108, 101, 32, 51, 32, -32, -78, -96
+ },
+ paeString);
+ }
+
+ @Test
+ @DisplayName("Test creating envelope from Statement")
+ public void
+ produceIntotoEnvelopeAsJson_shouldCorrectlyCreateAnEnvelope_whenCompleteStatementIsPassed()
+ throws InvalidModelException, JsonProcessingException, NoSuchAlgorithmException,
+ SignatureException, InvalidKeyException {
+ // ** The subject **
+ Subject subject = new Subject();
+ subject.setName("curl-7.72.0.tar.bz2");
+ subject.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+ // ** The predicate **
+ // Prepare BuildDefinition
+ BuildDefinition buildDefinition = new BuildDefinition();
+ buildDefinition.setBuildType("https://example.com/Makefile");
+ // Prepare ExternalParameters
+ Map externalParameters = new HashMap<>();
+ externalParameters.put("entryPoint", "src:foo");
+ externalParameters.put("source", "https://example.com/example-1.2.3.tar.gz");
+ buildDefinition.setExternalParameters(externalParameters);
+ // Prepare ResolvedDependencies
+ List resolvedDependencies = new ArrayList<>();
+ ResourceDescriptor configSourceResourceDescriptor = new ResourceDescriptor();
+ configSourceResourceDescriptor.setUri("https://example.com/example-1.2.3.tar.gz");
+ configSourceResourceDescriptor.setDigest(Map.of("sha256","323d323edvgd"));
+ resolvedDependencies.add(configSourceResourceDescriptor);
+ buildDefinition.setResolvedDependencies(resolvedDependencies);
+
+ // Prepare RunDetails
+ RunDetails runDetails = new RunDetails();
+ // Prepare Builder
+ Builder builder = new Builder();
+ builder.setId("mailto:person@example.com");
+ runDetails.setBuilder(builder);
+
+ // Putting the Provenance together
+ Provenance provenancePredicate = new Provenance();
+ provenancePredicate.setBuildDefinition(buildDefinition);
+ provenancePredicate.setRunDetails(runDetails);
+ // ** Putting the Statement together **
+ Statement statement = new Statement();
+
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(provenancePredicate);
+ String intotoEnvelope =
+ IntotoHelper.produceIntotoEnvelopeAsJson(statement, new FakeSigner(), true);
+ System.out.println(intotoEnvelope);
+ assertNotNull(intotoEnvelope);
+ final String EXPECTED_JSON_ENVELOPE =
+ "{\n"
+ + " \"payloadType\" : \"application/vnd.in-toto+json\",\n"
+ + " \"payload\" : \"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YxIiwicHJlZGljYXRlIjp7ImJ1aWxkRGVmaW5pdGlvbiI6eyJidWlsZFR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZXh0ZXJuYWxQYXJhbWV0ZXJzIjp7ImVudHJ5UG9pbnQiOiJzcmM6Zm9vIiwic291cmNlIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9leGFtcGxlLTEuMi4zLnRhci5neiJ9LCJyZXNvbHZlZERlcGVuZGVuY2llcyI6W3sibmFtZSI6bnVsbCwidXJpIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9leGFtcGxlLTEuMi4zLnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiIzMjNkMzIzZWR2Z2QifX1dfSwicnVuRGV0YWlscyI6eyJidWlsZGVyIjp7ImlkIjoibWFpbHRvOnBlcnNvbkBleGFtcGxlLmNvbSIsImJ1aWxkZXJEZXBlbmRlbmNpZXMiOm51bGwsInZlcnNpb24iOm51bGx9LCJtZXRhZGF0YSI6bnVsbH19fQ==\",\n"
+ + " \"signatures\" : [ {\n"
+ + " \"sig\" : \"RFNTRXYxIDI4IGFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24gNjQwIHsiX3R5cGUiOiJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiY3VybC03LjcyLjAudGFyLmJ6MiIsImRpZ2VzdCI6eyJzaGEyNTYiOiJkNGQ1ODk5YTM4NjhmYmI2YWUxODU2YzNlNTVhMzJjZTM1OTEzZGUzOTU2ZDE5NzNjYWNjZDM3YmQwMTc0ZmEyIn19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vc2xzYS5kZXYvcHJvdmVuYW5jZS92MSIsInByZWRpY2F0ZSI6eyJidWlsZERlZmluaXRpb24iOnsiYnVpbGRUeXBlIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9NYWtlZmlsZSIsImV4dGVybmFsUGFyYW1ldGVycyI6eyJlbnRyeVBvaW50Ijoic3JjOmZvbyIsInNvdXJjZSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oifSwicmVzb2x2ZWREZXBlbmRlbmNpZXMiOlt7Im5hbWUiOm51bGwsInVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMzIzZDMyM2VkdmdkIn19XX0sInJ1bkRldGFpbHMiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20iLCJidWlsZGVyRGVwZW5kZW5jaWVzIjpudWxsLCJ2ZXJzaW9uIjpudWxsfSwibWV0YWRhdGEiOm51bGx9fX0=\",\n"
+ + " \"keyid\" : \"Fake-Signer-Key-ID\"\n"
+ + " } ]\n"
+ + "}";
+ assertEquals(EXPECTED_JSON_ENVELOPE, intotoEnvelope);
+ }
+
+ @Test
+ @DisplayName("Test creating envelope with simple encryption")
+ public void
+ produceIntotoEnvelope_shouldCorrectlyCreateEncryptedSignature_whenUsingSimpleEncryption()
+ throws Exception {
+ // ** The subject **
+ Subject subject = new Subject();
+ subject.setName("curl-7.72.0.tar.bz2");
+ subject.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+ // ** The predicate **
+ // Prepare BuildDefinition
+ BuildDefinition buildDefinition = new BuildDefinition();
+ buildDefinition.setBuildType("https://example.com/Makefile");
+ // Prepare ExternalParameters
+ Map externalParameters = new HashMap<>();
+ externalParameters.put("entryPoint", "src:foo");
+ externalParameters.put("source", "https://example.com/example-1.2.3.tar.gz");
+ buildDefinition.setExternalParameters(externalParameters);
+ // Prepare ResolvedDependencies
+ List resolvedDependencies = new ArrayList<>();
+ ResourceDescriptor configSourceResourceDescriptor = new ResourceDescriptor();
+ configSourceResourceDescriptor.setUri("https://example.com/example-1.2.3.tar.gz");
+ configSourceResourceDescriptor.setDigest(Map.of("sha256","323d323edvgd"));
+ resolvedDependencies.add(configSourceResourceDescriptor);
+ buildDefinition.setResolvedDependencies(resolvedDependencies);
+
+ // Prepare RunDetails
+ RunDetails runDetails = new RunDetails();
+ // Prepare Builder
+ Builder builder = new Builder();
+ builder.setId("mailto:person@example.com");
+ runDetails.setBuilder(builder);
+ // Prepare Metadata
+ BuildMetadata metadata = new BuildMetadata();
+ metadata.setInvocationId("SomeBuildId");
+ metadata.setStartedOn(OffsetDateTime.parse("1986-12-18T15:20:30+08:00"));
+ metadata.setFinishedOn(OffsetDateTime.parse("1986-12-18T16:20:30+08:00"));
+ runDetails.setMetadata(metadata);
+
+ // Putting the Provenance together
+ Provenance provenancePredicate = new Provenance();
+ provenancePredicate.setBuildDefinition(buildDefinition);
+ provenancePredicate.setRunDetails(runDetails);
+ // ** Putting the Statement together **
+ Statement statement = new Statement();
+
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(provenancePredicate);
+
+ // Generate a key pair
+ KeyPair keyPair = getKeyPairFromFile();
+ SimpleECDSASigner signer = new SimpleECDSASigner(keyPair.getPrivate(), "MyKey");
+
+ IntotoEnvelope intotoEnvelope = IntotoHelper.produceIntotoEnvelope(statement, signer);
+ System.out.println(intotoEnvelope);
+ assertNotNull(intotoEnvelope);
+
+ final String EXPECTED_DSSE_PAYLOAD =
+ "DSSEv1 28 application/vnd.in-toto+json 747 {\"_type\":\"https://in-toto.io/Statement/v0.1\",\"subject\":[{\"name\":\"curl-7.72.0.tar.bz2\",\"digest\":{\"sha256\":\"d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2\"}}],\"predicateType\":\"https://slsa.dev/provenance/v1\",\"predicate\":{\"buildDefinition\":{\"buildType\":\"https://example.com/Makefile\",\"externalParameters\":{\"entryPoint\":\"src:foo\",\"source\":\"https://example.com/example-1.2.3.tar.gz\"},\"resolvedDependencies\":[{\"name\":null,\"uri\":\"https://example.com/example-1.2.3.tar.gz\",\"digest\":{\"sha256\":\"323d323edvgd\"}}]},\"runDetails\":{\"builder\":{\"id\":\"mailto:person@example.com\",\"builderDependencies\":null,\"version\":null},\"metadata\":{\"invocationId\":\"SomeBuildId\",\"startedOn\":\"1986-12-18T15:20:30+08:00\",\"finishedOn\":\"1986-12-18T16:20:30+08:00\"}}}}";
+
+ SimpleECDSAVerifier verifier = new SimpleECDSAVerifier();
+
+ boolean result =
+ verifier.verify(
+ keyPair.getPublic().getEncoded(),
+ Base64.decode(intotoEnvelope.getSignatures().get(0).getSig().getBytes()),
+ EXPECTED_DSSE_PAYLOAD);
+ Assertions.assertTrue(result);
+ }
+
+ /**
+ * Gets the keys from the resources directory (public.key and private.key) and loads them up as a
+ * {@link KeyPair}
+ */
+ private KeyPair getKeyPairFromFile() throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+ // Getting ClassLoader obj
+ ClassLoader classLoader = this.getClass().getClassLoader();
+
+ // Getting public key
+ File publicKeyFile =
+ new File(Objects.requireNonNull(classLoader.getResource("public.pem")).getFile());
+ PublicKey publicKey = readPublicKey(publicKeyFile);
+
+ // Getting private key
+ File privateKeyFile =
+ new File(Objects.requireNonNull(classLoader.getResource("p8private.pem")).getFile());
+ PrivateKey privateKey = readPrivateKey(privateKeyFile);
+
+ return new KeyPair(publicKey, privateKey);
+ }
+}
diff --git a/src/test/java/io/github/intoto/utilities/provenance02/IntotoStubFactory.java b/src/test/java/io/github/intoto/utilities/provenancev02/IntotoStubFactory.java
similarity index 98%
rename from src/test/java/io/github/intoto/utilities/provenance02/IntotoStubFactory.java
rename to src/test/java/io/github/intoto/utilities/provenancev02/IntotoStubFactory.java
index 59e8f8c..0bf2e06 100644
--- a/src/test/java/io/github/intoto/utilities/provenance02/IntotoStubFactory.java
+++ b/src/test/java/io/github/intoto/utilities/provenancev02/IntotoStubFactory.java
@@ -1,4 +1,4 @@
-package io.github.intoto.utilities.provenance02;
+package io.github.intoto.utilities.provenancev02;
import io.github.intoto.slsa.models.v02.*;
diff --git a/src/test/java/io/github/intoto/utilities/provenance02/TestEnvelopeGenerator.java b/src/test/java/io/github/intoto/utilities/provenancev02/TestEnvelopeGenerator.java
similarity index 98%
rename from src/test/java/io/github/intoto/utilities/provenance02/TestEnvelopeGenerator.java
rename to src/test/java/io/github/intoto/utilities/provenancev02/TestEnvelopeGenerator.java
index 9b6c0f3..6cb2fe5 100644
--- a/src/test/java/io/github/intoto/utilities/provenance02/TestEnvelopeGenerator.java
+++ b/src/test/java/io/github/intoto/utilities/provenancev02/TestEnvelopeGenerator.java
@@ -1,4 +1,4 @@
-package io.github.intoto.utilities.provenance02;
+package io.github.intoto.utilities.provenancev02;
import static io.github.intoto.utilities.KeyUtilities.readPrivateKey;
import static io.github.intoto.utilities.KeyUtilities.readPublicKey;
diff --git a/src/test/java/io/github/intoto/utilities/provenancev1/IntotoStubFactory.java b/src/test/java/io/github/intoto/utilities/provenancev1/IntotoStubFactory.java
new file mode 100644
index 0000000..4171440
--- /dev/null
+++ b/src/test/java/io/github/intoto/utilities/provenancev1/IntotoStubFactory.java
@@ -0,0 +1,89 @@
+package io.github.intoto.utilities.provenancev1;
+
+import io.github.intoto.slsa.models.v1.BuildDefinition;
+import io.github.intoto.slsa.models.v1.BuildMetadata;
+import io.github.intoto.slsa.models.v1.Builder;
+import io.github.intoto.slsa.models.v1.Provenance;
+import io.github.intoto.slsa.models.v1.ResourceDescriptor;
+import io.github.intoto.slsa.models.v1.RunDetails;
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Helper factory that produces fake stubs to help with testing. */
+public final class IntotoStubFactory {
+
+ /** Helper method that creates a simple correct {@link Provenance} */
+ public static Provenance createSimpleProvenancePredicate() {
+ // Prepare BuildDefinition
+ BuildDefinition buildDefinition = new BuildDefinition();
+ buildDefinition.setBuildType("https://example.com/Makefile");
+ // Prepare ExternalParameters
+ Map externalParameters = new HashMap<>();
+ externalParameters.put("entryPoint", "src:foo");
+ externalParameters.put("source", "https://example.com/example-1.2.3.tar.gz");
+ buildDefinition.setExternalParameters(externalParameters);
+ // Prepare ResolvedDependencies
+ List resolvedDependencies = new ArrayList<>();
+ ResourceDescriptor configSourceResourceDescriptor = new ResourceDescriptor();
+ configSourceResourceDescriptor.setUri("https://example.com/example-1.2.3.tar.gz");
+ configSourceResourceDescriptor.setDigest(Map.of("sha256","323d323edvgd"));
+ resolvedDependencies.add(configSourceResourceDescriptor);
+ buildDefinition.setResolvedDependencies(resolvedDependencies);
+
+ // Prepare RunDetails
+ RunDetails runDetails = new RunDetails();
+ // Prepare Builder
+ Builder builder = new Builder();
+ builder.setId("mailto:person@example.com");
+ runDetails.setBuilder(builder);
+
+ // Putting the Provenance together
+ Provenance provenancePredicate = new Provenance();
+ provenancePredicate.setBuildDefinition(buildDefinition);
+ provenancePredicate.setRunDetails(runDetails);
+ return provenancePredicate;
+ }
+
+ /**
+ * Helper method that creates a correct {@link Provenance} with a Metadata containing timestamps.
+ */
+ public static Provenance createProvenancePredicateWithMetadata() {
+ // Prepare BuildDefinition
+ BuildDefinition buildDefinition = new BuildDefinition();
+ buildDefinition.setBuildType("https://example.com/Makefile");
+ // Prepare ExternalParameters
+ Map externalParameters = new HashMap<>();
+ externalParameters.put("entryPoint", "src:foo");
+ externalParameters.put("source", "https://example.com/example-1.2.3.tar.gz");
+ buildDefinition.setExternalParameters(externalParameters);
+ // Prepare ResolvedDependencies
+ List resolvedDependencies = new ArrayList<>();
+ ResourceDescriptor configSourceResourceDescriptor = new ResourceDescriptor();
+ configSourceResourceDescriptor.setUri("https://example.com/example-1.2.3.tar.gz");
+ configSourceResourceDescriptor.setDigest(Map.of("sha256","323d323edvgd"));
+ resolvedDependencies.add(configSourceResourceDescriptor);
+ buildDefinition.setResolvedDependencies(resolvedDependencies);
+
+ // Prepare RunDetails
+ RunDetails runDetails = new RunDetails();
+ // Prepare Builder
+ Builder builder = new Builder();
+ builder.setId("mailto:person@example.com");
+ runDetails.setBuilder(builder);
+ // Prepare Metadata
+ BuildMetadata metadata = new BuildMetadata();
+ metadata.setInvocationId("SomeBuildId");
+ metadata.setStartedOn(OffsetDateTime.parse("1986-12-18T15:20:30+08:00"));
+ metadata.setFinishedOn(OffsetDateTime.parse("1986-12-18T16:20:30+08:00"));
+ runDetails.setMetadata(metadata);
+
+ // Putting the Provenance together
+ Provenance provenancePredicate = new Provenance();
+ provenancePredicate.setBuildDefinition(buildDefinition);
+ provenancePredicate.setRunDetails(runDetails);
+ return provenancePredicate;
+ }
+}
diff --git a/src/test/java/io/github/intoto/utilities/provenancev1/TestEnvelopeGenerator.java b/src/test/java/io/github/intoto/utilities/provenancev1/TestEnvelopeGenerator.java
new file mode 100644
index 0000000..d22a081
--- /dev/null
+++ b/src/test/java/io/github/intoto/utilities/provenancev1/TestEnvelopeGenerator.java
@@ -0,0 +1,119 @@
+package io.github.intoto.utilities.provenancev1;
+
+import static io.github.intoto.utilities.KeyUtilities.readPrivateKey;
+import static io.github.intoto.utilities.KeyUtilities.readPublicKey;
+
+import io.github.intoto.dsse.helpers.SimpleECDSASigner;
+import io.github.intoto.helpers.IntotoHelper;
+import io.github.intoto.models.DigestSetAlgorithmType;
+import io.github.intoto.models.Statement;
+import io.github.intoto.models.Subject;
+import io.github.intoto.slsa.models.v1.BuildDefinition;
+import io.github.intoto.slsa.models.v1.BuildMetadata;
+import io.github.intoto.slsa.models.v1.Builder;
+import io.github.intoto.slsa.models.v1.Provenance;
+import io.github.intoto.slsa.models.v1.ResourceDescriptor;
+import io.github.intoto.slsa.models.v1.RunDetails;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/**
+ * Generates an `intoto_test.attestation` file from the following configuration using the keys found
+ * in the resources directory.
+ */
+public class TestEnvelopeGenerator {
+
+ public static void main(String[] args) throws Exception {
+ // ** The subject **
+ Subject subject = new Subject();
+ subject.setName("curl-7.72.0.tar.bz2");
+ subject.setDigest(
+ Map.of(
+ DigestSetAlgorithmType.SHA256.getValue(),
+ "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
+
+ // ** The predicate **
+ // Prepare BuildDefinition
+ BuildDefinition buildDefinition = new BuildDefinition();
+ buildDefinition.setBuildType("https://example.com/Makefile");
+ // Prepare ExternalParameters
+ Map externalParameters = new HashMap<>();
+ externalParameters.put("entryPoint", "src:foo");
+ externalParameters.put("source", "https://example.com/example-1.2.3.tar.gz");
+ buildDefinition.setExternalParameters(externalParameters);
+ // Prepare ResolvedDependencies
+ List resolvedDependencies = new ArrayList<>();
+ ResourceDescriptor configSourceResourceDescriptor = new ResourceDescriptor();
+ configSourceResourceDescriptor.setUri("https://example.com/example-1.2.3.tar.gz");
+ configSourceResourceDescriptor.setDigest(Map.of("sha256","323d323edvgd"));
+ resolvedDependencies.add(configSourceResourceDescriptor);
+ buildDefinition.setResolvedDependencies(resolvedDependencies);
+
+ // Prepare RunDetails
+ RunDetails runDetails = new RunDetails();
+ // Prepare Builder
+ Builder builder = new Builder();
+ builder.setId("mailto:person@example.com");
+ runDetails.setBuilder(builder);
+ // Prepare Metadata
+ BuildMetadata metadata = new BuildMetadata();
+ metadata.setInvocationId("SomeBuildId");
+ metadata.setStartedOn(OffsetDateTime.parse("1986-12-18T15:20:30+08:00"));
+ metadata.setFinishedOn(OffsetDateTime.parse("1986-12-18T16:20:30+08:00"));
+ runDetails.setMetadata(metadata);
+
+ // Putting the Provenance together
+ Provenance provenancePredicate = new Provenance();
+ provenancePredicate.setBuildDefinition(buildDefinition);
+ provenancePredicate.setRunDetails(runDetails);
+
+ // ** Putting the Statement together **
+ Statement statement = new Statement();
+ statement.setSubject(List.of(subject));
+ statement.setPredicate(provenancePredicate);
+
+ // Generate a key pair
+ KeyPair keyPair = getKeyPairFromFile();
+ SimpleECDSASigner signer = new SimpleECDSASigner(keyPair.getPrivate(), "MyKey");
+
+ String intotoJsonEnvelope = IntotoHelper.produceIntotoEnvelopeAsJson(statement, signer, false);
+
+ Files.writeString(Path.of(".", "intoto_example.intoto.jsonl"), intotoJsonEnvelope);
+ }
+
+ /**
+ * Gets the keys from the resources directory (public.key and private.key) and loads them up as a
+ * {@link KeyPair}
+ */
+ private static KeyPair getKeyPairFromFile() throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+ // Getting ClassLoader obj
+ ClassLoader classLoader = TestEnvelopeGenerator.class.getClassLoader();
+
+ // Getting public key
+ File filePublicKey =
+ new File(Objects.requireNonNull(classLoader.getResource("public.pem")).getFile());
+ // Reading with PemReader
+ PublicKey publicKey = readPublicKey(filePublicKey);
+ System.out.println(publicKey.toString());
+
+ // Getting private key
+ File filePrivateKey =
+ new File(Objects.requireNonNull(classLoader.getResource("p8private.pem")).getFile());
+ PrivateKey privateKey = readPrivateKey(filePrivateKey);
+ System.out.println(privateKey.toString());
+ return new KeyPair(publicKey, privateKey);
+ }
+}