diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index c477fb99..b0fac636 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -31,6 +31,6 @@ jobs: - name: Unpack sigstore-java distribution run: tar -xvf ${{ github.workspace }}/sigstore-cli/build/distributions/sigstore-cli-*.tar --strip-components 1 - - uses: sigstore/sigstore-conformance@00922385de455be5ec46288a947044aa44fb0981 # v0.0.8 + - uses: sigstore/sigstore-conformance@c8d17eb7ee884cf86b93a3a3f471648fb0a83819 # v0.0.9 with: entrypoint: ${{ github.workspace }}/bin/sigstore-cli diff --git a/sigstore-cli/src/main/java/dev/sigstore/cli/Sign.java b/sigstore-cli/src/main/java/dev/sigstore/cli/Sign.java index ef30145b..2193ac4c 100644 --- a/sigstore-cli/src/main/java/dev/sigstore/cli/Sign.java +++ b/sigstore-cli/src/main/java/dev/sigstore/cli/Sign.java @@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Base64; import java.util.concurrent.Callable; import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; @@ -57,7 +58,9 @@ public Integer call() throws Exception { var signer = signerBuilder.build(); var signingResult = signer.signFile(artifact); if (signatureFiles.sigAndCert != null) { - Files.write(signatureFiles.sigAndCert.signatureFile, signingResult.getSignature()); + Files.write( + signatureFiles.sigAndCert.signatureFile, + Base64.getEncoder().encode(signingResult.getSignature())); Files.write( signatureFiles.sigAndCert.certificateFile, Certificates.toPemBytes(signingResult.getCertPath())); diff --git a/sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java b/sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java index 5f76355c..7e3d28f1 100644 --- a/sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java +++ b/sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java @@ -30,6 +30,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.cert.CertPath; +import java.util.Base64; import java.util.concurrent.Callable; import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; @@ -50,6 +51,12 @@ public class Verify implements Callable { @ArgGroup(multiplicity = "0..1", exclusive = false) Policy policy; + @Option( + names = {"--trusted-root"}, + description = "an alternative to the TUF managed sigstore public good trusted root", + required = false) + Path trustedRoot; + static class Policy { @Option( names = {"--certificate-identity"}, @@ -70,7 +77,9 @@ public Integer call() throws Exception { KeylessSignature keylessSignature; if (signatureFiles.sigAndCert != null) { - byte[] signature = Files.readAllBytes(signatureFiles.sigAndCert.signatureFile); + byte[] signature = + Base64.getMimeDecoder() + .decode(Files.readAllBytes(signatureFiles.sigAndCert.signatureFile)); CertPath certPath = Certificates.fromPemChain(Files.readAllBytes(signatureFiles.sigAndCert.certificateFile)); keylessSignature = @@ -91,7 +100,10 @@ public Integer call() throws Exception { } var verificationOptions = verificationOptionsBuilder.alwaysUseRemoteRekorEntry(false).build(); - var verifier = new KeylessVerifier.Builder().sigstorePublicDefaults().build(); + var verifier = + (trustedRoot == null) + ? new KeylessVerifier.Builder().sigstorePublicDefaults().build() + : new KeylessVerifier.Builder().fromTrustedRoot(trustedRoot).build(); verifier.verify( artifact, KeylessVerificationRequest.builder() diff --git a/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java b/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java index a462e349..9c44713d 100644 --- a/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java +++ b/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java @@ -64,14 +64,13 @@ public static KeylessVerifier.Builder builder() { } public static class Builder { - private SigstoreTufClient sigstoreTufClient; + private TrustedRootProvider trustedRootProvider; public KeylessVerifier build() throws InvalidAlgorithmParameterException, CertificateException, InvalidKeySpecException, NoSuchAlgorithmException, IOException, InvalidKeyException { - Preconditions.checkNotNull(sigstoreTufClient); - sigstoreTufClient.update(); - var trustedRoot = sigstoreTufClient.getSigstoreTrustedRoot(); + Preconditions.checkNotNull(trustedRootProvider); + var trustedRoot = trustedRootProvider.get(); var fulcioVerifier = FulcioVerifier.newFulcioVerifier(trustedRoot); var rekorClient = RekorClient.builder().setTransparencyLog(trustedRoot).build(); var rekorVerifier = RekorVerifier.newRekorVerifier(trustedRoot); @@ -79,12 +78,19 @@ public KeylessVerifier build() } public Builder sigstorePublicDefaults() throws IOException { - sigstoreTufClient = SigstoreTufClient.builder().usePublicGoodInstance().build(); + var sigstoreTufClient = SigstoreTufClient.builder().usePublicGoodInstance().build(); + trustedRootProvider = TrustedRootProvider.from(sigstoreTufClient); return this; } public Builder sigstoreStagingDefaults() throws IOException { - sigstoreTufClient = SigstoreTufClient.builder().useStagingInstance().build(); + var sigstoreTufClient = SigstoreTufClient.builder().useStagingInstance().build(); + trustedRootProvider = TrustedRootProvider.from(sigstoreTufClient); + return this; + } + + public Builder fromTrustedRoot(Path trustedRoot) { + trustedRootProvider = TrustedRootProvider.from(trustedRoot); return this; } } diff --git a/sigstore-java/src/main/java/dev/sigstore/TrustedRootProvider.java b/sigstore-java/src/main/java/dev/sigstore/TrustedRootProvider.java new file mode 100644 index 00000000..b615f118 --- /dev/null +++ b/sigstore-java/src/main/java/dev/sigstore/TrustedRootProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 The Sigstore Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.sigstore; + +import com.google.common.base.Preconditions; +import com.google.protobuf.util.JsonFormat; +import dev.sigstore.proto.trustroot.v1.TrustedRoot; +import dev.sigstore.trustroot.SigstoreTrustedRoot; +import dev.sigstore.tuf.SigstoreTufClient; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; + +@FunctionalInterface +public interface TrustedRootProvider { + + SigstoreTrustedRoot get() + throws InvalidAlgorithmParameterException, CertificateException, InvalidKeySpecException, + NoSuchAlgorithmException, IOException, InvalidKeyException; + + static TrustedRootProvider from(SigstoreTufClient tufClient) { + Preconditions.checkNotNull(tufClient); + return () -> { + tufClient.update(); + return tufClient.getSigstoreTrustedRoot(); + }; + } + + static TrustedRootProvider from(Path trustedRoot) { + Preconditions.checkNotNull(trustedRoot); + return () -> { + var trustedRootBuilder = TrustedRoot.newBuilder(); + JsonFormat.parser() + .merge(Files.readString(trustedRoot, StandardCharsets.UTF_8), trustedRootBuilder); + return SigstoreTrustedRoot.from(trustedRootBuilder.build()); + }; + } +}