Skip to content

Commit

Permalink
Save keyless signing result
Browse files Browse the repository at this point in the history
Signed-off-by: Appu Goundan <appu@google.com>
  • Loading branch information
loosebazooka committed Jul 13, 2022
1 parent f07e270 commit 372c938
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 24 deletions.
25 changes: 16 additions & 9 deletions src/main/java/dev/sigstore/KeylessSigner.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package dev.sigstore;

import com.google.api.client.util.Preconditions;
import com.google.common.hash.Hashing;
import com.google.common.io.Resources;
import dev.sigstore.encryption.signers.Signer;
import dev.sigstore.encryption.signers.Signers;
Expand All @@ -30,13 +31,13 @@
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import org.conscrypt.ct.SerializationException;

/** A full sigstore keyless signing flow. */
public class KeylessSigner {
private final FulcioClient fulcioClient;
private final FulcioVerifier fulcioVerifier;
Expand Down Expand Up @@ -153,7 +154,7 @@ public KeylessSigner build() {
}
}

public void sign(Path artifact)
public KeylessSigningResult sign(Path artifact)
throws OidcException, NoSuchAlgorithmException, SignatureException, InvalidKeyException,
UnsupportedAlgorithmException, SerializationException, CertificateException, IOException,
FulcioVerificationException, RekorVerificationException {
Expand All @@ -170,16 +171,22 @@ public void sign(Path artifact)
// allow that to be known
fulcioVerifier.verifySct(signingCert);

// TODO: this reads the whole artifact which is probably expensive for large files, the signer
// interface should probably just sign non-digest artifacts instead of always applying sha256
// before signing. Then we can just apply the input stream to the sha256 calculator
// and send that digest to the signer
var artifactBytes = Files.readAllBytes(artifact);
var artifactDigest = MessageDigest.getInstance("SHA-256").digest(artifactBytes);
var artifactByteSource = com.google.common.io.Files.asByteSource(artifact.toFile());
var artifactDigest = artifactByteSource.hash(Hashing.sha256()).asBytes();
byte[] signature;
try (var stream = artifactByteSource.openStream()) {
signature = signer.sign(stream);
}
var rekorRequest =
HashedRekordRequest.newHashedRekordRequest(
artifactDigest, signingCert.getLeafCertificate(), signer.sign(artifactBytes));
artifactDigest, signingCert.getLeafCertificate(), signature);
var rekorResponse = rekorClient.putEntry(rekorRequest);
rekorVerifier.verifyEntry(rekorResponse.getEntry());

return ImmutableKeylessSigningResult.builder()
.certPath(signingCert.getCertPath())
.signature(signature)
.entry(rekorResponse.getEntry())
.build();
}
}
32 changes: 32 additions & 0 deletions src/main/java/dev/sigstore/KeylessSigningResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2022 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 dev.sigstore.rekor.client.RekorEntry;
import java.security.cert.CertPath;
import org.immutables.value.Value;

@Value.Immutable
public interface KeylessSigningResult {
/** The full certificate chain provided by fulcio for the public key used to sign the artifact */
CertPath getCertPath();

/** The signature over the artifact */
byte[] getSignature();

/** The entry in the rekor transparency log */
RekorEntry getEntry();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2022 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.encryption.certificates;

import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;

public class Certificates {

/** Convert a certificate to a PEM encoded certificate. */
public static String toPemString(Certificate cert) throws IOException {
var certWriter = new StringWriter();
try (JcaPEMWriter pemWriter = new JcaPEMWriter(certWriter)) {
pemWriter.writeObject(cert);
pemWriter.flush();
}
return certWriter.toString();
}

/** Convert a certificate to a PEM encoded certificate. */
public static byte[] toPemBytes(Certificate cert) throws IOException {
return toPemString(cert).getBytes(StandardCharsets.UTF_8);
}
}
12 changes: 2 additions & 10 deletions src/main/java/dev/sigstore/rekor/client/HashedRekordRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@

import static dev.sigstore.json.GsonSupplier.GSON;

import dev.sigstore.encryption.certificates.Certificates;
import dev.sigstore.rekor.*;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import java.util.Base64;
import java.util.HashMap;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.util.encoders.Hex;

public class HashedRekordRequest {
Expand All @@ -46,13 +44,7 @@ private HashedRekordRequest(Hashedrekord hashedrekord) {
public static HashedRekordRequest newHashedRekordRequest(
byte[] artifactDigest, Certificate leafCert, byte[] signature) throws IOException {

var certWriter = new StringWriter();
try (JcaPEMWriter pemWriter = new JcaPEMWriter(certWriter)) {
pemWriter.writeObject(leafCert);
pemWriter.flush();
}

var certPem = certWriter.toString().getBytes(StandardCharsets.UTF_8);
var certPem = Certificates.toPemBytes(leafCert);
var hashedrekord =
new Hashedrekord()
.withData(
Expand Down
30 changes: 26 additions & 4 deletions src/test/java/dev/sigstore/KeylessSignerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
*/
package dev.sigstore;

import dev.sigstore.encryption.certificates.Certificates;
import dev.sigstore.oidc.client.GithubActionsOidcClient;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
import java.util.UUID;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
Expand All @@ -43,14 +46,16 @@ public static void setupArtifact() throws IOException {
@Tag("manual")
public void sign_production() throws Exception {
var signer = KeylessSigner.builderForProd().build();
signer.sign(testArtifact);
var result = signer.sign(testArtifact);
verifyResult(result);
}

@Test
@Tag("manual")
public void sign_staging() throws Exception {
var signer = KeylessSigner.builderForStaging().build();
signer.sign(testArtifact);
var result = signer.sign(testArtifact);
verifyResult(result);
}

@Test
Expand All @@ -60,7 +65,8 @@ public void sign_productionWithGithubOidc() throws Exception {
KeylessSigner.builderForProd()
.oidcClient(GithubActionsOidcClient.builder().build())
.build();
signer.sign(testArtifact);
var result = signer.sign(testArtifact);
verifyResult(result);
}

@Test
Expand All @@ -70,6 +76,22 @@ public void sign_stagingWithGithubOidc() throws Exception {
KeylessSigner.builderForStaging()
.oidcClient(GithubActionsOidcClient.builder().build())
.build();
signer.sign(testArtifact);
var result = signer.sign(testArtifact);
verifyResult(result);
}

private void verifyResult(KeylessSigningResult result) throws IOException {
Assertions.assertNotNull(result.getCertPath());
Assertions.assertNotNull(result.getEntry());
Assertions.assertNotNull(result.getSignature());

var hr = result.getEntry().getBodyAsHashedrekord();
// check if the rekor entry has the signature we sent
Assertions.assertArrayEquals(
Base64.getDecoder().decode(hr.getSignature().getContent()), result.getSignature());
// check if the rekor entry has the certificate we sent
Assertions.assertArrayEquals(
Base64.getDecoder().decode(hr.getSignature().getPublicKey().getContent()),
Certificates.toPemBytes(result.getCertPath().getCertificates().get(0)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public void getEntry_entryExists()
public void getEntry_entryDoesntExist() throws IOException {
Optional<RekorEntry> entry =
client.getEntry(
"c8d2b213aa7efc1b2c9ccfa2fa647d00b34c63972e04e90276b5c31e0f317afd"); // I made this up
"aaaaaaaaaaaaaaaac8d2b213aa7efc1b2c9ccfa2fa647d00b34c63972e04e90276b5c31e0f317afd"); // I made this up
assertTrue(entry.isEmpty());
}

Expand Down

0 comments on commit 372c938

Please sign in to comment.