From c9934f0637a754c5b19a970e9390a9608586d07d Mon Sep 17 00:00:00 2001 From: Chi Zhang Date: Fri, 15 Mar 2024 15:28:27 -0700 Subject: [PATCH] feat: add the SLSA models for its v1.0 spec Signed-off-by: Chi Zhang --- pom.xml | 2 +- .../intoto/slsa/models/v02/Provenance.java | 4 +- .../slsa/models/v1/BuildDefinition.java | 140 ++++ .../intoto/slsa/models/v1/BuildMetadata.java | 73 ++ .../github/intoto/slsa/models/v1/Builder.java | 93 +++ .../intoto/slsa/models/v1/Provenance.java | 63 ++ .../slsa/models/v1/ResourceDescriptor.java | 59 ++ .../intoto/slsa/models/v1/RunDetails.java | 79 ++ .../IntotoHelperTest.java | 14 +- .../provenancev1/IntotoHelperTest.java | 755 ++++++++++++++++++ .../IntotoStubFactory.java | 2 +- .../TestEnvelopeGenerator.java | 2 +- .../provenancev1/IntotoStubFactory.java | 89 +++ .../provenancev1/TestEnvelopeGenerator.java | 119 +++ 14 files changed, 1482 insertions(+), 12 deletions(-) create mode 100644 src/main/java/io/github/intoto/slsa/models/v1/BuildDefinition.java create mode 100644 src/main/java/io/github/intoto/slsa/models/v1/BuildMetadata.java create mode 100644 src/main/java/io/github/intoto/slsa/models/v1/Builder.java create mode 100644 src/main/java/io/github/intoto/slsa/models/v1/Provenance.java create mode 100644 src/main/java/io/github/intoto/slsa/models/v1/ResourceDescriptor.java create mode 100644 src/main/java/io/github/intoto/slsa/models/v1/RunDetails.java rename src/test/java/io/github/intoto/helpers/{provenance02 => provenancev02}/IntotoHelperTest.java (98%) create mode 100644 src/test/java/io/github/intoto/helpers/provenancev1/IntotoHelperTest.java rename src/test/java/io/github/intoto/utilities/{provenance02 => provenancev02}/IntotoStubFactory.java (98%) rename src/test/java/io/github/intoto/utilities/{provenance02 => provenancev02}/TestEnvelopeGenerator.java (98%) create mode 100644 src/test/java/io/github/intoto/utilities/provenancev1/IntotoStubFactory.java create mode 100644 src/test/java/io/github/intoto/utilities/provenancev1/TestEnvelopeGenerator.java 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); + } +}