From fcf8a274e539a3fef6d0b9e684fe62083759674e Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Fri, 10 Sep 2021 13:46:26 -0700 Subject: [PATCH 01/21] Add new implementation for in-toto 0.1.0 This is the fist set of changes for the new implementation of in-toto spec. This includes: * Moving of existing functionality into legacy package * Creating a set of base model classes * Implementing validators * Implementing helper class to start validation process and transform to JSON * Set of tests for the above --- .gitignore | 96 +++- README.md | 109 +++-- pom.xml | 52 +- src/main/java/io/github/in_toto/keys/Key.java | 28 -- .../java/io/github/in_toto/keys/RSAKey.java | 260 ---------- .../io/github/in_toto/keys/Signature.java | 27 -- src/main/java/io/github/in_toto/lib/App.java | 47 -- .../io/github/in_toto/lib/JSONEncoder.java | 151 ------ .../in_toto/lib/NumericJSONSerializer.java | 31 -- .../io/github/in_toto/models/Artifact.java | 99 ---- .../java/io/github/in_toto/models/Link.java | 237 --------- .../github/in_toto/models/LinkSignable.java | 71 --- .../io/github/in_toto/models/Metablock.java | 150 ------ .../io/github/in_toto/models/Signable.java | 25 - .../exceptions/InvalidModelException.java | 8 + .../github/intoto/helpers/IntotoHelper.java | 47 ++ .../intoto/models/DigestSetAlgorithmType.java | 24 + .../io/github/intoto/models/Predicate.java | 9 + .../github/intoto/models/PredicateType.java | 26 + .../io/github/intoto/models/Statement.java | 92 ++++ .../github/intoto/models/StatementType.java | 24 + .../java/io/github/intoto/models/Subject.java | 72 +++ .../intoto/validators/UniqueSubject.java | 38 ++ .../validators/UniqueSubjectValidator.java | 27 ++ src/main/java/io/github/legacy/keys/Key.java | 30 ++ .../java/io/github/legacy/keys/RSAKey.java | 233 +++++++++ .../java/io/github/legacy/keys/Signature.java | 21 + src/main/java/io/github/legacy/lib/App.java | 32 ++ .../io/github/legacy/lib/JSONEncoder.java | 136 ++++++ .../legacy/lib/NumericJSONSerializer.java | 26 + .../io/github/legacy/models/Artifact.java | 85 ++++ .../java/io/github/legacy/models/Link.java | 212 ++++++++ .../io/github/legacy/models/LinkSignable.java | 61 +++ .../io/github/legacy/models/Metablock.java | 140 ++++++ .../io/github/legacy/models/Signable.java | 18 + .../io/github/in_toto/TestJSONCanonical.java | 35 -- .../io/github/in_toto/keys/RSAKeyTest.java | 103 ---- .../java/io/github/in_toto/lib/AppTest.java | 1 - .../io/github/in_toto/models/LinkTest.java | 451 ------------------ .../intoto/helpers/IntotoHelperTest.java | 289 +++++++++++ .../io/github/legacy/TestJSONCanonical.java | 40 ++ .../io/github/legacy/keys/RSAKeyTest.java | 88 ++++ .../java/io/github/legacy/lib/AppTest.java | 1 + .../io/github/legacy/models/LinkTest.java | 418 ++++++++++++++++ 44 files changed, 2372 insertions(+), 1798 deletions(-) delete mode 100644 src/main/java/io/github/in_toto/keys/Key.java delete mode 100644 src/main/java/io/github/in_toto/keys/RSAKey.java delete mode 100644 src/main/java/io/github/in_toto/keys/Signature.java delete mode 100644 src/main/java/io/github/in_toto/lib/App.java delete mode 100644 src/main/java/io/github/in_toto/lib/JSONEncoder.java delete mode 100644 src/main/java/io/github/in_toto/lib/NumericJSONSerializer.java delete mode 100644 src/main/java/io/github/in_toto/models/Artifact.java delete mode 100644 src/main/java/io/github/in_toto/models/Link.java delete mode 100644 src/main/java/io/github/in_toto/models/LinkSignable.java delete mode 100644 src/main/java/io/github/in_toto/models/Metablock.java delete mode 100644 src/main/java/io/github/in_toto/models/Signable.java create mode 100644 src/main/java/io/github/intoto/exceptions/InvalidModelException.java create mode 100644 src/main/java/io/github/intoto/helpers/IntotoHelper.java create mode 100644 src/main/java/io/github/intoto/models/DigestSetAlgorithmType.java create mode 100644 src/main/java/io/github/intoto/models/Predicate.java create mode 100644 src/main/java/io/github/intoto/models/PredicateType.java create mode 100644 src/main/java/io/github/intoto/models/Statement.java create mode 100644 src/main/java/io/github/intoto/models/StatementType.java create mode 100644 src/main/java/io/github/intoto/models/Subject.java create mode 100644 src/main/java/io/github/intoto/validators/UniqueSubject.java create mode 100644 src/main/java/io/github/intoto/validators/UniqueSubjectValidator.java create mode 100644 src/main/java/io/github/legacy/keys/Key.java create mode 100644 src/main/java/io/github/legacy/keys/RSAKey.java create mode 100644 src/main/java/io/github/legacy/keys/Signature.java create mode 100644 src/main/java/io/github/legacy/lib/App.java create mode 100644 src/main/java/io/github/legacy/lib/JSONEncoder.java create mode 100644 src/main/java/io/github/legacy/lib/NumericJSONSerializer.java create mode 100644 src/main/java/io/github/legacy/models/Artifact.java create mode 100644 src/main/java/io/github/legacy/models/Link.java create mode 100644 src/main/java/io/github/legacy/models/LinkSignable.java create mode 100644 src/main/java/io/github/legacy/models/Metablock.java create mode 100644 src/main/java/io/github/legacy/models/Signable.java delete mode 100644 src/test/java/io/github/in_toto/TestJSONCanonical.java delete mode 100644 src/test/java/io/github/in_toto/keys/RSAKeyTest.java delete mode 100644 src/test/java/io/github/in_toto/lib/AppTest.java delete mode 100644 src/test/java/io/github/in_toto/models/LinkTest.java create mode 100644 src/test/java/io/github/intoto/helpers/IntotoHelperTest.java create mode 100644 src/test/java/io/github/legacy/TestJSONCanonical.java create mode 100644 src/test/java/io/github/legacy/keys/RSAKeyTest.java create mode 100644 src/test/java/io/github/legacy/lib/AppTest.java create mode 100644 src/test/java/io/github/legacy/models/LinkTest.java diff --git a/.gitignore b/.gitignore index cad296b..72bd7ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,88 @@ -# Compiled class file +############################## +## Java +############################## +.mtj.tmp/ *.class +*.jar +*.war +*.ear +*.nar +hs_err_pid* -# Log file -*.log +############################## +## Maven +############################## +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +pom.xml.bak +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar -# BlueJ files -*.ctxt +############################## +## Gradle +############################## +bin/ +build/ +.gradle +.gradletasknamecache +gradle-app.setting +!gradle-wrapper.jar -# Mobile Tools for Java (J2ME) -.mtj.tmp/ +############################## +## IntelliJ +############################## +out/ +.idea/ +.idea_modules/ +*.iml +*.ipr +*.iws -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar +############################## +## Eclipse +############################## +.settings/ +bin/ +tmp/ +.metadata +.classpath +.project +*.tmp +*.bak +*.swp +*~.nib +local.properties +.loadpath +.factorypath + +############################## +## NetBeans +############################## +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +nbactions.xml +nb-configuration.xml + +############################## +## Visual Studio Code +############################## +.vscode/ +.code-workspace + +############################## +## OS X +############################## +.DS_Store # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* -# mvn build dir -target/* diff --git a/README.md b/README.md index 851c78e..f48154c 100644 --- a/README.md +++ b/README.md @@ -9,38 +9,70 @@ its current limitations. ## installation -This library is intended to be used with maven buildsystem, although you can -probably easily move it to any other if you're familiar with those. To add it -to your mvn project edit the pom.xml file to add: +This library is intended to be used with maven build system, although you can +probably easily move it to any other if you're familiar with those. To add it to +your mvn project edit the pom.xml file to add: ```xml ... - - io.github.in-toto - in-toto - 0.1 - compile - + + io.github.in-toto + in-toto + 0.0.2 + ... ``` -With it you should be able to use the library inside of your project. +With it, you should be able to use the library inside your project. -## Using the library +## Using the new library -The library exposes a series of objects and convenience methods to create, -sign, and serialize in-toto metadata. As of now, only Link metadata is -supported (see the Limitations section to see what exactly is supported as of -now). +The library exposes a new set of models used for in-toto attestations 0.1.0. If +you wish to use the deprecated legacy Link library, please skip to the next +section. -Metadata classes are located in the `io.in_toto.models.*` namespace. You can, -for example create a link as follows: +The new library allows you to instantiate a Statement and populate it as +follows: ```java - Link link = new Link(null, null, "test", null, null); +Subject subject=new Subject(); + subject.setName("curl-7.72.0.tar.bz2"); + subject.setDigest( + Map.of( + DigestSetAlgorithmType.SHA256.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + Predicate predicate=new Predicate(); // Let's pretend this is an SLSA predicate + Statement statement=new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(predicate); ``` -This will create a link object that you can operate with. +Finally, you can use the built-in `IntotoHelper` class to validate and transform +it into its JSON representation as follows: + +```java + String jsonStatement=IntotoHelper.validateAndTransformToJson(statement); +``` + +If the statement passed to the method is malformed the library will throw +an `InvalidModelException` that will contain a message with the errors. + +## Using the legacy Link library + +The library exposes a series of objects and convenience methods to create, sign, +and serialize in-toto metadata. As of now, only Link metadata is supported (see +the Limitations section to see what exactly is supported as of now). + +Metadata classes are located in the `io.github.legacy.models.*` package. You +can, for example create a link as follows: + +```java + Link link=new Link(null,null,"test",null,null); +``` + +This will create a link object that you can operate with. You can populate a link and track artifacts using the Artifact class and the ArtifactHash subclass. You can also use the link's convenience method: @@ -49,54 +81,31 @@ ArtifactHash subclass. You can also use the link's convenience method: link.addArtifact("alice"); ``` -Once the artfifact is populated, it hashes the target artifact with any of the +Once the artifact is populated, it hashes the target artifact with any of the supported hashes. Finally, you can sign and dump a link by calling sign and dump respectively. ```java -import io.github.in_toto.keys.Key; -import io.github.in_toto.keys.RSAKey; + ... - Key thiskey = RSAKey.read("src/test/resources/somekey.pem"); - System.out.println("Loaded key: " + thiskey.computeKeyId()); + Key thiskey=RSAKey.read("src/test/resources/somekey.pem"); + System.out.println("Loaded key: "+thiskey.computeKeyId()); ... - Link link = new Link(null, null, "test", null, null, null); + Link link=new Link(null,null,"test",null,null,null); link.addMaterialt("alice"); link.sign(thiskey); link.dump(somelink); ``` -You can see a complete example on `src/java/io/github/in_toto/lib/App.java`. - -## Note on reduced feature-set - -in-toto java is not yet a fully compliant in-toto implementation. This -implementation is focused on providing a stable, usable core feature set for -its main goal. The features that we will add in the near future include: - -- A more user-friendly API to create and interact with metadata. -- Layout metadata support, including full supply chain verification. -- DSA (and possibly GPG) key support. -- A more thorough test suite that includes integration tests. - -We can guarantee that the dumped link metadata passes in-toto verification on -the [python](https://github.com/in-toto/in-toto) reference implementation when -providing the right key. - -As of now, the near-future goals of this library are to be used in a Jenkins -plugin and to support Android buildsystems. However, for any other step in the -supply chain I *highly* recommend you use the python implementation, for it has -more features, it's better tested and will be updated to comply wih the spec -before this one. - -If you'd like to help with the development of this library, patches are -welcome! +You can see a complete example on `src/java/io/github/legacy/lib/App.java`. ## Acknowledgements This work was mostly driven forward by the awesome guys at [control-plane](https://control-plane.io). If you're interested in cloud native security, do check out their website. + +If you'd like to help with the development of this library, patches are welcome! diff --git a/pom.xml b/pom.xml index 9d622a0..6d2e356 100644 --- a/pom.xml +++ b/pom.xml @@ -1,16 +1,18 @@ - + 4.0.0 io.github.in-toto in-toto jar - 0.3.1 + 0.3.2 in-toto https://maven.apache.org A framework to secure software supply chains. - scm:git:https://github.com/in-toto/in-toto-java - scm:git:ssh://git@github.com/in-toto/in-toto-java - http://github.com/in-toto/in-toto-java/ + scm:git:https://github.com/in-toto/in-toto-java + scm:git:ssh://git@github.com/in-toto/in-toto-java + + https://github.com/in-toto/in-toto-java/ HEAD @@ -20,6 +22,11 @@ New York University https://ssl.engineering.nyu.edu + + Sergio Felix + sfelix@google.com + Google + @@ -47,6 +54,12 @@ bcprov-jdk15on 1.67 + + + com.fasterxml.jackson.core + jackson-databind + 2.12.3 + com.google.code.gson @@ -58,7 +71,7 @@ org.junit.jupiter junit-jupiter-engine 5.7.0 - test + test org.junit.vintage @@ -80,24 +93,33 @@ junit-jupiter-migrationsupport 5.7.0 - + + javax.validation + validation-api + 2.0.1.Final + + + org.hibernate.validator + hibernate-validator + 6.0.12.Final + - org.junit-pioneer - junit-pioneer - 0.1.2 + org.glassfish + javax.el + 3.0.0 - --> + - 1.8 - 1.8 - -Xlint:unchecked + 11 + 11 maven-surefire-plugin - 2.21.0 + 2.22.2 org.junit.platform diff --git a/src/main/java/io/github/in_toto/keys/Key.java b/src/main/java/io/github/in_toto/keys/Key.java deleted file mode 100644 index a00cf8e..0000000 --- a/src/main/java/io/github/in_toto/keys/Key.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.in_toto.keys; - -import java.io.IOException; -import java.io.FileNotFoundException; - -import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.bouncycastle.crypto.Signer; - -/** - * Public class representing an in-toto key. - * - * This class is an abstract template from which all the keys for different - * signing algorithms will be based off of. - * - */ -public abstract class Key -{ - String keyid; - - public static Key read(String filename) { - throw new RuntimeException("Can't instantiate an abstract Key!"); - } - public abstract AsymmetricKeyParameter getPrivate() throws IOException; - public abstract AsymmetricKeyParameter getPublic() throws IOException; - public abstract String computeKeyId(); - public abstract void write(String filename) throws FileNotFoundException, IOException; - public abstract Signer getSigner(); -} diff --git a/src/main/java/io/github/in_toto/keys/RSAKey.java b/src/main/java/io/github/in_toto/keys/RSAKey.java deleted file mode 100644 index 5d4f5dc..0000000 --- a/src/main/java/io/github/in_toto/keys/RSAKey.java +++ /dev/null @@ -1,260 +0,0 @@ -package io.github.in_toto.keys; - -import io.github.in_toto.lib.JSONEncoder; - -import java.io.IOException; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.Reader; -import java.io.StringWriter; -import java.io.Writer; - -import java.util.HashMap; - -import org.bouncycastle.util.encoders.Hex; - -import org.bouncycastle.openssl.PEMKeyPair; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.MiscPEMGenerator; -import org.bouncycastle.openssl.jcajce.JcaPEMWriter; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.crypto.Signer; -import org.bouncycastle.crypto.util.PrivateKeyFactory; -import org.bouncycastle.crypto.util.PublicKeyFactory; -import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.bouncycastle.crypto.digests.SHA256Digest; -import org.bouncycastle.crypto.signers.PSSSigner; -import org.bouncycastle.crypto.engines.RSAEngine; - - - -/** - * RSA implementation of an in-toto RSA key. - * - */ -public class RSAKey - extends Key - implements JSONEncoder -{ - - PEMKeyPair kpr; - - /** - * - * Hardcoded method string. used to compute the keyid and to indicate - * which signing mechanism was used to compute this signature. - */ - private final String scheme = "rsassa-pss-sha256"; - - /** - * Hardcoded hashing algorithms. used to compute the keyid, as well as to - * indicate which hash algorithms can be used to compute the keyid itself. - */ - private final String[] keyid_hash_algorithms = {"sha256", "sha512"}; - - /** - * Hardcoded keytype. This field exists for backwards compatibility, as the scheme - * field is more descriptive. - */ - private final String keytype = "rsa"; - - /** - * HashMap containing the public and (if available) private portions of the key. - */ - private HashMap keyval; - - /** - * Default constructor for the RSAKey. - * - * You most likely want to use the static method {@link #read read} to instantiate this class. - * - * @param kpr: A PEMKeypair conatining the private and public key information - */ - public RSAKey(PEMKeyPair kpr) { - this.kpr = kpr; - this.keyval = new HashMap(); - this.keyval.put("private", getKeyval(true)); - this.keyval.put("public", getKeyval(false)); - } - - /** - * Static method to de-serialize a keypair from a PEM in disk. - * - * @param filename the location of the pem to de-serialize. - * - * @return An instance of an RSAKey that corresponds to the pem located in the filename parameter. - */ - public static RSAKey read(String filename) { - return RSAKey.readPem(filename); - } - - private static RSAKey readPem(String filename) - { - FileReader pemfile = null; - try { - pemfile = new FileReader(filename); - } catch (IOException e) { - throw new RuntimeException("Couldn't read key"); - } - - return readPemBuffer(pemfile); - } - - /** - * Static method to de-serialize a keypair from a reader. - * - * @param reader the reader that will be used to de-serialize the key - * - * @return An instance of an RSAKey contained in the reader instance - */ - public static RSAKey readPemBuffer(Reader reader) - { - - PEMParser pemReader = new PEMParser(reader); - PEMKeyPair kpr = null; - // FIXME: some proper exception handling here is in order - try { - Object pem = pemReader.readObject(); - - if (pem instanceof PEMKeyPair) { - kpr = (PEMKeyPair)pem; - } else if (pem instanceof SubjectPublicKeyInfo) { - kpr = new PEMKeyPair((SubjectPublicKeyInfo)pem, null); - } else { - throw new RuntimeException("Couldn't parse PEM object: " + - pem.toString()); - } - - } catch (IOException e) {} - return new RSAKey(kpr); - } - - /** - * Convenience method to obtain the private portion of the key. - * - * @return an AsymmetricKeyParameter that can be used for signing. - */ - public AsymmetricKeyParameter getPrivate() throws IOException{ - if (this.kpr == null) - return null; - if (this.kpr.getPrivateKeyInfo() == null) - return null; - return PrivateKeyFactory.createKey(this.kpr.getPrivateKeyInfo()); - } - - /** - * Convenience method to obtain the public portion of the key. - * - * @return an AsymmetricKeyParameter that can be used for verification. - */ - public AsymmetricKeyParameter getPublic() throws IOException { - if (this.kpr == null) - return null; - return PublicKeyFactory.createKey(this.kpr.getPublicKeyInfo()); - } - - /** - * Convenience method to serialize this key as a PEM - * - * @param filename the filename to where the key will be written to. - */ - public void write(String filename) { - try { - FileWriter out = new FileWriter(filename); - encodePem(out, false); - } catch (IOException e) { - throw new RuntimeException(e.toString()); - } - } - - /** - * Convenience method to obtain the keyid for this key - * - * @return the keyid for this key (Sha256 is baked in, for the time being) - */ - public String computeKeyId() { - if (this.kpr == null) - return null; - - byte[] JSONrepr = getJSONEncodeableFields(); - - // initialize digest - SHA256Digest digest = new SHA256Digest(); - byte[] result = new byte[digest.getDigestSize()]; - digest.update(JSONrepr, 0, JSONrepr.length); - digest.doFinal(result, 0); - return Hex.toHexString(result); - } - - private byte[] getJSONEncodeableFields() { - - // if we have a private portion, exclude it from the keyid computation - String privateBackup = null; - if (this.keyval.containsKey("private")) { - privateBackup = this.keyval.get("private"); - this.keyval.remove("private"); - } - - PEMKeyPair keyPairBackup = null; - if (this.kpr != null ) { - keyPairBackup = this.kpr; - this.kpr = null; - } - - byte[] JSONrepr = this.JSONEncodeCanonical(false).getBytes(); - - if (privateBackup != null) - this.keyval.put("private", privateBackup); - - if (keyPairBackup != null) - this.kpr = keyPairBackup; - - return JSONrepr; - } - - private void encodePem(Writer out, boolean privateKey) { - - JcaPEMWriter pemWriter = new JcaPEMWriter(out); - - try { - - if (privateKey && getPrivate() != null) - pemWriter.writeObject(new MiscPEMGenerator(this.kpr.getPrivateKeyInfo())); - else - pemWriter.writeObject(new MiscPEMGenerator(this.kpr.getPublicKeyInfo())); - pemWriter.flush(); - } catch (IOException e) { - throw new RuntimeException(e.toString()); - } - } - - private String getKeyval(boolean privateKey) { - StringWriter out = new StringWriter(); - encodePem(out, privateKey); - String result = out.toString(); - - // We need to truncate any trailing '\n' as different implementations - // may or may not add it as a consequence keyids. - if (result.charAt(result.length() -1) == '\n') - result = result.substring(0, result.length() - 1); - - return result; - } - - /** - * Returns the signer associated with the signing method for this key - * - * @return a Signer instance that can be used to sign or verify using - * RSASSA-PSS - */ - public Signer getSigner() { - RSAEngine engine = new RSAEngine(); - try { - engine.init(false, getPrivate()); - } catch (IOException e) { - throw new RuntimeException(e.toString()); - } - SHA256Digest digest = new SHA256Digest(); - return new PSSSigner(engine, digest, digest.getDigestSize()); - } -} diff --git a/src/main/java/io/github/in_toto/keys/Signature.java b/src/main/java/io/github/in_toto/keys/Signature.java deleted file mode 100644 index 9627d65..0000000 --- a/src/main/java/io/github/in_toto/keys/Signature.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.github.in_toto.keys; - -import java.io.IOException; - -import org.bouncycastle.crypto.params.AsymmetricKeyParameter; - -/** - * Public class representing an in-toto Signature. - * - * This class is an abstract template from which all the keys for different - * signing algorithms will be based off of. - * - */ -public class Signature -{ - String keyid; - String sig; - - public Signature(String keyid, String sig) { - this.keyid = keyid; - this.sig = sig; - } - - public String getKeyId() { - return this.keyid; - } -} diff --git a/src/main/java/io/github/in_toto/lib/App.java b/src/main/java/io/github/in_toto/lib/App.java deleted file mode 100644 index 7b93132..0000000 --- a/src/main/java/io/github/in_toto/lib/App.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.in_toto.lib; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.io.File; -import java.io.IOException; - -import io.github.in_toto.keys.RSAKey; -import io.github.in_toto.keys.Key; -import io.github.in_toto.models.Link; -import io.github.in_toto.models.Artifact; -import io.github.in_toto.models.Artifact.ArtifactHash; - -/** - * Hello world! - * - */ -public class App -{ - public static void main( String[] args ) - { - Key thiskey = RSAKey.read("src/test/resources/somekey.pem"); - - - Link link = new Link(null, null, "test", null, null, null); - try { - - System.out.println("Loaded key ID: " + thiskey.computeKeyId()); - - File fl = new File("alice"); - fl.createNewFile(); - - } catch (IOException e) { - System.out.println("Working Directory = " + - System.getProperty("user.dir")); - throw new RuntimeException("The file alice couldn't be created"); - } - - link.addMaterial("alice"); - System.out.println("dumping file..."); - link.sign(thiskey); - link.dump("somelink.link"); - - } - -} diff --git a/src/main/java/io/github/in_toto/lib/JSONEncoder.java b/src/main/java/io/github/in_toto/lib/JSONEncoder.java deleted file mode 100644 index e6c4e61..0000000 --- a/src/main/java/io/github/in_toto/lib/JSONEncoder.java +++ /dev/null @@ -1,151 +0,0 @@ -package io.github.in_toto.lib; - -import java.lang.System; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonArray; -import com.google.gson.JsonNull; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializer; -import com.google.gson.JsonSerializationContext; - - -import java.util.ArrayList; -import java.util.TreeSet; -import java.util.Map; -import java.lang.reflect.Type; - -/** - * JSONEncoder interface - * - * Provides a Mixin that encodes the instance in a canonical JSON encoding that - * can be used for hashing. - */ -public interface JSONEncoder - -{ - /** - * Static helper method to canonicalize a json string by escaping the - * double quotes `"` and backslash `\` characters using the backslash - * escape character `\`, based on the securesystemslib implementation used - * in the in-toto reference implementation, see: - * https://github.com/secure-systems-lab/securesystemslib/blob/v0.11.2/securesystemslib/formats.py#L688 - * - * @param src Source string to be canonicalized - * - * @return A canonicalized String - */ - static String canonicalizeString(String src) { - String pattern = "([\\\\\"])"; - return String.format("\"%s\"", src.replaceAll(pattern, "\\\\$1")); - } - - /** - * Static helper method to recursively create a canonical json encoded - * string of the passed JsonElement, based on the securesystemslib - * implementation used in the in-toto reference implementation, see: - * https://github.com/secure-systems-lab/securesystemslib/blob/v0.11.2/securesystemslib/formats.py#L712 - * - * @param src Source JsonElement to be traversed and encoded - * - * @return A canonical json encoded string of the passed JsonElement. - */ - static String canonicalize(JsonElement src) { - String result = new String(); - if (src instanceof JsonArray) { - // Canonicalize each element of the array - result += "["; - JsonArray array = (JsonArray) src; - for (int i = 0; i < array.size(); i++) { - result += canonicalize(array.get(i)); - - if (i < array.size() - 1) { - result += ","; - } - } - result += "]"; - - } else if (src instanceof JsonObject) { - result += "{"; - - JsonObject obj = (JsonObject)src; - // Create an ordered list of the JsonObject's keys - TreeSet keys = new TreeSet<>(); - for (Map.Entry entry : obj.entrySet()) { - keys.add(entry.getKey()); - } - - // Canonicalize json object - int i = 0; - for (String key : keys) { - // NOTE: It is okay to only call `canonicalizeString` (instead - // of `canonicalize` like in the reference implementation) - // because we know that the keys are always strings. - result += canonicalizeString(key); - result += ":"; - result += canonicalize(obj.get(key)); - - if (i < keys.size() - 1) { - result += ","; - } - i++; - } - result += "}"; - - } else if (src instanceof JsonNull) { - result += "null"; - - } else if (src instanceof JsonPrimitive) { - JsonPrimitive primitive = (JsonPrimitive) src; - - if (primitive.isNumber()) { - result += String.format("%d", primitive.getAsInt()); - - } else if (primitive.isBoolean()) { - result += primitive.getAsString(); - - } else if (primitive.isString()) { - String decodedPrimitive = new GsonBuilder() - .disableHtmlEscaping() - .create() - .fromJson(primitive, String.class); - result += canonicalizeString(decodedPrimitive); - } - } - return result; - } - - /** - * Method to create a canonical json encoded string of the calling object - * using the specification at http://wiki.laptop.org/go/Canonical_JSON. - * - * Attributes with `null` values are encoded as {@code "": null}. - * - * @return A canonical json encoded string of the calling object. - */ - default public String JSONEncodeCanonical() { - return this.JSONEncodeCanonical(true); - } - - /** - * Method to create a canonical json encoded string of the calling object - * using the specification at http://wiki.laptop.org/go/Canonical_JSON - * - * @param serializeNulls if true attributes with null values are - * are encoded as {@code "": null} and omitted otherwise. - * - * @return A canonical json encoded string of the calling object. - */ - default public String JSONEncodeCanonical(boolean serializeNulls) { - GsonBuilder gsonBuilder = new GsonBuilder(); - if (serializeNulls) { - gsonBuilder.serializeNulls(); - } - Gson gson = gsonBuilder.disableHtmlEscaping().create(); - - return canonicalize(gson.toJsonTree(this)); - } -} diff --git a/src/main/java/io/github/in_toto/lib/NumericJSONSerializer.java b/src/main/java/io/github/in_toto/lib/NumericJSONSerializer.java deleted file mode 100644 index 9f1a015..0000000 --- a/src/main/java/io/github/in_toto/lib/NumericJSONSerializer.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.github.in_toto.lib; - -import java.lang.reflect.Type; - -import com.google.gson.JsonSerializer; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonElement; -import com.google.gson.JsonPrimitive; - - -/** - * A custom implementation of the gson JsonSerializer to convert numeric values - * to non-floating point numbers when serializing JSON. - * - * This is required to handle generic data, such as `byproducts` or - * `environment`, where the type of the contained values is not declared. Gson - * treats any numeric value, with generic type in the target data structure as - * double. However, the in-toto reference implementation does not allow - * floating point numbers in JSON-formatted metadata. - * - */ -public class NumericJSONSerializer implements JsonSerializer { - public JsonElement serialize(Double src, Type typeOfSrc, - JsonSerializationContext context) { - if(src == src.longValue()) { - return new JsonPrimitive(src.longValue()); - } - - return new JsonPrimitive(src); - } -} diff --git a/src/main/java/io/github/in_toto/models/Artifact.java b/src/main/java/io/github/in_toto/models/Artifact.java deleted file mode 100644 index 7f1e66c..0000000 --- a/src/main/java/io/github/in_toto/models/Artifact.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.github.in_toto.models; - -import java.util.HashMap; - -import java.io.FileInputStream; -import java.io.Reader; -import java.io.IOException; -import java.io.FileNotFoundException; -import java.io.StringWriter; - -import org.bouncycastle.crypto.digests.SHA256Digest; - -import org.bouncycastle.util.encoders.Hex; - -/** - * A class representing an Artifact (that is, a material or a product). - * - * Used by the Link metdata type on the .add method. Can be also used to - * pre-populate the Link's artifact fields before instantiating a link. - */ -public class Artifact { - - /** - * A URI representing the location of the Artifact - */ - private String URI; - - /** - * An ArtifactHash containing the hash of the contents of the file following - * the hash object definition in the in-toto specification. - */ - private ArtifactHash hash; - - /** - * Default constructor, uses a filename to collect and automatically - * hash the contents of the file. - * - * This constructor does *not* perform file locking, so use with care. - * - * @param filename The filename (relative or absolute) of the Artifact to - * record (i.e., hash). - */ - public Artifact(String filename) { - - this.URI = filename; - this.hash = new ArtifactHash(); - this.hash.collect(filename); - - } - - public String getURI() { - return this.URI; - } - - public ArtifactHash getArtifactHashes() { - return this.hash; - } - - /** - * Nested subclass representing a hash object compliant with the in-toto specification. - * - * - * {"sha256": "...", - * "sha512": "..." - * } - * - */ - public class ArtifactHash - extends HashMap - { - - private void collect(String filename) { - - FileInputStream file = null; - try { - file = new FileInputStream(filename); - } catch (FileNotFoundException e) { - throw new RuntimeException("The file " + filename + " couldn't be recorded"); - } - - - SHA256Digest digest = new SHA256Digest(); - byte[] result = new byte[digest.getDigestSize()]; - int length; - try { - while ((length = file.read(result)) != -1) { - digest.update(result, 0, length); - } - } catch (IOException e) { - throw new RuntimeException("The file " + filename + " couldn't be recorded"); - } - digest.doFinal(result, 0); - - // We should be able to submit more hashes, but we will do sha256 - // only for the time being - this.put("sha256", Hex.toHexString(result)); - } - } -} diff --git a/src/main/java/io/github/in_toto/models/Link.java b/src/main/java/io/github/in_toto/models/Link.java deleted file mode 100644 index 93e87b5..0000000 --- a/src/main/java/io/github/in_toto/models/Link.java +++ /dev/null @@ -1,237 +0,0 @@ -package io.github.in_toto.models; - -import io.github.in_toto.models.Artifact; -import io.github.in_toto.models.Artifact.ArtifactHash; -import io.github.in_toto.keys.Signature; -import io.github.in_toto.models.LinkSignable; - -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.PathMatcher; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; - -import com.google.gson.Gson; - -/** - * Implementation of the in-toto Link metadata type. - * - */ -public class Link extends Metablock -{ - /** - * default exclude pattern used to filter out redundant artifacts - */ - transient String defaultExcludePattern = "**.{git,link}**"; - - /** - * Constuctor method used to populate the signable payload - * - * @param materials a HashMap keyed by artifact URI's and with hash - * objects as values represeting the artifacts used as materials in this - * step - * @param products a HashMap keyed by artifact URI's and with hash objects - * as values representing the artifacts created as products in this step. - * @param name The name of this step - * @param environment a HashMap containing any additional, relevant - * environment information. - * @param command the Argv array of the command executed. - * @param byproducts A HashMap containing the byproduct triplet - * stdin/stdout/retval. - * - * @see io.github.in_toto.models.Artifact - */ - public Link(HashMap materials, - HashMap products, String name, - HashMap environment, ArrayList command, - HashMap byproducts) { - super(null, null); - LinkSignable signable = new LinkSignable( - materials, products, name, environment, command, byproducts); - this.signed = signable; - } - - /** - * convenience method to save the Link metdata file using the name defined by - * the specification - * - */ - public void dump() { - dump(getFullName()); - } - - /** - * get full link name, including keyid bytes in the form of - * - * {@literal ..link } - * - * This method will always use the keyid of the first signature in the - * metadata. - * - * @return a string containing this name or null if no signatures are - * present - */ - public String getFullName() { - if (this.signatures == null || this.signatures.isEmpty()) - return getName() + ".UNSIGNED.link"; - - String keyId = ((Signature)this.signatures.get(0)).getKeyId(); - return getName() + "." + keyId.substring(0, 8) + ".link"; - } - - /** - * exclude artifacts matching the pattern - * @param materials the HashMap of artifacts - * @param pattern the exclude pattern - */ - public HashMapexcludeArtifactsByPattern - (HashMap materials, String pattern) - { - String patternString; - HashMap filtered_artifacts; - - if ( pattern != null && pattern.length() != 0) { - patternString = pattern; - } else { - patternString = defaultExcludePattern; - } - - FileSystem fileSystem = FileSystems.getDefault(); - - PathMatcher pathMatcher = - fileSystem.getPathMatcher("glob:" + patternString); - - filtered_artifacts = materials; - - Iterator> iterator = - filtered_artifacts.entrySet().iterator(); - - while(iterator.hasNext()){ - - HashMap.Entry entry = iterator.next(); - - if (pathMatcher.matches(Paths.get(entry.getKey()))) { - iterator.remove(); - } - } - - return filtered_artifacts; - } - - public void setMaterials(HashMap materials, String pattern) { - ((LinkSignable)this.signed).materials = - excludeArtifactsByPattern(materials, pattern); - } - - public void setMaterials(HashMap materials) { - setMaterials(materials, null); - } - - public HashMapgetMaterials() { - return ((LinkSignable)this.signed).materials; - } - - public void setProducts(HashMap products, String pattern) { - ((LinkSignable)this.signed).products = - excludeArtifactsByPattern(products, pattern); - } - - public void setProducts(HashMap products) { - setProducts(products, null); - } - - public HashMapgetProducts() { - return ((LinkSignable)this.signed).products; - } - - public void setName(String name) { - ((LinkSignable)this.signed).name = name; - } - - public String getName() { - return ((LinkSignable)this.signed).name; - } - - public void setEnvironment(HashMap environment) { - ((LinkSignable)this.signed).environment = environment; - } - - public HashMap getEnvironment() { - return ((LinkSignable)this.signed).environment; - } - - public void setCommand(ArrayList command) { - ((LinkSignable)this.signed).command = command; - } - - public ArrayList getCommand() { - return ((LinkSignable)this.signed).command; - } - - public void setByproducts(HashMap byproducts) { - ((LinkSignable)this.signed).byproducts = byproducts; - } - - public HashMap getByproducts() { - return ((LinkSignable)this.signed).byproducts; - } - - /** - * Convenience method to indicate this link to track an artifact as - * material - * - * @param filePath the path of the material to track - * @param pattern The exclude pattern - */ - public void addMaterial(String filePath, String pattern) { - - Artifact a = new Artifact(filePath); - - HashMap material = new HashMap(); - - material.put(a.getURI(), a.getArtifactHashes()); - - excludeArtifactsByPattern(material, pattern) - .forEach(((LinkSignable)this.signed).materials::putIfAbsent); - } - - public void addMaterial(String filePath) { - - addMaterial(filePath, null); - - } - - /** - * Convenience method to indicate this link to track an artifact as - * product - * - * @param filePath the path of the product to track - * @param pattern the exclude pattern - */ - public void addProduct(String filePath, String pattern) { - - Artifact a = new Artifact(filePath); - - HashMap product = new HashMap(); - - product.put(a.getURI(), a.getArtifactHashes()); - - excludeArtifactsByPattern(product, pattern) - .forEach(((LinkSignable)this.signed).products::putIfAbsent); - } - - public void addProduct(String filePath) { - - addProduct(filePath, null); - - } - - public static Link read(String jsonString) { - Gson gson = new Gson(); - return gson.fromJson(jsonString, Link.class); - } -} - - diff --git a/src/main/java/io/github/in_toto/models/LinkSignable.java b/src/main/java/io/github/in_toto/models/LinkSignable.java deleted file mode 100644 index 8dd3710..0000000 --- a/src/main/java/io/github/in_toto/models/LinkSignable.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * package-private class representing the signable payload of the in-toto link - * metadata. - */ -package io.github.in_toto.models; - -import io.github.in_toto.models.Artifact; -import io.github.in_toto.models.Artifact.ArtifactHash; -import io.github.in_toto.keys.Signature; -import io.github.in_toto.models.LinkSignable; -import java.util.ArrayList; -import java.util.HashMap; - - -class LinkSignable - extends Signable { - - HashMap materials; - HashMap products; - // NOTE: Caution when dealing with numeric values! - // Since gson does not know the type of the target, it will - // store any numeric value as `Double`, e.g.: - // {"byproducts": {"return-value": 1}} - // is parsed as - // {"byproducts": {"return-value": 1.0}} - HashMap byproducts; - HashMap environment; - ArrayList command; - String name; - - LinkSignable(HashMap materials, - HashMap products, String name, - HashMap environment, ArrayList command, - HashMap byproducts) { - - super(); - - if (materials == null) - materials = new HashMap(); - - if (products == null) - products = new HashMap(); - - //FIXME: probably warn about this would be a good idea - if (name == null) - name = "step"; - - if (environment == null) - environment = new HashMap(); - - if (command == null) - command = new ArrayList(); - - if (byproducts == null) - byproducts = new HashMap(); - - this.materials = materials; - this.products = products; - this.name = name; - this.environment = environment; - this.command = command; - this.byproducts = byproducts; - } - - @Override - public String getType() { - return "link"; - } -} - - diff --git a/src/main/java/io/github/in_toto/models/Metablock.java b/src/main/java/io/github/in_toto/models/Metablock.java deleted file mode 100644 index fb02c91..0000000 --- a/src/main/java/io/github/in_toto/models/Metablock.java +++ /dev/null @@ -1,150 +0,0 @@ -package io.github.in_toto.models; - -import java.util.ArrayList; - -import java.io.FileWriter; -import java.io.Writer; -import java.io.IOException; - -import io.github.in_toto.keys.Key; -import io.github.in_toto.keys.Signature; -import io.github.in_toto.models.Signable; -import io.github.in_toto.lib.NumericJSONSerializer; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -import org.bouncycastle.crypto.Signer; -import org.bouncycastle.util.encoders.Hex; -import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.bouncycastle.crypto.CryptoException; - -/** - * A metablock class that contains two elements - * - * - A signed field, with the signable portion of a piece of metadata. - * - A signatures field, a list of the signatures on this metadata. - */ -abstract class Metablock -{ - S signed; - ArrayList signatures; - - /** - * Base constructor. - * - * Ensures that, at the least, there is an empty list of signatures. - */ - public Metablock(S signed, ArrayList signatures) { - this.signed = signed; - - if (signatures == null) - signatures = new ArrayList(); - this.signatures = signatures; - } - - /** - * Serialize the current metadata into a JSON file - * - * @param filename The filename to which the metadata will be dumped. - */ - public void dump(String filename) { - FileWriter writer = null; - - try{ - writer = new FileWriter(filename); - dump(writer); - writer.close(); - } catch (IOException e) { - throw new RuntimeException("Couldn't serialize object: " + e.toString()); - } - } - - /** - * Serialize the current metadata into a writer - * - * @param writer the target writer - * - * @throws java.io.IOException if unable to write to the passed writer. - */ - public void dump(Writer writer) - throws IOException { - - writer.write(dumpString()); - writer.flush(); - } - - /** - * Serialize the current metadata to a string - * - * - * @return a JSON string representation of the metadata instance - */ - public String dumpString() { - Gson gson = new GsonBuilder() - .serializeNulls() - // Use custom serializer to enforce non-floating point numbers - .registerTypeAdapter(Double.class, new NumericJSONSerializer()) - .setPrettyPrinting() - .create(); - return gson.toJson(this); - } - - /** - * Signs the current signed payload using the key provided - * - * @param privateKey the key used to sign the payload. - */ - public void sign(Key privateKey) { - - String sig; - String keyid; - byte[] payload; - AsymmetricKeyParameter keyParameters; - - try { - keyParameters = privateKey.getPrivate(); - if (keyParameters == null || keyParameters.isPrivate() == false) { - System.out.println("Can't sign with a public key!"); return; } - } catch (IOException e) { - System.out.println("Can't sign with this key!"); - return; - } - - keyid = privateKey.computeKeyId(); - payload = this.signed.JSONEncodeCanonical().getBytes(); - - Signer signer = privateKey.getSigner(); - signer.init(true, keyParameters); - signer.update(payload, 0, payload.length); - try { - sig = Hex.toHexString(signer.generateSignature()); - } catch (CryptoException e) { - System.out.println("Coudln't sign payload!"); - return; - } - - this.signatures.add(new Signature(keyid, sig)); - - } - - /** - * Public shortcut to call JSONEncodeCanonical on the signed field of - * this metablock. - * - * @param serializeNulls if nulls should be included or not when encoding - * - * @return a JSON string representation of this obj - */ - public String getCanonicalJSON(boolean serializeNulls) { - return this.signed.JSONEncodeCanonical(serializeNulls); - } - - public S getSigned() { - return this.signed; - } - - public ArrayList getSignatures() { - return this.signatures; - } -} diff --git a/src/main/java/io/github/in_toto/models/Signable.java b/src/main/java/io/github/in_toto/models/Signable.java deleted file mode 100644 index 5226f28..0000000 --- a/src/main/java/io/github/in_toto/models/Signable.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.in_toto.models; - -import io.github.in_toto.lib.JSONEncoder; - - -/** - * A signable class is an abstract superclass that provides a representation method - * to prepare for signing - * - */ -abstract class Signable - implements JSONEncoder -{ - /** - * Subclasses must define the _type field appropriately for serialization - */ - protected String _type; - - public Signable() { - this._type = getType(); - } - - public abstract String getType(); - -} diff --git a/src/main/java/io/github/intoto/exceptions/InvalidModelException.java b/src/main/java/io/github/intoto/exceptions/InvalidModelException.java new file mode 100644 index 0000000..abd6186 --- /dev/null +++ b/src/main/java/io/github/intoto/exceptions/InvalidModelException.java @@ -0,0 +1,8 @@ +package io.github.intoto.exceptions; + +/** Exception thrown when there are issues validating the model. */ +public class InvalidModelException extends Exception { + public InvalidModelException(String message) { + super(message); + } +} diff --git a/src/main/java/io/github/intoto/helpers/IntotoHelper.java b/src/main/java/io/github/intoto/helpers/IntotoHelper.java new file mode 100644 index 0000000..2110672 --- /dev/null +++ b/src/main/java/io/github/intoto/helpers/IntotoHelper.java @@ -0,0 +1,47 @@ +package io.github.intoto.helpers; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import io.github.intoto.exceptions.InvalidModelException; +import io.github.intoto.models.Statement; +import java.util.Set; +import java.util.stream.Collectors; +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; + +/** + * Helper class for the intoto-java implementation. This class provides with helper methods to + * validate and transform {@link Statement} into their JSON representations. + */ +public class IntotoHelper { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final Validator validator = + Validation.buildDefaultValidatorFactory().getValidator(); + + /** + * Validates a {@link Statement} and transforms it to its JSON representation. + * + * @param statement the statement thet needs to be validated and tested. + * @return the String with the JSON representation of the Statement. + * @throws JsonProcessingException thrown when there is a problem serializing the Statement into + * JSON + * @throws InvalidModelException thrown when there are problems with the statement. + */ + public static String validateAndTransformToJson(Statement statement) + throws JsonProcessingException, InvalidModelException { + + Set> results = validator.validate(statement); + + if (results.isEmpty()) { + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + return objectMapper.writeValueAsString(statement); + } else { + String errorMessage = + results.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(",/n")); + throw new InvalidModelException(errorMessage); + } + } +} diff --git a/src/main/java/io/github/intoto/models/DigestSetAlgorithmType.java b/src/main/java/io/github/intoto/models/DigestSetAlgorithmType.java new file mode 100644 index 0000000..2ceabfb --- /dev/null +++ b/src/main/java/io/github/intoto/models/DigestSetAlgorithmType.java @@ -0,0 +1,24 @@ +package io.github.intoto.models; + +/** Helper Enum with common algorithm types, could be used to populate the {@link Subject} digest */ +public enum DigestSetAlgorithmType { + SHA256, + SHA224, + SHA384, + SHA512, + SHA512_224, + SHA512_256, + SHA3_224, + SHA3_256, + SHA3_384, + SHA3_512, + SHAKE128, + SHAKE256, + BLAKE2B, + BLAKE2S, + RIPEMD160, + SM3, + GOST, + SHA1, + MD5 +} diff --git a/src/main/java/io/github/intoto/models/Predicate.java b/src/main/java/io/github/intoto/models/Predicate.java new file mode 100644 index 0000000..ab651e4 --- /dev/null +++ b/src/main/java/io/github/intoto/models/Predicate.java @@ -0,0 +1,9 @@ +package io.github.intoto.models; + +/** + * A generic attestation type with a schema isomorphic to in-toto 0.9. This allows existing in-toto + * users to make minimal changes to upgrade to the new attestation format. + * + *

Most users should migrate to a more specific attestation type, such as Provenance. + */ +public class Predicate {} diff --git a/src/main/java/io/github/intoto/models/PredicateType.java b/src/main/java/io/github/intoto/models/PredicateType.java new file mode 100644 index 0000000..27e189f --- /dev/null +++ b/src/main/java/io/github/intoto/models/PredicateType.java @@ -0,0 +1,26 @@ +package io.github.intoto.models; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * This enumeration is meant to represent the supported predicate types. + * + * @see in-toto/attestation/blob/main/spec/README.md#predicate + */ +public enum PredicateType { + SLSA_PROVENANCE_V_0_1("https://slsa.dev/provenance/v0.1"), + LINK_V_0_2("https://in-toto.io/Link/v0.2"), + SPDX_V_0_1("https://spdx.dev/Document"); + + private final String value; + + PredicateType(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return this.value; + } +} diff --git a/src/main/java/io/github/intoto/models/Statement.java b/src/main/java/io/github/intoto/models/Statement.java new file mode 100644 index 0000000..379601e --- /dev/null +++ b/src/main/java/io/github/intoto/models/Statement.java @@ -0,0 +1,92 @@ +package io.github.intoto.models; + +import io.github.intoto.validators.UniqueSubject; +import java.util.List; +import java.util.Objects; +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * The Statement is the middle layer of the attestation, binding it to a particular subject and + * unambiguously identifying the types of the predicate. + */ +public class Statement { + + /** Identifier for the schema of the Statement. */ + @NotNull(message = "_type may not be null") + private StatementType _type; + + /** + * Set of software artifacts that the attestation applies to. Each element represents a single + * software artifact. + * + *

IMPORTANT: Subject artifacts are matched purely by digest, regardless of content type. If + * this matters to you, please comment on GitHub Issue #28 + */ + @NotEmpty(message = "subject may not be null or empty") + @UniqueSubject + private List<@Valid Subject> subject; + + /** URI identifying the type of the Predicate. */ + @NotNull(message = "predicateType may not be null") + private PredicateType predicateType; + + /** + * Additional parameters of the Predicate. Unset is treated the same as set-but-empty. MAY be + * omitted if predicateType fully describes the predicate. + */ + private Predicate predicate; + + public StatementType get_type() { + return _type; + } + + public void set_type(StatementType _type) { + this._type = _type; + } + + public List getSubject() { + return subject; + } + + public void setSubject(List subject) { + this.subject = subject; + } + + public PredicateType getPredicateType() { + return predicateType; + } + + public void setPredicateType(PredicateType predicateType) { + this.predicateType = predicateType; + } + + public Predicate getPredicate() { + return predicate; + } + + public void setPredicate(Predicate predicate) { + this.predicate = predicate; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Statement statement = (Statement) o; + return _type == statement._type + && subject.equals(statement.subject) + && predicateType == statement.predicateType + && Objects.equals(predicate, statement.predicate); + } + + @Override + public int hashCode() { + return Objects.hash(_type, subject, predicateType, predicate); + } +} diff --git a/src/main/java/io/github/intoto/models/StatementType.java b/src/main/java/io/github/intoto/models/StatementType.java new file mode 100644 index 0000000..af13bc5 --- /dev/null +++ b/src/main/java/io/github/intoto/models/StatementType.java @@ -0,0 +1,24 @@ +package io.github.intoto.models; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * This enum is meant to represent the Statement entity type. + * + * @see in-toto/attestation/blob/main/spec/README.md#statement + */ +public enum StatementType { + STATEMENT_V_0_1("https://in-toto.io/Statement/v0.1"); + + private final String value; + + StatementType(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return this.value; + } +} diff --git a/src/main/java/io/github/intoto/models/Subject.java b/src/main/java/io/github/intoto/models/Subject.java new file mode 100644 index 0000000..47eb775 --- /dev/null +++ b/src/main/java/io/github/intoto/models/Subject.java @@ -0,0 +1,72 @@ +package io.github.intoto.models; + +import java.util.Map; +import java.util.Objects; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; + +/** + * Set of software artifacts that the attestation applies to. Each element represents a single * + * software artifact. + */ +public class Subject { + /** + * Identifier to distinguish this artifact from others within the subject. + * + *

The semantics are up to the producer and consumer. Because consumers evaluate the name + * against a policy, the name SHOULD be stable between attestations. If the name is not + * meaningful, use "_". For example, a SLSA Provenance attestation might use the name to specify + * output filename, expecting the consumer to only considers entries with a particular name. + * Alternatively, a vulnerability scan attestation might use the name "_" because the results + * apply regardless of what the artifact is named. + * + *

MUST be non-empty and unique within subject. + */ + @NotBlank(message = "subject name must not be blank") + private String name; + /** + * Collection of cryptographic digests for the contents of this artifact. + * + *

Two DigestSets are considered matching if ANY of the fields match. The producer and consumer + * must agree on acceptable algorithms. If there are no overlapping algorithms, the subject is + * considered not matching. + */ + @NotEmpty(message = "digest must not be empty") + private Map< + @NotBlank(message = "digest key contents can be empty strings") String, + @NotBlank(message = "digest value contents can be empty strings") String> + digest; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + 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; + } + Subject subject = (Subject) o; + return name.equals(subject.name) && digest.equals(subject.digest); + } + + @Override + public int hashCode() { + return Objects.hash(name, digest); + } +} diff --git a/src/main/java/io/github/intoto/validators/UniqueSubject.java b/src/main/java/io/github/intoto/validators/UniqueSubject.java new file mode 100644 index 0000000..f1f8f8a --- /dev/null +++ b/src/main/java/io/github/intoto/validators/UniqueSubject.java @@ -0,0 +1,38 @@ +package io.github.intoto.validators; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import io.github.intoto.validators.UniqueSubject.List; +import java.lang.annotation.Documented; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import javax.validation.Constraint; +import javax.validation.Payload; + +@Documented +@Constraint(validatedBy = {UniqueSubjectValidator.class}) +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) +@Retention(RUNTIME) +@Repeatable(List.class) +public @interface UniqueSubject { + + String message() default "subjects must be unique"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) + @Retention(RUNTIME) + @Documented + public @interface List { + UniqueSubject[] value(); + } +} diff --git a/src/main/java/io/github/intoto/validators/UniqueSubjectValidator.java b/src/main/java/io/github/intoto/validators/UniqueSubjectValidator.java new file mode 100644 index 0000000..39f33ea --- /dev/null +++ b/src/main/java/io/github/intoto/validators/UniqueSubjectValidator.java @@ -0,0 +1,27 @@ +package io.github.intoto.validators; + +import io.github.intoto.models.Subject; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** Validator that makes sure that Subjects are unique. */ +public class UniqueSubjectValidator implements ConstraintValidator> { + + @Override + public void initialize(UniqueSubject constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(List value, ConstraintValidatorContext context) { + if (Objects.isNull(value)) { + return true; + } + List uniqueSubjects = + value.stream().map(Subject::getName).distinct().collect(Collectors.toList()); + return uniqueSubjects.size() == value.size(); + } +} diff --git a/src/main/java/io/github/legacy/keys/Key.java b/src/main/java/io/github/legacy/keys/Key.java new file mode 100644 index 0000000..20765d6 --- /dev/null +++ b/src/main/java/io/github/legacy/keys/Key.java @@ -0,0 +1,30 @@ +package io.github.legacy.keys; + +import java.io.FileNotFoundException; +import java.io.IOException; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; + +/** + * Public class representing an in-toto key. + * + *

This class is an abstract template from which all the keys for different signing algorithms + * will be based off of. + */ +public abstract class Key { + String keyid; + + public static Key read(String filename) { + throw new RuntimeException("Can't instantiate an abstract Key!"); + } + + public abstract AsymmetricKeyParameter getPrivate() throws IOException; + + public abstract AsymmetricKeyParameter getPublic() throws IOException; + + public abstract String computeKeyId(); + + public abstract void write(String filename) throws FileNotFoundException, IOException; + + public abstract Signer getSigner(); +} diff --git a/src/main/java/io/github/legacy/keys/RSAKey.java b/src/main/java/io/github/legacy/keys/RSAKey.java new file mode 100644 index 0000000..cac33b3 --- /dev/null +++ b/src/main/java/io/github/legacy/keys/RSAKey.java @@ -0,0 +1,233 @@ +package io.github.legacy.keys; + +import io.github.legacy.lib.JSONEncoder; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.HashMap; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.engines.RSAEngine; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.signers.PSSSigner; +import org.bouncycastle.crypto.util.PrivateKeyFactory; +import org.bouncycastle.crypto.util.PublicKeyFactory; +import org.bouncycastle.openssl.MiscPEMGenerator; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.util.encoders.Hex; + +/** RSA implementation of an in-toto RSA key. */ +public class RSAKey extends Key implements JSONEncoder { + + PEMKeyPair kpr; + + /** + * Hardcoded method string. used to compute the keyid and to indicate which signing mechanism was + * used to compute this signature. + */ + private final String scheme = "rsassa-pss-sha256"; + + /** + * Hardcoded hashing algorithms. used to compute the keyid, as well as to indicate which hash + * algorithms can be used to compute the keyid itself. + */ + private final String[] keyid_hash_algorithms = {"sha256", "sha512"}; + + /** + * Hardcoded keytype. This field exists for backwards compatibility, as the scheme field is more + * descriptive. + */ + private final String keytype = "rsa"; + + /** HashMap containing the public and (if available) private portions of the key. */ + private HashMap keyval; + + /** + * Default constructor for the RSAKey. + * + *

You most likely want to use the static method {@link #read read} to instantiate this class. + * + * @param kpr: A PEMKeypair conatining the private and public key information + */ + public RSAKey(PEMKeyPair kpr) { + this.kpr = kpr; + this.keyval = new HashMap(); + this.keyval.put("private", getKeyval(true)); + this.keyval.put("public", getKeyval(false)); + } + + /** + * Static method to de-serialize a keypair from a PEM in disk. + * + * @param filename the location of the pem to de-serialize. + * @return An instance of an RSAKey that corresponds to the pem located in the filename parameter. + */ + public static RSAKey read(String filename) { + return RSAKey.readPem(filename); + } + + private static RSAKey readPem(String filename) { + FileReader pemfile = null; + try { + pemfile = new FileReader(filename); + } catch (IOException e) { + throw new RuntimeException("Couldn't read key"); + } + + return readPemBuffer(pemfile); + } + + /** + * Static method to de-serialize a keypair from a reader. + * + * @param reader the reader that will be used to de-serialize the key + * @return An instance of an RSAKey contained in the reader instance + */ + public static RSAKey readPemBuffer(Reader reader) { + + PEMParser pemReader = new PEMParser(reader); + PEMKeyPair kpr = null; + // FIXME: some proper exception handling here is in order + try { + Object pem = pemReader.readObject(); + + if (pem instanceof PEMKeyPair) { + kpr = (PEMKeyPair) pem; + } else if (pem instanceof SubjectPublicKeyInfo) { + kpr = new PEMKeyPair((SubjectPublicKeyInfo) pem, null); + } else { + throw new RuntimeException("Couldn't parse PEM object: " + pem.toString()); + } + + } catch (IOException e) { + } + return new RSAKey(kpr); + } + + /** + * Convenience method to obtain the private portion of the key. + * + * @return an AsymmetricKeyParameter that can be used for signing. + */ + public AsymmetricKeyParameter getPrivate() throws IOException { + if (this.kpr == null) return null; + if (this.kpr.getPrivateKeyInfo() == null) return null; + return PrivateKeyFactory.createKey(this.kpr.getPrivateKeyInfo()); + } + + /** + * Convenience method to obtain the public portion of the key. + * + * @return an AsymmetricKeyParameter that can be used for verification. + */ + public AsymmetricKeyParameter getPublic() throws IOException { + if (this.kpr == null) return null; + return PublicKeyFactory.createKey(this.kpr.getPublicKeyInfo()); + } + + /** + * Convenience method to serialize this key as a PEM + * + * @param filename the filename to where the key will be written to. + */ + public void write(String filename) { + try { + FileWriter out = new FileWriter(filename); + encodePem(out, false); + } catch (IOException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Convenience method to obtain the keyid for this key + * + * @return the keyid for this key (Sha256 is baked in, for the time being) + */ + public String computeKeyId() { + if (this.kpr == null) return null; + + byte[] JSONrepr = getJSONEncodeableFields(); + + // initialize digest + SHA256Digest digest = new SHA256Digest(); + byte[] result = new byte[digest.getDigestSize()]; + digest.update(JSONrepr, 0, JSONrepr.length); + digest.doFinal(result, 0); + return Hex.toHexString(result); + } + + private byte[] getJSONEncodeableFields() { + + // if we have a private portion, exclude it from the keyid computation + String privateBackup = null; + if (this.keyval.containsKey("private")) { + privateBackup = this.keyval.get("private"); + this.keyval.remove("private"); + } + + PEMKeyPair keyPairBackup = null; + if (this.kpr != null) { + keyPairBackup = this.kpr; + this.kpr = null; + } + + byte[] JSONrepr = this.JSONEncodeCanonical(false).getBytes(); + + if (privateBackup != null) this.keyval.put("private", privateBackup); + + if (keyPairBackup != null) this.kpr = keyPairBackup; + + return JSONrepr; + } + + private void encodePem(Writer out, boolean privateKey) { + + JcaPEMWriter pemWriter = new JcaPEMWriter(out); + + try { + + if (privateKey && getPrivate() != null) + pemWriter.writeObject(new MiscPEMGenerator(this.kpr.getPrivateKeyInfo())); + else pemWriter.writeObject(new MiscPEMGenerator(this.kpr.getPublicKeyInfo())); + pemWriter.flush(); + } catch (IOException e) { + throw new RuntimeException(e.toString()); + } + } + + private String getKeyval(boolean privateKey) { + StringWriter out = new StringWriter(); + encodePem(out, privateKey); + String result = out.toString(); + + // We need to truncate any trailing '\n' as different implementations + // may or may not add it as a consequence keyids. + if (result.charAt(result.length() - 1) == '\n') + result = result.substring(0, result.length() - 1); + + return result; + } + + /** + * Returns the signer associated with the signing method for this key + * + * @return a Signer instance that can be used to sign or verify using RSASSA-PSS + */ + public Signer getSigner() { + RSAEngine engine = new RSAEngine(); + try { + engine.init(false, getPrivate()); + } catch (IOException e) { + throw new RuntimeException(e.toString()); + } + SHA256Digest digest = new SHA256Digest(); + return new PSSSigner(engine, digest, digest.getDigestSize()); + } +} diff --git a/src/main/java/io/github/legacy/keys/Signature.java b/src/main/java/io/github/legacy/keys/Signature.java new file mode 100644 index 0000000..52d7b65 --- /dev/null +++ b/src/main/java/io/github/legacy/keys/Signature.java @@ -0,0 +1,21 @@ +package io.github.legacy.keys; + +/** + * Public class representing an in-toto Signature. + * + *

This class is an abstract template from which all the keys for different signing algorithms + * will be based off of. + */ +public class Signature { + String keyid; + String sig; + + public Signature(String keyid, String sig) { + this.keyid = keyid; + this.sig = sig; + } + + public String getKeyId() { + return this.keyid; + } +} diff --git a/src/main/java/io/github/legacy/lib/App.java b/src/main/java/io/github/legacy/lib/App.java new file mode 100644 index 0000000..841be85 --- /dev/null +++ b/src/main/java/io/github/legacy/lib/App.java @@ -0,0 +1,32 @@ +package io.github.legacy.lib; + +import io.github.legacy.keys.Key; +import io.github.legacy.keys.RSAKey; +import io.github.legacy.models.Link; +import java.io.File; +import java.io.IOException; + +/** Hello world! */ +public class App { + public static void main(String[] args) { + Key thiskey = RSAKey.read("src/test/resources/somekey.pem"); + + Link link = new Link(null, null, "test", null, null, null); + try { + + System.out.println("Loaded key ID: " + thiskey.computeKeyId()); + + File fl = new File("alice"); + fl.createNewFile(); + + } catch (IOException e) { + System.out.println("Working Directory = " + System.getProperty("user.dir")); + throw new RuntimeException("The file alice couldn't be created"); + } + + link.addMaterial("alice"); + System.out.println("dumping file..."); + link.sign(thiskey); + link.dump("somelink.link"); + } +} diff --git a/src/main/java/io/github/legacy/lib/JSONEncoder.java b/src/main/java/io/github/legacy/lib/JSONEncoder.java new file mode 100644 index 0000000..f61d34d --- /dev/null +++ b/src/main/java/io/github/legacy/lib/JSONEncoder.java @@ -0,0 +1,136 @@ +package io.github.legacy.lib; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import java.util.Map; +import java.util.TreeSet; + +/** + * JSONEncoder interface + * + *

Provides a Mixin that encodes the instance in a canonical JSON encoding that can be used for + * hashing. + */ +public interface JSONEncoder { + + /** + * Static helper method to canonicalize a json string by escaping the double quotes `"` and + * backslash `\` characters using the backslash escape character `\`, based on the + * securesystemslib implementation used in the in-toto reference implementation, see: + * https://github.com/secure-systems-lab/securesystemslib/blob/v0.11.2/securesystemslib/formats.py#L688 + * + * @param src Source string to be canonicalized + * @return A canonicalized String + */ + static String canonicalizeString(String src) { + String pattern = "([\\\\\"])"; + return String.format("\"%s\"", src.replaceAll(pattern, "\\\\$1")); + } + + /** + * Static helper method to recursively create a canonical json encoded string of the passed + * JsonElement, based on the securesystemslib implementation used in the in-toto reference + * implementation, see: + * https://github.com/secure-systems-lab/securesystemslib/blob/v0.11.2/securesystemslib/formats.py#L712 + * + * @param src Source JsonElement to be traversed and encoded + * @return A canonical json encoded string of the passed JsonElement. + */ + static String canonicalize(JsonElement src) { + String result = new String(); + if (src instanceof JsonArray) { + // Canonicalize each element of the array + result += "["; + JsonArray array = (JsonArray) src; + for (int i = 0; i < array.size(); i++) { + result += canonicalize(array.get(i)); + + if (i < array.size() - 1) { + result += ","; + } + } + result += "]"; + + } else if (src instanceof JsonObject) { + result += "{"; + + JsonObject obj = (JsonObject) src; + // Create an ordered list of the JsonObject's keys + TreeSet keys = new TreeSet<>(); + for (Map.Entry entry : obj.entrySet()) { + keys.add(entry.getKey()); + } + + // Canonicalize json object + int i = 0; + for (String key : keys) { + // NOTE: It is okay to only call `canonicalizeString` (instead + // of `canonicalize` like in the reference implementation) + // because we know that the keys are always strings. + result += canonicalizeString(key); + result += ":"; + result += canonicalize(obj.get(key)); + + if (i < keys.size() - 1) { + result += ","; + } + i++; + } + result += "}"; + + } else if (src instanceof JsonNull) { + result += "null"; + + } else if (src instanceof JsonPrimitive) { + JsonPrimitive primitive = (JsonPrimitive) src; + + if (primitive.isNumber()) { + result += String.format("%d", primitive.getAsInt()); + + } else if (primitive.isBoolean()) { + result += primitive.getAsString(); + + } else if (primitive.isString()) { + String decodedPrimitive = + new GsonBuilder().disableHtmlEscaping().create().fromJson(primitive, String.class); + result += canonicalizeString(decodedPrimitive); + } + } + return result; + } + + /** + * Method to create a canonical json encoded string of the calling object using the specification + * at http://wiki.laptop.org/go/Canonical_JSON. + * + *

Attributes with `null` values are encoded as {@code "": null}. + * + * @return A canonical json encoded string of the calling object. + */ + public default String JSONEncodeCanonical() { + return this.JSONEncodeCanonical(true); + } + + /** + * Method to create a canonical json encoded string of the calling object using the specification + * at http://wiki.laptop.org/go/Canonical_JSON + * + * @param serializeNulls if true attributes with null values are are encoded as {@code "": + * null} and omitted otherwise. + * @return A canonical json encoded string of the calling object. + */ + public default String JSONEncodeCanonical(boolean serializeNulls) { + GsonBuilder gsonBuilder = new GsonBuilder(); + if (serializeNulls) { + gsonBuilder.serializeNulls(); + } + Gson gson = gsonBuilder.disableHtmlEscaping().create(); + + return canonicalize(gson.toJsonTree(this)); + } +} diff --git a/src/main/java/io/github/legacy/lib/NumericJSONSerializer.java b/src/main/java/io/github/legacy/lib/NumericJSONSerializer.java new file mode 100644 index 0000000..406c20d --- /dev/null +++ b/src/main/java/io/github/legacy/lib/NumericJSONSerializer.java @@ -0,0 +1,26 @@ +package io.github.legacy.lib; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.lang.reflect.Type; + +/** + * A custom implementation of the gson JsonSerializer to convert numeric values to non-floating + * point numbers when serializing JSON. + * + *

This is required to handle generic data, such as `byproducts` or `environment`, where the type + * of the contained values is not declared. Gson treats any numeric value, with generic type in the + * target data structure as double. However, the in-toto reference implementation does not allow + * floating point numbers in JSON-formatted metadata. + */ +public class NumericJSONSerializer implements JsonSerializer { + public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) { + if (src == src.longValue()) { + return new JsonPrimitive(src.longValue()); + } + + return new JsonPrimitive(src); + } +} diff --git a/src/main/java/io/github/legacy/models/Artifact.java b/src/main/java/io/github/legacy/models/Artifact.java new file mode 100644 index 0000000..566b169 --- /dev/null +++ b/src/main/java/io/github/legacy/models/Artifact.java @@ -0,0 +1,85 @@ +package io.github.legacy.models; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.util.encoders.Hex; + +/** + * A class representing an Artifact (that is, a material or a product). + * + *

Used by the Link metdata type on the .add method. Can be also used to pre-populate the Link's + * artifact fields before instantiating a link. + */ +public class Artifact { + + /** A URI representing the location of the Artifact */ + private String URI; + + /** + * An ArtifactHash containing the hash of the contents of the file following the hash object + * definition in the in-toto specification. + */ + private ArtifactHash hash; + + /** + * Default constructor, uses a filename to collect and automatically hash the contents of the + * file. + * + *

This constructor does *not* perform file locking, so use with care. + * + * @param filename The filename (relative or absolute) of the Artifact to record (i.e., hash). + */ + public Artifact(String filename) { + + this.URI = filename; + this.hash = new ArtifactHash(); + this.hash.collect(filename); + } + + public String getURI() { + return this.URI; + } + + public ArtifactHash getArtifactHashes() { + return this.hash; + } + + /** + * Nested subclass representing a hash object compliant with the in-toto specification. + * {"sha256": "...", + * "sha512": "..." + * } + * + */ + public class ArtifactHash extends HashMap { + + private void collect(String filename) { + + FileInputStream file = null; + try { + file = new FileInputStream(filename); + } catch (FileNotFoundException e) { + throw new RuntimeException("The file " + filename + " couldn't be recorded"); + } + + SHA256Digest digest = new SHA256Digest(); + byte[] result = new byte[digest.getDigestSize()]; + int length; + try { + while ((length = file.read(result)) != -1) { + digest.update(result, 0, length); + } + } catch (IOException e) { + throw new RuntimeException("The file " + filename + " couldn't be recorded"); + } + digest.doFinal(result, 0); + + // We should be able to submit more hashes, but we will do sha256 + // only for the time being + this.put("sha256", Hex.toHexString(result)); + } + } +} diff --git a/src/main/java/io/github/legacy/models/Link.java b/src/main/java/io/github/legacy/models/Link.java new file mode 100644 index 0000000..8833566 --- /dev/null +++ b/src/main/java/io/github/legacy/models/Link.java @@ -0,0 +1,212 @@ +package io.github.legacy.models; + +import com.google.gson.Gson; +import io.github.legacy.keys.Signature; +import io.github.legacy.models.Artifact.ArtifactHash; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +/** Implementation of the in-toto Link metadata type. */ +public class Link extends Metablock { + /** default exclude pattern used to filter out redundant artifacts */ + transient String defaultExcludePattern = "**.{git,link}**"; + + /** + * Constuctor method used to populate the signable payload + * + * @param materials a HashMap keyed by artifact URI's and with hash objects as values represeting + * the artifacts used as materials in this step + * @param products a HashMap keyed by artifact URI's and with hash objects as values representing + * the artifacts created as products in this step. + * @param name The name of this step + * @param environment a HashMap containing any additional, relevant environment information. + * @param command the Argv array of the command executed. + * @param byproducts A HashMap containing the byproduct triplet stdin/stdout/retval. + * @see io.github.legacy.models.Artifact + */ + public Link( + HashMap materials, + HashMap products, + String name, + HashMap environment, + ArrayList command, + HashMap byproducts) { + super(null, null); + LinkSignable signable = + new LinkSignable(materials, products, name, environment, command, byproducts); + this.signed = signable; + } + + /** + * convenience method to save the Link metdata file using the name defined by the specification + */ + public void dump() { + dump(getFullName()); + } + + /** + * get full link name, including keyid bytes in the form of + * + *

{@literal ..link } + * + *

This method will always use the keyid of the first signature in the metadata. + * + * @return a string containing this name or null if no signatures are present + */ + public String getFullName() { + if (this.signatures == null || this.signatures.isEmpty()) return getName() + ".UNSIGNED.link"; + + String keyId = ((Signature) this.signatures.get(0)).getKeyId(); + return getName() + "." + keyId.substring(0, 8) + ".link"; + } + + /** + * exclude artifacts matching the pattern + * + * @param materials the HashMap of artifacts + * @param pattern the exclude pattern + */ + public HashMap excludeArtifactsByPattern( + HashMap materials, String pattern) { + String patternString; + HashMap filtered_artifacts; + + if (pattern != null && pattern.length() != 0) { + patternString = pattern; + } else { + patternString = defaultExcludePattern; + } + + FileSystem fileSystem = FileSystems.getDefault(); + + PathMatcher pathMatcher = fileSystem.getPathMatcher("glob:" + patternString); + + filtered_artifacts = materials; + + Iterator> iterator = + filtered_artifacts.entrySet().iterator(); + + while (iterator.hasNext()) { + + HashMap.Entry entry = iterator.next(); + + if (pathMatcher.matches(Paths.get(entry.getKey()))) { + iterator.remove(); + } + } + + return filtered_artifacts; + } + + public void setMaterials(HashMap materials, String pattern) { + ((LinkSignable) this.signed).materials = excludeArtifactsByPattern(materials, pattern); + } + + public void setMaterials(HashMap materials) { + setMaterials(materials, null); + } + + public HashMap getMaterials() { + return ((LinkSignable) this.signed).materials; + } + + public void setProducts(HashMap products, String pattern) { + ((LinkSignable) this.signed).products = excludeArtifactsByPattern(products, pattern); + } + + public void setProducts(HashMap products) { + setProducts(products, null); + } + + public HashMap getProducts() { + return ((LinkSignable) this.signed).products; + } + + public void setName(String name) { + ((LinkSignable) this.signed).name = name; + } + + public String getName() { + return ((LinkSignable) this.signed).name; + } + + public void setEnvironment(HashMap environment) { + ((LinkSignable) this.signed).environment = environment; + } + + public HashMap getEnvironment() { + return ((LinkSignable) this.signed).environment; + } + + public void setCommand(ArrayList command) { + ((LinkSignable) this.signed).command = command; + } + + public ArrayList getCommand() { + return ((LinkSignable) this.signed).command; + } + + public void setByproducts(HashMap byproducts) { + ((LinkSignable) this.signed).byproducts = byproducts; + } + + public HashMap getByproducts() { + return ((LinkSignable) this.signed).byproducts; + } + + /** + * Convenience method to indicate this link to track an artifact as material + * + * @param filePath the path of the material to track + * @param pattern The exclude pattern + */ + public void addMaterial(String filePath, String pattern) { + + Artifact a = new Artifact(filePath); + + HashMap material = new HashMap(); + + material.put(a.getURI(), a.getArtifactHashes()); + + excludeArtifactsByPattern(material, pattern) + .forEach(((LinkSignable) this.signed).materials::putIfAbsent); + } + + public void addMaterial(String filePath) { + + addMaterial(filePath, null); + } + + /** + * Convenience method to indicate this link to track an artifact as product + * + * @param filePath the path of the product to track + * @param pattern the exclude pattern + */ + public void addProduct(String filePath, String pattern) { + + Artifact a = new Artifact(filePath); + + HashMap product = new HashMap(); + + product.put(a.getURI(), a.getArtifactHashes()); + + excludeArtifactsByPattern(product, pattern) + .forEach(((LinkSignable) this.signed).products::putIfAbsent); + } + + public void addProduct(String filePath) { + + addProduct(filePath, null); + } + + public static Link read(String jsonString) { + Gson gson = new Gson(); + return gson.fromJson(jsonString, Link.class); + } +} diff --git a/src/main/java/io/github/legacy/models/LinkSignable.java b/src/main/java/io/github/legacy/models/LinkSignable.java new file mode 100644 index 0000000..ed8001b --- /dev/null +++ b/src/main/java/io/github/legacy/models/LinkSignable.java @@ -0,0 +1,61 @@ +/* + * package-private class representing the signable payload of the in-toto link + * metadata. + */ +package io.github.legacy.models; + +import io.github.legacy.models.Artifact.ArtifactHash; +import java.util.ArrayList; +import java.util.HashMap; + +class LinkSignable extends Signable { + + HashMap materials; + HashMap products; + // NOTE: Caution when dealing with numeric values! + // Since gson does not know the type of the target, it will + // store any numeric value as `Double`, e.g.: + // {"byproducts": {"return-value": 1}} + // is parsed as + // {"byproducts": {"return-value": 1.0}} + HashMap byproducts; + HashMap environment; + ArrayList command; + String name; + + LinkSignable( + HashMap materials, + HashMap products, + String name, + HashMap environment, + ArrayList command, + HashMap byproducts) { + + super(); + + if (materials == null) materials = new HashMap(); + + if (products == null) products = new HashMap(); + + // FIXME: probably warn about this would be a good idea + if (name == null) name = "step"; + + if (environment == null) environment = new HashMap(); + + if (command == null) command = new ArrayList(); + + if (byproducts == null) byproducts = new HashMap(); + + this.materials = materials; + this.products = products; + this.name = name; + this.environment = environment; + this.command = command; + this.byproducts = byproducts; + } + + @Override + public String getType() { + return "link"; + } +} diff --git a/src/main/java/io/github/legacy/models/Metablock.java b/src/main/java/io/github/legacy/models/Metablock.java new file mode 100644 index 0000000..335645c --- /dev/null +++ b/src/main/java/io/github/legacy/models/Metablock.java @@ -0,0 +1,140 @@ +package io.github.legacy.models; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import io.github.legacy.keys.Key; +import io.github.legacy.keys.Signature; +import io.github.legacy.lib.NumericJSONSerializer; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.util.encoders.Hex; + +/** + * A metablock class that contains two elements + * + *

- A signed field, with the signable portion of a piece of metadata. - A signatures field, a + * list of the signatures on this metadata. + */ +abstract class Metablock { + S signed; + ArrayList signatures; + + /** + * Base constructor. + * + *

Ensures that, at the least, there is an empty list of signatures. + */ + public Metablock(S signed, ArrayList signatures) { + this.signed = signed; + + if (signatures == null) signatures = new ArrayList(); + this.signatures = signatures; + } + + /** + * Serialize the current metadata into a JSON file + * + * @param filename The filename to which the metadata will be dumped. + */ + public void dump(String filename) { + FileWriter writer = null; + + try { + writer = new FileWriter(filename); + dump(writer); + writer.close(); + } catch (IOException e) { + throw new RuntimeException("Couldn't serialize object: " + e.toString()); + } + } + + /** + * Serialize the current metadata into a writer + * + * @param writer the target writer + * @throws java.io.IOException if unable to write to the passed writer. + */ + public void dump(Writer writer) throws IOException { + + writer.write(dumpString()); + writer.flush(); + } + + /** + * Serialize the current metadata to a string + * + * @return a JSON string representation of the metadata instance + */ + public String dumpString() { + Gson gson = + new GsonBuilder() + .serializeNulls() + // Use custom serializer to enforce non-floating point numbers + .registerTypeAdapter(Double.class, new NumericJSONSerializer()) + .setPrettyPrinting() + .create(); + return gson.toJson(this); + } + + /** + * Signs the current signed payload using the key provided + * + * @param privateKey the key used to sign the payload. + */ + public void sign(Key privateKey) { + + String sig; + String keyid; + byte[] payload; + AsymmetricKeyParameter keyParameters; + + try { + keyParameters = privateKey.getPrivate(); + if (keyParameters == null || keyParameters.isPrivate() == false) { + System.out.println("Can't sign with a public key!"); + return; + } + } catch (IOException e) { + System.out.println("Can't sign with this key!"); + return; + } + + keyid = privateKey.computeKeyId(); + payload = this.signed.JSONEncodeCanonical().getBytes(); + + Signer signer = privateKey.getSigner(); + signer.init(true, keyParameters); + signer.update(payload, 0, payload.length); + try { + sig = Hex.toHexString(signer.generateSignature()); + } catch (CryptoException e) { + System.out.println("Coudln't sign payload!"); + return; + } + + this.signatures.add(new Signature(keyid, sig)); + } + + /** + * Public shortcut to call JSONEncodeCanonical on the signed field of this metablock. + * + * @param serializeNulls if nulls should be included or not when encoding + * @return a JSON string representation of this obj + */ + public String getCanonicalJSON(boolean serializeNulls) { + return this.signed.JSONEncodeCanonical(serializeNulls); + } + + public S getSigned() { + return this.signed; + } + + public ArrayList getSignatures() { + return this.signatures; + } +} diff --git a/src/main/java/io/github/legacy/models/Signable.java b/src/main/java/io/github/legacy/models/Signable.java new file mode 100644 index 0000000..f8e1d6c --- /dev/null +++ b/src/main/java/io/github/legacy/models/Signable.java @@ -0,0 +1,18 @@ +package io.github.legacy.models; + +import io.github.legacy.lib.JSONEncoder; + +/** + * A signable class is an abstract superclass that provides a representation method to prepare for + * signing + */ +abstract class Signable implements JSONEncoder { + /** Subclasses must define the _type field appropriately for serialization */ + protected String _type; + + public Signable() { + this._type = getType(); + } + + public abstract String getType(); +} diff --git a/src/test/java/io/github/in_toto/TestJSONCanonical.java b/src/test/java/io/github/in_toto/TestJSONCanonical.java deleted file mode 100644 index 3c369f5..0000000 --- a/src/test/java/io/github/in_toto/TestJSONCanonical.java +++ /dev/null @@ -1,35 +0,0 @@ -import io.github.in_toto.models.Link; -import java.io.*; -import java.nio.file.*; -import org.bouncycastle.util.encoders.Hex; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; - -class TestJSONCanonical { - @Test - public void testCanonicalJSONEdgeCases () throws IOException { - // from securesystemslib.formats import encode_canonical - // from in_toto.models.metadata import Metablock - // linkMb = Metablock.load("src/test/resources/testvalues.link") - // encode_canonical( - // linkMb.signed.signable_dict).encode("UTF-8").hex() - String referenceCanonicalLinkHex = "7b225f74797065223a226c696e6b222" + - "c22627970726f6475637473223a7b7d2c22636f6d6d616e64223a5b5d2" + - "c22656e7669726f6e6d656e74223a7b2261223a22575446222c2262223" + - "a747275652c2263223a66616c73652c2264223a6e756c6c2c2265223a3" + - "12c2266223a221befbfbf465c5c6e5c22227d2c226d6174657269616c7" + - "3223a7b7d2c226e616d65223a2274657374222c2270726f64756374732" + - "23a7b7d7d"; - - // Load test link with special values (edge cases) in opaque - // environment field - String linkString = new String(Files.readAllBytes( - Paths.get("src/test/resources/testvalues.link"))); - Link link = Link.read(linkString); - - // Assert that Java's canonical json representation of the link is - // equal to reference implementation's canonical json representation - assertEquals(Hex.toHexString(link.getCanonicalJSON(true).getBytes()), - referenceCanonicalLinkHex); - } -} diff --git a/src/test/java/io/github/in_toto/keys/RSAKeyTest.java b/src/test/java/io/github/in_toto/keys/RSAKeyTest.java deleted file mode 100644 index 1fe2ef4..0000000 --- a/src/test/java/io/github/in_toto/keys/RSAKeyTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package io.github.in_toto.keys; - -import io.github.in_toto.keys.RSAKey; - -import java.io.File; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport; -import org.junit.rules.TemporaryFolder; -import org.junit.rules.ExpectedException; -import org.junit.Rule; - -/** - * RSAKey-specific tests - */ -@EnableRuleMigrationSupport -class RSAKeyTest -{ - - private static final String private_key_path = "src/test/resources/somekey.pem"; - private static final String public_key_path = "src/test/resources/someotherkey.pem"; - - /** - * test pem loading methods; - */ - @Test - public void testPemLoading() - { - // load a private key and make sure the key is private - RSAKey testKey = RSAKey.read(private_key_path); - try { - assertTrue(testKey.getPrivate().isPrivate()); - } catch (IOException e) { - throw new RuntimeException(e); - } - - // test loading a public key and make sure the key is not marked as - // private - testKey = RSAKey.read(public_key_path); - try { - assertTrue(testKey.getPrivate() == null); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - /** - * test keyid computation - */ - @Test - public void testGetKeyID() - { - final String targetKeyID = "0b70eafb5d4d7c0f36a21442fcf066903d09cf5050ad0c8443b18f1f232c7dd7"; - - // load a privatekey pem and compare the keyid - RSAKey testKey = RSAKey.read(private_key_path); - assertTrue(targetKeyID.equals(testKey.computeKeyId())); - - // load a public key pem and compare the keyid - testKey = RSAKey.read(public_key_path); - assertTrue(targetKeyID.equals(testKey.computeKeyId())); - } - - /** - * test public key serialization - * - * Test methods to serialize a public key - * FIXME: will wait for junit-pioneer to add TempDirectory for this test - */ - - - @Rule public ExpectedException thrown = ExpectedException.none(); - - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @Test - @DisplayName("Test RSAKey ComputeKeyID()") - public void testComputeKeyID() throws IOException { - - Key thiskey = RSAKey.read("src/test/resources/someotherkey.pem"); - File keyfile = temporaryFolder.newFile("key.pem"); - String keypath = keyfile.getAbsolutePath(); - thiskey.write(keypath); - Key key = RSAKey.read(keypath); - assertEquals(key.computeKeyId(), thiskey.computeKeyId()); - keyfile.delete(); - - Key thiskey2 = RSAKey.read("src/test/resources/somekey.pem"); - File keyfile2 = temporaryFolder.newFile("key2.pem"); - String keypath2 = keyfile2.getAbsolutePath(); - thiskey2.write(keypath2); - Key key2 = RSAKey.read(keypath2); - assertEquals(key2.computeKeyId(), "0b70eafb5d4d7c0f36a21442fcf066903d09cf5050ad0c8443b18f1f232c7dd7"); - keyfile2.delete(); - } -} diff --git a/src/test/java/io/github/in_toto/lib/AppTest.java b/src/test/java/io/github/in_toto/lib/AppTest.java deleted file mode 100644 index 840fe1d..0000000 --- a/src/test/java/io/github/in_toto/lib/AppTest.java +++ /dev/null @@ -1 +0,0 @@ -package io.github.in_toto.lib; diff --git a/src/test/java/io/github/in_toto/models/LinkTest.java b/src/test/java/io/github/in_toto/models/LinkTest.java deleted file mode 100644 index 5a6fd58..0000000 --- a/src/test/java/io/github/in_toto/models/LinkTest.java +++ /dev/null @@ -1,451 +0,0 @@ -package io.github.in_toto.models; - -import io.github.in_toto.models.Artifact.ArtifactHash; -import io.github.in_toto.models.Link; -import io.github.in_toto.keys.RSAKey; -import io.github.in_toto.keys.Key; - -import java.util.ArrayList; -import java.util.Map; -import java.util.HashMap; - -import java.io.File; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport; -import org.junit.rules.TemporaryFolder; -import org.junit.rules.ExpectedException; -import org.junit.Rule; - -/** - * Link-specific tests - */ - -@DisplayName("Link-specific tests") -@EnableRuleMigrationSupport -@TestInstance(Lifecycle.PER_CLASS) -class LinkTest -{ - private Link link = new Link(null, null, "test", null, null, null); - private Key key = RSAKey.read("src/test/resources/somekey.pem"); - - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - @DisplayName("Test Link Constructor") - public void testLinkContructorEqual() - { - // test a link object - - assertEquals("test",link.getName()); - assertNotEquals(null,link.getName()); - } - - @Test - @DisplayName("Test Valid Link Object Types") - public void testValidObject() - { - // test link data types - - assertTrue(link instanceof Link); - assertTrue(link.getByproducts() instanceof HashMap); - assertTrue(link.getEnvironment() instanceof HashMap); - assertTrue(link.getName() instanceof String); - assertTrue(link.getProducts() instanceof HashMap); - assertTrue(link.getMaterials() instanceof HashMap); - assertTrue(link.getCommand() instanceof ArrayList); - } - - @Test - @DisplayName("Validate Link addMaterials") - public void testValidMaterial() throws IOException - { - File file = temporaryFolder.newFile("alice"); - String path = file.getAbsolutePath(); - link.addMaterial(path); - - Map material = link.getMaterials(); - Map.Entry entry = material.entrySet().iterator().next(); - assertEquals(entry.getKey(), path); - - file.delete(); - } - - @Test - @DisplayName("Validate Link addProducts") - public void testValidProduct() throws IOException - { - File file = temporaryFolder.newFile("bob"); - String path = file.getAbsolutePath(); - - link.addProduct(path); - - Map product = link.getProducts(); - Map.Entry entry = product.entrySet().iterator().next(); - assertEquals(entry.getKey(), path); - - file.delete(); - } - - @Test - @DisplayName("Validate Link setByproducts") - public void testValidByproduct() - { - HashMap byproduct = new HashMap<>(); - byproduct.put("stdin",""); - link.setByproducts(byproduct); - assertEquals(byproduct, link.getByproducts()); - } - - @Test - @DisplayName("Validate Link setEnvironments") - public void testValidEnvironment() - { - HashMap environment = new HashMap<>(); - environment.put("variables", ""); - link.setEnvironment(environment); - assertEquals(environment, link.getEnvironment()); - } - - @Test - @DisplayName("Validate Link setCommand") - public void testValidCommand() - { - ArrayList command = new ArrayList(); - command.add(""); - link.setCommand(command); - assertEquals(command.get(0), link.getCommand().get(0)); - } - - @AfterAll - @Test - @DisplayName("Validate Link sign & dump") - public void testLinkSignDump() - { - link.sign(key); - link.dump("dump.link"); - File fl = new File("dump.link"); - assertTrue(fl.exists()); - fl.delete(); - } - - @Test - @DisplayName("Validate link serialization and de-serialization") - public void testLinkDeSerialization() - { - - Link testLink = new Link(null, null, "sometestname", - null, null, null); - - String jsonString = testLink.dumpString(); - Link newLink = Link.read(jsonString); - - assertTrue(newLink.getName() != null); - assertEquals(testLink.getName(), newLink.getName()); - } - - @Test - @DisplayName("Test Apply Exclude Patterns") - public void testApplyExcludePatterns() throws IOException - { - Link testLink = new Link(null, null, "sometestname", - null, null, null); - - File file1 = temporaryFolder.newFile("foo"); - File file2 = temporaryFolder.newFile("bar"); - File file3 = temporaryFolder.newFile("baz"); - - String path1 = file1.getAbsolutePath(); - String path2 = file2.getAbsolutePath(); - String path3 = file3.getAbsolutePath(); - - String pattern = "**{foo,bar}"; - - testLink.addProduct(path1, pattern); - testLink.addProduct(path2, pattern); - testLink.addProduct(path3, pattern); - - testLink.addMaterial(path1, pattern); - testLink.addMaterial(path2, pattern); - testLink.addMaterial(path3, pattern); - - Map product = testLink.getProducts(); - assertEquals(product.size(), 1); - - Map.Entry entry1 = product.entrySet().iterator().next(); - assertEquals(entry1.getKey(), path3); - - Map material = testLink.getMaterials(); - assertEquals(material.size(), 1); - - Map.Entry entry2 = material.entrySet().iterator().next(); - assertEquals(entry2.getKey(), path3); - - file1.delete(); - file2.delete(); - file3.delete(); - } - - @Test - @DisplayName("Test Apply Exclude Default Patterns") - public void testApplyExcludeDefaultPatterns() throws IOException - { - Link testLink = new Link(null, null, "sometestname", - null, null, null); - - File file1 = temporaryFolder.newFile("foo.link"); - File file2 = temporaryFolder.newFile("bar"); - File file3 = temporaryFolder.newFolder(".git"); - File file4 = temporaryFolder.newFile(file3.getName() + "/baz"); - - String path1 = file1.getAbsolutePath(); - String path2 = file2.getAbsolutePath(); - String path4 = file4.getAbsolutePath(); - - testLink.addProduct(path1); - testLink.addProduct(path2); - testLink.addProduct(path4); - - testLink.addMaterial(path1); - testLink.addMaterial(path2); - testLink.addMaterial(path4); - - Map product = testLink.getProducts(); - assertEquals(product.size(), 1); - - Map.Entry entry1 = product.entrySet().iterator().next(); - assertEquals(entry1.getKey(), path2); - - Map material = testLink.getMaterials(); - assertEquals(material.size(), 1); - - Map.Entry entry2 = material.entrySet().iterator().next(); - assertEquals(entry2.getKey(), path2); - - file1.delete(); - file2.delete(); - file3.delete(); - } - - @Test - @DisplayName("Test Apply Exclude All") - public void testApplyExcludeAll() throws IOException - { - Link testLink = new Link(null, null, "sometestname", - null, null, null); - - File file1 = temporaryFolder.newFile("foo"); - File file2 = temporaryFolder.newFile("bar"); - File file3 = temporaryFolder.newFile("baz"); - - String path1 = file1.getAbsolutePath(); - String path2 = file2.getAbsolutePath(); - String path3 = file3.getAbsolutePath(); - - String pattern = "**"; - - testLink.addProduct(path1, pattern); - testLink.addProduct(path2, pattern); - testLink.addProduct(path3, pattern); - - testLink.addMaterial(path1, pattern); - testLink.addMaterial(path2, pattern); - testLink.addMaterial(path3, pattern); - - Map product = testLink.getProducts(); - assertEquals(product.size(), 0); - assertFalse(product.entrySet().iterator().hasNext()); - - Map material = testLink.getMaterials(); - assertEquals(material.size(), 0); - assertFalse(material.entrySet().iterator().hasNext()); - - file1.delete(); - file2.delete(); - file3.delete(); - } - - @Test - @DisplayName("Test Apply Exclude Multiple Star") - public void testApplyExcludeMultipleStar() throws IOException - { - Link testLink = new Link(null, null, "sometestname", - null, null, null); - - File file1 = temporaryFolder.newFile("foo"); - File file2 = temporaryFolder.newFile("bar"); - File file3 = temporaryFolder.newFile("baz"); - - String path1 = file1.getAbsolutePath(); - String path2 = file2.getAbsolutePath(); - String path3 = file3.getAbsolutePath(); - - String pattern = "**a**"; - - testLink.addProduct(path1, pattern); - testLink.addProduct(path2, pattern); - testLink.addProduct(path3, pattern); - - testLink.addMaterial(path1, pattern); - testLink.addMaterial(path2, pattern); - testLink.addMaterial(path3, pattern); - - Map product = testLink.getProducts(); - assertEquals(product.size(), 1); - - Map.Entry entry1 = product.entrySet().iterator().next(); - assertEquals(entry1.getKey(), path1); - - Map material = testLink.getMaterials(); - assertEquals(material.size(), 1); - - Map.Entry entry2 = material.entrySet().iterator().next(); - assertEquals(entry2.getKey(), path1); - - file1.delete(); - file2.delete(); - file3.delete(); - } - - @Test - @DisplayName("Test Apply Exclude Question Mark") - public void testApplyExcludeQuestionMark() throws IOException - { - Link testLink = new Link(null, null, "sometestname", - null, null, null); - - File file1 = temporaryFolder.newFile("foo"); - File file2 = temporaryFolder.newFile("bazfoo"); - File file3 = temporaryFolder.newFile("barfoo"); - - String path1 = file1.getAbsolutePath(); - String path2 = file2.getAbsolutePath(); - String path3 = file3.getAbsolutePath(); - - String pattern = "**ba?foo"; - - testLink.addProduct(path1, pattern); - testLink.addProduct(path2, pattern); - testLink.addProduct(path3, pattern); - - testLink.addMaterial(path1, pattern); - testLink.addMaterial(path2, pattern); - testLink.addMaterial(path3, pattern); - - Map product = testLink.getProducts(); - assertEquals(product.size(), 1); - - Map.Entry entry1 = product.entrySet().iterator().next(); - assertEquals(entry1.getKey(), path1); - - Map material = testLink.getProducts(); - assertEquals(product.size(), 1); - - Map.Entry entry2 = material.entrySet().iterator().next(); - assertEquals(entry2.getKey(), path1); - - file1.delete(); - file2.delete(); - file3.delete(); - } - - @Test - @DisplayName("Test Apply Exclude Sequence") - public void testApplyExcludeSeq() throws IOException - { - Link testLink = new Link(null, null, "sometestname", - null, null, null); - - File file1 = temporaryFolder.newFile("baxfoo"); - File file2 = temporaryFolder.newFile("bazfoo"); - File file3 = temporaryFolder.newFile("barfoo"); - - String path1 = file1.getAbsolutePath(); - String path2 = file2.getAbsolutePath(); - String path3 = file3.getAbsolutePath(); - - String pattern = "**ba[xz]foo"; - - testLink.addProduct(path1, pattern); - testLink.addProduct(path2, pattern); - testLink.addProduct(path3, pattern); - - testLink.addMaterial(path1, pattern); - testLink.addMaterial(path2, pattern); - testLink.addMaterial(path3, pattern); - - Map product = testLink.getProducts(); - assertEquals(product.size(), 1); - - Map.Entry entry1 = product.entrySet().iterator().next(); - assertEquals(entry1.getKey(), path3); - - Map material = testLink.getMaterials(); - assertEquals(material.size(), 1); - - Map.Entry entry2 = material.entrySet().iterator().next(); - assertEquals(entry2.getKey(), path3); - - file1.delete(); - file2.delete(); - file3.delete(); - } - - - @Test - @DisplayName("Test Apply Exclude Negate Sequence") - public void testApplyExcludeNegSeq() throws IOException - { - Link testLink = new Link(null, null, "sometestname", - null, null, null); - - File file1 = temporaryFolder.newFile("baxfoo"); - File file2 = temporaryFolder.newFile("bazfoo"); - File file3 = temporaryFolder.newFile("barfoo"); - - String path1 = file1.getAbsolutePath(); - String path2 = file2.getAbsolutePath(); - String path3 = file3.getAbsolutePath(); - - String pattern = "**ba[!r]foo"; - - testLink.addProduct(path1, pattern); - testLink.addProduct(path2, pattern); - testLink.addProduct(path3, pattern); - - testLink.addMaterial(path1, pattern); - testLink.addMaterial(path2, pattern); - testLink.addMaterial(path3, pattern); - - Map product = testLink.getProducts(); - assertEquals(product.size(), 1); - - Map.Entry entry1 = product.entrySet().iterator().next(); - assertEquals(entry1.getKey(), path3); - - Map material = testLink.getMaterials(); - assertEquals(product.size(), 1); - - Map.Entry entry2 = material.entrySet().iterator().next(); - assertEquals(entry2.getKey(), path3); - - file1.delete(); - file2.delete(); - file3.delete(); - } - -} diff --git a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java new file mode 100644 index 0000000..927e34a --- /dev/null +++ b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java @@ -0,0 +1,289 @@ +package io.github.intoto.helpers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.exceptions.InvalidModelException; +import io.github.intoto.models.DigestSetAlgorithmType; +import io.github.intoto.models.Predicate; +import io.github.intoto.models.PredicateType; +import io.github.intoto.models.Statement; +import io.github.intoto.models.StatementType; +import io.github.intoto.models.Subject; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class IntotoHelperTest { + + @Test + @DisplayName("Can transform basic Statement to JSON") + public void + validateAndTransformToJson_shouldTransformStatementToJsonString_whenStatementIsCorrect() + throws JsonProcessingException, InvalidModelException { + Subject subject = new Subject(); + subject.setName("curl-7.72.0.tar.bz2"); + subject.setDigest( + Map.of( + DigestSetAlgorithmType.SHA256.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(predicate); + + String jsonStatement = IntotoHelper.validateAndTransformToJson(statement); + System.out.println(jsonStatement); + assertNotNull(jsonStatement); + String SIMPLE_JSON_STATEMENT = + "{\"_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\":{}}"; + assertEquals(SIMPLE_JSON_STATEMENT, jsonStatement); + } + + @Test + @DisplayName("Testing Statement Type can't be null") + public void validateAndTransformToJson_shouldThrowException_whenStatementTypeIsMissing() { + Subject subject = new Subject(); + subject.setName("curl-7.72.0.tar.bz2"); + subject.setDigest( + Map.of( + DigestSetAlgorithmType.SHA256.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + + Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + + Statement statement = new Statement(); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(predicate); + + InvalidModelException thrown = + assertThrows( + InvalidModelException.class, + () -> { + IntotoHelper.validateAndTransformToJson(statement); + }); + + assertEquals("_type may not be null", thrown.getMessage()); + } + + @Test + @DisplayName("Testing Statement Subject can't be null") + public void toJson_shouldThrowException_whenStatementSubjectIsNull() { + Subject subject = new Subject(); + subject.setName("curl-7.72.0.tar.bz2"); + subject.setDigest( + Map.of( + DigestSetAlgorithmType.SHA256.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(predicate); + + InvalidModelException thrown = + assertThrows( + InvalidModelException.class, + () -> { + IntotoHelper.validateAndTransformToJson(statement); + }); + + assertEquals("subject may not be null or empty", thrown.getMessage()); + } + + @Test + @DisplayName("Testing Statement Subject can't be empty") + public void toJson_shouldThrowException_whenStatementSubjectIsEmpty() { + Subject subject = new Subject(); + subject.setName("curl-7.72.0.tar.bz2"); + subject.setDigest( + Map.of( + DigestSetAlgorithmType.SHA256.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(Collections.emptyList()); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(predicate); + + InvalidModelException thrown = + assertThrows( + InvalidModelException.class, + () -> { + IntotoHelper.validateAndTransformToJson(statement); + }); + + 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.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(predicate); + + InvalidModelException thrown = + assertThrows( + InvalidModelException.class, + () -> { + IntotoHelper.validateAndTransformToJson(statement); + }); + + 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.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(predicate); + + InvalidModelException thrown = + assertThrows( + InvalidModelException.class, + () -> { + IntotoHelper.validateAndTransformToJson(statement); + }); + + 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 = new Predicate(); // Let's pretend this is an SLSA predicate + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(predicate); + + InvalidModelException thrown = + assertThrows( + InvalidModelException.class, + () -> { + IntotoHelper.validateAndTransformToJson(statement); + }); + + 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 = new Predicate(); // Let's pretend this is an SLSA predicate + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(predicate); + + InvalidModelException thrown = + assertThrows( + InvalidModelException.class, + () -> { + IntotoHelper.validateAndTransformToJson(statement); + }); + + 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.toString(), "")); + Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(predicate); + + InvalidModelException thrown = + assertThrows( + InvalidModelException.class, + () -> { + IntotoHelper.validateAndTransformToJson(statement); + }); + + assertEquals("digest value contents can be empty strings", thrown.getMessage()); + } + + @Test + @DisplayName("Testing Subjects uniqueness") + public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreNotUnique() + throws JsonProcessingException, InvalidModelException { + Subject subject = new Subject(); + subject.setName("curl-7.72.0.tar.bz2"); + subject.setDigest( + Map.of( + DigestSetAlgorithmType.SHA256.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + + Subject subject2 = new Subject(); + subject2.setName("curl-7.72.0.tar.bz2"); + subject2.setDigest( + Map.of( + DigestSetAlgorithmType.SHA256.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + + Subject subject3 = new Subject(); + subject3.setName("curl-7.72.0.tar.bz2"); + subject3.setDigest( + Map.of( + DigestSetAlgorithmType.SHA256.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject, subject2, subject3)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(predicate); + + InvalidModelException thrown = + assertThrows( + InvalidModelException.class, + () -> { + IntotoHelper.validateAndTransformToJson(statement); + }); + + assertEquals("subjects must be unique", thrown.getMessage()); + } +} diff --git a/src/test/java/io/github/legacy/TestJSONCanonical.java b/src/test/java/io/github/legacy/TestJSONCanonical.java new file mode 100644 index 0000000..c4176d0 --- /dev/null +++ b/src/test/java/io/github/legacy/TestJSONCanonical.java @@ -0,0 +1,40 @@ +package io.github.legacy; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.github.legacy.models.Link; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import org.bouncycastle.util.encoders.Hex; +import org.junit.jupiter.api.Test; + +class TestJSONCanonical { + @Test + public void testCanonicalJSONEdgeCases() throws IOException { + // from securesystemslib.formats import encode_canonical + // from in_toto.models.metadata import Metablock + // linkMb = Metablock.load("src/test/resources/testvalues.link") + // encode_canonical( + // linkMb.signed.signable_dict).encode("UTF-8").hex() + String referenceCanonicalLinkHex = + "7b225f74797065223a226c696e6b222" + + "c22627970726f6475637473223a7b7d2c22636f6d6d616e64223a5b5d2" + + "c22656e7669726f6e6d656e74223a7b2261223a22575446222c2262223" + + "a747275652c2263223a66616c73652c2264223a6e756c6c2c2265223a3" + + "12c2266223a221befbfbf465c5c6e5c22227d2c226d6174657269616c7" + + "3223a7b7d2c226e616d65223a2274657374222c2270726f64756374732" + + "23a7b7d7d"; + + // Load test link with special values (edge cases) in opaque + // environment field + String linkString = + new String(Files.readAllBytes(Paths.get("src/test/resources/testvalues.link"))); + Link link = Link.read(linkString); + + // Assert that Java's canonical json representation of the link is + // equal to reference implementation's canonical json representation + assertEquals( + Hex.toHexString(link.getCanonicalJSON(true).getBytes()), referenceCanonicalLinkHex); + } +} diff --git a/src/test/java/io/github/legacy/keys/RSAKeyTest.java b/src/test/java/io/github/legacy/keys/RSAKeyTest.java new file mode 100644 index 0000000..eb0871d --- /dev/null +++ b/src/test/java/io/github/legacy/keys/RSAKeyTest.java @@ -0,0 +1,88 @@ +package io.github.legacy.keys; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import org.junit.Rule; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; + +/** RSAKey-specific tests */ +@EnableRuleMigrationSupport +class RSAKeyTest { + + private static final String private_key_path = "src/test/resources/somekey.pem"; + private static final String public_key_path = "src/test/resources/someotherkey.pem"; + + /** test pem loading methods; */ + @Test + public void testPemLoading() { + // load a private key and make sure the key is private + RSAKey testKey = RSAKey.read(private_key_path); + try { + assertTrue(testKey.getPrivate().isPrivate()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // test loading a public key and make sure the key is not marked as + // private + testKey = RSAKey.read(public_key_path); + try { + assertTrue(testKey.getPrivate() == null); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** test keyid computation */ + @Test + public void testGetKeyID() { + final String targetKeyID = "0b70eafb5d4d7c0f36a21442fcf066903d09cf5050ad0c8443b18f1f232c7dd7"; + + // load a privatekey pem and compare the keyid + RSAKey testKey = RSAKey.read(private_key_path); + assertTrue(targetKeyID.equals(testKey.computeKeyId())); + + // load a public key pem and compare the keyid + testKey = RSAKey.read(public_key_path); + assertTrue(targetKeyID.equals(testKey.computeKeyId())); + } + + /** + * test public key serialization + * + *

Test methods to serialize a public key FIXME: will wait for junit-pioneer to add + * TempDirectory for this test + */ + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + @DisplayName("Test RSAKey ComputeKeyID()") + public void testComputeKeyID() throws IOException { + + Key thiskey = RSAKey.read("src/test/resources/someotherkey.pem"); + File keyfile = temporaryFolder.newFile("key.pem"); + String keypath = keyfile.getAbsolutePath(); + thiskey.write(keypath); + Key key = RSAKey.read(keypath); + assertEquals(key.computeKeyId(), thiskey.computeKeyId()); + keyfile.delete(); + + Key thiskey2 = RSAKey.read("src/test/resources/somekey.pem"); + File keyfile2 = temporaryFolder.newFile("key2.pem"); + String keypath2 = keyfile2.getAbsolutePath(); + thiskey2.write(keypath2); + Key key2 = RSAKey.read(keypath2); + assertEquals( + key2.computeKeyId(), "0b70eafb5d4d7c0f36a21442fcf066903d09cf5050ad0c8443b18f1f232c7dd7"); + keyfile2.delete(); + } +} diff --git a/src/test/java/io/github/legacy/lib/AppTest.java b/src/test/java/io/github/legacy/lib/AppTest.java new file mode 100644 index 0000000..46e7d7e --- /dev/null +++ b/src/test/java/io/github/legacy/lib/AppTest.java @@ -0,0 +1 @@ +package io.github.legacy.lib; diff --git a/src/test/java/io/github/legacy/models/LinkTest.java b/src/test/java/io/github/legacy/models/LinkTest.java new file mode 100644 index 0000000..ed01bed --- /dev/null +++ b/src/test/java/io/github/legacy/models/LinkTest.java @@ -0,0 +1,418 @@ +package io.github.legacy.models; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.github.legacy.keys.Key; +import io.github.legacy.keys.RSAKey; +import io.github.legacy.models.Artifact.ArtifactHash; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import org.junit.Rule; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; + +/** Link-specific tests */ +@DisplayName("Link-specific tests") +@EnableRuleMigrationSupport +@TestInstance(Lifecycle.PER_CLASS) +class LinkTest { + private Link link = new Link(null, null, "test", null, null, null); + private Key key = RSAKey.read("src/test/resources/somekey.pem"); + + @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + @DisplayName("Test Link Constructor") + public void testLinkContructorEqual() { + // test a link object + + assertEquals("test", link.getName()); + assertNotEquals(null, link.getName()); + } + + @Test + @DisplayName("Test Valid Link Object Types") + public void testValidObject() { + // test link data types + + assertTrue(link instanceof Link); + assertTrue(link.getByproducts() instanceof HashMap); + assertTrue(link.getEnvironment() instanceof HashMap); + assertTrue(link.getName() instanceof String); + assertTrue(link.getProducts() instanceof HashMap); + assertTrue(link.getMaterials() instanceof HashMap); + assertTrue(link.getCommand() instanceof ArrayList); + } + + @Test + @DisplayName("Validate Link addMaterials") + public void testValidMaterial() throws IOException { + File file = temporaryFolder.newFile("alice"); + String path = file.getAbsolutePath(); + link.addMaterial(path); + + Map material = link.getMaterials(); + Map.Entry entry = material.entrySet().iterator().next(); + assertEquals(entry.getKey(), path); + + file.delete(); + } + + @Test + @DisplayName("Validate Link addProducts") + public void testValidProduct() throws IOException { + File file = temporaryFolder.newFile("bob"); + String path = file.getAbsolutePath(); + + link.addProduct(path); + + Map product = link.getProducts(); + Map.Entry entry = product.entrySet().iterator().next(); + assertEquals(entry.getKey(), path); + + file.delete(); + } + + @Test + @DisplayName("Validate Link setByproducts") + public void testValidByproduct() { + HashMap byproduct = new HashMap<>(); + byproduct.put("stdin", ""); + link.setByproducts(byproduct); + assertEquals(byproduct, link.getByproducts()); + } + + @Test + @DisplayName("Validate Link setEnvironments") + public void testValidEnvironment() { + HashMap environment = new HashMap<>(); + environment.put("variables", ""); + link.setEnvironment(environment); + assertEquals(environment, link.getEnvironment()); + } + + @Test + @DisplayName("Validate Link setCommand") + public void testValidCommand() { + ArrayList command = new ArrayList(); + command.add(""); + link.setCommand(command); + assertEquals(command.get(0), link.getCommand().get(0)); + } + + @AfterAll + @Test + @DisplayName("Validate Link sign & dump") + public void testLinkSignDump() { + link.sign(key); + link.dump("dump.link"); + File fl = new File("dump.link"); + assertTrue(fl.exists()); + fl.delete(); + } + + @Test + @DisplayName("Validate link serialization and de-serialization") + public void testLinkDeSerialization() { + + Link testLink = new Link(null, null, "sometestname", null, null, null); + + String jsonString = testLink.dumpString(); + Link newLink = Link.read(jsonString); + + assertTrue(newLink.getName() != null); + assertEquals(testLink.getName(), newLink.getName()); + } + + @Test + @DisplayName("Test Apply Exclude Patterns") + public void testApplyExcludePatterns() throws IOException { + Link testLink = new Link(null, null, "sometestname", null, null, null); + + File file1 = temporaryFolder.newFile("foo"); + File file2 = temporaryFolder.newFile("bar"); + File file3 = temporaryFolder.newFile("baz"); + + String path1 = file1.getAbsolutePath(); + String path2 = file2.getAbsolutePath(); + String path3 = file3.getAbsolutePath(); + + String pattern = "**{foo,bar}"; + + testLink.addProduct(path1, pattern); + testLink.addProduct(path2, pattern); + testLink.addProduct(path3, pattern); + + testLink.addMaterial(path1, pattern); + testLink.addMaterial(path2, pattern); + testLink.addMaterial(path3, pattern); + + Map product = testLink.getProducts(); + assertEquals(product.size(), 1); + + Map.Entry entry1 = product.entrySet().iterator().next(); + assertEquals(entry1.getKey(), path3); + + Map material = testLink.getMaterials(); + assertEquals(material.size(), 1); + + Map.Entry entry2 = material.entrySet().iterator().next(); + assertEquals(entry2.getKey(), path3); + + file1.delete(); + file2.delete(); + file3.delete(); + } + + @Test + @DisplayName("Test Apply Exclude Default Patterns") + public void testApplyExcludeDefaultPatterns() throws IOException { + Link testLink = new Link(null, null, "sometestname", null, null, null); + + File file1 = temporaryFolder.newFile("foo.link"); + File file2 = temporaryFolder.newFile("bar"); + File file3 = temporaryFolder.newFolder(".git"); + File file4 = temporaryFolder.newFile(file3.getName() + "/baz"); + + String path1 = file1.getAbsolutePath(); + String path2 = file2.getAbsolutePath(); + String path4 = file4.getAbsolutePath(); + + testLink.addProduct(path1); + testLink.addProduct(path2); + testLink.addProduct(path4); + + testLink.addMaterial(path1); + testLink.addMaterial(path2); + testLink.addMaterial(path4); + + Map product = testLink.getProducts(); + assertEquals(product.size(), 1); + + Map.Entry entry1 = product.entrySet().iterator().next(); + assertEquals(entry1.getKey(), path2); + + Map material = testLink.getMaterials(); + assertEquals(material.size(), 1); + + Map.Entry entry2 = material.entrySet().iterator().next(); + assertEquals(entry2.getKey(), path2); + + file1.delete(); + file2.delete(); + file3.delete(); + } + + @Test + @DisplayName("Test Apply Exclude All") + public void testApplyExcludeAll() throws IOException { + Link testLink = new Link(null, null, "sometestname", null, null, null); + + File file1 = temporaryFolder.newFile("foo"); + File file2 = temporaryFolder.newFile("bar"); + File file3 = temporaryFolder.newFile("baz"); + + String path1 = file1.getAbsolutePath(); + String path2 = file2.getAbsolutePath(); + String path3 = file3.getAbsolutePath(); + + String pattern = "**"; + + testLink.addProduct(path1, pattern); + testLink.addProduct(path2, pattern); + testLink.addProduct(path3, pattern); + + testLink.addMaterial(path1, pattern); + testLink.addMaterial(path2, pattern); + testLink.addMaterial(path3, pattern); + + Map product = testLink.getProducts(); + assertEquals(product.size(), 0); + assertFalse(product.entrySet().iterator().hasNext()); + + Map material = testLink.getMaterials(); + assertEquals(material.size(), 0); + assertFalse(material.entrySet().iterator().hasNext()); + + file1.delete(); + file2.delete(); + file3.delete(); + } + + @Test + @Disabled( + "Not sure what was the intention of testing using **, the double stars are used to match across path.") + @DisplayName("Test Apply Exclude Multiple Star") + public void testApplyExcludeMultipleStar() throws IOException { + Link testLink = new Link(null, null, "sometestname", null, null, null); + + File file1 = temporaryFolder.newFile("foo"); + File file2 = temporaryFolder.newFile("bar"); + File file3 = temporaryFolder.newFile("baz"); + + String path1 = file1.getAbsolutePath(); + String path2 = file2.getAbsolutePath(); + String path3 = file3.getAbsolutePath(); + + String pattern = "**a**"; + + testLink.addProduct(path1, pattern); + testLink.addProduct(path2, pattern); + testLink.addProduct(path3, pattern); + + testLink.addMaterial(path1, pattern); + testLink.addMaterial(path2, pattern); + testLink.addMaterial(path3, pattern); + + Map product = testLink.getProducts(); + assertEquals(product.size(), 1); + + Map.Entry entry1 = product.entrySet().iterator().next(); + assertEquals(entry1.getKey(), path1); + + Map material = testLink.getMaterials(); + assertEquals(material.size(), 1); + + Map.Entry entry2 = material.entrySet().iterator().next(); + assertEquals(entry2.getKey(), path1); + + file1.delete(); + file2.delete(); + file3.delete(); + } + + @Test + @DisplayName("Test Apply Exclude Question Mark") + public void testApplyExcludeQuestionMark() throws IOException { + Link testLink = new Link(null, null, "sometestname", null, null, null); + + File file1 = temporaryFolder.newFile("foo"); + File file2 = temporaryFolder.newFile("bazfoo"); + File file3 = temporaryFolder.newFile("barfoo"); + + String path1 = file1.getAbsolutePath(); + String path2 = file2.getAbsolutePath(); + String path3 = file3.getAbsolutePath(); + + String pattern = "**ba?foo"; + + testLink.addProduct(path1, pattern); + testLink.addProduct(path2, pattern); + testLink.addProduct(path3, pattern); + + testLink.addMaterial(path1, pattern); + testLink.addMaterial(path2, pattern); + testLink.addMaterial(path3, pattern); + + Map product = testLink.getProducts(); + assertEquals(product.size(), 1); + + Map.Entry entry1 = product.entrySet().iterator().next(); + assertEquals(entry1.getKey(), path1); + + Map material = testLink.getProducts(); + assertEquals(product.size(), 1); + + Map.Entry entry2 = material.entrySet().iterator().next(); + assertEquals(entry2.getKey(), path1); + + file1.delete(); + file2.delete(); + file3.delete(); + } + + @Test + @DisplayName("Test Apply Exclude Sequence") + public void testApplyExcludeSeq() throws IOException { + Link testLink = new Link(null, null, "sometestname", null, null, null); + + File file1 = temporaryFolder.newFile("baxfoo"); + File file2 = temporaryFolder.newFile("bazfoo"); + File file3 = temporaryFolder.newFile("barfoo"); + + String path1 = file1.getAbsolutePath(); + String path2 = file2.getAbsolutePath(); + String path3 = file3.getAbsolutePath(); + + String pattern = "**ba[xz]foo"; + + testLink.addProduct(path1, pattern); + testLink.addProduct(path2, pattern); + testLink.addProduct(path3, pattern); + + testLink.addMaterial(path1, pattern); + testLink.addMaterial(path2, pattern); + testLink.addMaterial(path3, pattern); + + Map product = testLink.getProducts(); + assertEquals(product.size(), 1); + + Map.Entry entry1 = product.entrySet().iterator().next(); + assertEquals(entry1.getKey(), path3); + + Map material = testLink.getMaterials(); + assertEquals(material.size(), 1); + + Map.Entry entry2 = material.entrySet().iterator().next(); + assertEquals(entry2.getKey(), path3); + + file1.delete(); + file2.delete(); + file3.delete(); + } + + @Test + @DisplayName("Test Apply Exclude Negate Sequence") + public void testApplyExcludeNegSeq() throws IOException { + Link testLink = new Link(null, null, "sometestname", null, null, null); + + File file1 = temporaryFolder.newFile("baxfoo"); + File file2 = temporaryFolder.newFile("bazfoo"); + File file3 = temporaryFolder.newFile("barfoo"); + + String path1 = file1.getAbsolutePath(); + String path2 = file2.getAbsolutePath(); + String path3 = file3.getAbsolutePath(); + + String pattern = "**ba[!r]foo"; + + testLink.addProduct(path1, pattern); + testLink.addProduct(path2, pattern); + testLink.addProduct(path3, pattern); + + testLink.addMaterial(path1, pattern); + testLink.addMaterial(path2, pattern); + testLink.addMaterial(path3, pattern); + + Map product = testLink.getProducts(); + assertEquals(product.size(), 1); + + Map.Entry entry1 = product.entrySet().iterator().next(); + assertEquals(entry1.getKey(), path3); + + Map material = testLink.getMaterials(); + assertEquals(product.size(), 1); + + Map.Entry entry2 = material.entrySet().iterator().next(); + assertEquals(entry2.getKey(), path3); + + file1.delete(); + file2.delete(); + file3.delete(); + } +} From 02230cb8550b3c337676df203c8b3f5681d4507e Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Fri, 10 Sep 2021 13:58:05 -0700 Subject: [PATCH 02/21] Bumped version in the pom and javadoc fixes --- README.md | 2 +- pom.xml | 2 +- src/main/java/io/github/intoto/validators/UniqueSubject.java | 1 + src/test/java/io/github/intoto/helpers/IntotoHelperTest.java | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f48154c..ebaef0b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ your mvn project edit the pom.xml file to add: io.github.in-toto in-toto - 0.0.2 + 0.3.3 ... ``` diff --git a/pom.xml b/pom.xml index 6d2e356..30ddeed 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.github.in-toto in-toto jar - 0.3.2 + 0.3.3 in-toto https://maven.apache.org A framework to secure software supply chains. diff --git a/src/main/java/io/github/intoto/validators/UniqueSubject.java b/src/main/java/io/github/intoto/validators/UniqueSubject.java index f1f8f8a..a7ed5ca 100644 --- a/src/main/java/io/github/intoto/validators/UniqueSubject.java +++ b/src/main/java/io/github/intoto/validators/UniqueSubject.java @@ -16,6 +16,7 @@ import javax.validation.Constraint; import javax.validation.Payload; +/** This interface creates the annotation used to validate Subject uniqueness. */ @Documented @Constraint(validatedBy = {UniqueSubjectValidator.class}) @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) diff --git a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java index 927e34a..7d7f849 100644 --- a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java +++ b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java @@ -247,7 +247,7 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEm } @Test - @DisplayName("Testing Subjects uniqueness") + @DisplayName("Testing Subject uniqueness") public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreNotUnique() throws JsonProcessingException, InvalidModelException { Subject subject = new Subject(); From 23ae2106356bc8a066bd6a184c400425b6585a13 Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Mon, 13 Sep 2021 09:15:55 -0700 Subject: [PATCH 03/21] Added @Deprecated to legacy classes --- src/main/java/io/github/legacy/models/Artifact.java | 1 + src/main/java/io/github/legacy/models/Link.java | 1 + src/main/java/io/github/legacy/models/LinkSignable.java | 1 + src/main/java/io/github/legacy/models/Metablock.java | 1 + src/main/java/io/github/legacy/models/Signable.java | 1 + 5 files changed, 5 insertions(+) diff --git a/src/main/java/io/github/legacy/models/Artifact.java b/src/main/java/io/github/legacy/models/Artifact.java index 566b169..6192ce5 100644 --- a/src/main/java/io/github/legacy/models/Artifact.java +++ b/src/main/java/io/github/legacy/models/Artifact.java @@ -13,6 +13,7 @@ *

Used by the Link metdata type on the .add method. Can be also used to pre-populate the Link's * artifact fields before instantiating a link. */ +@Deprecated public class Artifact { /** A URI representing the location of the Artifact */ diff --git a/src/main/java/io/github/legacy/models/Link.java b/src/main/java/io/github/legacy/models/Link.java index 8833566..2330025 100644 --- a/src/main/java/io/github/legacy/models/Link.java +++ b/src/main/java/io/github/legacy/models/Link.java @@ -12,6 +12,7 @@ import java.util.Iterator; /** Implementation of the in-toto Link metadata type. */ +@Deprecated public class Link extends Metablock { /** default exclude pattern used to filter out redundant artifacts */ transient String defaultExcludePattern = "**.{git,link}**"; diff --git a/src/main/java/io/github/legacy/models/LinkSignable.java b/src/main/java/io/github/legacy/models/LinkSignable.java index ed8001b..4b76164 100644 --- a/src/main/java/io/github/legacy/models/LinkSignable.java +++ b/src/main/java/io/github/legacy/models/LinkSignable.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.HashMap; +@Deprecated class LinkSignable extends Signable { HashMap materials; diff --git a/src/main/java/io/github/legacy/models/Metablock.java b/src/main/java/io/github/legacy/models/Metablock.java index 335645c..1f004ab 100644 --- a/src/main/java/io/github/legacy/models/Metablock.java +++ b/src/main/java/io/github/legacy/models/Metablock.java @@ -20,6 +20,7 @@ *

- A signed field, with the signable portion of a piece of metadata. - A signatures field, a * list of the signatures on this metadata. */ +@Deprecated abstract class Metablock { S signed; ArrayList signatures; diff --git a/src/main/java/io/github/legacy/models/Signable.java b/src/main/java/io/github/legacy/models/Signable.java index f8e1d6c..a4f06ab 100644 --- a/src/main/java/io/github/legacy/models/Signable.java +++ b/src/main/java/io/github/legacy/models/Signable.java @@ -6,6 +6,7 @@ * A signable class is an abstract superclass that provides a representation method to prepare for * signing */ +@Deprecated abstract class Signable implements JSONEncoder { /** Subclasses must define the _type field appropriately for serialization */ protected String _type; From 6b0725c6e9d15fddf0e2061faaf4facc8ea6b3c1 Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Mon, 13 Sep 2021 09:20:47 -0700 Subject: [PATCH 04/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ebaef0b..6fa496d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ its current limitations. # Usage -## installation +## Installation This library is intended to be used with maven build system, although you can probably easily move it to any other if you're familiar with those. To add it to From 2f8373fcd50c14175446a47d55b8bec6604b2241 Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Mon, 13 Sep 2021 09:24:33 -0700 Subject: [PATCH 05/21] Update the changelog - Updated the changelog - Removed unused Makefile - Remove unused .travis.yml --- .travis.yml | 3 --- CHANGELOG.md | 9 ++++++++- Makefile | 2 -- 3 files changed, 8 insertions(+), 6 deletions(-) delete mode 100644 .travis.yml delete mode 100644 Makefile diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4ea431b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -language: java -sudo: true -script: mvn test diff --git a/CHANGELOG.md b/CHANGELOG.md index a65d7ee..00f1c76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Version 0.3.3 + +- Added implementation for in-toto 0.1.0 +- Moved Link to legacy directory +- Update Dependencies for validation + ## Version 0.3 - Improve javadoc documentation @@ -17,5 +23,6 @@ ## Version 0.1 - Initial release. -- Adds support for creation, serialization and de-serialization of link metadata. +- Adds support for creation, serialization and de-serialization of link + metadata. - Adds support for RSA-PSSS signatures and PKCS1 key loading. diff --git a/Makefile b/Makefile deleted file mode 100644 index 35e4002..0000000 --- a/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -default: - mvn exec:java -Dexec.mainClass="io.in_toto.lib.App" From 53e07e32567488df945179343041fbd594fb5f37 Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Mon, 13 Sep 2021 17:54:06 -0700 Subject: [PATCH 06/21] Added DSSE and SLSA implementations - Added SLSA models and tests. - Added DSSE implementations and simple Signers and Verifiers --- .../dsse/helpers/SimpleECDSASigner.java | 30 ++ .../dsse/helpers/SimpleECDSAVerifier.java | 35 ++ .../io/github/dsse/models/IntotoEnvelope.java | 80 ++++ .../java/io/github/dsse/models/Signature.java | 52 +++ .../java/io/github/dsse/models/Signer.java | 20 + .../java/io/github/dsse/models/Verifier.java | 14 + .../github/intoto/helpers/IntotoHelper.java | 109 +++++- .../io/github/intoto/models/Statement.java | 2 +- .../java/io/github/slsa/models/Builder.java | 58 +++ .../io/github/slsa/models/Completeness.java | 20 + .../java/io/github/slsa/models/Material.java | 55 +++ .../java/io/github/slsa/models/Metadata.java | 29 ++ .../io/github/slsa/models/Provenance.java | 91 +++++ .../java/io/github/slsa/models/Recipe.java | 91 +++++ .../github/dsse/helpers/SimpleECDSATest.java | 70 ++++ .../intoto/helpers/IntotoHelperTest.java | 355 +++++++++++++++++- .../intoto/implementations/FakeSigner.java | 18 + 17 files changed, 1111 insertions(+), 18 deletions(-) create mode 100644 src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java create mode 100644 src/main/java/io/github/dsse/helpers/SimpleECDSAVerifier.java create mode 100644 src/main/java/io/github/dsse/models/IntotoEnvelope.java create mode 100644 src/main/java/io/github/dsse/models/Signature.java create mode 100644 src/main/java/io/github/dsse/models/Signer.java create mode 100644 src/main/java/io/github/dsse/models/Verifier.java create mode 100644 src/main/java/io/github/slsa/models/Builder.java create mode 100644 src/main/java/io/github/slsa/models/Completeness.java create mode 100644 src/main/java/io/github/slsa/models/Material.java create mode 100644 src/main/java/io/github/slsa/models/Metadata.java create mode 100644 src/main/java/io/github/slsa/models/Provenance.java create mode 100644 src/main/java/io/github/slsa/models/Recipe.java create mode 100644 src/test/java/io/github/dsse/helpers/SimpleECDSATest.java create mode 100644 src/test/java/io/github/intoto/implementations/FakeSigner.java diff --git a/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java b/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java new file mode 100644 index 0000000..8e5c520 --- /dev/null +++ b/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java @@ -0,0 +1,30 @@ +package io.github.dsse.helpers; + +import io.github.dsse.models.Signer; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.SignatureException; + +public class SimpleECDSASigner implements Signer { + private PrivateKey privateKey; + + public SimpleECDSASigner(PrivateKey privateKey) { + this.privateKey = privateKey; + } + + @Override + public byte[] sign(String payload) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Signature signature = Signature.getInstance("SHA1withECDSA"); + signature.initSign(privateKey); + signature.update(payload.getBytes()); + return signature.sign(); + } + + @Override + public String getKeyId() { + return null; + } +} diff --git a/src/main/java/io/github/dsse/helpers/SimpleECDSAVerifier.java b/src/main/java/io/github/dsse/helpers/SimpleECDSAVerifier.java new file mode 100644 index 0000000..8ee5575 --- /dev/null +++ b/src/main/java/io/github/dsse/helpers/SimpleECDSAVerifier.java @@ -0,0 +1,35 @@ +package io.github.dsse.helpers; + +import io.github.dsse.models.Verifier; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; + +public class SimpleECDSAVerifier implements Verifier { + + private PublicKey publicKey; + + @Override + public boolean verify(byte[] publicKeyByteArray, byte[] encryptedMessage, String message) + throws NoSuchAlgorithmException, SignatureException, InvalidKeySpecException, + InvalidKeyException { + Signature signature = Signature.getInstance("SHA1withECDSA"); + // Create the public key from the byte array + PublicKey publicKey = + KeyFactory.getInstance("ECDSA").generatePublic(new X509EncodedKeySpec(publicKeyByteArray)); + this.publicKey = publicKey; + signature.initVerify(publicKey); + signature.update(message.getBytes()); + return signature.verify(encryptedMessage); + } + + @Override + public String getKeyId() { + return publicKey.toString(); + } +} diff --git a/src/main/java/io/github/dsse/models/IntotoEnvelope.java b/src/main/java/io/github/dsse/models/IntotoEnvelope.java new file mode 100644 index 0000000..5a4297f --- /dev/null +++ b/src/main/java/io/github/dsse/models/IntotoEnvelope.java @@ -0,0 +1,80 @@ +package io.github.dsse.models; + +import java.util.List; +import java.util.Objects; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; + +/** + * Implementation of the a DSSE Envelope. The Envelope is the outermost layer of the attestation, + * handling authentication and serialization. The format and protocol are defined in DSSE and + * adopted by in-toto in ITE-5. It is a JSON object with the following fields: payloadType string, + * required + * + *

Identifier for the encoding of the payload. Always application/vnd.in-toto+json, which + * indicates that it is a JSON object with a _type field indicating its schema. + * + *

payload string, required + * + *

Base64-encoded JSON Statement. + * + *

signatures array of objects, required + * + *

One or more signatures over payloadType and payload, as defined in DSSE. + * + *

Defined in https://github.com/secure-systems-lab/dsse/blob/master/envelope.md + */ +public class IntotoEnvelope { + + /** + * Identifier for the encoding of the payload. Always application/vnd.in-toto+json, which + * indicates that it is a JSON object with a _type field indicating its schema. + */ + private final String payloadType = "application/vnd.in-toto+json"; + + /** Base64-encoded JSON {@link io.github.intoto.models.Statement} */ + @NotBlank(message = "payload cannot be null or empty") + private String payload; + + @NotEmpty(message = "signatures cannot be null or empty") + private List signatures; + + public String getPayloadType() { + return payloadType; + } + + public String getPayload() { + return payload; + } + + public void setPayload(String payload) { + this.payload = payload; + } + + public List getSignatures() { + return signatures; + } + + public void setSignatures(List signatures) { + this.signatures = signatures; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IntotoEnvelope that = (IntotoEnvelope) o; + return payloadType.equals(that.payloadType) + && payload.equals(that.payload) + && signatures.equals(that.signatures); + } + + @Override + public int hashCode() { + return Objects.hash(payloadType, payload, signatures); + } +} diff --git a/src/main/java/io/github/dsse/models/Signature.java b/src/main/java/io/github/dsse/models/Signature.java new file mode 100644 index 0000000..d22a486 --- /dev/null +++ b/src/main/java/io/github/dsse/models/Signature.java @@ -0,0 +1,52 @@ +package io.github.dsse.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Objects; +import javax.validation.constraints.NotBlank; + +public class Signature { + + /** + * Signature itself. (In JSON, this is encoded as base64.) Base64() is Base64 + * encoding, transforming a byte sequence to a unicode string. Either standard or URL-safe + * encoding is allowed. + */ + @NotBlank private String sig; + + /** *Unauthenticated* hint identifying which public key was used. */ + @JsonProperty("keyid") + private String keyId; + + public String getSig() { + return sig; + } + + public void setSig(String sig) { + this.sig = sig; + } + + public String getKeyId() { + return keyId; + } + + public void setKeyId(String keyId) { + this.keyId = keyId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Signature signature = (Signature) o; + return sig.equals(signature.sig) && Objects.equals(keyId, signature.keyId); + } + + @Override + public int hashCode() { + return Objects.hash(sig, keyId); + } +} diff --git a/src/main/java/io/github/dsse/models/Signer.java b/src/main/java/io/github/dsse/models/Signer.java new file mode 100644 index 0000000..9ce9fea --- /dev/null +++ b/src/main/java/io/github/dsse/models/Signer.java @@ -0,0 +1,20 @@ +package io.github.dsse.models; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; + +/** Interface for a DSSE Signer. */ +public interface Signer { + + /** + * Returns the signature of the payload. + * + * @param payload the message that you want to sign. + */ + byte[] sign(String payload) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException; + + /** Returns the ID of this key, or null if not supported. */ + String getKeyId(); +} diff --git a/src/main/java/io/github/dsse/models/Verifier.java b/src/main/java/io/github/dsse/models/Verifier.java new file mode 100644 index 0000000..734cccc --- /dev/null +++ b/src/main/java/io/github/dsse/models/Verifier.java @@ -0,0 +1,14 @@ +package io.github.dsse.models; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; + +public interface Verifier { + boolean verify(byte[] publicKey, byte[] encryptedMessage, String message) + throws NoSuchAlgorithmException, SignatureException, InvalidKeySpecException, + InvalidKeyException; + + String getKeyId(); +} diff --git a/src/main/java/io/github/intoto/helpers/IntotoHelper.java b/src/main/java/io/github/intoto/helpers/IntotoHelper.java index 2110672..e953974 100644 --- a/src/main/java/io/github/intoto/helpers/IntotoHelper.java +++ b/src/main/java/io/github/intoto/helpers/IntotoHelper.java @@ -3,8 +3,16 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import io.github.dsse.models.IntotoEnvelope; +import io.github.dsse.models.Signature; +import io.github.dsse.models.Signer; import io.github.intoto.exceptions.InvalidModelException; import io.github.intoto.models.Statement; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; +import java.util.Base64; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; import javax.validation.ConstraintViolation; @@ -21,22 +29,121 @@ public class IntotoHelper { private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + /** + * Creates a JSON String representation of a DSSE Envelope. + * + * @param statement the Statement to add to the envelope + * @param signer the Signer that will be used to sign the payloads. + * @param prettyPrint if true it will pretty print the final Envelope JSON representation + * @return a JSON representation for the envelope. + * @throws InvalidModelException + * @throws JsonProcessingException + * @throws NoSuchAlgorithmException + * @throws SignatureException + * @throws InvalidKeyException + */ + public static String produceIntotoEnvelopeAsJson( + Statement statement, Signer signer, boolean prettyPrint) + throws InvalidModelException, JsonProcessingException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException { + // Get the Base64 encoded Statement to use as the payload + String jsonStatement = validateAndTransformToJson(statement, false); + String base64EncodedStatement = Base64.getEncoder().encodeToString(jsonStatement.getBytes()); + + IntotoEnvelope envelope = new IntotoEnvelope(); + // Create the signed payload with the DSSEv1 format and sign it! + byte[] signedDsseV1Payload = + signer.sign( + createPreAuthenticationEncoding(envelope.getPayloadType(), base64EncodedStatement)); + + Signature signature = new Signature(); + signature.setKeyId(signer.getKeyId()); + // The sig contains the base64 encoded version of the signedDsseV1Payload + signature.setSig(Base64.getEncoder().encodeToString(signedDsseV1Payload)); + // Let's complete the envelope + envelope.setPayload(base64EncodedStatement); + envelope.setSignatures(List.of(signature)); + if (prettyPrint) { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(envelope); + } + return objectMapper.writeValueAsString(envelope); + } + + /** + * Produces an {@link IntotoEnvelope} and signs the payload with the given Signer. Note: There is + * another convenience method that returns the serialized JSON representation for the envelope + * + * @param statement the Statement to add to the envelope + * @param signer the Signer that will be used to sign the payloads. + * @return + * @throws InvalidModelException + * @throws JsonProcessingException + * @throws NoSuchAlgorithmException + * @throws SignatureException + * @throws InvalidKeyException + */ + public static IntotoEnvelope produceIntotoEnvelope(Statement statement, Signer signer) + throws InvalidModelException, JsonProcessingException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException { + // Get the Base64 encoded Statement to use as the payload + String jsonStatement = validateAndTransformToJson(statement, false); + String base64EncodedStatement = Base64.getEncoder().encodeToString(jsonStatement.getBytes()); + + IntotoEnvelope envelope = new IntotoEnvelope(); + // Create the signed payload with the DSSEv1 format and sign it! + byte[] signedDsseV1Payload = + signer.sign( + createPreAuthenticationEncoding(envelope.getPayloadType(), base64EncodedStatement)); + Signature signature = new Signature(); + signature.setKeyId(signer.getKeyId()); + // The sig contains the base64 encoded version of the signedDsseV1Payload + signature.setSig(Base64.getEncoder().encodeToString(signedDsseV1Payload)); + // Let's complete the envelope + envelope.setPayload(base64EncodedStatement); + envelope.setSignatures(List.of(signature)); + return envelope; + } + + /** + * Generates the Pre-Authentication Encoding + *

+   * "DSSEv1" + SP + LEN(type) + SP + type + SP + LEN(body) + SP + body
+   *
+   * where:
+   * + = concatenation
+   * SP = ASCII space [0x20]
+   * "DSSEv1" = ASCII [0x44, 0x53, 0x53, 0x45, 0x76, 0x31]
+   * LEN(s) = ASCII decimal encoding of the byte length of s, with no leading zeros
+   *
+   * @param payloadType the type of payload. Fixed for in-toto Envelopes
+   * @param payload the base64 encoded Statement in JSON
+   * @return Strin
+   */
+  public static String createPreAuthenticationEncoding(String payloadType, String payload) {
+    return String.format(
+        "DSSEv1 %d %s %d %s", payloadType.length(), payloadType, payload.length(), payload);
+  }
+
   /**
    * Validates a {@link Statement} and transforms it to its JSON representation.
    *
    * @param statement the statement thet needs to be validated and tested.
+   * @param prettyPrint indicates if you want the output result to be formatted for human reading.
    * @return the String with the JSON representation of the Statement.
    * @throws JsonProcessingException thrown when there is a problem serializing the Statement into
    *     JSON
    * @throws InvalidModelException thrown when there are problems with the statement.
    */
-  public static String validateAndTransformToJson(Statement statement)
+  public static String validateAndTransformToJson(Statement statement, boolean prettyPrint)
       throws JsonProcessingException, InvalidModelException {
 
     Set> results = validator.validate(statement);
 
     if (results.isEmpty()) {
       objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+      if (prettyPrint) {
+        return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(statement);
+      }
       return objectMapper.writeValueAsString(statement);
     } else {
       String errorMessage =
diff --git a/src/main/java/io/github/intoto/models/Statement.java b/src/main/java/io/github/intoto/models/Statement.java
index 379601e..6812433 100644
--- a/src/main/java/io/github/intoto/models/Statement.java
+++ b/src/main/java/io/github/intoto/models/Statement.java
@@ -36,7 +36,7 @@ public class Statement {
    * Additional parameters of the Predicate. Unset is treated the same as set-but-empty. MAY be
    * omitted if predicateType fully describes the predicate.
    */
-  private Predicate predicate;
+  private @Valid Predicate predicate;
 
   public StatementType get_type() {
     return _type;
diff --git a/src/main/java/io/github/slsa/models/Builder.java b/src/main/java/io/github/slsa/models/Builder.java
new file mode 100644
index 0000000..c282de6
--- /dev/null
+++ b/src/main/java/io/github/slsa/models/Builder.java
@@ -0,0 +1,58 @@
+package io.github.slsa.models;
+
+import java.util.Objects;
+import javax.validation.constraints.NotBlank;
+
+/**
+ * Identifies the entity that executed the recipe, which is trusted to have correctly performed the
+ * operation and populated this provenance.
+ *
+ * 

The identity 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, the “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 because one signer may generate + * 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; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Builder builder = (Builder) o; + return id.equals(builder.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/src/main/java/io/github/slsa/models/Completeness.java b/src/main/java/io/github/slsa/models/Completeness.java new file mode 100644 index 0000000..de253e4 --- /dev/null +++ b/src/main/java/io/github/slsa/models/Completeness.java @@ -0,0 +1,20 @@ +package io.github.slsa.models; + +/** Indicates that the builder claims certain fields in this message to be complete. */ +public class Completeness { + + /** + * If true, the builder claims that recipe.arguments is complete, meaning that all external inputs + * are properly captured in recipe. + */ + private boolean arguments; + + /** If true, the builder claims that recipe.environment is claimed to be complete. */ + private boolean environment; + + /** + * If true, the builder claims that materials is complete, usually through some controls to + * prevent network access. Sometimes called “hermetic”. + */ + private boolean materials; +} diff --git a/src/main/java/io/github/slsa/models/Material.java b/src/main/java/io/github/slsa/models/Material.java new file mode 100644 index 0000000..c530995 --- /dev/null +++ b/src/main/java/io/github/slsa/models/Material.java @@ -0,0 +1,55 @@ +package io.github.slsa.models; + +import java.util.Map; +import java.util.Objects; + +/** + * The collection of artifacts that influenced the build including sources, dependencies, build + * tools, base images, and so on. + * + *

This is considered to be incomplete unless metadata.completeness.materials is true. Unset or + * null is equivalent to empty. + */ +public class Material { + /** + * The method by which this artifact was referenced during the build. (ResourceURI) + */ + private String uri; + + /** Collection of cryptographic digests for the contents of this artifact. */ + private Map digest; + + 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; + } + Material material = (Material) o; + return Objects.equals(uri, material.uri) && Objects.equals(digest, material.digest); + } + + @Override + public int hashCode() { + return Objects.hash(uri, digest); + } +} diff --git a/src/main/java/io/github/slsa/models/Metadata.java b/src/main/java/io/github/slsa/models/Metadata.java new file mode 100644 index 0000000..58606d8 --- /dev/null +++ b/src/main/java/io/github/slsa/models/Metadata.java @@ -0,0 +1,29 @@ +package io.github.slsa.models; + +import java.time.Instant; + +/** Other properties of the build. */ +public class Metadata { + + /** + * 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 buildInvocationId; + + /** The timestamp of when the build started. */ + private Instant buildStartedOn; + + /** The timestamp of when the build completed. */ + private Instant buildFinishedOn; + + /** Indicates that the builder claims certain fields in this message to be complete. */ + private Completeness completeness; + + /** + * If true, the builder claims that running recipe on materials will produce bit-for-bit identical + * output. + */ + private boolean reproducible; +} diff --git a/src/main/java/io/github/slsa/models/Provenance.java b/src/main/java/io/github/slsa/models/Provenance.java new file mode 100644 index 0000000..d2c29f2 --- /dev/null +++ b/src/main/java/io/github/slsa/models/Provenance.java @@ -0,0 +1,91 @@ +package io.github.slsa.models; + +import io.github.intoto.models.Predicate; +import java.util.List; +import java.util.Objects; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +/** Implementation of the https://slsa.dev/provenance/v0.1 */ +public class Provenance extends Predicate { + + /** + * Identifies the entity that executed the recipe, which is trusted to have correctly performed + * the operation and populated this provenance. + */ + @NotNull(message = "builder must not be null") + private Builder builder; + + /** + * Identifies the configuration used for the build. When combined with materials, this SHOULD + * fully describe the build, such that re-running this recipe results in bit-for-bit identical + * output (if the build is reproducible). + * + *

MAY be unset/null if unknown, but this is DISCOURAGED. + */ + private @Valid Recipe recipe; + + /** Other properties of the build. */ + private Metadata metadata; + + /** + * The collection of artifacts that influenced the build including sources, dependencies, build + * tools, base images, and so on. + * + *

This is considered to be incomplete unless metadata.completeness.materials is true. Unset or + * null is equivalent to empty. + */ + private List materials; + + public Builder getBuilder() { + return builder; + } + + public void setBuilder(Builder builder) { + this.builder = builder; + } + + public Recipe getRecipe() { + return recipe; + } + + public void setRecipe(Recipe recipe) { + this.recipe = recipe; + } + + public Metadata getMetadata() { + return metadata; + } + + public void setMetadata(Metadata metadata) { + this.metadata = metadata; + } + + public List getMaterials() { + return materials; + } + + public void setMaterials(List materials) { + this.materials = materials; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Provenance that = (Provenance) o; + return builder.equals(that.builder) + && Objects.equals(recipe, that.recipe) + && Objects.equals(metadata, that.metadata) + && Objects.equals(materials, that.materials); + } + + @Override + public int hashCode() { + return Objects.hash(builder, recipe, metadata, materials); + } +} diff --git a/src/main/java/io/github/slsa/models/Recipe.java b/src/main/java/io/github/slsa/models/Recipe.java new file mode 100644 index 0000000..b41fc4a --- /dev/null +++ b/src/main/java/io/github/slsa/models/Recipe.java @@ -0,0 +1,91 @@ +package io.github.slsa.models; + +import java.util.Objects; +import javax.validation.constraints.NotNull; + +/** + * Identifies the configuration used for the build. When combined with materials, this SHOULD fully + * describe the build, such that re-running this recipe results in bit-for-bit identical output (if + * the build is reproducible). + * + *

MAY be unset/null if unknown, but this is DISCOURAGED. + * + *

NOTE: The Recipe entity has additional properties: arguments and environment that are + * classified as generic objects in the spec. For this reason it would be expected that builders + * using this library would extend from the default Recipe and create their own custom class. + */ +public class Recipe { + + /** + * URI indicating what type of recipe was performed. It determines the meaning of + * recipe.entryPoint, recipe.arguments, recipe.environment, and materials. (TypeURI) + */ + @NotNull(message = "recipe type must not be blank") + private String type; + + /** + * Index in materials containing the recipe steps that are not implied by recipe.type. For + * example, if the recipe type were “make”, then this would point to the source containing the + * Makefile, not the make program itself. + * + *

Omit this field (or use null) if the recipe does’t come from a material. + */ + private int definedInMaterial; + + /** + * String identifying the entry point into the build. This is often a path to a configuration file + * and/or a target label within that file. The syntax and meaning are defined by recipe.type. For + * example, if the recipe type were “make”, then this would reference the directory in which to + * run make as well as which target to use. + * + *

Consumers SHOULD accept only specific recipe.entryPoint values. For example, a policy might + * only allow the “release” entry point but not the “debug” entry point. + * + *

MAY be omitted if the recipe type specifies a default value. + */ + private String entryPoint; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public int getDefinedInMaterial() { + return definedInMaterial; + } + + public void setDefinedInMaterial(int definedInMaterial) { + this.definedInMaterial = definedInMaterial; + } + + public String getEntryPoint() { + return entryPoint; + } + + public void setEntryPoint(String entryPoint) { + this.entryPoint = entryPoint; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Recipe recipe = (Recipe) o; + return definedInMaterial == recipe.definedInMaterial + && type.equals(recipe.type) + && Objects.equals(entryPoint, recipe.entryPoint); + } + + @Override + public int hashCode() { + return Objects.hash(type, definedInMaterial, entryPoint); + } +} diff --git a/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java b/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java new file mode 100644 index 0000000..8ed41c3 --- /dev/null +++ b/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java @@ -0,0 +1,70 @@ +package io.github.dsse.helpers; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.spec.ECGenParameterSpec; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class SimpleECDSATest { + + private KeyPairGenerator keygen; + + @BeforeEach + public void setup() + throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException { + Security.addProvider(new BouncyCastleProvider()); + this.keygen = KeyPairGenerator.getInstance("ECDSA", "BC"); + keygen.initialize(new ECGenParameterSpec("brainpoolP384r1")); + } + + @Test + @DisplayName("Test simple ECDSA signing") + public void simpleEcdsa_sign_shouldCorrectlySignString_whenGivenCorrectKey() throws Exception { + String message = "hello world"; + + // Generate a key pair + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); + keyGen.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom()); + KeyPair pair = keyGen.generateKeyPair(); + PrivateKey privateKey = pair.getPrivate(); + PublicKey publicKey = pair.getPublic(); + + SimpleECDSASigner signer = new SimpleECDSASigner(privateKey); + byte[] encryptedMessage = signer.sign(message); + + SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); + boolean result = verifier.verify(publicKey.getEncoded(), encryptedMessage, message); + Assertions.assertTrue(result); + } + + @Test + @DisplayName("Test simple ECDSA signing with DSAA String") + public void test() throws Exception { + String message = + "DSSEv1 28 application/vnd.in-toto+json 656 eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7IlNIQTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjpudWxsLCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV19fQ=="; + // Generate a key pair + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); + keyGen.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom()); + KeyPair pair = keyGen.generateKeyPair(); + PrivateKey privateKey = pair.getPrivate(); + PublicKey publicKey = pair.getPublic(); + + SimpleECDSASigner signer = new SimpleECDSASigner(privateKey); + byte[] encryptedMessage = signer.sign(message); + + SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); + boolean result = verifier.verify(publicKey.getEncoded(), encryptedMessage, message); + Assertions.assertTrue(result); + } +} diff --git a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java index 7d7f849..0e31906 100644 --- a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java +++ b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java @@ -5,16 +5,39 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.fasterxml.jackson.core.JsonProcessingException; +import io.github.dsse.helpers.SimpleECDSASigner; +import io.github.dsse.helpers.SimpleECDSAVerifier; +import io.github.dsse.models.IntotoEnvelope; import io.github.intoto.exceptions.InvalidModelException; +import io.github.intoto.implementations.FakeSigner; import io.github.intoto.models.DigestSetAlgorithmType; import io.github.intoto.models.Predicate; import io.github.intoto.models.PredicateType; import io.github.intoto.models.Statement; import io.github.intoto.models.StatementType; import io.github.intoto.models.Subject; +import io.github.slsa.models.Builder; +import io.github.slsa.models.Material; +import io.github.slsa.models.Provenance; +import io.github.slsa.models.Recipe; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.SignatureException; +import java.security.spec.ECGenParameterSpec; +import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Map; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -24,7 +47,7 @@ public class IntotoHelperTest { @DisplayName("Can transform basic Statement to JSON") public void validateAndTransformToJson_shouldTransformStatementToJsonString_whenStatementIsCorrect() - throws JsonProcessingException, InvalidModelException { + throws IOException, InvalidModelException { Subject subject = new Subject(); subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest( @@ -38,14 +61,95 @@ public class IntotoHelperTest { statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(predicate); - String jsonStatement = IntotoHelper.validateAndTransformToJson(statement); - System.out.println(jsonStatement); + String jsonStatement = IntotoHelper.validateAndTransformToJson(statement, true); assertNotNull(jsonStatement); - String SIMPLE_JSON_STATEMENT = - "{\"_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\":{}}"; + final String SIMPLE_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/v0.1\",\n" + + " \"predicate\" : { }\n" + + "}"; assertEquals(SIMPLE_JSON_STATEMENT, jsonStatement); } + @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.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + // ** The predicate ** + // Prepare the Builder + Builder builder = new Builder(); + builder.setId("mailto:person@example.com"); + // Prepare the Recipe + Recipe recipe = new Recipe(); + recipe.setType("https://example.com/Makefile"); + recipe.setEntryPoint("src:foo"); + recipe.setDefinedInMaterial(0); + // Prepare the Materials + Material material = new Material(); + material.setUri("https://example.com/example-1.2.3.tar.gz"); + material.setDigest(Map.of("sha256", "1234...")); + // Putting the Provenance together + Provenance provenancePredicate = new Provenance(); + provenancePredicate.setBuilder(builder); + provenancePredicate.setRecipe(recipe); + provenancePredicate.setMaterials(List.of(material)); + // ** Putting the Statement together ** + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + 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/v0.1\",\n" + + " \"predicate\" : {\n" + + " \"builder\" : {\n" + + " \"id\" : \"mailto:person@example.com\"\n" + + " },\n" + + " \"recipe\" : {\n" + + " \"type\" : \"https://example.com/Makefile\",\n" + + " \"definedInMaterial\" : 0,\n" + + " \"entryPoint\" : \"src:foo\"\n" + + " },\n" + + " \"metadata\" : null,\n" + + " \"materials\" : [ {\n" + + " \"uri\" : \"https://example.com/example-1.2.3.tar.gz\",\n" + + " \"digest\" : {\n" + + " \"sha256\" : \"1234...\"\n" + + " }\n" + + " } ]\n" + + " }\n" + + "}"; + + assertEquals(JSON_STATEMENT, jsonStatement); + } + @Test @DisplayName("Testing Statement Type can't be null") public void validateAndTransformToJson_shouldThrowException_whenStatementTypeIsMissing() { @@ -67,7 +171,7 @@ public void validateAndTransformToJson_shouldThrowException_whenStatementTypeIsM assertThrows( InvalidModelException.class, () -> { - IntotoHelper.validateAndTransformToJson(statement); + IntotoHelper.validateAndTransformToJson(statement, true); }); assertEquals("_type may not be null", thrown.getMessage()); @@ -92,7 +196,7 @@ public void toJson_shouldThrowException_whenStatementSubjectIsNull() { assertThrows( InvalidModelException.class, () -> { - IntotoHelper.validateAndTransformToJson(statement); + IntotoHelper.validateAndTransformToJson(statement, true); }); assertEquals("subject may not be null or empty", thrown.getMessage()); @@ -118,7 +222,7 @@ public void toJson_shouldThrowException_whenStatementSubjectIsEmpty() { assertThrows( InvalidModelException.class, () -> { - IntotoHelper.validateAndTransformToJson(statement); + IntotoHelper.validateAndTransformToJson(statement, true); }); assertEquals("subject may not be null or empty", thrown.getMessage()); @@ -143,7 +247,7 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNameIsNul assertThrows( InvalidModelException.class, () -> { - IntotoHelper.validateAndTransformToJson(statement); + IntotoHelper.validateAndTransformToJson(statement, true); }); assertEquals("subject name must not be blank", thrown.getMessage()); @@ -169,7 +273,7 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNameIsBla assertThrows( InvalidModelException.class, () -> { - IntotoHelper.validateAndTransformToJson(statement); + IntotoHelper.validateAndTransformToJson(statement, true); }); assertEquals("subject name must not be blank", thrown.getMessage()); @@ -191,7 +295,7 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEm assertThrows( InvalidModelException.class, () -> { - IntotoHelper.validateAndTransformToJson(statement); + IntotoHelper.validateAndTransformToJson(statement, true); }); assertEquals("digest must not be empty", thrown.getMessage()); @@ -216,7 +320,7 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEm assertThrows( InvalidModelException.class, () -> { - IntotoHelper.validateAndTransformToJson(statement); + IntotoHelper.validateAndTransformToJson(statement, true); }); assertEquals("digest key contents can be empty strings", thrown.getMessage()); @@ -240,7 +344,7 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEm assertThrows( InvalidModelException.class, () -> { - IntotoHelper.validateAndTransformToJson(statement); + IntotoHelper.validateAndTransformToJson(statement, true); }); assertEquals("digest value contents can be empty strings", thrown.getMessage()); @@ -248,8 +352,7 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEm @Test @DisplayName("Testing Subject uniqueness") - public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreNotUnique() - throws JsonProcessingException, InvalidModelException { + public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreNotUnique() { Subject subject = new Subject(); subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest( @@ -281,9 +384,229 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreN assertThrows( InvalidModelException.class, () -> { - IntotoHelper.validateAndTransformToJson(statement); + 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.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + // ** The predicate ** + // Prepare the Recipe + Recipe recipe = new Recipe(); + recipe.setType("https://example.com/Makefile"); + recipe.setEntryPoint("src:foo"); + recipe.setDefinedInMaterial(0); + // Prepare the Materials + Material material = new Material(); + material.setUri("https://example.com/example-1.2.3.tar.gz"); + material.setDigest(Map.of("sha256", "1234...")); + // Putting the Provenance together + Provenance provenancePredicate = new Provenance(); + provenancePredicate.setRecipe(recipe); + provenancePredicate.setMaterials(List.of(material)); + // ** Putting the Statement together ** + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + 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 Recipe myst have type") + public void + validateAndTransformToJson_shouldThrowException_whenStatementContainsProvenanceWithNoRecipeType() { + // ** The subject ** + Subject subject = new Subject(); + subject.setName("curl-7.72.0.tar.bz2"); + subject.setDigest( + Map.of( + DigestSetAlgorithmType.SHA256.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + // ** The predicate ** + // Prepare the Builder + Builder builder = new Builder(); + builder.setId("mailto:person@example.com"); + // Prepare the Recipe + Recipe recipe = new Recipe(); + recipe.setEntryPoint("src:foo"); + recipe.setDefinedInMaterial(0); + // Prepare the Materials + Material material = new Material(); + material.setUri("https://example.com/example-1.2.3.tar.gz"); + material.setDigest(Map.of("sha256", "1234...")); + // Putting the Provenance together + Provenance provenancePredicate = new Provenance(); + provenancePredicate.setBuilder(builder); + provenancePredicate.setRecipe(recipe); + provenancePredicate.setMaterials(List.of(material)); + // ** Putting the Statement together ** + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(provenancePredicate); + + InvalidModelException thrown = + assertThrows( + InvalidModelException.class, + () -> { + IntotoHelper.validateAndTransformToJson(statement, true); + }); + + assertEquals("recipe type must not be blank", thrown.getMessage()); + } + + @Test + @DisplayName("Test createPreAuthenticationEncoding") + public void createPreAuthenticationEncoding_shouldCorrectlyEncode_whenSimpleValues() { + String paeString = + IntotoHelper.createPreAuthenticationEncoding( + "http://example.com/HelloWorld", "hello world"); + assertEquals("DSSEv1 29 http://example.com/HelloWorld 11 hello world", paeString); + } + + @Test + @DisplayName("Test creating envelope from Statement") + public void + createPreAuthenticationEncoding_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.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + // ** The predicate ** + // Prepare the Builder + Builder builder = new Builder(); + builder.setId("mailto:person@example.com"); + // Prepare the Recipe + Recipe recipe = new Recipe(); + recipe.setType("https://example.com/Makefile"); + recipe.setEntryPoint("src:foo"); + recipe.setDefinedInMaterial(0); + // Prepare the Materials + Material material = new Material(); + material.setUri("https://example.com/example-1.2.3.tar.gz"); + material.setDigest(Map.of("sha256", "1234...")); + // Putting the Provenance together + Provenance provenancePredicate = new Provenance(); + provenancePredicate.setBuilder(builder); + provenancePredicate.setRecipe(recipe); + provenancePredicate.setMaterials(List.of(material)); + // ** Putting the Statement together ** + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + 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\" : \"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7IlNIQTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjpudWxsLCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV19fQ==\",\n" + + " \"signatures\" : [ {\n" + + " \"sig\" : \"RFNTRXYxIDI4IGFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24gNjU2IGV5SmZkSGx3WlNJNkltaDBkSEJ6T2k4dmFXNHRkRzkwYnk1cGJ5OVRkR0YwWlcxbGJuUXZkakF1TVNJc0luTjFZbXBsWTNRaU9sdDdJbTVoYldVaU9pSmpkWEpzTFRjdU56SXVNQzUwWVhJdVlub3lJaXdpWkdsblpYTjBJanA3SWxOSVFUSTFOaUk2SW1RMFpEVTRPVGxoTXpnMk9HWmlZalpoWlRFNE5UWmpNMlUxTldFek1tTmxNelU1TVROa1pUTTVOVFprTVRrM00yTmhZMk5rTXpkaVpEQXhOelJtWVRJaWZYMWRMQ0p3Y21Wa2FXTmhkR1ZVZVhCbElqb2lhSFIwY0hNNkx5OXpiSE5oTG1SbGRpOXdjbTkyWlc1aGJtTmxMM1l3TGpFaUxDSndjbVZrYVdOaGRHVWlPbnNpWW5WcGJHUmxjaUk2ZXlKcFpDSTZJbTFoYVd4MGJ6cHdaWEp6YjI1QVpYaGhiWEJzWlM1amIyMGlmU3dpY21WamFYQmxJanA3SW5SNWNHVWlPaUpvZEhSd2N6b3ZMMlY0WVcxd2JHVXVZMjl0TDAxaGEyVm1hV3hsSWl3aVpHVm1hVzVsWkVsdVRXRjBaWEpwWVd3aU9qQXNJbVZ1ZEhKNVVHOXBiblFpT2lKemNtTTZabTl2SW4wc0ltMWxkR0ZrWVhSaElqcHVkV3hzTENKdFlYUmxjbWxoYkhNaU9sdDdJblZ5YVNJNkltaDBkSEJ6T2k4dlpYaGhiWEJzWlM1amIyMHZaWGhoYlhCc1pTMHhMakl1TXk1MFlYSXVaM29pTENKa2FXZGxjM1FpT25zaWMyaGhNalUySWpvaU1USXpOQzR1TGlKOWZWMTlmUT09\",\n" + + " \"keyid\" : \"Fake-Signer-Key-ID\"\n" + + " } ]\n" + + "}"; + assertEquals(EXPECTED_JSON_ENVELOPE, intotoEnvelope); + } + + @Test + @DisplayName("Test creating envelope with simple encryption") + public void + createPreAuthenticationEncoding_shouldCorrectlyCreateAnEnvelope_whenUsingSimpleEncryption() + throws Exception { + // ** The subject ** + Subject subject = new Subject(); + subject.setName("curl-7.72.0.tar.bz2"); + subject.setDigest( + Map.of( + DigestSetAlgorithmType.SHA256.toString(), + "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); + // ** The predicate ** + // Prepare the Builder + Builder builder = new Builder(); + builder.setId("mailto:person@example.com"); + // Prepare the Recipe + Recipe recipe = new Recipe(); + recipe.setType("https://example.com/Makefile"); + recipe.setEntryPoint("src:foo"); + recipe.setDefinedInMaterial(0); + // Prepare the Materials + Material material = new Material(); + material.setUri("https://example.com/example-1.2.3.tar.gz"); + material.setDigest(Map.of("sha256", "1234...")); + // Putting the Provenance together + Provenance provenancePredicate = new Provenance(); + provenancePredicate.setBuilder(builder); + provenancePredicate.setRecipe(recipe); + provenancePredicate.setMaterials(List.of(material)); + // ** Putting the Statement together ** + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); + statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); + statement.setPredicate(provenancePredicate); + + // Generate a key pair + Security.addProvider(new BouncyCastleProvider()); + + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); + keyGen.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom()); + KeyPair pair = keyGen.generateKeyPair(); + PrivateKey privateKey = pair.getPrivate(); + PublicKey publicKey = pair.getPublic(); + + SimpleECDSASigner signer = new SimpleECDSASigner(privateKey); + + IntotoEnvelope intotoEnvelope = IntotoHelper.produceIntotoEnvelope(statement, signer); + System.out.println(intotoEnvelope); + assertNotNull(intotoEnvelope); + + final String DSSE_PAYLOAD = + "DSSEv1 28 application/vnd.in-toto+json 656 eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7IlNIQTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjpudWxsLCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV19fQ=="; + + SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); + + boolean result = + verifier.verify( + publicKey.getEncoded(), + Base64.getDecoder() + .decode( + intotoEnvelope + .getSignatures() + .get(0) + .getSig() + .getBytes(StandardCharsets.UTF_8)), + DSSE_PAYLOAD); + Assertions.assertTrue(result); + } } diff --git a/src/test/java/io/github/intoto/implementations/FakeSigner.java b/src/test/java/io/github/intoto/implementations/FakeSigner.java new file mode 100644 index 0000000..00155fa --- /dev/null +++ b/src/test/java/io/github/intoto/implementations/FakeSigner.java @@ -0,0 +1,18 @@ +package io.github.intoto.implementations; + +import io.github.dsse.models.Signer; +import java.nio.charset.StandardCharsets; + +/** Fake Signer implementation for testing only. */ +public class FakeSigner implements Signer { + + @Override + public byte[] sign(String payload) { + return payload.getBytes(StandardCharsets.UTF_8); + } + + @Override + public String getKeyId() { + return "Fake-Signer-Key-ID"; + } +} From dd964a3111f0de5830449c916410b12a94a1e88e Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Thu, 16 Sep 2021 17:02:21 -0700 Subject: [PATCH 07/21] Updated the README * Added better description of helper methods and Signer/Verifier interface * Made sure the `pivateKey` field is final. --- README.md | 17 +++++++++++++++++ .../github/dsse/helpers/SimpleECDSASigner.java | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6fa496d..fcc0303 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,23 @@ it into its JSON representation as follows: If the statement passed to the method is malformed the library will throw an `InvalidModelException` that will contain a message with the errors. +If you, however wish to create a DSSE based In-toto envelope, The library +features a convenience method: + +```java +IntotoEnvelope intotoEnvelope=IntotoHelper.produceIntotoEnvelope(statement,signer); +``` + +This method will accept a `io.github.intoto.models.Statement` and an +implementation of the ` io.github.dsse.models.Signer` interface. + +### Implementing a Signer and a Verifier + +The Signer and Verifier are used to abstract away the sign and verify mechanism +from this library. This allows the user to implement their own Signer/Verifier. +An example of such an implementation is available in +the `io.github.dsse.helpers package`. + ## Using the legacy Link library The library exposes a series of objects and convenience methods to create, sign, diff --git a/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java b/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java index 8e5c520..a2082b4 100644 --- a/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java +++ b/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java @@ -8,7 +8,7 @@ import java.security.SignatureException; public class SimpleECDSASigner implements Signer { - private PrivateKey privateKey; + private final PrivateKey privateKey; public SimpleECDSASigner(PrivateKey privateKey) { this.privateKey = privateKey; From 48d02d9b62c7fb243deb0175c12038956a0bb592 Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Thu, 16 Sep 2021 17:19:17 -0700 Subject: [PATCH 08/21] Added additional JavaDoc Make sure new classes have proper JavaDoc --- .../java/io/github/dsse/models/Signature.java | 4 +++ .../java/io/github/dsse/models/Verifier.java | 15 ++++++++++ .../github/intoto/helpers/IntotoHelper.java | 28 +++++++++++-------- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/github/dsse/models/Signature.java b/src/main/java/io/github/dsse/models/Signature.java index d22a486..1f48dea 100644 --- a/src/main/java/io/github/dsse/models/Signature.java +++ b/src/main/java/io/github/dsse/models/Signature.java @@ -4,6 +4,10 @@ import java.util.Objects; import javax.validation.constraints.NotBlank; +/** + * Implementation of a DSSE signature as described in: + * https://github.com/secure-systems-lab/dsse/blob/c0d39aae3b9063a67cc20ae8ec3e1ea289821ebf/envelope.proto#L26 + */ public class Signature { /** diff --git a/src/main/java/io/github/dsse/models/Verifier.java b/src/main/java/io/github/dsse/models/Verifier.java index 734cccc..b3fca6a 100644 --- a/src/main/java/io/github/dsse/models/Verifier.java +++ b/src/main/java/io/github/dsse/models/Verifier.java @@ -5,10 +5,25 @@ import java.security.SignatureException; import java.security.spec.InvalidKeySpecException; +/** Interface for a DSSE Verifier */ public interface Verifier { + + /** + * Validates the given message based on the given public key and encrypted message. + * + * @param publicKey the public key that should be used to verify the message + * @param encryptedMessage the encrypted message + * @param message the message we are validating against. + * @return true if the given message matches the encryptedMessage + * @throws NoSuchAlgorithmException + * @throws SignatureException + * @throws InvalidKeySpecException + * @throws InvalidKeyException + */ boolean verify(byte[] publicKey, byte[] encryptedMessage, String message) throws NoSuchAlgorithmException, SignatureException, InvalidKeySpecException, InvalidKeyException; + /** Returns the ID of this key, or null if not supported. */ String getKeyId(); } diff --git a/src/main/java/io/github/intoto/helpers/IntotoHelper.java b/src/main/java/io/github/intoto/helpers/IntotoHelper.java index e953974..e2e59bd 100644 --- a/src/main/java/io/github/intoto/helpers/IntotoHelper.java +++ b/src/main/java/io/github/intoto/helpers/IntotoHelper.java @@ -36,11 +36,13 @@ public class IntotoHelper { * @param signer the Signer that will be used to sign the payloads. * @param prettyPrint if true it will pretty print the final Envelope JSON representation * @return a JSON representation for the envelope. - * @throws InvalidModelException - * @throws JsonProcessingException - * @throws NoSuchAlgorithmException - * @throws SignatureException - * @throws InvalidKeyException + * @throws InvalidModelException thrown when the given statement is not valid + * @throws JsonProcessingException thrown when there are issues generating the JSON string + * @throws NoSuchAlgorithmException thrown when there are issues encrypting the payloads in the + * Envelope + * @throws SignatureException thrown when there are issues with the given key in the Signer + * @throws InvalidKeyException thrown when there are issues matching the key with the given + * algorithm */ public static String produceIntotoEnvelopeAsJson( Statement statement, Signer signer, boolean prettyPrint) @@ -75,12 +77,14 @@ public static String produceIntotoEnvelopeAsJson( * * @param statement the Statement to add to the envelope * @param signer the Signer that will be used to sign the payloads. - * @return - * @throws InvalidModelException - * @throws JsonProcessingException - * @throws NoSuchAlgorithmException - * @throws SignatureException - * @throws InvalidKeyException + * @return will return a {@link IntotoEnvelope} instead of the JSON representation. + * @throws InvalidModelException thrown when the given statement is not valid + * @throws JsonProcessingException thrown when there are issues generating the JSON string + * @throws NoSuchAlgorithmException thrown when there are issues encrypting the payloads in the * + * Envelope + * @throws SignatureException thrown when there are issues with the given key in the Signer + * @throws InvalidKeyException thrown when there are issues matching the key with the given * + * algorithm */ public static IntotoEnvelope produceIntotoEnvelope(Statement statement, Signer signer) throws InvalidModelException, JsonProcessingException, NoSuchAlgorithmException, @@ -117,7 +121,7 @@ public static IntotoEnvelope produceIntotoEnvelope(Statement statement, Signer s *

    * @param payloadType the type of payload. Fixed for in-toto Envelopes
    * @param payload the base64 encoded Statement in JSON
-   * @return Strin
+   * @return will return a Pre Authentication Encoding String.
    */
   public static String createPreAuthenticationEncoding(String payloadType, String payload) {
     return String.format(

From 521e076060dfd5f0d254edf161a923bbeec9aee6 Mon Sep 17 00:00:00 2001
From: Sergio Felix 
Date: Thu, 16 Sep 2021 17:21:21 -0700
Subject: [PATCH 09/21] Update IntotoEnvelope.java

Added missing JavaDoc
---
 src/main/java/io/github/dsse/models/IntotoEnvelope.java | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/java/io/github/dsse/models/IntotoEnvelope.java b/src/main/java/io/github/dsse/models/IntotoEnvelope.java
index 5a4297f..c8cce3e 100644
--- a/src/main/java/io/github/dsse/models/IntotoEnvelope.java
+++ b/src/main/java/io/github/dsse/models/IntotoEnvelope.java
@@ -36,6 +36,7 @@ public class IntotoEnvelope {
   @NotBlank(message = "payload cannot be null or empty")
   private String payload;
 
+  /** A list of DSSE signatures. See: {@link Signature} */
   @NotEmpty(message = "signatures cannot be null or empty")
   private List signatures;
 

From 251b162753934433857c9728ae0726a9f5c7a7e4 Mon Sep 17 00:00:00 2001
From: Sergio Felix 
Date: Fri, 17 Sep 2021 08:53:59 -0700
Subject: [PATCH 10/21] Update README.md

Changed some of the description
---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index fcc0303..f789766 100644
--- a/README.md
+++ b/README.md
@@ -27,8 +27,8 @@ With it, you should be able to use the library inside your project.
 
 ## Using the new library
 
-The library exposes a new set of models used for in-toto attestations 0.1.0. If
-you wish to use the deprecated legacy Link library, please skip to the next
+The library exposes a new set of models used for in-toto 0.1.0, DSSE 1.0.0 and .
+If you wish to use the deprecated legacy Link library, please skip to the next
 section.
 
 The new library allows you to instantiate a Statement and populate it as

From c02a91c4cb3d481958fafbe48ea622e57fd5945f Mon Sep 17 00:00:00 2001
From: Sergio Felix 
Date: Thu, 23 Sep 2021 12:49:45 -0700
Subject: [PATCH 11/21] Fixed issues from the PR

* Added missing getters and getters
* Fixed issues with time formatting
* Replaced `int` to `Integer` to allow `definedInMaterial` to be null
* Added SHA256 in the sample signer and verifier.
* Removed duplicated code.
* Clarified and reformatted README.md
* Added the ability to pass key id in the sample Signer
* Removed PredicateType enum as its no longer needed.
---
 README.md                                     |  68 +++++----
 pom.xml                                       |   6 +
 .../dsse/helpers/SimpleECDSASigner.java       |   9 +-
 .../dsse/helpers/SimpleECDSAVerifier.java     |   3 +-
 .../io/github/dsse/models/IntotoEnvelope.java |  13 +-
 .../github/intoto/helpers/IntotoHelper.java   |  22 +--
 .../io/github/intoto/models/Predicate.java    |   8 +-
 .../github/intoto/models/PredicateType.java   |  26 ----
 .../io/github/intoto/models/Statement.java    |  12 +-
 .../io/github/slsa/models/Completeness.java   |  45 ++++++
 .../java/io/github/slsa/models/Metadata.java  |  82 +++++++++-
 .../io/github/slsa/models/Provenance.java     |   5 +
 .../java/io/github/slsa/models/Recipe.java    |   2 +-
 .../github/dsse/helpers/SimpleECDSATest.java  |   4 +-
 .../intoto/helpers/IntotoHelperTest.java      | 142 +++++++++++-------
 .../intoto/utilities/IntotoStubFactory.java   |  77 ++++++++++
 src/test/resources/SimpleJsonStatement.json   |  32 ++++
 17 files changed, 402 insertions(+), 154 deletions(-)
 delete mode 100644 src/main/java/io/github/intoto/models/PredicateType.java
 create mode 100644 src/test/java/io/github/intoto/utilities/IntotoStubFactory.java
 create mode 100644 src/test/resources/SimpleJsonStatement.json

diff --git a/README.md b/README.md
index f789766..f71251e 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-in-toto java
-============
+# in-toto java
 
 This repository contains an in-toto compliant library in Java. This document
 describes the repository layout, the usage purpose of this library as well as
@@ -15,11 +14,11 @@ your mvn project edit the pom.xml file to add:
 
 ```xml
     ...
-
-  io.github.in-toto
-  in-toto
-  0.3.3
-
+    
+    io.github.in-toto
+    in-toto
+    0.3.3
+    
     ...
 ```
 
@@ -36,24 +35,24 @@ follows:
 
 ```java
 Subject subject=new Subject();
-    subject.setName("curl-7.72.0.tar.bz2");
-    subject.setDigest(
-    Map.of(
-    DigestSetAlgorithmType.SHA256.toString(),
-    "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"));
-    Predicate predicate=new Predicate(); // Let's pretend this is an SLSA predicate
-    Statement statement=new Statement();
-    statement.set_type(StatementType.STATEMENT_V_0_1);
-    statement.setSubject(List.of(subject));
-    statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1);
-    statement.setPredicate(predicate);
+subject.setName("curl-7.72.0.tar.bz2");
+subject.setDigest(
+Map.of(
+DigestSetAlgorithmType.SHA256.toString(),
+"d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2"))
+Predicate predicate=createPredicate();
+Statement statement=new Statement();
+statement.set_type(StatementType.STATEMENT_V_0_1);
+statement.setSubject(List.of(subject));
+statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1);
+statement.setPredicate(predicate);
 ```
 
 Finally, you can use the built-in `IntotoHelper` class to validate and transform
 it into its JSON representation as follows:
 
 ```java
-    String jsonStatement=IntotoHelper.validateAndTransformToJson(statement);
+String jsonStatement=IntotoHelper.validateAndTransformToJson(statement);
 ```
 
 If the statement passed to the method is malformed the library will throw
@@ -66,15 +65,31 @@ features a convenience method:
 IntotoEnvelope intotoEnvelope=IntotoHelper.produceIntotoEnvelope(statement,signer);
 ```
 
-This method will accept a `io.github.intoto.models.Statement` and an
-implementation of the ` io.github.dsse.models.Signer` interface.
+This method accepts a `io.github.intoto.models.Statement` and an implementation
+of the ` io.github.dsse.models.Signer` interface.
 
 ### Implementing a Signer and a Verifier
 
 The Signer and Verifier are used to abstract away the sign and verify mechanism
 from this library. This allows the user to implement their own Signer/Verifier.
 An example of such an implementation is available in
-the `io.github.dsse.helpers package`.
+the [io.github.dsse.helpers](/src/main/java/io/github/dsse/helpers) package.
+
+### Creating a new Predicate
+
+Users that wish to extend the Predicate in the library will see that the
+Predicate contains an abstract method:
+
+```java
+String getPredicateType();
+```
+
+When extending the base Predicate type to create your own, make sure that this
+method returns a String that contains a URI identifying the type of the
+Predicate.
+
+The library will use the Predicate type and automatically fill in the
+Statement's predicateType field with its value.
 
 ## Using the legacy Link library
 
@@ -86,7 +101,7 @@ Metadata classes are located in the `io.github.legacy.models.*` package. You
 can, for example create a link as follows:
 
 ```java
-    Link link=new Link(null,null,"test",null,null);
+    Link link = new Link(null,null,"test",null,null);
 ```
 
 This will create a link object that you can operate with.
@@ -104,13 +119,12 @@ supported hashes.
 Finally, you can sign and dump a link by calling sign and dump respectively.
 
 ```java
-
-...
-    Key thiskey=RSAKey.read("src/test/resources/somekey.pem");
+    ...
+    Key thiskey = RSAKey.read("src/test/resources/somekey.pem");
     System.out.println("Loaded key: "+thiskey.computeKeyId());
 
     ...
-    Link link=new Link(null,null,"test",null,null,null);
+    Link link = new Link(null,null,"test",null,null,null);
     link.addMaterialt("alice");
 
     link.sign(thiskey);
diff --git a/pom.xml b/pom.xml
index 30ddeed..41314a2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,6 +60,11 @@
       jackson-databind
       2.12.3
     
+    
+      com.fasterxml.jackson.datatype
+      jackson-datatype-jsr310
+      2.12.3
+    
     
     
       com.google.code.gson
@@ -67,6 +72,7 @@
       2.8.6
       compile
     
+    
     
       org.junit.jupiter
       junit-jupiter-engine
diff --git a/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java b/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java
index a2082b4..f7abd86 100644
--- a/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java
+++ b/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java
@@ -7,17 +7,20 @@
 import java.security.Signature;
 import java.security.SignatureException;
 
+/** Example implementation of a {@link Signer} */
 public class SimpleECDSASigner implements Signer {
   private final PrivateKey privateKey;
+  private final String keyId;
 
-  public SimpleECDSASigner(PrivateKey privateKey) {
+  public SimpleECDSASigner(PrivateKey privateKey, String keyId) {
     this.privateKey = privateKey;
+    this.keyId = keyId;
   }
 
   @Override
   public byte[] sign(String payload)
       throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
-    Signature signature = Signature.getInstance("SHA1withECDSA");
+    Signature signature = Signature.getInstance("SHA256withECDSA");
     signature.initSign(privateKey);
     signature.update(payload.getBytes());
     return signature.sign();
@@ -25,6 +28,6 @@ public byte[] sign(String payload)
 
   @Override
   public String getKeyId() {
-    return null;
+    return this.keyId;
   }
 }
diff --git a/src/main/java/io/github/dsse/helpers/SimpleECDSAVerifier.java b/src/main/java/io/github/dsse/helpers/SimpleECDSAVerifier.java
index 8ee5575..efb537b 100644
--- a/src/main/java/io/github/dsse/helpers/SimpleECDSAVerifier.java
+++ b/src/main/java/io/github/dsse/helpers/SimpleECDSAVerifier.java
@@ -10,6 +10,7 @@
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.X509EncodedKeySpec;
 
+/** Example implementation of a {@link Verifier} */
 public class SimpleECDSAVerifier implements Verifier {
 
   private PublicKey publicKey;
@@ -18,7 +19,7 @@ public class SimpleECDSAVerifier implements Verifier {
   public boolean verify(byte[] publicKeyByteArray, byte[] encryptedMessage, String message)
       throws NoSuchAlgorithmException, SignatureException, InvalidKeySpecException,
           InvalidKeyException {
-    Signature signature = Signature.getInstance("SHA1withECDSA");
+    Signature signature = Signature.getInstance("SHA256withECDSA");
     // Create the public key from the byte array
     PublicKey publicKey =
         KeyFactory.getInstance("ECDSA").generatePublic(new X509EncodedKeySpec(publicKeyByteArray));
diff --git a/src/main/java/io/github/dsse/models/IntotoEnvelope.java b/src/main/java/io/github/dsse/models/IntotoEnvelope.java
index c8cce3e..b8bde83 100644
--- a/src/main/java/io/github/dsse/models/IntotoEnvelope.java
+++ b/src/main/java/io/github/dsse/models/IntotoEnvelope.java
@@ -9,18 +9,7 @@
  * Implementation of the a DSSE Envelope. The Envelope is the outermost layer of the attestation,
  * handling authentication and serialization. The format and protocol are defined in DSSE and
  * adopted by in-toto in ITE-5. It is a JSON object with the following fields: payloadType string,
- * required
- *
- * 

Identifier for the encoding of the payload. Always application/vnd.in-toto+json, which - * indicates that it is a JSON object with a _type field indicating its schema. - * - *

payload string, required - * - *

Base64-encoded JSON Statement. - * - *

signatures array of objects, required - * - *

One or more signatures over payloadType and payload, as defined in DSSE. + * required. * *

Defined in https://github.com/secure-systems-lab/dsse/blob/master/envelope.md */ diff --git a/src/main/java/io/github/intoto/helpers/IntotoHelper.java b/src/main/java/io/github/intoto/helpers/IntotoHelper.java index e2e59bd..749260b 100644 --- a/src/main/java/io/github/intoto/helpers/IntotoHelper.java +++ b/src/main/java/io/github/intoto/helpers/IntotoHelper.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; import io.github.dsse.models.IntotoEnvelope; import io.github.dsse.models.Signature; import io.github.dsse.models.Signer; @@ -25,7 +26,8 @@ */ public class IntotoHelper { - private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final ObjectMapper objectMapper = JsonMapper.builder().findAndAddModules().build(); + private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); @@ -48,23 +50,7 @@ public static String produceIntotoEnvelopeAsJson( Statement statement, Signer signer, boolean prettyPrint) throws InvalidModelException, JsonProcessingException, NoSuchAlgorithmException, SignatureException, InvalidKeyException { - // Get the Base64 encoded Statement to use as the payload - String jsonStatement = validateAndTransformToJson(statement, false); - String base64EncodedStatement = Base64.getEncoder().encodeToString(jsonStatement.getBytes()); - - IntotoEnvelope envelope = new IntotoEnvelope(); - // Create the signed payload with the DSSEv1 format and sign it! - byte[] signedDsseV1Payload = - signer.sign( - createPreAuthenticationEncoding(envelope.getPayloadType(), base64EncodedStatement)); - - Signature signature = new Signature(); - signature.setKeyId(signer.getKeyId()); - // The sig contains the base64 encoded version of the signedDsseV1Payload - signature.setSig(Base64.getEncoder().encodeToString(signedDsseV1Payload)); - // Let's complete the envelope - envelope.setPayload(base64EncodedStatement); - envelope.setSignatures(List.of(signature)); + IntotoEnvelope envelope = produceIntotoEnvelope(statement, signer); if (prettyPrint) { return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(envelope); } diff --git a/src/main/java/io/github/intoto/models/Predicate.java b/src/main/java/io/github/intoto/models/Predicate.java index ab651e4..0a10152 100644 --- a/src/main/java/io/github/intoto/models/Predicate.java +++ b/src/main/java/io/github/intoto/models/Predicate.java @@ -6,4 +6,10 @@ * *

Most users should migrate to a more specific attestation type, such as Provenance. */ -public class Predicate {} +public abstract class Predicate { + + /** + * Method that should return a String that contains an URI identifying the type of the Predicate. + */ + public abstract String getPredicateType(); +} diff --git a/src/main/java/io/github/intoto/models/PredicateType.java b/src/main/java/io/github/intoto/models/PredicateType.java deleted file mode 100644 index 27e189f..0000000 --- a/src/main/java/io/github/intoto/models/PredicateType.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.intoto.models; - -import com.fasterxml.jackson.annotation.JsonValue; - -/** - * This enumeration is meant to represent the supported predicate types. - * - * @see in-toto/attestation/blob/main/spec/README.md#predicate - */ -public enum PredicateType { - SLSA_PROVENANCE_V_0_1("https://slsa.dev/provenance/v0.1"), - LINK_V_0_2("https://in-toto.io/Link/v0.2"), - SPDX_V_0_1("https://spdx.dev/Document"); - - private final String value; - - PredicateType(String value) { - this.value = value; - } - - @JsonValue - public String getValue() { - return this.value; - } -} diff --git a/src/main/java/io/github/intoto/models/Statement.java b/src/main/java/io/github/intoto/models/Statement.java index 6812433..8e4eda6 100644 --- a/src/main/java/io/github/intoto/models/Statement.java +++ b/src/main/java/io/github/intoto/models/Statement.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Objects; import javax.validation.Valid; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; @@ -29,8 +30,8 @@ public class Statement { private List<@Valid Subject> subject; /** URI identifying the type of the Predicate. */ - @NotNull(message = "predicateType may not be null") - private PredicateType predicateType; + @NotBlank(message = "predicateType may not be null") + private String predicateType; /** * Additional parameters of the Predicate. Unset is treated the same as set-but-empty. MAY be @@ -54,20 +55,17 @@ public void setSubject(List subject) { this.subject = subject; } - public PredicateType getPredicateType() { + public String getPredicateType() { return predicateType; } - public void setPredicateType(PredicateType predicateType) { - this.predicateType = predicateType; - } - public Predicate getPredicate() { return predicate; } public void setPredicate(Predicate predicate) { this.predicate = predicate; + this.predicateType = predicate.getPredicateType(); } @Override diff --git a/src/main/java/io/github/slsa/models/Completeness.java b/src/main/java/io/github/slsa/models/Completeness.java index de253e4..d7f6d90 100644 --- a/src/main/java/io/github/slsa/models/Completeness.java +++ b/src/main/java/io/github/slsa/models/Completeness.java @@ -1,5 +1,7 @@ package io.github.slsa.models; +import java.util.Objects; + /** Indicates that the builder claims certain fields in this message to be complete. */ public class Completeness { @@ -17,4 +19,47 @@ public class Completeness { * prevent network access. Sometimes called “hermetic”. */ private boolean materials; + + public boolean isArguments() { + return arguments; + } + + public void setArguments(boolean arguments) { + this.arguments = arguments; + } + + public boolean isEnvironment() { + return environment; + } + + public void setEnvironment(boolean environment) { + this.environment = environment; + } + + public boolean isMaterials() { + return materials; + } + + public void setMaterials(boolean materials) { + this.materials = materials; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Completeness that = (Completeness) o; + return arguments == that.arguments + && environment == that.environment + && materials == that.materials; + } + + @Override + public int hashCode() { + return Objects.hash(arguments, environment, materials); + } } diff --git a/src/main/java/io/github/slsa/models/Metadata.java b/src/main/java/io/github/slsa/models/Metadata.java index 58606d8..79851a1 100644 --- a/src/main/java/io/github/slsa/models/Metadata.java +++ b/src/main/java/io/github/slsa/models/Metadata.java @@ -1,6 +1,8 @@ package io.github.slsa.models; -import java.time.Instant; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.time.OffsetDateTime; +import java.util.Objects; /** Other properties of the build. */ public class Metadata { @@ -12,11 +14,19 @@ public class Metadata { */ private String buildInvocationId; - /** The timestamp of when the build started. */ - private Instant buildStartedOn; + /** + * 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 buildStartedOn; - /** The timestamp of when the build completed. */ - private Instant buildFinishedOn; + /** + * 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 buildFinishedOn; /** Indicates that the builder claims certain fields in this message to be complete. */ private Completeness completeness; @@ -26,4 +36,66 @@ public class Metadata { * output. */ private boolean reproducible; + + public String getBuildInvocationId() { + return buildInvocationId; + } + + public void setBuildInvocationId(String buildInvocationId) { + this.buildInvocationId = buildInvocationId; + } + + public OffsetDateTime getBuildStartedOn() { + return buildStartedOn; + } + + public void setBuildStartedOn(OffsetDateTime buildStartedOn) { + this.buildStartedOn = buildStartedOn; + } + + public OffsetDateTime getBuildFinishedOn() { + return buildFinishedOn; + } + + public void setBuildFinishedOn(OffsetDateTime buildFinishedOn) { + this.buildFinishedOn = buildFinishedOn; + } + + public Completeness getCompleteness() { + return completeness; + } + + public void setCompleteness(Completeness completeness) { + this.completeness = completeness; + } + + public boolean isReproducible() { + return reproducible; + } + + public void setReproducible(boolean reproducible) { + this.reproducible = reproducible; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Metadata metadata = (Metadata) o; + return reproducible == metadata.reproducible + && Objects.equals(buildInvocationId, metadata.buildInvocationId) + && Objects.equals(buildStartedOn, metadata.buildStartedOn) + && Objects.equals(buildFinishedOn, metadata.buildFinishedOn) + && Objects.equals(completeness, metadata.completeness); + } + + @Override + public int hashCode() { + return Objects.hash( + buildInvocationId, buildStartedOn, buildFinishedOn, completeness, reproducible); + } } diff --git a/src/main/java/io/github/slsa/models/Provenance.java b/src/main/java/io/github/slsa/models/Provenance.java index d2c29f2..5a3bb98 100644 --- a/src/main/java/io/github/slsa/models/Provenance.java +++ b/src/main/java/io/github/slsa/models/Provenance.java @@ -88,4 +88,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(builder, recipe, metadata, materials); } + + @Override + public String getPredicateType() { + return "https://slsa.dev/provenance/v0.1"; + } } diff --git a/src/main/java/io/github/slsa/models/Recipe.java b/src/main/java/io/github/slsa/models/Recipe.java index b41fc4a..6872e0c 100644 --- a/src/main/java/io/github/slsa/models/Recipe.java +++ b/src/main/java/io/github/slsa/models/Recipe.java @@ -31,7 +31,7 @@ public class Recipe { * *

Omit this field (or use null) if the recipe does’t come from a material. */ - private int definedInMaterial; + private Integer definedInMaterial; /** * String identifying the entry point into the build. This is often a path to a configuration file diff --git a/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java b/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java index 8ed41c3..4db2891 100644 --- a/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java +++ b/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java @@ -40,7 +40,7 @@ public void simpleEcdsa_sign_shouldCorrectlySignString_whenGivenCorrectKey() thr PrivateKey privateKey = pair.getPrivate(); PublicKey publicKey = pair.getPublic(); - SimpleECDSASigner signer = new SimpleECDSASigner(privateKey); + SimpleECDSASigner signer = new SimpleECDSASigner(privateKey, "MyKey"); byte[] encryptedMessage = signer.sign(message); SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); @@ -60,7 +60,7 @@ public void test() throws Exception { PrivateKey privateKey = pair.getPrivate(); PublicKey publicKey = pair.getPublic(); - SimpleECDSASigner signer = new SimpleECDSASigner(privateKey); + SimpleECDSASigner signer = new SimpleECDSASigner(privateKey, "MyKey"); byte[] encryptedMessage = signer.sign(message); SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); diff --git a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java index 0e31906..984c5aa 100644 --- a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java +++ b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java @@ -12,15 +12,14 @@ import io.github.intoto.implementations.FakeSigner; import io.github.intoto.models.DigestSetAlgorithmType; import io.github.intoto.models.Predicate; -import io.github.intoto.models.PredicateType; import io.github.intoto.models.Statement; import io.github.intoto.models.StatementType; import io.github.intoto.models.Subject; +import io.github.intoto.utilities.IntotoStubFactory; import io.github.slsa.models.Builder; import io.github.slsa.models.Material; import io.github.slsa.models.Provenance; import io.github.slsa.models.Recipe; -import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyPair; @@ -44,26 +43,45 @@ public class IntotoHelperTest { @Test - @DisplayName("Can transform basic Statement to JSON") + @DisplayName("Can transform a Statement with provenance to JSON") public void - validateAndTransformToJson_shouldTransformStatementToJsonString_whenStatementIsCorrect() - throws IOException, InvalidModelException { + 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.toString(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); - Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + // ** The predicate ** + // Prepare the Builder + Builder builder = new Builder(); + builder.setId("mailto:person@example.com"); + // Prepare the Recipe + Recipe recipe = new Recipe(); + recipe.setType("https://example.com/Makefile"); + recipe.setEntryPoint("src:foo"); + recipe.setDefinedInMaterial(0); + // Prepare the Materials + Material material = new Material(); + material.setUri("https://example.com/example-1.2.3.tar.gz"); + material.setDigest(Map.of("sha256", "1234...")); + // Putting the Provenance together + Provenance provenancePredicate = new Provenance(); + provenancePredicate.setBuilder(builder); + provenancePredicate.setRecipe(recipe); + provenancePredicate.setMaterials(List.of(material)); + // ** Putting the Statement together ** Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); - statement.setPredicate(predicate); + statement.setPredicate(provenancePredicate); String jsonStatement = IntotoHelper.validateAndTransformToJson(statement, true); + System.out.println(jsonStatement); assertNotNull(jsonStatement); - final String SIMPLE_JSON_STATEMENT = + String JSON_STATEMENT = "{\n" + " \"_type\" : \"https://in-toto.io/Statement/v0.1\",\n" + " \"subject\" : [ {\n" @@ -73,16 +91,33 @@ public class IntotoHelperTest { + " }\n" + " } ],\n" + " \"predicateType\" : \"https://slsa.dev/provenance/v0.1\",\n" - + " \"predicate\" : { }\n" + + " \"predicate\" : {\n" + + " \"builder\" : {\n" + + " \"id\" : \"mailto:person@example.com\"\n" + + " },\n" + + " \"recipe\" : {\n" + + " \"type\" : \"https://example.com/Makefile\",\n" + + " \"definedInMaterial\" : 0,\n" + + " \"entryPoint\" : \"src:foo\"\n" + + " },\n" + + " \"metadata\" : null,\n" + + " \"materials\" : [ {\n" + + " \"uri\" : \"https://example.com/example-1.2.3.tar.gz\",\n" + + " \"digest\" : {\n" + + " \"sha256\" : \"1234...\"\n" + + " }\n" + + " } ],\n" + + " \"predicateType\" : \"https://slsa.dev/provenance/v0.1\"\n" + + " }\n" + "}"; - assertEquals(SIMPLE_JSON_STATEMENT, jsonStatement); + + assertEquals(JSON_STATEMENT, jsonStatement); } @Test - @DisplayName("Can transform a Statement with provenance to JSON") - public void - validateAndTransformToJson_shouldTransformStatementToJsonString_whenStatementContainsProvenance() - throws JsonProcessingException, InvalidModelException { + @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"); @@ -103,22 +138,19 @@ public class IntotoHelperTest { Material material = new Material(); material.setUri("https://example.com/example-1.2.3.tar.gz"); material.setDigest(Map.of("sha256", "1234...")); + // Putting the Provenance together - Provenance provenancePredicate = new Provenance(); - provenancePredicate.setBuilder(builder); - provenancePredicate.setRecipe(recipe); - provenancePredicate.setMaterials(List.of(material)); + Provenance provenancePredicate = IntotoStubFactory.createProvenancePredicateWithMetadata(); // ** Putting the Statement together ** Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(provenancePredicate); String jsonStatement = IntotoHelper.validateAndTransformToJson(statement, true); System.out.println(jsonStatement); assertNotNull(jsonStatement); - String JSON_STATEMENT = + String EXPECTED_JSON_STATEMENT = "{\n" + " \"_type\" : \"https://in-toto.io/Statement/v0.1\",\n" + " \"subject\" : [ {\n" @@ -137,17 +169,28 @@ public class IntotoHelperTest { + " \"definedInMaterial\" : 0,\n" + " \"entryPoint\" : \"src:foo\"\n" + " },\n" - + " \"metadata\" : null,\n" + + " \"metadata\" : {\n" + + " \"buildInvocationId\" : \"SomeBuildId\",\n" + + " \"buildStartedOn\" : \"1986-12-18T15:20:30+08:00\",\n" + + " \"buildFinishedOn\" : \"1986-12-18T16:20:30+08:00\",\n" + + " \"completeness\" : {\n" + + " \"arguments\" : true,\n" + + " \"environment\" : false,\n" + + " \"materials\" : true\n" + + " },\n" + + " \"reproducible\" : false\n" + + " },\n" + " \"materials\" : [ {\n" + " \"uri\" : \"https://example.com/example-1.2.3.tar.gz\",\n" + " \"digest\" : {\n" + " \"sha256\" : \"1234...\"\n" + " }\n" - + " } ]\n" + + " } ],\n" + + " \"predicateType\" : \"https://slsa.dev/provenance/v0.1\"\n" + " }\n" + "}"; - assertEquals(JSON_STATEMENT, jsonStatement); + assertEquals(EXPECTED_JSON_STATEMENT, jsonStatement); } @Test @@ -160,11 +203,10 @@ public void validateAndTransformToJson_shouldThrowException_whenStatementTypeIsM DigestSetAlgorithmType.SHA256.toString(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); - Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); statement.setSubject(List.of(subject)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(predicate); InvalidModelException thrown = @@ -186,10 +228,9 @@ public void toJson_shouldThrowException_whenStatementSubjectIsNull() { Map.of( DigestSetAlgorithmType.SHA256.toString(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); - Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(predicate); InvalidModelException thrown = @@ -211,11 +252,10 @@ public void toJson_shouldThrowException_whenStatementSubjectIsEmpty() { Map.of( DigestSetAlgorithmType.SHA256.toString(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); - Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(Collections.emptyList()); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(predicate); InvalidModelException thrown = @@ -236,11 +276,10 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNameIsNul Map.of( DigestSetAlgorithmType.SHA256.toString(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); - Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(predicate); InvalidModelException thrown = @@ -262,11 +301,10 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNameIsBla Map.of( DigestSetAlgorithmType.SHA256.toString(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); - Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(predicate); InvalidModelException thrown = @@ -284,11 +322,10 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNameIsBla public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEmpty() { Subject subject = new Subject(); subject.setName("curl-7.72.0.tar.bz2"); - Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(predicate); InvalidModelException thrown = @@ -309,11 +346,10 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEm subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest( Map.of("", "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); - Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(predicate); InvalidModelException thrown = @@ -333,11 +369,10 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEm Subject subject = new Subject(); subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest(Map.of(DigestSetAlgorithmType.SHA256.toString(), "")); - Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(predicate); InvalidModelException thrown = @@ -373,11 +408,10 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreN Map.of( DigestSetAlgorithmType.SHA256.toString(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); - Predicate predicate = new Predicate(); // Let's pretend this is an SLSA predicate + Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject, subject2, subject3)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(predicate); InvalidModelException thrown = @@ -419,7 +453,6 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreN Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(provenancePredicate); InvalidModelException thrown = @@ -464,7 +497,6 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreN Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(provenancePredicate); InvalidModelException thrown = @@ -486,6 +518,16 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_whenSimpleValu assertEquals("DSSEv1 29 http://example.com/HelloWorld 11 hello world", paeString); } + @Test + @DisplayName("Test createPreAuthenticationEncoding with UTF 8 characters") + public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharacters() { + String paeString = + IntotoHelper.createPreAuthenticationEncoding( + "http://example.com/HelloWorld", "Entwickeln Sie mit Vergnügen"); + assertEquals( + "DSSEv1 29 http://example.com/HelloWorld 28 Entwickeln Sie mit Vergnügen", paeString); + } + @Test @DisplayName("Test creating envelope from Statement") public void @@ -521,7 +563,6 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_whenSimpleValu Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(provenancePredicate); String intotoEnvelope = IntotoHelper.produceIntotoEnvelopeAsJson(statement, new FakeSigner(), true); @@ -530,9 +571,9 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_whenSimpleValu final String EXPECTED_JSON_ENVELOPE = "{\n" + " \"payloadType\" : \"application/vnd.in-toto+json\",\n" - + " \"payload\" : \"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7IlNIQTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjpudWxsLCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV19fQ==\",\n" + + " \"payload\" : \"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7IlNIQTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjpudWxsLCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMSJ9fQ==\",\n" + " \"signatures\" : [ {\n" - + " \"sig\" : \"RFNTRXYxIDI4IGFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24gNjU2IGV5SmZkSGx3WlNJNkltaDBkSEJ6T2k4dmFXNHRkRzkwYnk1cGJ5OVRkR0YwWlcxbGJuUXZkakF1TVNJc0luTjFZbXBsWTNRaU9sdDdJbTVoYldVaU9pSmpkWEpzTFRjdU56SXVNQzUwWVhJdVlub3lJaXdpWkdsblpYTjBJanA3SWxOSVFUSTFOaUk2SW1RMFpEVTRPVGxoTXpnMk9HWmlZalpoWlRFNE5UWmpNMlUxTldFek1tTmxNelU1TVROa1pUTTVOVFprTVRrM00yTmhZMk5rTXpkaVpEQXhOelJtWVRJaWZYMWRMQ0p3Y21Wa2FXTmhkR1ZVZVhCbElqb2lhSFIwY0hNNkx5OXpiSE5oTG1SbGRpOXdjbTkyWlc1aGJtTmxMM1l3TGpFaUxDSndjbVZrYVdOaGRHVWlPbnNpWW5WcGJHUmxjaUk2ZXlKcFpDSTZJbTFoYVd4MGJ6cHdaWEp6YjI1QVpYaGhiWEJzWlM1amIyMGlmU3dpY21WamFYQmxJanA3SW5SNWNHVWlPaUpvZEhSd2N6b3ZMMlY0WVcxd2JHVXVZMjl0TDAxaGEyVm1hV3hsSWl3aVpHVm1hVzVsWkVsdVRXRjBaWEpwWVd3aU9qQXNJbVZ1ZEhKNVVHOXBiblFpT2lKemNtTTZabTl2SW4wc0ltMWxkR0ZrWVhSaElqcHVkV3hzTENKdFlYUmxjbWxoYkhNaU9sdDdJblZ5YVNJNkltaDBkSEJ6T2k4dlpYaGhiWEJzWlM1amIyMHZaWGhoYlhCc1pTMHhMakl1TXk1MFlYSXVaM29pTENKa2FXZGxjM1FpT25zaWMyaGhNalUySWpvaU1USXpOQzR1TGlKOWZWMTlmUT09\",\n" + + " \"sig\" : \"RFNTRXYxIDI4IGFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24gNzI0IGV5SmZkSGx3WlNJNkltaDBkSEJ6T2k4dmFXNHRkRzkwYnk1cGJ5OVRkR0YwWlcxbGJuUXZkakF1TVNJc0luTjFZbXBsWTNRaU9sdDdJbTVoYldVaU9pSmpkWEpzTFRjdU56SXVNQzUwWVhJdVlub3lJaXdpWkdsblpYTjBJanA3SWxOSVFUSTFOaUk2SW1RMFpEVTRPVGxoTXpnMk9HWmlZalpoWlRFNE5UWmpNMlUxTldFek1tTmxNelU1TVROa1pUTTVOVFprTVRrM00yTmhZMk5rTXpkaVpEQXhOelJtWVRJaWZYMWRMQ0p3Y21Wa2FXTmhkR1ZVZVhCbElqb2lhSFIwY0hNNkx5OXpiSE5oTG1SbGRpOXdjbTkyWlc1aGJtTmxMM1l3TGpFaUxDSndjbVZrYVdOaGRHVWlPbnNpWW5WcGJHUmxjaUk2ZXlKcFpDSTZJbTFoYVd4MGJ6cHdaWEp6YjI1QVpYaGhiWEJzWlM1amIyMGlmU3dpY21WamFYQmxJanA3SW5SNWNHVWlPaUpvZEhSd2N6b3ZMMlY0WVcxd2JHVXVZMjl0TDAxaGEyVm1hV3hsSWl3aVpHVm1hVzVsWkVsdVRXRjBaWEpwWVd3aU9qQXNJbVZ1ZEhKNVVHOXBiblFpT2lKemNtTTZabTl2SW4wc0ltMWxkR0ZrWVhSaElqcHVkV3hzTENKdFlYUmxjbWxoYkhNaU9sdDdJblZ5YVNJNkltaDBkSEJ6T2k4dlpYaGhiWEJzWlM1amIyMHZaWGhoYlhCc1pTMHhMakl1TXk1MFlYSXVaM29pTENKa2FXZGxjM1FpT25zaWMyaGhNalUySWpvaU1USXpOQzR1TGlKOWZWMHNJbkJ5WldScFkyRjBaVlI1Y0dVaU9pSm9kSFJ3Y3pvdkwzTnNjMkV1WkdWMkwzQnliM1psYm1GdVkyVXZkakF1TVNKOWZRPT0=\",\n" + " \"keyid\" : \"Fake-Signer-Key-ID\"\n" + " } ]\n" + "}"; @@ -573,7 +614,6 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_whenSimpleValu Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); - statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(provenancePredicate); // Generate a key pair @@ -585,13 +625,13 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_whenSimpleValu PrivateKey privateKey = pair.getPrivate(); PublicKey publicKey = pair.getPublic(); - SimpleECDSASigner signer = new SimpleECDSASigner(privateKey); + SimpleECDSASigner signer = new SimpleECDSASigner(privateKey, "MyKey"); IntotoEnvelope intotoEnvelope = IntotoHelper.produceIntotoEnvelope(statement, signer); System.out.println(intotoEnvelope); assertNotNull(intotoEnvelope); - final String DSSE_PAYLOAD = + final String EXPECTED_DSSE_PAYLOAD = "DSSEv1 28 application/vnd.in-toto+json 656 eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7IlNIQTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjpudWxsLCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV19fQ=="; SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); @@ -606,7 +646,7 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_whenSimpleValu .get(0) .getSig() .getBytes(StandardCharsets.UTF_8)), - DSSE_PAYLOAD); + EXPECTED_DSSE_PAYLOAD); Assertions.assertTrue(result); } } diff --git a/src/test/java/io/github/intoto/utilities/IntotoStubFactory.java b/src/test/java/io/github/intoto/utilities/IntotoStubFactory.java new file mode 100644 index 0000000..c65ebd3 --- /dev/null +++ b/src/test/java/io/github/intoto/utilities/IntotoStubFactory.java @@ -0,0 +1,77 @@ +package io.github.intoto.utilities; + +import io.github.slsa.models.Builder; +import io.github.slsa.models.Completeness; +import io.github.slsa.models.Material; +import io.github.slsa.models.Metadata; +import io.github.slsa.models.Provenance; +import io.github.slsa.models.Recipe; +import java.time.OffsetDateTime; +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 the Builder + Builder builder = new Builder(); + builder.setId("mailto:person@example.com"); + // Prepare the Recipe + Recipe recipe = new Recipe(); + recipe.setType("https://example.com/Makefile"); + recipe.setEntryPoint("src:foo"); + recipe.setDefinedInMaterial(0); + // Prepare the Materials + Material material = new Material(); + material.setUri("https://example.com/example-1.2.3.tar.gz"); + material.setDigest(Map.of("sha256", "1234...")); + // Putting the Provenance together + Provenance provenancePredicate = new Provenance(); + provenancePredicate.setBuilder(builder); + provenancePredicate.setRecipe(recipe); + provenancePredicate.setMaterials(List.of(material)); + return provenancePredicate; + } + + /** + * Helper method that creates a correct {@link Provenance} with a Metadata containing timestamps. + */ + public static Provenance createProvenancePredicateWithMetadata() { + // Prepare the Builder + Builder builder = new Builder(); + builder.setId("mailto:person@example.com"); + + // Prepare the Recipe + Recipe recipe = new Recipe(); + recipe.setType("https://example.com/Makefile"); + recipe.setEntryPoint("src:foo"); + recipe.setDefinedInMaterial(0); + + // Prepare the Materials + Material material = new Material(); + material.setUri("https://example.com/example-1.2.3.tar.gz"); + material.setDigest(Map.of("sha256", "1234...")); + + // Prepare Metadata + Metadata metadata = new Metadata(); + metadata.setBuildInvocationId("SomeBuildId"); + metadata.setBuildStartedOn(OffsetDateTime.parse("1986-12-18T15:20:30+08:00")); + metadata.setBuildFinishedOn(OffsetDateTime.parse("1986-12-18T16:20:30+08:00")); + + Completeness completeness = new Completeness(); + completeness.setArguments(true); + completeness.setMaterials(true); + completeness.setEnvironment(false); + metadata.setCompleteness(completeness); + + // Putting the Provenance together + Provenance provenancePredicate = new Provenance(); + provenancePredicate.setBuilder(builder); + provenancePredicate.setRecipe(recipe); + provenancePredicate.setMaterials(List.of(material)); + provenancePredicate.setMetadata(metadata); + return provenancePredicate; + } +} diff --git a/src/test/resources/SimpleJsonStatement.json b/src/test/resources/SimpleJsonStatement.json new file mode 100644 index 0000000..be24a48 --- /dev/null +++ b/src/test/resources/SimpleJsonStatement.json @@ -0,0 +1,32 @@ +{ + "_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" + }, + "recipe": { + "type": "https://example.com/Makefile", + "definedInMaterial": 0, + "entryPoint": "src:foo" + }, + "metadata": null, + "materials": [ + { + "uri": "https://example.com/example-1.2.3.tar.gz", + "digest": { + "sha256": "1234..." + } + } + ], + "predicateType": "https://slsa.dev/provenance/v0.1" + } +} \ No newline at end of file From c1da808da1c12a5d66e83b2891bf314e0baf8ece Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Tue, 28 Sep 2021 12:21:16 -0700 Subject: [PATCH 12/21] Fixed issues detected in PR * Made sure enums are lowercase when converted to JSON * Made sure that predicate inner types does not show when converted to JSON * Created some keys for testing * Made sure the Signer accepts a byte array instead of a Java String * Removed unnecessary Base64 encoding while creating the PAE * Updated tests to reflect the changes --- .../dsse/helpers/SimpleECDSASigner.java | 4 +- .../java/io/github/dsse/models/Signer.java | 2 +- .../github/intoto/helpers/IntotoHelper.java | 18 ++- .../intoto/models/DigestSetAlgorithmType.java | 59 ++++--- .../io/github/intoto/models/Predicate.java | 3 + .../java/io/github/intoto/models/Subject.java | 3 + .../io/github/slsa/models/Provenance.java | 3 + .../github/dsse/helpers/SimpleECDSATest.java | 5 +- .../intoto/helpers/IntotoHelperTest.java | 145 +++++++++++------- .../intoto/implementations/FakeSigner.java | 5 +- .../intoto/utilities/KeyGeneratorForTest.java | 51 ++++++ src/test/resources/private.key | Bin 0 -> 67 bytes src/test/resources/public.key | Bin 0 -> 91 bytes 13 files changed, 210 insertions(+), 88 deletions(-) create mode 100644 src/test/java/io/github/intoto/utilities/KeyGeneratorForTest.java create mode 100644 src/test/resources/private.key create mode 100644 src/test/resources/public.key diff --git a/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java b/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java index f7abd86..7616bc0 100644 --- a/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java +++ b/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java @@ -18,11 +18,11 @@ public SimpleECDSASigner(PrivateKey privateKey, String keyId) { } @Override - public byte[] sign(String payload) + public byte[] sign(byte[] payload) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { Signature signature = Signature.getInstance("SHA256withECDSA"); signature.initSign(privateKey); - signature.update(payload.getBytes()); + signature.update(payload); return signature.sign(); } diff --git a/src/main/java/io/github/dsse/models/Signer.java b/src/main/java/io/github/dsse/models/Signer.java index 9ce9fea..f54627a 100644 --- a/src/main/java/io/github/dsse/models/Signer.java +++ b/src/main/java/io/github/dsse/models/Signer.java @@ -12,7 +12,7 @@ public interface Signer { * * @param payload the message that you want to sign. */ - byte[] sign(String payload) + byte[] sign(byte[] payload) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException; /** Returns the ID of this key, or null if not supported. */ diff --git a/src/main/java/io/github/intoto/helpers/IntotoHelper.java b/src/main/java/io/github/intoto/helpers/IntotoHelper.java index 749260b..6e8088b 100644 --- a/src/main/java/io/github/intoto/helpers/IntotoHelper.java +++ b/src/main/java/io/github/intoto/helpers/IntotoHelper.java @@ -9,6 +9,7 @@ import io.github.dsse.models.Signer; import io.github.intoto.exceptions.InvalidModelException; import io.github.intoto.models.Statement; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; @@ -81,9 +82,9 @@ public static IntotoEnvelope produceIntotoEnvelope(Statement statement, Signer s IntotoEnvelope envelope = new IntotoEnvelope(); // Create the signed payload with the DSSEv1 format and sign it! - byte[] signedDsseV1Payload = - signer.sign( - createPreAuthenticationEncoding(envelope.getPayloadType(), base64EncodedStatement)); + byte[] paeByteArray = + createPreAuthenticationEncoding(envelope.getPayloadType(), jsonStatement.getBytes()); + byte[] signedDsseV1Payload = signer.sign(paeByteArray); Signature signature = new Signature(); signature.setKeyId(signer.getKeyId()); // The sig contains the base64 encoded version of the signedDsseV1Payload @@ -106,12 +107,17 @@ public static IntotoEnvelope produceIntotoEnvelope(Statement statement, Signer s * LEN(s) = ASCII decimal encoding of the byte length of s, with no leading zeros *

    * @param payloadType the type of payload. Fixed for in-toto Envelopes
-   * @param payload the base64 encoded Statement in JSON
+   * @param payload raw payload in bytes
    * @return will return a Pre Authentication Encoding String.
    */
-  public static String createPreAuthenticationEncoding(String payloadType, String payload) {
+  public static byte[] createPreAuthenticationEncoding(String payloadType, byte[] payload) {
     return String.format(
-        "DSSEv1 %d %s %d %s", payloadType.length(), payloadType, payload.length(), payload);
+            "DSSEv1 %d %s %d %s",
+            payloadType.length(),
+            payloadType,
+            payload.length,
+            new String(payload, StandardCharsets.UTF_8))
+        .getBytes(StandardCharsets.UTF_8);
   }
 
   /**
diff --git a/src/main/java/io/github/intoto/models/DigestSetAlgorithmType.java b/src/main/java/io/github/intoto/models/DigestSetAlgorithmType.java
index 2ceabfb..24afbf8 100644
--- a/src/main/java/io/github/intoto/models/DigestSetAlgorithmType.java
+++ b/src/main/java/io/github/intoto/models/DigestSetAlgorithmType.java
@@ -1,24 +1,45 @@
 package io.github.intoto.models;
 
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonValue;
+
 /** Helper Enum with common algorithm types, could be used to populate the {@link Subject} digest */
 public enum DigestSetAlgorithmType {
-  SHA256,
-  SHA224,
-  SHA384,
-  SHA512,
-  SHA512_224,
-  SHA512_256,
-  SHA3_224,
-  SHA3_256,
-  SHA3_384,
-  SHA3_512,
-  SHAKE128,
-  SHAKE256,
-  BLAKE2B,
-  BLAKE2S,
-  RIPEMD160,
-  SM3,
-  GOST,
-  SHA1,
-  MD5
+  @JsonProperty("sha256")
+  SHA256("sha256"),
+  SHA224("sha224"),
+  SHA384("sha384"),
+  SHA512("sha512"),
+  SHA512_224("sha512_224"),
+  SHA512_256("sha512_256"),
+  SHA3_224("sha3_224"),
+  SHA3_256("sha3_256"),
+  SHA3_384("sha3_384"),
+  SHA3_512("sha3_512"),
+  SHAKE128("shake128"),
+  SHAKE256("shake256"),
+  BLAKE2B("blake2B"),
+  BLAKE2S("blake2S"),
+  RIPEMD160("ripemd160"),
+  SM3("sm3"),
+  GOST("gost"),
+  SHA1("sha1"),
+  MD5("md5");
+
+  private final String value;
+
+  DigestSetAlgorithmType(String key) {
+    this.value = key;
+  }
+
+  @JsonCreator
+  public static DigestSetAlgorithmType fromString(String key) {
+    return key == null ? null : DigestSetAlgorithmType.valueOf(key.toUpperCase());
+  }
+
+  @JsonValue
+  public String getValue() {
+    return value;
+  }
 }
diff --git a/src/main/java/io/github/intoto/models/Predicate.java b/src/main/java/io/github/intoto/models/Predicate.java
index 0a10152..7a61fdf 100644
--- a/src/main/java/io/github/intoto/models/Predicate.java
+++ b/src/main/java/io/github/intoto/models/Predicate.java
@@ -1,5 +1,7 @@
 package io.github.intoto.models;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
 /**
  * A generic attestation type with a schema isomorphic to in-toto 0.9. This allows existing in-toto
  * users to make minimal changes to upgrade to the new attestation format.
@@ -11,5 +13,6 @@ public abstract class Predicate {
   /**
    * Method that should return a String that contains an URI identifying the type of the Predicate.
    */
+  @JsonIgnore
   public abstract String getPredicateType();
 }
diff --git a/src/main/java/io/github/intoto/models/Subject.java b/src/main/java/io/github/intoto/models/Subject.java
index 47eb775..245bba4 100644
--- a/src/main/java/io/github/intoto/models/Subject.java
+++ b/src/main/java/io/github/intoto/models/Subject.java
@@ -30,6 +30,9 @@ public class Subject {
    * 

Two DigestSets are considered matching if ANY of the fields match. The producer and consumer * must agree on acceptable algorithms. If there are no overlapping algorithms, the subject is * considered not matching. + * + *

This implementation 2 Strings instead of an enum as the key in order to facilitate future + * extensions. */ @NotEmpty(message = "digest must not be empty") private Map< diff --git a/src/main/java/io/github/slsa/models/Provenance.java b/src/main/java/io/github/slsa/models/Provenance.java index 5a3bb98..71e3959 100644 --- a/src/main/java/io/github/slsa/models/Provenance.java +++ b/src/main/java/io/github/slsa/models/Provenance.java @@ -1,5 +1,7 @@ package io.github.slsa.models; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import io.github.intoto.models.Predicate; import java.util.List; import java.util.Objects; @@ -26,6 +28,7 @@ public class Provenance extends Predicate { private @Valid Recipe recipe; /** Other properties of the build. */ + @JsonInclude(Include.NON_NULL) private Metadata metadata; /** diff --git a/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java b/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java index 4db2891..b8bd66d 100644 --- a/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java +++ b/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java @@ -1,5 +1,6 @@ package io.github.dsse.helpers; +import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -41,7 +42,7 @@ public void simpleEcdsa_sign_shouldCorrectlySignString_whenGivenCorrectKey() thr PublicKey publicKey = pair.getPublic(); SimpleECDSASigner signer = new SimpleECDSASigner(privateKey, "MyKey"); - byte[] encryptedMessage = signer.sign(message); + byte[] encryptedMessage = signer.sign(message.getBytes(StandardCharsets.UTF_8)); SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); boolean result = verifier.verify(publicKey.getEncoded(), encryptedMessage, message); @@ -61,7 +62,7 @@ public void test() throws Exception { PublicKey publicKey = pair.getPublic(); SimpleECDSASigner signer = new SimpleECDSASigner(privateKey, "MyKey"); - byte[] encryptedMessage = signer.sign(message); + byte[] encryptedMessage = signer.sign(message.getBytes(StandardCharsets.UTF_8)); SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); boolean result = verifier.verify(publicKey.getEncoded(), encryptedMessage, message); diff --git a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java index 984c5aa..dcd35b8 100644 --- a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java +++ b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java @@ -1,5 +1,6 @@ package io.github.intoto.helpers; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -20,22 +21,26 @@ import io.github.slsa.models.Material; import io.github.slsa.models.Provenance; import io.github.slsa.models.Recipe; +import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.security.InvalidKeyException; +import java.security.KeyFactory; import java.security.KeyPair; -import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.SecureRandom; import java.security.Security; import java.security.SignatureException; -import java.security.spec.ECGenParameterSpec; -import java.util.Base64; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.util.Collections; import java.util.List; import java.util.Map; 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; @@ -52,7 +57,7 @@ public class IntotoHelperTest { subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); // ** The predicate ** // Prepare the Builder @@ -87,7 +92,7 @@ public class IntotoHelperTest { + " \"subject\" : [ {\n" + " \"name\" : \"curl-7.72.0.tar.bz2\",\n" + " \"digest\" : {\n" - + " \"SHA256\" : \"d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2\"\n" + + " \"sha256\" : \"d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2\"\n" + " }\n" + " } ],\n" + " \"predicateType\" : \"https://slsa.dev/provenance/v0.1\",\n" @@ -100,14 +105,12 @@ public class IntotoHelperTest { + " \"definedInMaterial\" : 0,\n" + " \"entryPoint\" : \"src:foo\"\n" + " },\n" - + " \"metadata\" : null,\n" + " \"materials\" : [ {\n" + " \"uri\" : \"https://example.com/example-1.2.3.tar.gz\",\n" + " \"digest\" : {\n" + " \"sha256\" : \"1234...\"\n" + " }\n" - + " } ],\n" - + " \"predicateType\" : \"https://slsa.dev/provenance/v0.1\"\n" + + " } ]\n" + " }\n" + "}"; @@ -123,7 +126,7 @@ public void validateAndTransformToJson_shouldTransformStatementToJsonString_With subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); // ** The predicate ** // Prepare the Builder @@ -156,7 +159,7 @@ public void validateAndTransformToJson_shouldTransformStatementToJsonString_With + " \"subject\" : [ {\n" + " \"name\" : \"curl-7.72.0.tar.bz2\",\n" + " \"digest\" : {\n" - + " \"SHA256\" : \"d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2\"\n" + + " \"sha256\" : \"d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2\"\n" + " }\n" + " } ],\n" + " \"predicateType\" : \"https://slsa.dev/provenance/v0.1\",\n" @@ -185,8 +188,7 @@ public void validateAndTransformToJson_shouldTransformStatementToJsonString_With + " \"digest\" : {\n" + " \"sha256\" : \"1234...\"\n" + " }\n" - + " } ],\n" - + " \"predicateType\" : \"https://slsa.dev/provenance/v0.1\"\n" + + " } ]\n" + " }\n" + "}"; @@ -200,7 +202,7 @@ public void validateAndTransformToJson_shouldThrowException_whenStatementTypeIsM subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); @@ -226,7 +228,7 @@ public void toJson_shouldThrowException_whenStatementSubjectIsNull() { subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); @@ -250,7 +252,7 @@ public void toJson_shouldThrowException_whenStatementSubjectIsEmpty() { subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); @@ -274,7 +276,7 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNameIsNul Subject subject = new Subject(); subject.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); @@ -299,7 +301,7 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNameIsBla subject.setName(""); subject.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); @@ -368,7 +370,7 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEm validateAndTransformToJson_shouldThrowException_whenSubjectDigestContainsEmptyValueStrings() { Subject subject = new Subject(); subject.setName("curl-7.72.0.tar.bz2"); - subject.setDigest(Map.of(DigestSetAlgorithmType.SHA256.toString(), "")); + subject.setDigest(Map.of(DigestSetAlgorithmType.SHA256.getValue(), "")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); @@ -392,21 +394,21 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreN subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Subject subject2 = new Subject(); subject2.setName("curl-7.72.0.tar.bz2"); subject2.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Subject subject3 = new Subject(); subject3.setName("curl-7.72.0.tar.bz2"); subject3.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); @@ -433,7 +435,7 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreN subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); // ** The predicate ** // Prepare the Recipe @@ -474,7 +476,7 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreN subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); // ** The predicate ** // Prepare the Builder @@ -512,26 +514,46 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreN @Test @DisplayName("Test createPreAuthenticationEncoding") public void createPreAuthenticationEncoding_shouldCorrectlyEncode_whenSimpleValues() { - String paeString = + String helloWordString = "hello world"; + byte[] paeString = IntotoHelper.createPreAuthenticationEncoding( - "http://example.com/HelloWorld", "hello world"); - assertEquals("DSSEv1 29 http://example.com/HelloWorld 11 hello world", paeString); + "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 paeString = + String utf8String = "Entwickeln Sie mit Vergnügen"; + byte[] paeString = IntotoHelper.createPreAuthenticationEncoding( - "http://example.com/HelloWorld", "Entwickeln Sie mit Vergnügen"); - assertEquals( - "DSSEv1 29 http://example.com/HelloWorld 28 Entwickeln Sie mit Vergnügen", paeString); + "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 creating envelope from Statement") public void - createPreAuthenticationEncoding_shouldCorrectlyCreateAnEnvelope_whenCompleteStatementIsPassed() + produceIntotoEnvelopeAsJson_shouldCorrectlyCreateAnEnvelope_whenCompleteStatementIsPassed() throws InvalidModelException, JsonProcessingException, NoSuchAlgorithmException, SignatureException, InvalidKeyException { // ** The subject ** @@ -539,7 +561,7 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact subject.setName("curl-7.72.0.tar.bz2"); subject.setDigest( Map.of( - DigestSetAlgorithmType.SHA256.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); // ** The predicate ** // Prepare the Builder @@ -571,9 +593,9 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact final String EXPECTED_JSON_ENVELOPE = "{\n" + " \"payloadType\" : \"application/vnd.in-toto+json\",\n" - + " \"payload\" : \"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7IlNIQTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjpudWxsLCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMSJ9fQ==\",\n" + + " \"payload\" : \"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1hdGVyaWFscyI6W3sidXJpIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9leGFtcGxlLTEuMi4zLnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiIxMjM0Li4uIn19XX19\",\n" + " \"signatures\" : [ {\n" - + " \"sig\" : \"RFNTRXYxIDI4IGFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24gNzI0IGV5SmZkSGx3WlNJNkltaDBkSEJ6T2k4dmFXNHRkRzkwYnk1cGJ5OVRkR0YwWlcxbGJuUXZkakF1TVNJc0luTjFZbXBsWTNRaU9sdDdJbTVoYldVaU9pSmpkWEpzTFRjdU56SXVNQzUwWVhJdVlub3lJaXdpWkdsblpYTjBJanA3SWxOSVFUSTFOaUk2SW1RMFpEVTRPVGxoTXpnMk9HWmlZalpoWlRFNE5UWmpNMlUxTldFek1tTmxNelU1TVROa1pUTTVOVFprTVRrM00yTmhZMk5rTXpkaVpEQXhOelJtWVRJaWZYMWRMQ0p3Y21Wa2FXTmhkR1ZVZVhCbElqb2lhSFIwY0hNNkx5OXpiSE5oTG1SbGRpOXdjbTkyWlc1aGJtTmxMM1l3TGpFaUxDSndjbVZrYVdOaGRHVWlPbnNpWW5WcGJHUmxjaUk2ZXlKcFpDSTZJbTFoYVd4MGJ6cHdaWEp6YjI1QVpYaGhiWEJzWlM1amIyMGlmU3dpY21WamFYQmxJanA3SW5SNWNHVWlPaUpvZEhSd2N6b3ZMMlY0WVcxd2JHVXVZMjl0TDAxaGEyVm1hV3hsSWl3aVpHVm1hVzVsWkVsdVRXRjBaWEpwWVd3aU9qQXNJbVZ1ZEhKNVVHOXBiblFpT2lKemNtTTZabTl2SW4wc0ltMWxkR0ZrWVhSaElqcHVkV3hzTENKdFlYUmxjbWxoYkhNaU9sdDdJblZ5YVNJNkltaDBkSEJ6T2k4dlpYaGhiWEJzWlM1amIyMHZaWGhoYlhCc1pTMHhMakl1TXk1MFlYSXVaM29pTENKa2FXZGxjM1FpT25zaWMyaGhNalUySWpvaU1USXpOQzR1TGlKOWZWMHNJbkJ5WldScFkyRjBaVlI1Y0dVaU9pSm9kSFJ3Y3pvdkwzTnNjMkV1WkdWMkwzQnliM1psYm1GdVkyVXZkakF1TVNKOWZRPT0=\",\n" + + " \"sig\" : \"RFNTRXYxIDI4IGFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24gNDc0IHsiX3R5cGUiOiJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiY3VybC03LjcyLjAudGFyLmJ6MiIsImRpZ2VzdCI6eyJzaGEyNTYiOiJkNGQ1ODk5YTM4NjhmYmI2YWUxODU2YzNlNTVhMzJjZTM1OTEzZGUzOTU2ZDE5NzNjYWNjZDM3YmQwMTc0ZmEyIn19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vc2xzYS5kZXYvcHJvdmVuYW5jZS92MC4xIiwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJtYWlsdG86cGVyc29uQGV4YW1wbGUuY29tIn0sInJlY2lwZSI6eyJ0eXBlIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9NYWtlZmlsZSIsImRlZmluZWRJbk1hdGVyaWFsIjowLCJlbnRyeVBvaW50Ijoic3JjOmZvbyJ9LCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV19fQ==\",\n" + " \"keyid\" : \"Fake-Signer-Key-ID\"\n" + " } ]\n" + "}"; @@ -583,14 +605,14 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact @Test @DisplayName("Test creating envelope with simple encryption") public void - createPreAuthenticationEncoding_shouldCorrectlyCreateAnEnvelope_whenUsingSimpleEncryption() + 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.toString(), + DigestSetAlgorithmType.SHA256.getValue(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); // ** The predicate ** // Prepare the Builder @@ -617,36 +639,49 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact statement.setPredicate(provenancePredicate); // Generate a key pair - Security.addProvider(new BouncyCastleProvider()); - - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); - keyGen.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom()); - KeyPair pair = keyGen.generateKeyPair(); - PrivateKey privateKey = pair.getPrivate(); - PublicKey publicKey = pair.getPublic(); - - SimpleECDSASigner signer = new SimpleECDSASigner(privateKey, "MyKey"); + 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 656 eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7IlNIQTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjpudWxsLCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV19fQ=="; + "DSSEv1 28 application/vnd.in-toto+json 474 {\"_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\"},\"recipe\":{\"type\":\"https://example.com/Makefile\",\"definedInMaterial\":0,\"entryPoint\":\"src:foo\"},\"materials\":[{\"uri\":\"https://example.com/example-1.2.3.tar.gz\",\"digest\":{\"sha256\":\"1234...\"}}]}}"; SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); boolean result = verifier.verify( - publicKey.getEncoded(), - Base64.getDecoder() - .decode( - intotoEnvelope - .getSignatures() - .get(0) - .getSig() - .getBytes(StandardCharsets.UTF_8)), + 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 IOException, NoSuchAlgorithmException, InvalidKeySpecException { + Security.addProvider(new BouncyCastleProvider()); + // Getting ClassLoader obj + ClassLoader classLoader = this.getClass().getClassLoader(); + + // Getting public key + File filePublicKey = new File(classLoader.getResource("public.key").getFile()); + byte[] encodedPublicKey = Files.readAllBytes(filePublicKey.toPath()); + PublicKey publicKey = + KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(encodedPublicKey)); + System.out.println(publicKey.toString()); + + // Getting private key + File filePrivateKey = new File(classLoader.getResource("private.key").getFile()); + byte[] encodedPrivateKey = Files.readAllBytes(filePrivateKey.toPath()); + PrivateKey privateKey = + KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(encodedPrivateKey)); + System.out.println(privateKey.toString()); + return new KeyPair(publicKey, privateKey); + } } diff --git a/src/test/java/io/github/intoto/implementations/FakeSigner.java b/src/test/java/io/github/intoto/implementations/FakeSigner.java index 00155fa..ae1a6a5 100644 --- a/src/test/java/io/github/intoto/implementations/FakeSigner.java +++ b/src/test/java/io/github/intoto/implementations/FakeSigner.java @@ -1,14 +1,13 @@ package io.github.intoto.implementations; import io.github.dsse.models.Signer; -import java.nio.charset.StandardCharsets; /** Fake Signer implementation for testing only. */ public class FakeSigner implements Signer { @Override - public byte[] sign(String payload) { - return payload.getBytes(StandardCharsets.UTF_8); + public byte[] sign(byte[] payload) { + return payload; } @Override diff --git a/src/test/java/io/github/intoto/utilities/KeyGeneratorForTest.java b/src/test/java/io/github/intoto/utilities/KeyGeneratorForTest.java new file mode 100644 index 0000000..6f4fea8 --- /dev/null +++ b/src/test/java/io/github/intoto/utilities/KeyGeneratorForTest.java @@ -0,0 +1,51 @@ +package io.github.intoto.utilities; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +/** Utility class to generate new private.key and public.key files */ +public class KeyGeneratorForTest { + public static void main(String[] args) + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + Security.addProvider(new BouncyCastleProvider()); + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); + keyGen.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom()); + KeyPair keyPair = keyGen.generateKeyPair(); + PrivateKey privateKey = keyPair.getPrivate(); + PublicKey publicKey = keyPair.getPublic(); + + X509EncodedKeySpec x509EncodedKeySpecPublic = new X509EncodedKeySpec(publicKey.getEncoded()); + PKCS8EncodedKeySpec pkcs8EncodedKeySpecPrivate = + new PKCS8EncodedKeySpec(privateKey.getEncoded()); + + try { + FileOutputStream fos = new FileOutputStream("public.key"); + fos.write(x509EncodedKeySpecPublic.getEncoded()); + fos.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + try { + FileOutputStream fos = new FileOutputStream("private.key"); + fos.write(pkcs8EncodedKeySpecPrivate.getEncoded()); + fos.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/test/resources/private.key b/src/test/resources/private.key new file mode 100644 index 0000000000000000000000000000000000000000..6c25fb5b10167fc75cb1db87a5c3e41392eda697 GIT binary patch literal 67 zcmXqTWMX765N2c7YV$Z}%f!gW0cJ2Wva_fgs4_7!vM5~Ioz!Y$^?1eeE0s*wa-8y~ VEIw#wzwFuRbd@is^xS4;0suiq7P$Zb literal 0 HcmV?d00001 diff --git a/src/test/resources/public.key b/src/test/resources/public.key new file mode 100644 index 0000000000000000000000000000000000000000..99e728b2deaf68099db246e4634527dd206907e6 GIT binary patch literal 91 zcmXqrG!SNE*J|@PXUoLM#sOw9GqN)~F|crbep#~6?Z(DARjb!eE8c5;?=#z4X7!6I u4}TfenYcZfHIY{~d}r2AZ-?!UJDUY}_x(D#ai4sunf0=;y@C~PTv7n;h9tlM literal 0 HcmV?d00001 From cac7f15f4e3b6649dbd2a9dc1a2c85ac4fe2acfd Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Mon, 4 Oct 2021 12:30:20 -0700 Subject: [PATCH 13/21] Made Java implementation work with fixed keys generated by OpenSSL In an effort to make testing easier against other libs. The Java implementation now uses standard keys generated with the OpenSSL command. --- README.md | 13 ++- intoto_test.attestation | 1 + .../intoto/helpers/IntotoHelperTest.java | 27 ++--- .../intoto/utilities/KeyGeneratorForTest.java | 51 --------- .../github/intoto/utilities/KeyUtilities.java | 54 ++++++++++ .../utilities/TestEnvelopeGenerator.java | 98 ++++++++++++++++++ src/test/resources/p8private.pem | 8 ++ src/test/resources/private.key | Bin 67 -> 0 bytes src/test/resources/private.pem | 7 ++ src/test/resources/public.key | Bin 91 -> 0 bytes src/test/resources/public.pem | 6 ++ 11 files changed, 197 insertions(+), 68 deletions(-) create mode 100644 intoto_test.attestation delete mode 100644 src/test/java/io/github/intoto/utilities/KeyGeneratorForTest.java create mode 100644 src/test/java/io/github/intoto/utilities/KeyUtilities.java create mode 100644 src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java create mode 100644 src/test/resources/p8private.pem delete mode 100644 src/test/resources/private.key create mode 100644 src/test/resources/private.pem delete mode 100644 src/test/resources/public.key create mode 100644 src/test/resources/public.pem diff --git a/README.md b/README.md index f71251e..b1aeb58 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ String jsonStatement=IntotoHelper.validateAndTransformToJson(statement); If the statement passed to the method is malformed the library will throw an `InvalidModelException` that will contain a message with the errors. -If you, however wish to create a DSSE based In-toto envelope, The library +If you, however wish to create a DSSE based In-toto envelope, the library features a convenience method: ```java @@ -91,6 +91,17 @@ Predicate. The library will use the Predicate type and automatically fill in the Statement's predicateType field with its value. + +### Generating keys + +The keys in the project where generated with: + +``` +openssl ecparam -genkey -name secp521r1 -noout -out private.pem #generate private key +openssl ec -in private.pem -pubout -out public.pem #generate public key +openssl pkcs8 -topk8 -nocrypt -in private.pem -out p8private.pem #convert to pkcs8 format +``` + ## Using the legacy Link library The library exposes a series of objects and convenience methods to create, sign, diff --git a/intoto_test.attestation b/intoto_test.attestation new file mode 100644 index 0000000..42407cf --- /dev/null +++ b/intoto_test.attestation @@ -0,0 +1 @@ +{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1hdGVyaWFscyI6W3sidXJpIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9leGFtcGxlLTEuMi4zLnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiIxMjM0Li4uIn19XX19","signatures":[{"sig":"MIGIAkIBSNsRnhB2KspzighMidZplukYb2Gnd6l+1gDIk4V/yyAm75fPEKSa6k+ysDNWlqiKlkjbrNVfxSJxOt5LaO6RtPsCQgHibHfYTN5KzBRA5Ax6A6vdDA2jwx5LfFjHKAJVze+BeA7RXDsmLIO9YgVwxnvys0Mu/3I4We5AeVglCXOJo+LydA==","keyid":"MyKey"}]} \ No newline at end of file diff --git a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java index dcd35b8..722427e 100644 --- a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java +++ b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java @@ -1,5 +1,7 @@ package io.github.intoto.helpers; +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.assertNotNull; @@ -22,23 +24,18 @@ import io.github.slsa.models.Provenance; import io.github.slsa.models.Recipe; import java.io.File; -import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.security.InvalidKeyException; -import java.security.KeyFactory; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Security; import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; import java.util.Collections; 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; @@ -663,24 +660,22 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact * Gets the keys from the resources directory (public.key and private.key) and loads them up as a * {@link KeyPair} */ - private KeyPair getKeyPairFromFile() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + private KeyPair getKeyPairFromFile() throws Exception { Security.addProvider(new BouncyCastleProvider()); // Getting ClassLoader obj ClassLoader classLoader = this.getClass().getClassLoader(); // Getting public key - File filePublicKey = new File(classLoader.getResource("public.key").getFile()); - byte[] encodedPublicKey = Files.readAllBytes(filePublicKey.toPath()); - PublicKey publicKey = - KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(encodedPublicKey)); + File publicKeyFile = + new File(Objects.requireNonNull(classLoader.getResource("public.pem")).getFile()); + PublicKey publicKey = readPublicKey(publicKeyFile); System.out.println(publicKey.toString()); // Getting private key - File filePrivateKey = new File(classLoader.getResource("private.key").getFile()); - byte[] encodedPrivateKey = Files.readAllBytes(filePrivateKey.toPath()); - PrivateKey privateKey = - KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(encodedPrivateKey)); + File privateKeyFile = + new File(Objects.requireNonNull(classLoader.getResource("p8private.pem")).getFile()); + PrivateKey privateKey = readPrivateKey(privateKeyFile); + System.out.println(privateKey.toString()); return new KeyPair(publicKey, privateKey); } diff --git a/src/test/java/io/github/intoto/utilities/KeyGeneratorForTest.java b/src/test/java/io/github/intoto/utilities/KeyGeneratorForTest.java deleted file mode 100644 index 6f4fea8..0000000 --- a/src/test/java/io/github/intoto/utilities/KeyGeneratorForTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.intoto.utilities; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.Security; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -/** Utility class to generate new private.key and public.key files */ -public class KeyGeneratorForTest { - public static void main(String[] args) - throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { - Security.addProvider(new BouncyCastleProvider()); - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); - keyGen.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom()); - KeyPair keyPair = keyGen.generateKeyPair(); - PrivateKey privateKey = keyPair.getPrivate(); - PublicKey publicKey = keyPair.getPublic(); - - X509EncodedKeySpec x509EncodedKeySpecPublic = new X509EncodedKeySpec(publicKey.getEncoded()); - PKCS8EncodedKeySpec pkcs8EncodedKeySpecPrivate = - new PKCS8EncodedKeySpec(privateKey.getEncoded()); - - try { - FileOutputStream fos = new FileOutputStream("public.key"); - fos.write(x509EncodedKeySpecPublic.getEncoded()); - fos.close(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - try { - FileOutputStream fos = new FileOutputStream("private.key"); - fos.write(pkcs8EncodedKeySpecPrivate.getEncoded()); - fos.close(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } -} diff --git a/src/test/java/io/github/intoto/utilities/KeyUtilities.java b/src/test/java/io/github/intoto/utilities/KeyUtilities.java new file mode 100644 index 0000000..9e8ef88 --- /dev/null +++ b/src/test/java/io/github/intoto/utilities/KeyUtilities.java @@ -0,0 +1,54 @@ +package io.github.intoto.utilities; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; + +/** Convenience methods for handling keys. */ +public final class KeyUtilities { + + /** + * Reads Public Key from file using {@link PEMParser} + * + * @param file the file that contains the public key + * @return a PublicKey + * @throws IOException thrown when there are issues reading the file. + */ + public static PublicKey readPublicKey(File file) throws IOException { + try (FileReader keyReader = new FileReader(file)) { + PEMParser pemParser = new PEMParser(keyReader); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(pemParser.readObject()); + return converter.getPublicKey(publicKeyInfo); + } + } + + /** + * Reads the private key from a file using the PemReader. + * + * @param file the file that contains the pkcs8 encoded pivate key. + * @return the PrivateKey + * @throws Exception + */ + public static PrivateKey readPrivateKey(File file) throws Exception { + KeyFactory factory = KeyFactory.getInstance("EC"); + + try (FileReader keyReader = new FileReader(file); + PemReader pemReader = new PemReader(keyReader)) { + + PemObject pemObject = pemReader.readPemObject(); + byte[] content = pemObject.getContent(); + PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content); + return factory.generatePrivate(privKeySpec); + } + } +} diff --git a/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java b/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java new file mode 100644 index 0000000..ec44a15 --- /dev/null +++ b/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java @@ -0,0 +1,98 @@ +package io.github.intoto.utilities; + +import static io.github.intoto.utilities.KeyUtilities.readPrivateKey; +import static io.github.intoto.utilities.KeyUtilities.readPublicKey; + +import io.github.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.StatementType; +import io.github.intoto.models.Subject; +import io.github.slsa.models.Builder; +import io.github.slsa.models.Material; +import io.github.slsa.models.Provenance; +import io.github.slsa.models.Recipe; +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.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 the Builder + Builder builder = new Builder(); + builder.setId("mailto:person@example.com"); + // Prepare the Recipe + Recipe recipe = new Recipe(); + recipe.setType("https://example.com/Makefile"); + recipe.setEntryPoint("src:foo"); + recipe.setDefinedInMaterial(0); + // Prepare the Materials + Material material = new Material(); + material.setUri("https://example.com/example-1.2.3.tar.gz"); + material.setDigest(Map.of("sha256", "1234...")); + // Putting the Provenance together + Provenance provenancePredicate = new Provenance(); + provenancePredicate.setBuilder(builder); + provenancePredicate.setRecipe(recipe); + provenancePredicate.setMaterials(List.of(material)); + // ** Putting the Statement together ** + Statement statement = new Statement(); + statement.set_type(StatementType.STATEMENT_V_0_1); + 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_test.attestation"), 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); + } +} diff --git a/src/test/resources/p8private.pem b/src/test/resources/p8private.pem new file mode 100644 index 0000000..5e5006c --- /dev/null +++ b/src/test/resources/p8private.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAZGkSfHSBU+RhEFA5 +dE35/CbdCdTTNcN1xEx00sFVh4AE75XOjg7n2Ol9U8viVwvL0/KMYrdjJ9ORz9HM +ul2CL9qhgYkDgYYABADBaCgDpkf8lh0csRXUdYE3YW1oqq40Jj1N0IrnBLVuP95f +qTAN8SG55p3MI7OiGQ5MAD04ThhfzrK06JwUB+w4YgCJW4QTVfbXmVOu++2yWxpk +8o6IKgPBDc5p4UrOEDIEpa5H1kvNDszOjajSRBKB+oeFLPjipkjPY8xHcYbRVrna +qg== +-----END PRIVATE KEY----- diff --git a/src/test/resources/private.key b/src/test/resources/private.key deleted file mode 100644 index 6c25fb5b10167fc75cb1db87a5c3e41392eda697..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67 zcmXqTWMX765N2c7YV$Z}%f!gW0cJ2Wva_fgs4_7!vM5~Ioz!Y$^?1eeE0s*wa-8y~ VEIw#wzwFuRbd@is^xS4;0suiq7P$Zb diff --git a/src/test/resources/private.pem b/src/test/resources/private.pem new file mode 100644 index 0000000..675fd92 --- /dev/null +++ b/src/test/resources/private.pem @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIAZGkSfHSBU+RhEFA5dE35/CbdCdTTNcN1xEx00sFVh4AE75XOjg7n +2Ol9U8viVwvL0/KMYrdjJ9ORz9HMul2CL9qgBwYFK4EEACOhgYkDgYYABADBaCgD +pkf8lh0csRXUdYE3YW1oqq40Jj1N0IrnBLVuP95fqTAN8SG55p3MI7OiGQ5MAD04 +ThhfzrK06JwUB+w4YgCJW4QTVfbXmVOu++2yWxpk8o6IKgPBDc5p4UrOEDIEpa5H +1kvNDszOjajSRBKB+oeFLPjipkjPY8xHcYbRVrnaqg== +-----END EC PRIVATE KEY----- diff --git a/src/test/resources/public.key b/src/test/resources/public.key deleted file mode 100644 index 99e728b2deaf68099db246e4634527dd206907e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91 zcmXqrG!SNE*J|@PXUoLM#sOw9GqN)~F|crbep#~6?Z(DARjb!eE8c5;?=#z4X7!6I u4}TfenYcZfHIY{~d}r2AZ-?!UJDUY}_x(D#ai4sunf0=;y@C~PTv7n;h9tlM diff --git a/src/test/resources/public.pem b/src/test/resources/public.pem new file mode 100644 index 0000000..e410438 --- /dev/null +++ b/src/test/resources/public.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAwWgoA6ZH/JYdHLEV1HWBN2FtaKqu +NCY9TdCK5wS1bj/eX6kwDfEhueadzCOzohkOTAA9OE4YX86ytOicFAfsOGIAiVuE +E1X215lTrvvtslsaZPKOiCoDwQ3OaeFKzhAyBKWuR9ZLzQ7Mzo2o0kQSgfqHhSz4 +4qZIz2PMR3GG0Va52qo= +-----END PUBLIC KEY----- From 3638bfeda191e1500f4de4e21f9c9d31bb316e54 Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Mon, 4 Oct 2021 12:55:15 -0700 Subject: [PATCH 14/21] Updates with a bigger example Added bigger example with metadata. --- intoto_test.attestation | 2 +- .../intoto/utilities/TestEnvelopeGenerator.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/intoto_test.attestation b/intoto_test.attestation index 42407cf..4df7b62 100644 --- a/intoto_test.attestation +++ b/intoto_test.attestation @@ -1 +1 @@ -{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1hdGVyaWFscyI6W3sidXJpIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9leGFtcGxlLTEuMi4zLnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiIxMjM0Li4uIn19XX19","signatures":[{"sig":"MIGIAkIBSNsRnhB2KspzighMidZplukYb2Gnd6l+1gDIk4V/yyAm75fPEKSa6k+ysDNWlqiKlkjbrNVfxSJxOt5LaO6RtPsCQgHibHfYTN5KzBRA5Ax6A6vdDA2jwx5LfFjHKAJVze+BeA7RXDsmLIO9YgVwxnvys0Mu/3I4We5AeVglCXOJo+LydA==","keyid":"MyKey"}]} \ No newline at end of file +{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjp7ImJ1aWxkSW52b2NhdGlvbklkIjoiU29tZUJ1aWxkSWQiLCJidWlsZFN0YXJ0ZWRPbiI6IjE5ODYtMTItMThUMTU6MjA6MzArMDg6MDAiLCJidWlsZEZpbmlzaGVkT24iOiIxOTg2LTEyLTE4VDE2OjIwOjMwKzA4OjAwIiwiY29tcGxldGVuZXNzIjp7ImFyZ3VtZW50cyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOnRydWV9LCJyZXByb2R1Y2libGUiOmZhbHNlfSwibWF0ZXJpYWxzIjpbeyJ1cmkiOiJodHRwczovL2V4YW1wbGUuY29tL2V4YW1wbGUtMS4yLjMudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjEyMzQuLi4ifX1dfX0=","signatures":[{"sig":"MIGIAkIA1T0wx8okWtfwzLyhyy7tRNFonqDIqe9bJ4tCtBhGX+6SengcnLMxSTr0xMz1jJCufJWazl1bbpuwWzuwDcmRqKoCQgF9mmiFP6QjZGgDQqXZFre3tK+1cwNNpBpRyvJdN3HAki9hlzbkXJURcN/0KkuyspLXn2r6rfysVEzlgUzIz8wwDg==","keyid":"MyKey"}]} \ No newline at end of file diff --git a/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java b/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java index ec44a15..b19321d 100644 --- a/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java +++ b/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java @@ -10,7 +10,9 @@ import io.github.intoto.models.StatementType; import io.github.intoto.models.Subject; import io.github.slsa.models.Builder; +import io.github.slsa.models.Completeness; import io.github.slsa.models.Material; +import io.github.slsa.models.Metadata; import io.github.slsa.models.Provenance; import io.github.slsa.models.Recipe; import java.io.File; @@ -20,6 +22,7 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.Security; +import java.time.OffsetDateTime; import java.util.List; import java.util.Map; import java.util.Objects; @@ -52,11 +55,25 @@ public static void main(String[] args) throws Exception { Material material = new Material(); material.setUri("https://example.com/example-1.2.3.tar.gz"); material.setDigest(Map.of("sha256", "1234...")); + // Prepare Metadata + Metadata metadata = new Metadata(); + metadata.setBuildInvocationId("SomeBuildId"); + metadata.setBuildStartedOn(OffsetDateTime.parse("1986-12-18T15:20:30+08:00")); + metadata.setBuildFinishedOn(OffsetDateTime.parse("1986-12-18T16:20:30+08:00")); + + Completeness completeness = new Completeness(); + completeness.setArguments(true); + completeness.setMaterials(true); + completeness.setEnvironment(false); + metadata.setCompleteness(completeness); + // Putting the Provenance together Provenance provenancePredicate = new Provenance(); provenancePredicate.setBuilder(builder); provenancePredicate.setRecipe(recipe); provenancePredicate.setMaterials(List.of(material)); + provenancePredicate.setMetadata(metadata); + // ** Putting the Statement together ** Statement statement = new Statement(); statement.set_type(StatementType.STATEMENT_V_0_1); From bb5e1e0593b99beeb3a6a2234a9a33abee6fb0bb Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Mon, 4 Oct 2021 16:43:19 -0700 Subject: [PATCH 15/21] Additional PR updates * Updated the Statements so that they include their type by default. * Updated README to match implementation * Updated the example output to match the bundle name and extension. * Clarified the Predicate JavaDoc. * Clarified testing comments --- README.md | 2 - ...attestation => intoto_example.intoto.jsonl | 2 +- .../io/github/intoto/models/Predicate.java | 6 +-- .../io/github/intoto/models/Statement.java | 16 +++---- .../github/intoto/models/StatementType.java | 24 ----------- .../github/dsse/helpers/SimpleECDSATest.java | 10 +++-- .../intoto/helpers/IntotoHelperTest.java | 43 +------------------ .../utilities/TestEnvelopeGenerator.java | 4 +- 8 files changed, 17 insertions(+), 90 deletions(-) rename intoto_test.attestation => intoto_example.intoto.jsonl (84%) delete mode 100644 src/main/java/io/github/intoto/models/StatementType.java diff --git a/README.md b/README.md index b1aeb58..8a8d9e4 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,7 @@ DigestSetAlgorithmType.SHA256.toString(), "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")) Predicate predicate=createPredicate(); Statement statement=new Statement(); -statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); -statement.setPredicateType(PredicateType.SLSA_PROVENANCE_V_0_1); statement.setPredicate(predicate); ``` diff --git a/intoto_test.attestation b/intoto_example.intoto.jsonl similarity index 84% rename from intoto_test.attestation rename to intoto_example.intoto.jsonl index 4df7b62..7d7960e 100644 --- a/intoto_test.attestation +++ b/intoto_example.intoto.jsonl @@ -1 +1 @@ -{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjp7ImJ1aWxkSW52b2NhdGlvbklkIjoiU29tZUJ1aWxkSWQiLCJidWlsZFN0YXJ0ZWRPbiI6IjE5ODYtMTItMThUMTU6MjA6MzArMDg6MDAiLCJidWlsZEZpbmlzaGVkT24iOiIxOTg2LTEyLTE4VDE2OjIwOjMwKzA4OjAwIiwiY29tcGxldGVuZXNzIjp7ImFyZ3VtZW50cyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOnRydWV9LCJyZXByb2R1Y2libGUiOmZhbHNlfSwibWF0ZXJpYWxzIjpbeyJ1cmkiOiJodHRwczovL2V4YW1wbGUuY29tL2V4YW1wbGUtMS4yLjMudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjEyMzQuLi4ifX1dfX0=","signatures":[{"sig":"MIGIAkIA1T0wx8okWtfwzLyhyy7tRNFonqDIqe9bJ4tCtBhGX+6SengcnLMxSTr0xMz1jJCufJWazl1bbpuwWzuwDcmRqKoCQgF9mmiFP6QjZGgDQqXZFre3tK+1cwNNpBpRyvJdN3HAki9hlzbkXJURcN/0KkuyspLXn2r6rfysVEzlgUzIz8wwDg==","keyid":"MyKey"}]} \ No newline at end of file +{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjp7ImJ1aWxkSW52b2NhdGlvbklkIjoiU29tZUJ1aWxkSWQiLCJidWlsZFN0YXJ0ZWRPbiI6IjE5ODYtMTItMThUMTU6MjA6MzArMDg6MDAiLCJidWlsZEZpbmlzaGVkT24iOiIxOTg2LTEyLTE4VDE2OjIwOjMwKzA4OjAwIiwiY29tcGxldGVuZXNzIjp7ImFyZ3VtZW50cyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOnRydWV9LCJyZXByb2R1Y2libGUiOmZhbHNlfSwibWF0ZXJpYWxzIjpbeyJ1cmkiOiJodHRwczovL2V4YW1wbGUuY29tL2V4YW1wbGUtMS4yLjMudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjEyMzQuLi4ifX1dfX0=","signatures":[{"sig":"MIGIAkIBNGDwW0mXlQFKYYorL8vRmeCM8iDnVC2IGPMcuxg5ree9piIOQBqD2hLGcO6PoX6Oo7eWxeXMFY/IXHONbHiUKFUCQgDZW5oNOvKtd1VMwHSy1iglglkf5+m/n0/hBt3L5N2mW19+pnHS29URXxSHvYhrtbJTUhxk+0JD1uktbXeCLM+8Kw==","keyid":"MyKey"}]} \ No newline at end of file diff --git a/src/main/java/io/github/intoto/models/Predicate.java b/src/main/java/io/github/intoto/models/Predicate.java index 7a61fdf..d7e0aee 100644 --- a/src/main/java/io/github/intoto/models/Predicate.java +++ b/src/main/java/io/github/intoto/models/Predicate.java @@ -3,10 +3,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; /** - * A generic attestation type with a schema isomorphic to in-toto 0.9. This allows existing in-toto - * users to make minimal changes to upgrade to the new attestation format. - * - *

Most users should migrate to a more specific attestation type, such as Provenance. + * The Predicate is the innermost layer of the attestation, containing arbitrary metadata about the + * Statement's subject. */ public abstract class Predicate { diff --git a/src/main/java/io/github/intoto/models/Statement.java b/src/main/java/io/github/intoto/models/Statement.java index 8e4eda6..5ff1067 100644 --- a/src/main/java/io/github/intoto/models/Statement.java +++ b/src/main/java/io/github/intoto/models/Statement.java @@ -6,7 +6,6 @@ import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; /** * The Statement is the middle layer of the attestation, binding it to a particular subject and @@ -15,8 +14,7 @@ public class Statement { /** Identifier for the schema of the Statement. */ - @NotNull(message = "_type may not be null") - private StatementType _type; + private final String _type = "https://in-toto.io/Statement/v0.1"; /** * Set of software artifacts that the attestation applies to. Each element represents a single @@ -39,14 +37,6 @@ public class Statement { */ private @Valid Predicate predicate; - public StatementType get_type() { - return _type; - } - - public void set_type(StatementType _type) { - this._type = _type; - } - public List getSubject() { return subject; } @@ -63,6 +53,10 @@ public Predicate getPredicate() { return predicate; } + public String get_type() { + return _type; + } + public void setPredicate(Predicate predicate) { this.predicate = predicate; this.predicateType = predicate.getPredicateType(); diff --git a/src/main/java/io/github/intoto/models/StatementType.java b/src/main/java/io/github/intoto/models/StatementType.java deleted file mode 100644 index af13bc5..0000000 --- a/src/main/java/io/github/intoto/models/StatementType.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.intoto.models; - -import com.fasterxml.jackson.annotation.JsonValue; - -/** - * This enum is meant to represent the Statement entity type. - * - * @see in-toto/attestation/blob/main/spec/README.md#statement - */ -public enum StatementType { - STATEMENT_V_0_1("https://in-toto.io/Statement/v0.1"); - - private final String value; - - StatementType(String value) { - this.value = value; - } - - @JsonValue - public String getValue() { - return this.value; - } -} diff --git a/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java b/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java index b8bd66d..2e8b9b0 100644 --- a/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java +++ b/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java @@ -50,9 +50,10 @@ public void simpleEcdsa_sign_shouldCorrectlySignString_whenGivenCorrectKey() thr } @Test - @DisplayName("Test simple ECDSA signing with DSAA String") + @DisplayName("Test simple ECDSA signing against a fix Pre-Authentication Encoding String") public void test() throws Exception { - String message = + // The following is a fixed Pre-Authentication Encoding String used for testing + String effectiveTestMessage = "DSSEv1 28 application/vnd.in-toto+json 656 eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7IlNIQTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjpudWxsLCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV19fQ=="; // Generate a key pair KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); @@ -62,10 +63,11 @@ public void test() throws Exception { PublicKey publicKey = pair.getPublic(); SimpleECDSASigner signer = new SimpleECDSASigner(privateKey, "MyKey"); - byte[] encryptedMessage = signer.sign(message.getBytes(StandardCharsets.UTF_8)); + byte[] encryptedMessage = signer.sign(effectiveTestMessage.getBytes(StandardCharsets.UTF_8)); SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); - boolean result = verifier.verify(publicKey.getEncoded(), encryptedMessage, message); + boolean result = + verifier.verify(publicKey.getEncoded(), encryptedMessage, effectiveTestMessage); Assertions.assertTrue(result); } } diff --git a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java index 722427e..2df2368 100644 --- a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java +++ b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java @@ -16,7 +16,6 @@ import io.github.intoto.models.DigestSetAlgorithmType; import io.github.intoto.models.Predicate; import io.github.intoto.models.Statement; -import io.github.intoto.models.StatementType; import io.github.intoto.models.Subject; import io.github.intoto.utilities.IntotoStubFactory; import io.github.slsa.models.Builder; @@ -76,7 +75,6 @@ public class IntotoHelperTest { provenancePredicate.setMaterials(List.of(material)); // ** Putting the Statement together ** Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); statement.setPredicate(provenancePredicate); @@ -143,7 +141,6 @@ public void validateAndTransformToJson_shouldTransformStatementToJsonString_With Provenance provenancePredicate = IntotoStubFactory.createProvenancePredicateWithMetadata(); // ** Putting the Statement together ** Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); statement.setPredicate(provenancePredicate); @@ -192,32 +189,6 @@ public void validateAndTransformToJson_shouldTransformStatementToJsonString_With assertEquals(EXPECTED_JSON_STATEMENT, jsonStatement); } - @Test - @DisplayName("Testing Statement Type can't be null") - public void validateAndTransformToJson_shouldThrowException_whenStatementTypeIsMissing() { - Subject subject = new Subject(); - subject.setName("curl-7.72.0.tar.bz2"); - 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("_type may not be null", thrown.getMessage()); - } - @Test @DisplayName("Testing Statement Subject can't be null") public void toJson_shouldThrowException_whenStatementSubjectIsNull() { @@ -229,7 +200,6 @@ public void toJson_shouldThrowException_whenStatementSubjectIsNull() { "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setPredicate(predicate); InvalidModelException thrown = @@ -253,7 +223,6 @@ public void toJson_shouldThrowException_whenStatementSubjectIsEmpty() { "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(Collections.emptyList()); statement.setPredicate(predicate); @@ -277,7 +246,6 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNameIsNul "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); statement.setPredicate(predicate); @@ -302,7 +270,6 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNameIsBla "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); statement.setPredicate(predicate); @@ -323,7 +290,6 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEm subject.setName("curl-7.72.0.tar.bz2"); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); statement.setPredicate(predicate); @@ -347,7 +313,6 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEm Map.of("", "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); statement.setPredicate(predicate); @@ -370,7 +335,6 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectDigstIsEm subject.setDigest(Map.of(DigestSetAlgorithmType.SHA256.getValue(), "")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); statement.setPredicate(predicate); @@ -409,7 +373,6 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreN "d4d5899a3868fbb6ae1856c3e55a32ce35913de3956d1973caccd37bd0174fa2")); Predicate predicate = IntotoStubFactory.createSimpleProvenancePredicate(); Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject, subject2, subject3)); statement.setPredicate(predicate); @@ -450,7 +413,6 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreN provenancePredicate.setMaterials(List.of(material)); // ** Putting the Statement together ** Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); statement.setPredicate(provenancePredicate); @@ -494,7 +456,6 @@ public void validateAndTransformToJson_shouldThrowException_whenSubjectNamesAreN provenancePredicate.setMaterials(List.of(material)); // ** Putting the Statement together ** Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); statement.setPredicate(provenancePredicate); @@ -580,7 +541,7 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact provenancePredicate.setMaterials(List.of(material)); // ** Putting the Statement together ** Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); statement.setPredicate(provenancePredicate); String intotoEnvelope = @@ -631,7 +592,7 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact provenancePredicate.setMaterials(List.of(material)); // ** Putting the Statement together ** Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); + statement.setSubject(List.of(subject)); statement.setPredicate(provenancePredicate); diff --git a/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java b/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java index b19321d..e5c9877 100644 --- a/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java +++ b/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java @@ -7,7 +7,6 @@ import io.github.intoto.helpers.IntotoHelper; import io.github.intoto.models.DigestSetAlgorithmType; import io.github.intoto.models.Statement; -import io.github.intoto.models.StatementType; import io.github.intoto.models.Subject; import io.github.slsa.models.Builder; import io.github.slsa.models.Completeness; @@ -76,7 +75,6 @@ public static void main(String[] args) throws Exception { // ** Putting the Statement together ** Statement statement = new Statement(); - statement.set_type(StatementType.STATEMENT_V_0_1); statement.setSubject(List.of(subject)); statement.setPredicate(provenancePredicate); @@ -86,7 +84,7 @@ public static void main(String[] args) throws Exception { String intotoJsonEnvelope = IntotoHelper.produceIntotoEnvelopeAsJson(statement, signer, false); - Files.writeString(Path.of(".", "intoto_test.attestation"), intotoJsonEnvelope); + Files.writeString(Path.of(".", "intoto_example.intoto.jsonl"), intotoJsonEnvelope); } /** From 937a0338cf230d101d5f90a57125bba7486ba776 Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Wed, 6 Oct 2021 12:18:52 -0700 Subject: [PATCH 16/21] Update SimpleECDSATest.java Made sure the tests are using the test keys from the files. --- .../github/dsse/helpers/SimpleECDSATest.java | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java b/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java index 2e8b9b0..6b9efbb 100644 --- a/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java +++ b/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java @@ -1,16 +1,16 @@ package io.github.dsse.helpers; +import static io.github.intoto.utilities.KeyUtilities.readPrivateKey; +import static io.github.intoto.utilities.KeyUtilities.readPublicKey; + +import io.github.intoto.utilities.TestEnvelopeGenerator; +import java.io.File; import java.nio.charset.StandardCharsets; -import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.SecureRandom; import java.security.Security; -import java.security.spec.ECGenParameterSpec; +import java.util.Objects; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -19,14 +19,9 @@ public class SimpleECDSATest { - private KeyPairGenerator keygen; - @BeforeEach - public void setup() - throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException { + public void setup() { Security.addProvider(new BouncyCastleProvider()); - this.keygen = KeyPairGenerator.getInstance("ECDSA", "BC"); - keygen.initialize(new ECGenParameterSpec("brainpoolP384r1")); } @Test @@ -34,18 +29,13 @@ public void setup() public void simpleEcdsa_sign_shouldCorrectlySignString_whenGivenCorrectKey() throws Exception { String message = "hello world"; - // Generate a key pair - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); - keyGen.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom()); - KeyPair pair = keyGen.generateKeyPair(); - PrivateKey privateKey = pair.getPrivate(); - PublicKey publicKey = pair.getPublic(); + // Get KeyPair from files. + KeyPair keyPair = getKeyPairFromFile(); - SimpleECDSASigner signer = new SimpleECDSASigner(privateKey, "MyKey"); + SimpleECDSASigner signer = new SimpleECDSASigner(keyPair.getPrivate(), "MyKey"); byte[] encryptedMessage = signer.sign(message.getBytes(StandardCharsets.UTF_8)); - SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); - boolean result = verifier.verify(publicKey.getEncoded(), encryptedMessage, message); + boolean result = verifier.verify(keyPair.getPublic().getEncoded(), encryptedMessage, message); Assertions.assertTrue(result); } @@ -55,19 +45,39 @@ public void test() throws Exception { // The following is a fixed Pre-Authentication Encoding String used for testing String effectiveTestMessage = "DSSEv1 28 application/vnd.in-toto+json 656 eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7IlNIQTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjpudWxsLCJtYXRlcmlhbHMiOlt7InVyaSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZXhhbXBsZS0xLjIuMy50YXIuZ3oiLCJkaWdlc3QiOnsic2hhMjU2IjoiMTIzNC4uLiJ9fV19fQ=="; - // Generate a key pair - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); - keyGen.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom()); - KeyPair pair = keyGen.generateKeyPair(); - PrivateKey privateKey = pair.getPrivate(); - PublicKey publicKey = pair.getPublic(); + // Get KeyPair from files. + KeyPair keyPair = getKeyPairFromFile(); - SimpleECDSASigner signer = new SimpleECDSASigner(privateKey, "MyKey"); + SimpleECDSASigner signer = new SimpleECDSASigner(keyPair.getPrivate(), "MyKey"); byte[] encryptedMessage = signer.sign(effectiveTestMessage.getBytes(StandardCharsets.UTF_8)); SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); boolean result = - verifier.verify(publicKey.getEncoded(), encryptedMessage, effectiveTestMessage); + verifier.verify(keyPair.getPublic().getEncoded(), encryptedMessage, effectiveTestMessage); Assertions.assertTrue(result); } + + /** + * 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); + } } From 4405b88a9eb3d99fa616fe728e40f4498b81a5b2 Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Wed, 13 Oct 2021 13:59:41 -0700 Subject: [PATCH 17/21] Updates to JavaDoc and Additional tests * Added additional test that can be used to manually verify a .intoto.jsonl file * Added JavaDoc and made sure pre tags are correctly closed in them. --- intoto_example.intoto.jsonl | 2 +- .../java/io/github/dsse/models/Verifier.java | 10 +-- .../github/intoto/helpers/IntotoHelper.java | 6 +- .../intoto/utilities/TestEnvelopeVerify.java | 64 +++++++++++++++++++ 4 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 src/test/java/io/github/intoto/utilities/TestEnvelopeVerify.java diff --git a/intoto_example.intoto.jsonl b/intoto_example.intoto.jsonl index 7d7960e..1134a12 100644 --- a/intoto_example.intoto.jsonl +++ b/intoto_example.intoto.jsonl @@ -1 +1 @@ -{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjp7ImJ1aWxkSW52b2NhdGlvbklkIjoiU29tZUJ1aWxkSWQiLCJidWlsZFN0YXJ0ZWRPbiI6IjE5ODYtMTItMThUMTU6MjA6MzArMDg6MDAiLCJidWlsZEZpbmlzaGVkT24iOiIxOTg2LTEyLTE4VDE2OjIwOjMwKzA4OjAwIiwiY29tcGxldGVuZXNzIjp7ImFyZ3VtZW50cyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOnRydWV9LCJyZXByb2R1Y2libGUiOmZhbHNlfSwibWF0ZXJpYWxzIjpbeyJ1cmkiOiJodHRwczovL2V4YW1wbGUuY29tL2V4YW1wbGUtMS4yLjMudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjEyMzQuLi4ifX1dfX0=","signatures":[{"sig":"MIGIAkIBNGDwW0mXlQFKYYorL8vRmeCM8iDnVC2IGPMcuxg5ree9piIOQBqD2hLGcO6PoX6Oo7eWxeXMFY/IXHONbHiUKFUCQgDZW5oNOvKtd1VMwHSy1iglglkf5+m/n0/hBt3L5N2mW19+pnHS29URXxSHvYhrtbJTUhxk+0JD1uktbXeCLM+8Kw==","keyid":"MyKey"}]} \ No newline at end of file +{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJjdXJsLTcuNzIuMC50YXIuYnoyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImQ0ZDU4OTlhMzg2OGZiYjZhZTE4NTZjM2U1NWEzMmNlMzU5MTNkZTM5NTZkMTk3M2NhY2NkMzdiZDAxNzRmYTIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YwLjEiLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6Im1haWx0bzpwZXJzb25AZXhhbXBsZS5jb20ifSwicmVjaXBlIjp7InR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL01ha2VmaWxlIiwiZGVmaW5lZEluTWF0ZXJpYWwiOjAsImVudHJ5UG9pbnQiOiJzcmM6Zm9vIn0sIm1ldGFkYXRhIjp7ImJ1aWxkSW52b2NhdGlvbklkIjoiU29tZUJ1aWxkSWQiLCJidWlsZFN0YXJ0ZWRPbiI6IjE5ODYtMTItMThUMTU6MjA6MzArMDg6MDAiLCJidWlsZEZpbmlzaGVkT24iOiIxOTg2LTEyLTE4VDE2OjIwOjMwKzA4OjAwIiwiY29tcGxldGVuZXNzIjp7ImFyZ3VtZW50cyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOnRydWV9LCJyZXByb2R1Y2libGUiOmZhbHNlfSwibWF0ZXJpYWxzIjpbeyJ1cmkiOiJodHRwczovL2V4YW1wbGUuY29tL2V4YW1wbGUtMS4yLjMudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjEyMzQuLi4ifX1dfX0=","signatures":[{"sig":"MIGIAkIBA9e9+cgYpo46iIOpRKhDCE+tOBtUDKlZsdKP70EGze5yvb8pOAH1i85T8bgvO70qai6kGMl6gSsAWoa05lBT3QACQgHMmDi9bs4CyFC3Ed7EgKPNgEVW9iLGFfoZRjjXHxx6leEyZc9lFRUzrKZkV+fiEg5a1bNeEtgLTz2aPH4ipUnIaA==","keyid":"MyKey"}]} \ No newline at end of file diff --git a/src/main/java/io/github/dsse/models/Verifier.java b/src/main/java/io/github/dsse/models/Verifier.java index b3fca6a..c2f1315 100644 --- a/src/main/java/io/github/dsse/models/Verifier.java +++ b/src/main/java/io/github/dsse/models/Verifier.java @@ -15,10 +15,12 @@ public interface Verifier { * @param encryptedMessage the encrypted message * @param message the message we are validating against. * @return true if the given message matches the encryptedMessage - * @throws NoSuchAlgorithmException - * @throws SignatureException - * @throws InvalidKeySpecException - * @throws InvalidKeyException + * @throws NoSuchAlgorithmException thrown when a particular cryptographic algorithm is requested + * but is not available in the environment. + * @throws SignatureException This is the generic Signature exception. + * @throws InvalidKeySpecException This is the exception for invalid key specifications. + * @throws InvalidKeyException This is the exception for invalid Keys (invalid encoding, wrong + * length, uninitialized, etc). */ boolean verify(byte[] publicKey, byte[] encryptedMessage, String message) throws NoSuchAlgorithmException, SignatureException, InvalidKeySpecException, diff --git a/src/main/java/io/github/intoto/helpers/IntotoHelper.java b/src/main/java/io/github/intoto/helpers/IntotoHelper.java index 6e8088b..63d8d15 100644 --- a/src/main/java/io/github/intoto/helpers/IntotoHelper.java +++ b/src/main/java/io/github/intoto/helpers/IntotoHelper.java @@ -97,7 +97,8 @@ public static IntotoEnvelope produceIntotoEnvelope(Statement statement, Signer s /** * Generates the Pre-Authentication Encoding - *

+   *
+   * 
    * "DSSEv1" + SP + LEN(type) + SP + type + SP + LEN(body) + SP + body
    *
    * where:
@@ -105,7 +106,8 @@ public static IntotoEnvelope produceIntotoEnvelope(Statement statement, Signer s
    * SP = ASCII space [0x20]
    * "DSSEv1" = ASCII [0x44, 0x53, 0x53, 0x45, 0x76, 0x31]
    * LEN(s) = ASCII decimal encoding of the byte length of s, with no leading zeros
-   *
+   * 
+ * * @param payloadType the type of payload. Fixed for in-toto Envelopes * @param payload raw payload in bytes * @return will return a Pre Authentication Encoding String. diff --git a/src/test/java/io/github/intoto/utilities/TestEnvelopeVerify.java b/src/test/java/io/github/intoto/utilities/TestEnvelopeVerify.java new file mode 100644 index 0000000..73d8344 --- /dev/null +++ b/src/test/java/io/github/intoto/utilities/TestEnvelopeVerify.java @@ -0,0 +1,64 @@ +package io.github.intoto.utilities; + +import static io.github.intoto.utilities.KeyUtilities.readPrivateKey; +import static io.github.intoto.utilities.KeyUtilities.readPublicKey; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import io.github.dsse.helpers.SimpleECDSAVerifier; +import io.github.dsse.models.IntotoEnvelope; +import io.github.intoto.helpers.IntotoHelper; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Security; +import java.util.Base64; +import java.util.Objects; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +public class TestEnvelopeVerify { + public static void main(String[] args) throws Exception { + Path filePath = Paths.get("intoto_example.intoto.jsonl"); + String fileContents = Files.readString(filePath); + // Generate a key pair + KeyPair keyPair = getKeyPairFromFile(); + ObjectMapper objectMapper = JsonMapper.builder().findAndAddModules().build(); + IntotoEnvelope envelope = objectMapper.readValue(fileContents, IntotoEnvelope.class); + byte[] decodedPayload = Base64.getDecoder().decode(envelope.getPayload()); + byte[] decodedSig = Base64.getDecoder().decode(envelope.getSignatures().get(0).getSig()); + byte[] paeString = + IntotoHelper.createPreAuthenticationEncoding(envelope.getPayloadType(), decodedPayload); + SimpleECDSAVerifier verifier = new SimpleECDSAVerifier(); + boolean result = + verifier.verify(keyPair.getPublic().getEncoded(), decodedSig, new String(paeString)); + System.out.println("Verification is:" + result); + } + + /** + * 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); + } +} From f2d770ce4bd7eb7c23c18d0b7ccc9ec054c79479 Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Mon, 25 Oct 2021 10:55:25 -0700 Subject: [PATCH 18/21] refactor: Move implementation to intoto package name and bump version to 0.4.0 --- CHANGELOG.md | 2 +- README.md | 12 ++++++------ pom.xml | 2 +- .../dsse/helpers/SimpleECDSASigner.java | 4 ++-- .../dsse/helpers/SimpleECDSAVerifier.java | 4 ++-- .../{ => intoto}/dsse/models/IntotoEnvelope.java | 2 +- .../github/{ => intoto}/dsse/models/Signature.java | 2 +- .../io/github/{ => intoto}/dsse/models/Signer.java | 2 +- .../github/{ => intoto}/dsse/models/Verifier.java | 2 +- .../io/github/intoto/helpers/IntotoHelper.java | 6 +++--- .../io/github/{ => intoto}/legacy/keys/Key.java | 2 +- .../io/github/{ => intoto}/legacy/keys/RSAKey.java | 4 ++-- .../github/{ => intoto}/legacy/keys/Signature.java | 2 +- .../io/github/{ => intoto}/legacy/lib/App.java | 8 ++++---- .../{ => intoto}/legacy/lib/JSONEncoder.java | 2 +- .../legacy/lib/NumericJSONSerializer.java | 2 +- .../{ => intoto}/legacy/models/Artifact.java | 2 +- .../io/github/{ => intoto}/legacy/models/Link.java | 8 ++++---- .../{ => intoto}/legacy/models/LinkSignable.java | 4 ++-- .../{ => intoto}/legacy/models/Metablock.java | 8 ++++---- .../{ => intoto}/legacy/models/Signable.java | 4 ++-- .../github/{ => intoto}/slsa/models/Builder.java | 2 +- .../{ => intoto}/slsa/models/Completeness.java | 2 +- .../github/{ => intoto}/slsa/models/Material.java | 2 +- .../github/{ => intoto}/slsa/models/Metadata.java | 2 +- .../{ => intoto}/slsa/models/Provenance.java | 2 +- .../io/github/{ => intoto}/slsa/models/Recipe.java | 2 +- .../{ => intoto}/dsse/helpers/SimpleECDSATest.java | 2 +- .../io/github/intoto/helpers/IntotoHelperTest.java | 14 +++++++------- .../github/intoto/implementations/FakeSigner.java | 2 +- .../{ => intoto}/legacy/TestJSONCanonical.java | 4 ++-- .../{ => intoto}/legacy/keys/RSAKeyTest.java | 2 +- .../java/io/github/intoto/legacy/lib/AppTest.java | 1 + .../{ => intoto}/legacy/models/LinkTest.java | 8 ++++---- .../github/intoto/utilities/IntotoStubFactory.java | 12 ++++++------ .../intoto/utilities/TestEnvelopeGenerator.java | 14 +++++++------- .../intoto/utilities/TestEnvelopeVerify.java | 4 ++-- src/test/java/io/github/legacy/lib/AppTest.java | 1 - 38 files changed, 80 insertions(+), 80 deletions(-) rename src/main/java/io/github/{ => intoto}/dsse/helpers/SimpleECDSASigner.java (91%) rename src/main/java/io/github/{ => intoto}/dsse/helpers/SimpleECDSAVerifier.java (93%) rename src/main/java/io/github/{ => intoto}/dsse/models/IntotoEnvelope.java (98%) rename src/main/java/io/github/{ => intoto}/dsse/models/Signature.java (97%) rename src/main/java/io/github/{ => intoto}/dsse/models/Signer.java (93%) rename src/main/java/io/github/{ => intoto}/dsse/models/Verifier.java (97%) rename src/main/java/io/github/{ => intoto}/legacy/keys/Key.java (95%) rename src/main/java/io/github/{ => intoto}/legacy/keys/RSAKey.java (98%) rename src/main/java/io/github/{ => intoto}/legacy/keys/Signature.java (91%) rename src/main/java/io/github/{ => intoto}/legacy/lib/App.java (81%) rename src/main/java/io/github/{ => intoto}/legacy/lib/JSONEncoder.java (99%) rename src/main/java/io/github/{ => intoto}/legacy/lib/NumericJSONSerializer.java (96%) rename src/main/java/io/github/{ => intoto}/legacy/models/Artifact.java (98%) rename src/main/java/io/github/{ => intoto}/legacy/models/Link.java (97%) rename src/main/java/io/github/{ => intoto}/legacy/models/LinkSignable.java (94%) rename src/main/java/io/github/{ => intoto}/legacy/models/Metablock.java (95%) rename src/main/java/io/github/{ => intoto}/legacy/models/Signable.java (81%) rename src/main/java/io/github/{ => intoto}/slsa/models/Builder.java (98%) rename src/main/java/io/github/{ => intoto}/slsa/models/Completeness.java (97%) rename src/main/java/io/github/{ => intoto}/slsa/models/Material.java (97%) rename src/main/java/io/github/{ => intoto}/slsa/models/Metadata.java (98%) rename src/main/java/io/github/{ => intoto}/slsa/models/Provenance.java (98%) rename src/main/java/io/github/{ => intoto}/slsa/models/Recipe.java (98%) rename src/test/java/io/github/{ => intoto}/dsse/helpers/SimpleECDSATest.java (98%) rename src/test/java/io/github/{ => intoto}/legacy/TestJSONCanonical.java (95%) rename src/test/java/io/github/{ => intoto}/legacy/keys/RSAKeyTest.java (98%) create mode 100644 src/test/java/io/github/intoto/legacy/lib/AppTest.java rename src/test/java/io/github/{ => intoto}/legacy/models/LinkTest.java (98%) delete mode 100644 src/test/java/io/github/legacy/lib/AppTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 00f1c76..eb1de71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Version 0.3.3 +## Version 0.4.0 - Added implementation for in-toto 0.1.0 - Moved Link to legacy directory diff --git a/README.md b/README.md index 8a8d9e4..d490b65 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ your mvn project edit the pom.xml file to add: io.github.in-toto in-toto - 0.3.3 + 0.4.0 ... ``` @@ -64,14 +64,15 @@ IntotoEnvelope intotoEnvelope=IntotoHelper.produceIntotoEnvelope(statement,signe ``` This method accepts a `io.github.intoto.models.Statement` and an implementation -of the ` io.github.dsse.models.Signer` interface. +of the ` io.github.intoto.dsse.models.Signer` interface. ### Implementing a Signer and a Verifier The Signer and Verifier are used to abstract away the sign and verify mechanism from this library. This allows the user to implement their own Signer/Verifier. An example of such an implementation is available in -the [io.github.dsse.helpers](/src/main/java/io/github/dsse/helpers) package. +the [io.github.intoto.dsse.helpers](/src/main/java/io/github/dsse/helpers) +package. ### Creating a new Predicate @@ -89,7 +90,6 @@ Predicate. The library will use the Predicate type and automatically fill in the Statement's predicateType field with its value. - ### Generating keys The keys in the project where generated with: @@ -106,8 +106,8 @@ The library exposes a series of objects and convenience methods to create, sign, and serialize in-toto metadata. As of now, only Link metadata is supported (see the Limitations section to see what exactly is supported as of now). -Metadata classes are located in the `io.github.legacy.models.*` package. You -can, for example create a link as follows: +Metadata classes are located in the `io.github.intoto.legacy.models.*` package. +You can, for example create a link as follows: ```java Link link = new Link(null,null,"test",null,null); diff --git a/pom.xml b/pom.xml index 41314a2..f095f1e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.github.in-toto in-toto jar - 0.3.3 + 0.4.0 in-toto https://maven.apache.org A framework to secure software supply chains. diff --git a/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java b/src/main/java/io/github/intoto/dsse/helpers/SimpleECDSASigner.java similarity index 91% rename from src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java rename to src/main/java/io/github/intoto/dsse/helpers/SimpleECDSASigner.java index 7616bc0..41a2085 100644 --- a/src/main/java/io/github/dsse/helpers/SimpleECDSASigner.java +++ b/src/main/java/io/github/intoto/dsse/helpers/SimpleECDSASigner.java @@ -1,6 +1,6 @@ -package io.github.dsse.helpers; +package io.github.intoto.dsse.helpers; -import io.github.dsse.models.Signer; +import io.github.intoto.dsse.models.Signer; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; diff --git a/src/main/java/io/github/dsse/helpers/SimpleECDSAVerifier.java b/src/main/java/io/github/intoto/dsse/helpers/SimpleECDSAVerifier.java similarity index 93% rename from src/main/java/io/github/dsse/helpers/SimpleECDSAVerifier.java rename to src/main/java/io/github/intoto/dsse/helpers/SimpleECDSAVerifier.java index efb537b..a84a9d5 100644 --- a/src/main/java/io/github/dsse/helpers/SimpleECDSAVerifier.java +++ b/src/main/java/io/github/intoto/dsse/helpers/SimpleECDSAVerifier.java @@ -1,6 +1,6 @@ -package io.github.dsse.helpers; +package io.github.intoto.dsse.helpers; -import io.github.dsse.models.Verifier; +import io.github.intoto.dsse.models.Verifier; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; diff --git a/src/main/java/io/github/dsse/models/IntotoEnvelope.java b/src/main/java/io/github/intoto/dsse/models/IntotoEnvelope.java similarity index 98% rename from src/main/java/io/github/dsse/models/IntotoEnvelope.java rename to src/main/java/io/github/intoto/dsse/models/IntotoEnvelope.java index b8bde83..3418aab 100644 --- a/src/main/java/io/github/dsse/models/IntotoEnvelope.java +++ b/src/main/java/io/github/intoto/dsse/models/IntotoEnvelope.java @@ -1,4 +1,4 @@ -package io.github.dsse.models; +package io.github.intoto.dsse.models; import java.util.List; import java.util.Objects; diff --git a/src/main/java/io/github/dsse/models/Signature.java b/src/main/java/io/github/intoto/dsse/models/Signature.java similarity index 97% rename from src/main/java/io/github/dsse/models/Signature.java rename to src/main/java/io/github/intoto/dsse/models/Signature.java index 1f48dea..e30a4e6 100644 --- a/src/main/java/io/github/dsse/models/Signature.java +++ b/src/main/java/io/github/intoto/dsse/models/Signature.java @@ -1,4 +1,4 @@ -package io.github.dsse.models; +package io.github.intoto.dsse.models; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; diff --git a/src/main/java/io/github/dsse/models/Signer.java b/src/main/java/io/github/intoto/dsse/models/Signer.java similarity index 93% rename from src/main/java/io/github/dsse/models/Signer.java rename to src/main/java/io/github/intoto/dsse/models/Signer.java index f54627a..4928d9e 100644 --- a/src/main/java/io/github/dsse/models/Signer.java +++ b/src/main/java/io/github/intoto/dsse/models/Signer.java @@ -1,4 +1,4 @@ -package io.github.dsse.models; +package io.github.intoto.dsse.models; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; diff --git a/src/main/java/io/github/dsse/models/Verifier.java b/src/main/java/io/github/intoto/dsse/models/Verifier.java similarity index 97% rename from src/main/java/io/github/dsse/models/Verifier.java rename to src/main/java/io/github/intoto/dsse/models/Verifier.java index c2f1315..b432f8e 100644 --- a/src/main/java/io/github/dsse/models/Verifier.java +++ b/src/main/java/io/github/intoto/dsse/models/Verifier.java @@ -1,4 +1,4 @@ -package io.github.dsse.models; +package io.github.intoto.dsse.models; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; diff --git a/src/main/java/io/github/intoto/helpers/IntotoHelper.java b/src/main/java/io/github/intoto/helpers/IntotoHelper.java index 63d8d15..5b57621 100644 --- a/src/main/java/io/github/intoto/helpers/IntotoHelper.java +++ b/src/main/java/io/github/intoto/helpers/IntotoHelper.java @@ -4,9 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; -import io.github.dsse.models.IntotoEnvelope; -import io.github.dsse.models.Signature; -import io.github.dsse.models.Signer; +import io.github.intoto.dsse.models.IntotoEnvelope; +import io.github.intoto.dsse.models.Signature; +import io.github.intoto.dsse.models.Signer; import io.github.intoto.exceptions.InvalidModelException; import io.github.intoto.models.Statement; import java.nio.charset.StandardCharsets; diff --git a/src/main/java/io/github/legacy/keys/Key.java b/src/main/java/io/github/intoto/legacy/keys/Key.java similarity index 95% rename from src/main/java/io/github/legacy/keys/Key.java rename to src/main/java/io/github/intoto/legacy/keys/Key.java index 20765d6..61dcad5 100644 --- a/src/main/java/io/github/legacy/keys/Key.java +++ b/src/main/java/io/github/intoto/legacy/keys/Key.java @@ -1,4 +1,4 @@ -package io.github.legacy.keys; +package io.github.intoto.legacy.keys; import java.io.FileNotFoundException; import java.io.IOException; diff --git a/src/main/java/io/github/legacy/keys/RSAKey.java b/src/main/java/io/github/intoto/legacy/keys/RSAKey.java similarity index 98% rename from src/main/java/io/github/legacy/keys/RSAKey.java rename to src/main/java/io/github/intoto/legacy/keys/RSAKey.java index cac33b3..2c03494 100644 --- a/src/main/java/io/github/legacy/keys/RSAKey.java +++ b/src/main/java/io/github/intoto/legacy/keys/RSAKey.java @@ -1,6 +1,6 @@ -package io.github.legacy.keys; +package io.github.intoto.legacy.keys; -import io.github.legacy.lib.JSONEncoder; +import io.github.intoto.legacy.lib.JSONEncoder; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; diff --git a/src/main/java/io/github/legacy/keys/Signature.java b/src/main/java/io/github/intoto/legacy/keys/Signature.java similarity index 91% rename from src/main/java/io/github/legacy/keys/Signature.java rename to src/main/java/io/github/intoto/legacy/keys/Signature.java index 52d7b65..9716011 100644 --- a/src/main/java/io/github/legacy/keys/Signature.java +++ b/src/main/java/io/github/intoto/legacy/keys/Signature.java @@ -1,4 +1,4 @@ -package io.github.legacy.keys; +package io.github.intoto.legacy.keys; /** * Public class representing an in-toto Signature. diff --git a/src/main/java/io/github/legacy/lib/App.java b/src/main/java/io/github/intoto/legacy/lib/App.java similarity index 81% rename from src/main/java/io/github/legacy/lib/App.java rename to src/main/java/io/github/intoto/legacy/lib/App.java index 841be85..f5a197b 100644 --- a/src/main/java/io/github/legacy/lib/App.java +++ b/src/main/java/io/github/intoto/legacy/lib/App.java @@ -1,8 +1,8 @@ -package io.github.legacy.lib; +package io.github.intoto.legacy.lib; -import io.github.legacy.keys.Key; -import io.github.legacy.keys.RSAKey; -import io.github.legacy.models.Link; +import io.github.intoto.legacy.keys.Key; +import io.github.intoto.legacy.keys.RSAKey; +import io.github.intoto.legacy.models.Link; import java.io.File; import java.io.IOException; diff --git a/src/main/java/io/github/legacy/lib/JSONEncoder.java b/src/main/java/io/github/intoto/legacy/lib/JSONEncoder.java similarity index 99% rename from src/main/java/io/github/legacy/lib/JSONEncoder.java rename to src/main/java/io/github/intoto/legacy/lib/JSONEncoder.java index f61d34d..8ee3914 100644 --- a/src/main/java/io/github/legacy/lib/JSONEncoder.java +++ b/src/main/java/io/github/intoto/legacy/lib/JSONEncoder.java @@ -1,4 +1,4 @@ -package io.github.legacy.lib; +package io.github.intoto.legacy.lib; import com.google.gson.Gson; import com.google.gson.GsonBuilder; diff --git a/src/main/java/io/github/legacy/lib/NumericJSONSerializer.java b/src/main/java/io/github/intoto/legacy/lib/NumericJSONSerializer.java similarity index 96% rename from src/main/java/io/github/legacy/lib/NumericJSONSerializer.java rename to src/main/java/io/github/intoto/legacy/lib/NumericJSONSerializer.java index 406c20d..507ac8f 100644 --- a/src/main/java/io/github/legacy/lib/NumericJSONSerializer.java +++ b/src/main/java/io/github/intoto/legacy/lib/NumericJSONSerializer.java @@ -1,4 +1,4 @@ -package io.github.legacy.lib; +package io.github.intoto.legacy.lib; import com.google.gson.JsonElement; import com.google.gson.JsonPrimitive; diff --git a/src/main/java/io/github/legacy/models/Artifact.java b/src/main/java/io/github/intoto/legacy/models/Artifact.java similarity index 98% rename from src/main/java/io/github/legacy/models/Artifact.java rename to src/main/java/io/github/intoto/legacy/models/Artifact.java index 6192ce5..30efd1b 100644 --- a/src/main/java/io/github/legacy/models/Artifact.java +++ b/src/main/java/io/github/intoto/legacy/models/Artifact.java @@ -1,4 +1,4 @@ -package io.github.legacy.models; +package io.github.intoto.legacy.models; import java.io.FileInputStream; import java.io.FileNotFoundException; diff --git a/src/main/java/io/github/legacy/models/Link.java b/src/main/java/io/github/intoto/legacy/models/Link.java similarity index 97% rename from src/main/java/io/github/legacy/models/Link.java rename to src/main/java/io/github/intoto/legacy/models/Link.java index 2330025..92b6d1e 100644 --- a/src/main/java/io/github/legacy/models/Link.java +++ b/src/main/java/io/github/intoto/legacy/models/Link.java @@ -1,8 +1,8 @@ -package io.github.legacy.models; +package io.github.intoto.legacy.models; import com.google.gson.Gson; -import io.github.legacy.keys.Signature; -import io.github.legacy.models.Artifact.ArtifactHash; +import io.github.intoto.legacy.keys.Signature; +import io.github.intoto.legacy.models.Artifact.ArtifactHash; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.PathMatcher; @@ -28,7 +28,7 @@ public class Link extends Metablock { * @param environment a HashMap containing any additional, relevant environment information. * @param command the Argv array of the command executed. * @param byproducts A HashMap containing the byproduct triplet stdin/stdout/retval. - * @see io.github.legacy.models.Artifact + * @see io.github.intoto.legacy.models.Artifact */ public Link( HashMap materials, diff --git a/src/main/java/io/github/legacy/models/LinkSignable.java b/src/main/java/io/github/intoto/legacy/models/LinkSignable.java similarity index 94% rename from src/main/java/io/github/legacy/models/LinkSignable.java rename to src/main/java/io/github/intoto/legacy/models/LinkSignable.java index 4b76164..415a759 100644 --- a/src/main/java/io/github/legacy/models/LinkSignable.java +++ b/src/main/java/io/github/intoto/legacy/models/LinkSignable.java @@ -2,9 +2,9 @@ * package-private class representing the signable payload of the in-toto link * metadata. */ -package io.github.legacy.models; +package io.github.intoto.legacy.models; -import io.github.legacy.models.Artifact.ArtifactHash; +import io.github.intoto.legacy.models.Artifact.ArtifactHash; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/io/github/legacy/models/Metablock.java b/src/main/java/io/github/intoto/legacy/models/Metablock.java similarity index 95% rename from src/main/java/io/github/legacy/models/Metablock.java rename to src/main/java/io/github/intoto/legacy/models/Metablock.java index 1f004ab..e173a80 100644 --- a/src/main/java/io/github/legacy/models/Metablock.java +++ b/src/main/java/io/github/intoto/legacy/models/Metablock.java @@ -1,10 +1,10 @@ -package io.github.legacy.models; +package io.github.intoto.legacy.models; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import io.github.legacy.keys.Key; -import io.github.legacy.keys.Signature; -import io.github.legacy.lib.NumericJSONSerializer; +import io.github.intoto.legacy.keys.Key; +import io.github.intoto.legacy.keys.Signature; +import io.github.intoto.legacy.lib.NumericJSONSerializer; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; diff --git a/src/main/java/io/github/legacy/models/Signable.java b/src/main/java/io/github/intoto/legacy/models/Signable.java similarity index 81% rename from src/main/java/io/github/legacy/models/Signable.java rename to src/main/java/io/github/intoto/legacy/models/Signable.java index a4f06ab..2bf953a 100644 --- a/src/main/java/io/github/legacy/models/Signable.java +++ b/src/main/java/io/github/intoto/legacy/models/Signable.java @@ -1,6 +1,6 @@ -package io.github.legacy.models; +package io.github.intoto.legacy.models; -import io.github.legacy.lib.JSONEncoder; +import io.github.intoto.legacy.lib.JSONEncoder; /** * A signable class is an abstract superclass that provides a representation method to prepare for diff --git a/src/main/java/io/github/slsa/models/Builder.java b/src/main/java/io/github/intoto/slsa/models/Builder.java similarity index 98% rename from src/main/java/io/github/slsa/models/Builder.java rename to src/main/java/io/github/intoto/slsa/models/Builder.java index c282de6..aca480f 100644 --- a/src/main/java/io/github/slsa/models/Builder.java +++ b/src/main/java/io/github/intoto/slsa/models/Builder.java @@ -1,4 +1,4 @@ -package io.github.slsa.models; +package io.github.intoto.slsa.models; import java.util.Objects; import javax.validation.constraints.NotBlank; diff --git a/src/main/java/io/github/slsa/models/Completeness.java b/src/main/java/io/github/intoto/slsa/models/Completeness.java similarity index 97% rename from src/main/java/io/github/slsa/models/Completeness.java rename to src/main/java/io/github/intoto/slsa/models/Completeness.java index d7f6d90..0990613 100644 --- a/src/main/java/io/github/slsa/models/Completeness.java +++ b/src/main/java/io/github/intoto/slsa/models/Completeness.java @@ -1,4 +1,4 @@ -package io.github.slsa.models; +package io.github.intoto.slsa.models; import java.util.Objects; diff --git a/src/main/java/io/github/slsa/models/Material.java b/src/main/java/io/github/intoto/slsa/models/Material.java similarity index 97% rename from src/main/java/io/github/slsa/models/Material.java rename to src/main/java/io/github/intoto/slsa/models/Material.java index c530995..58993e5 100644 --- a/src/main/java/io/github/slsa/models/Material.java +++ b/src/main/java/io/github/intoto/slsa/models/Material.java @@ -1,4 +1,4 @@ -package io.github.slsa.models; +package io.github.intoto.slsa.models; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/io/github/slsa/models/Metadata.java b/src/main/java/io/github/intoto/slsa/models/Metadata.java similarity index 98% rename from src/main/java/io/github/slsa/models/Metadata.java rename to src/main/java/io/github/intoto/slsa/models/Metadata.java index 79851a1..05559c3 100644 --- a/src/main/java/io/github/slsa/models/Metadata.java +++ b/src/main/java/io/github/intoto/slsa/models/Metadata.java @@ -1,4 +1,4 @@ -package io.github.slsa.models; +package io.github.intoto.slsa.models; import com.fasterxml.jackson.annotation.JsonFormat; import java.time.OffsetDateTime; diff --git a/src/main/java/io/github/slsa/models/Provenance.java b/src/main/java/io/github/intoto/slsa/models/Provenance.java similarity index 98% rename from src/main/java/io/github/slsa/models/Provenance.java rename to src/main/java/io/github/intoto/slsa/models/Provenance.java index 71e3959..f2bb2b9 100644 --- a/src/main/java/io/github/slsa/models/Provenance.java +++ b/src/main/java/io/github/intoto/slsa/models/Provenance.java @@ -1,4 +1,4 @@ -package io.github.slsa.models; +package io.github.intoto.slsa.models; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; diff --git a/src/main/java/io/github/slsa/models/Recipe.java b/src/main/java/io/github/intoto/slsa/models/Recipe.java similarity index 98% rename from src/main/java/io/github/slsa/models/Recipe.java rename to src/main/java/io/github/intoto/slsa/models/Recipe.java index 6872e0c..3a924a0 100644 --- a/src/main/java/io/github/slsa/models/Recipe.java +++ b/src/main/java/io/github/intoto/slsa/models/Recipe.java @@ -1,4 +1,4 @@ -package io.github.slsa.models; +package io.github.intoto.slsa.models; import java.util.Objects; import javax.validation.constraints.NotNull; diff --git a/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java b/src/test/java/io/github/intoto/dsse/helpers/SimpleECDSATest.java similarity index 98% rename from src/test/java/io/github/dsse/helpers/SimpleECDSATest.java rename to src/test/java/io/github/intoto/dsse/helpers/SimpleECDSATest.java index 6b9efbb..a8edf41 100644 --- a/src/test/java/io/github/dsse/helpers/SimpleECDSATest.java +++ b/src/test/java/io/github/intoto/dsse/helpers/SimpleECDSATest.java @@ -1,4 +1,4 @@ -package io.github.dsse.helpers; +package io.github.intoto.dsse.helpers; 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/helpers/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java index 2df2368..6a45290 100644 --- a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java +++ b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java @@ -8,20 +8,20 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.fasterxml.jackson.core.JsonProcessingException; -import io.github.dsse.helpers.SimpleECDSASigner; -import io.github.dsse.helpers.SimpleECDSAVerifier; -import io.github.dsse.models.IntotoEnvelope; +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.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.Builder; +import io.github.intoto.slsa.models.Material; +import io.github.intoto.slsa.models.Provenance; +import io.github.intoto.slsa.models.Recipe; import io.github.intoto.utilities.IntotoStubFactory; -import io.github.slsa.models.Builder; -import io.github.slsa.models.Material; -import io.github.slsa.models.Provenance; -import io.github.slsa.models.Recipe; import java.io.File; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; diff --git a/src/test/java/io/github/intoto/implementations/FakeSigner.java b/src/test/java/io/github/intoto/implementations/FakeSigner.java index ae1a6a5..80d6380 100644 --- a/src/test/java/io/github/intoto/implementations/FakeSigner.java +++ b/src/test/java/io/github/intoto/implementations/FakeSigner.java @@ -1,6 +1,6 @@ package io.github.intoto.implementations; -import io.github.dsse.models.Signer; +import io.github.intoto.dsse.models.Signer; /** Fake Signer implementation for testing only. */ public class FakeSigner implements Signer { diff --git a/src/test/java/io/github/legacy/TestJSONCanonical.java b/src/test/java/io/github/intoto/legacy/TestJSONCanonical.java similarity index 95% rename from src/test/java/io/github/legacy/TestJSONCanonical.java rename to src/test/java/io/github/intoto/legacy/TestJSONCanonical.java index c4176d0..8a67c04 100644 --- a/src/test/java/io/github/legacy/TestJSONCanonical.java +++ b/src/test/java/io/github/intoto/legacy/TestJSONCanonical.java @@ -1,8 +1,8 @@ -package io.github.legacy; +package io.github.intoto.legacy; import static org.junit.jupiter.api.Assertions.assertEquals; -import io.github.legacy.models.Link; +import io.github.intoto.legacy.models.Link; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; diff --git a/src/test/java/io/github/legacy/keys/RSAKeyTest.java b/src/test/java/io/github/intoto/legacy/keys/RSAKeyTest.java similarity index 98% rename from src/test/java/io/github/legacy/keys/RSAKeyTest.java rename to src/test/java/io/github/intoto/legacy/keys/RSAKeyTest.java index eb0871d..204d3f6 100644 --- a/src/test/java/io/github/legacy/keys/RSAKeyTest.java +++ b/src/test/java/io/github/intoto/legacy/keys/RSAKeyTest.java @@ -1,4 +1,4 @@ -package io.github.legacy.keys; +package io.github.intoto.legacy.keys; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/io/github/intoto/legacy/lib/AppTest.java b/src/test/java/io/github/intoto/legacy/lib/AppTest.java new file mode 100644 index 0000000..76e356f --- /dev/null +++ b/src/test/java/io/github/intoto/legacy/lib/AppTest.java @@ -0,0 +1 @@ +package io.github.intoto.legacy.lib; diff --git a/src/test/java/io/github/legacy/models/LinkTest.java b/src/test/java/io/github/intoto/legacy/models/LinkTest.java similarity index 98% rename from src/test/java/io/github/legacy/models/LinkTest.java rename to src/test/java/io/github/intoto/legacy/models/LinkTest.java index ed01bed..9a1a299 100644 --- a/src/test/java/io/github/legacy/models/LinkTest.java +++ b/src/test/java/io/github/intoto/legacy/models/LinkTest.java @@ -1,13 +1,13 @@ -package io.github.legacy.models; +package io.github.intoto.legacy.models; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import io.github.legacy.keys.Key; -import io.github.legacy.keys.RSAKey; -import io.github.legacy.models.Artifact.ArtifactHash; +import io.github.intoto.legacy.keys.Key; +import io.github.intoto.legacy.keys.RSAKey; +import io.github.intoto.legacy.models.Artifact.ArtifactHash; import java.io.File; import java.io.IOException; import java.util.ArrayList; diff --git a/src/test/java/io/github/intoto/utilities/IntotoStubFactory.java b/src/test/java/io/github/intoto/utilities/IntotoStubFactory.java index c65ebd3..94dd3ed 100644 --- a/src/test/java/io/github/intoto/utilities/IntotoStubFactory.java +++ b/src/test/java/io/github/intoto/utilities/IntotoStubFactory.java @@ -1,11 +1,11 @@ package io.github.intoto.utilities; -import io.github.slsa.models.Builder; -import io.github.slsa.models.Completeness; -import io.github.slsa.models.Material; -import io.github.slsa.models.Metadata; -import io.github.slsa.models.Provenance; -import io.github.slsa.models.Recipe; +import io.github.intoto.slsa.models.Builder; +import io.github.intoto.slsa.models.Completeness; +import io.github.intoto.slsa.models.Material; +import io.github.intoto.slsa.models.Metadata; +import io.github.intoto.slsa.models.Provenance; +import io.github.intoto.slsa.models.Recipe; import java.time.OffsetDateTime; import java.util.List; import java.util.Map; diff --git a/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java b/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java index e5c9877..500fd35 100644 --- a/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java +++ b/src/test/java/io/github/intoto/utilities/TestEnvelopeGenerator.java @@ -3,17 +3,17 @@ import static io.github.intoto.utilities.KeyUtilities.readPrivateKey; import static io.github.intoto.utilities.KeyUtilities.readPublicKey; -import io.github.dsse.helpers.SimpleECDSASigner; +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.slsa.models.Builder; -import io.github.slsa.models.Completeness; -import io.github.slsa.models.Material; -import io.github.slsa.models.Metadata; -import io.github.slsa.models.Provenance; -import io.github.slsa.models.Recipe; +import io.github.intoto.slsa.models.Builder; +import io.github.intoto.slsa.models.Completeness; +import io.github.intoto.slsa.models.Material; +import io.github.intoto.slsa.models.Metadata; +import io.github.intoto.slsa.models.Provenance; +import io.github.intoto.slsa.models.Recipe; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; diff --git a/src/test/java/io/github/intoto/utilities/TestEnvelopeVerify.java b/src/test/java/io/github/intoto/utilities/TestEnvelopeVerify.java index 73d8344..4423fd2 100644 --- a/src/test/java/io/github/intoto/utilities/TestEnvelopeVerify.java +++ b/src/test/java/io/github/intoto/utilities/TestEnvelopeVerify.java @@ -5,8 +5,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; -import io.github.dsse.helpers.SimpleECDSAVerifier; -import io.github.dsse.models.IntotoEnvelope; +import io.github.intoto.dsse.helpers.SimpleECDSAVerifier; +import io.github.intoto.dsse.models.IntotoEnvelope; import io.github.intoto.helpers.IntotoHelper; import java.io.File; import java.nio.file.Files; diff --git a/src/test/java/io/github/legacy/lib/AppTest.java b/src/test/java/io/github/legacy/lib/AppTest.java deleted file mode 100644 index 46e7d7e..0000000 --- a/src/test/java/io/github/legacy/lib/AppTest.java +++ /dev/null @@ -1 +0,0 @@ -package io.github.legacy.lib; From 206bb9ef4c1173f8b41bdbaef670d648981ec56f Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Thu, 28 Oct 2021 13:42:20 -0700 Subject: [PATCH 19/21] Added additional tests for PAE transformation --- .../intoto/helpers/IntotoHelperTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java index 6a45290..643bbf3 100644 --- a/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java +++ b/src/test/java/io/github/intoto/helpers/IntotoHelperTest.java @@ -508,6 +508,24 @@ public void createPreAuthenticationEncoding_shouldCorrectlyEncode_withUtfCharact 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 From a52c3a847b40d7e7ab9aa3a6436d3d7ce7a0c321 Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Thu, 28 Oct 2021 14:02:27 -0700 Subject: [PATCH 20/21] Create maven.yml --- .github/workflows/maven.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/maven.yml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..b2099fb --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,26 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: maven + - name: Build with Maven + run: mvn -B package --file pom.xml From 32fad8de1e5c65240f632b1b73a138b324d4073f Mon Sep 17 00:00:00 2001 From: Sergio Felix Date: Thu, 28 Oct 2021 14:03:38 -0700 Subject: [PATCH 21/21] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb1de71..30ef938 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Version 0.4.0 -- Added implementation for in-toto 0.1.0 +- Added implementation for the new version (in-toto/attestation)[https://github.com/in-toto/attestation] - Moved Link to legacy directory - Update Dependencies for validation