Skip to content

Commit 03433f6

Browse files
committed
Rework signing initialization
Make the signing clients configured with URIs instead of objects from the trusted_root.json. This code still uses the URIs from the trusted_root.json to populate those values, but this is a setup towards using the SigningConfiguration type that is coming in the future. Signed-off-by: Appu Goundan <[email protected]>
1 parent 9ec514d commit 03433f6

File tree

7 files changed

+77
-100
lines changed

7 files changed

+77
-100
lines changed

sigstore-java/src/main/java/dev/sigstore/KeylessSigner.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,11 @@ public KeylessSigner build()
191191
Preconditions.checkNotNull(sigstoreTufClient, "sigstoreTufClient");
192192
sigstoreTufClient.update();
193193
var trustedRoot = sigstoreTufClient.getSigstoreTrustedRoot();
194-
var fulcioClient = FulcioClient.builder().setCertificateAuthority(trustedRoot).build();
194+
var fulcioClient =
195+
FulcioClient.builder().setUri(trustedRoot.getCAs().current().getUri()).build();
195196
var fulcioVerifier = FulcioVerifier.newFulcioVerifier(trustedRoot);
196-
var rekorClient = RekorClient.builder().setTransparencyLog(trustedRoot).build();
197+
var rekorClient =
198+
RekorClient.builder().setUri(trustedRoot.getTLogs().current().getBaseUrl()).build();
197199
var rekorVerifier = RekorVerifier.newRekorVerifier(trustedRoot);
198200
return new KeylessSigner(
199201
fulcioClient,
@@ -354,7 +356,7 @@ private void renewSigningCertificate()
354356
}
355357
}
356358

357-
CertPath signingCert =
359+
CertPath renewedSigningCert =
358360
fulcioClient.signingCertificate(
359361
CertificateRequest.newCertificateRequest(
360362
signer.getPublicKey(),
@@ -363,8 +365,11 @@ private void renewSigningCertificate()
363365
tokenInfo.getSubjectAlternativeName().getBytes(StandardCharsets.UTF_8))));
364366
// TODO: this signing workflow mandates SCTs, but fulcio itself doesn't, figure out a way to
365367
// allow that to be known
366-
fulcioVerifier.verifySigningCertificate(signingCert);
367-
this.signingCert = signingCert;
368+
369+
var trimmed = fulcioVerifier.trimTrustedParent(renewedSigningCert);
370+
371+
fulcioVerifier.verifySigningCertificate(trimmed);
372+
this.signingCert = trimmed;
368373
signingCertPemBytes = Certificates.toPemBytes(signingCert);
369374
} finally {
370375
lock.writeLock().unlock();

sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,34 +29,39 @@
2929
import dev.sigstore.rekor.client.RekorParseException;
3030
import dev.sigstore.rekor.client.RekorVerificationException;
3131
import dev.sigstore.rekor.client.RekorVerifier;
32+
import dev.sigstore.trustroot.TransparencyLog;
3233
import dev.sigstore.tuf.SigstoreTufClient;
3334
import java.io.IOException;
3435
import java.nio.file.Path;
3536
import java.security.InvalidAlgorithmParameterException;
3637
import java.security.InvalidKeyException;
3738
import java.security.NoSuchAlgorithmException;
3839
import java.security.SignatureException;
39-
import java.security.cert.Certificate;
4040
import java.security.cert.CertificateException;
4141
import java.security.cert.CertificateExpiredException;
4242
import java.security.cert.CertificateNotYetValidException;
43+
import java.security.cert.X509Certificate;
4344
import java.security.spec.InvalidKeySpecException;
4445
import java.sql.Date;
4546
import java.util.Arrays;
47+
import java.util.List;
4648
import java.util.Optional;
49+
import java.util.stream.Collectors;
4750
import org.bouncycastle.util.encoders.Hex;
4851

4952
/** Verify hashrekords from rekor signed using the keyless signing flow with fulcio certificates. */
5053
public class KeylessVerifier {
5154
private final FulcioVerifier fulcioVerifier;
5255
private final RekorVerifier rekorVerifier;
53-
private final RekorClient rekorClient;
56+
57+
// a client per remote trusted log
58+
private final List<RekorClient> rekorClients;
5459

5560
private KeylessVerifier(
56-
FulcioVerifier fulcioVerifier, RekorClient rekorClient, RekorVerifier rekorVerifier) {
61+
FulcioVerifier fulcioVerifier, List<RekorClient> rekorClients, RekorVerifier rekorVerifier) {
5762
this.fulcioVerifier = fulcioVerifier;
58-
this.rekorClient = rekorClient;
5963
this.rekorVerifier = rekorVerifier;
64+
this.rekorClients = rekorClients;
6065
}
6166

6267
public static KeylessVerifier.Builder builder() {
@@ -72,9 +77,14 @@ public KeylessVerifier build()
7277
Preconditions.checkNotNull(trustedRootProvider);
7378
var trustedRoot = trustedRootProvider.get();
7479
var fulcioVerifier = FulcioVerifier.newFulcioVerifier(trustedRoot);
75-
var rekorClient = RekorClient.builder().setTransparencyLog(trustedRoot).build();
7680
var rekorVerifier = RekorVerifier.newRekorVerifier(trustedRoot);
77-
return new KeylessVerifier(fulcioVerifier, rekorClient, rekorVerifier);
81+
var rekorClients =
82+
trustedRoot.getTLogs().getTransparencyLogs().stream()
83+
.map(TransparencyLog::getBaseUrl)
84+
.distinct()
85+
.map(uri -> RekorClient.builder().setUri(uri).build())
86+
.collect(Collectors.toList());
87+
return new KeylessVerifier(fulcioVerifier, rekorClients, rekorVerifier);
7888
}
7989

8090
public Builder sigstorePublicDefaults() throws IOException {
@@ -207,7 +217,7 @@ public void verify(byte[] artifactDigest, KeylessVerificationRequest request)
207217
}
208218

209219
private RekorEntry getEntryFromRekor(
210-
byte[] artifactDigest, Certificate leafCert, byte[] signature)
220+
byte[] artifactDigest, X509Certificate leafCert, byte[] signature)
211221
throws KeylessVerificationException {
212222
// rebuild the hashedRekord so we can query the log for it
213223
HashedRekordRequest hashedRekordRequest = null;
@@ -221,15 +231,24 @@ private RekorEntry getEntryFromRekor(
221231
}
222232
Optional<RekorEntry> rekorEntry;
223233

224-
// attempt to grab the rekord from the rekor instance
234+
// attempt to grab a valid rekord from all known rekor instances
225235
try {
226-
rekorEntry = rekorClient.getEntry(hashedRekordRequest);
227-
if (rekorEntry.isEmpty()) {
228-
throw new KeylessVerificationException("Rekor entry was not found");
236+
for (var rekorClient : rekorClients) {
237+
rekorEntry = rekorClient.getEntry(hashedRekordRequest);
238+
if (rekorEntry.isPresent()) {
239+
var entryTime = Date.from(rekorEntry.get().getIntegratedTimeInstant());
240+
try {
241+
// only return this entry if it's valid for the certificate
242+
leafCert.checkValidity(entryTime);
243+
} catch (CertificateExpiredException | CertificateNotYetValidException ex) {
244+
continue;
245+
}
246+
return rekorEntry.get();
247+
}
229248
}
230249
} catch (IOException | RekorParseException e) {
231250
throw new KeylessVerificationException("Could not retrieve rekor entry", e);
232251
}
233-
return rekorEntry.get();
252+
throw new KeylessVerificationException("No valid rekor entry was not found in any known logs");
234253
}
235254
}

sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioClient.java

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import com.google.common.annotations.VisibleForTesting;
2121
import com.google.protobuf.ByteString;
22-
import dev.sigstore.encryption.certificates.Certificates;
2322
import dev.sigstore.fulcio.v2.CAGrpc;
2423
import dev.sigstore.fulcio.v2.CertificateChain;
2524
import dev.sigstore.fulcio.v2.CreateSigningCertificateRequest;
@@ -29,9 +28,8 @@
2928
import dev.sigstore.http.GrpcChannels;
3029
import dev.sigstore.http.HttpParams;
3130
import dev.sigstore.http.ImmutableHttpParams;
32-
import dev.sigstore.trustroot.CertificateAuthority;
33-
import dev.sigstore.trustroot.SigstoreTrustedRoot;
3431
import java.io.ByteArrayInputStream;
32+
import java.net.URI;
3533
import java.security.cert.CertPath;
3634
import java.security.cert.CertificateException;
3735
import java.security.cert.CertificateFactory;
@@ -45,19 +43,19 @@
4543
public class FulcioClient {
4644

4745
private final HttpParams httpParams;
48-
private final CertificateAuthority certificateAuthority;
46+
private final URI uri;
4947

5048
public static Builder builder() {
5149
return new Builder();
5250
}
5351

54-
private FulcioClient(HttpParams httpParams, CertificateAuthority certificateAuthority) {
55-
this.certificateAuthority = certificateAuthority;
52+
private FulcioClient(HttpParams httpParams, URI uri) {
53+
this.uri = uri;
5654
this.httpParams = httpParams;
5755
}
5856

5957
public static class Builder {
60-
private CertificateAuthority certificateAuthority;
58+
private URI uri = URI.create("https://fulcio.sigstore.dev");
6159
private HttpParams httpParams = ImmutableHttpParams.builder().build();
6260

6361
private Builder() {}
@@ -68,20 +66,14 @@ public Builder setHttpParams(HttpParams httpParams) {
6866
return this;
6967
}
7068

71-
/** The remote fulcio instance. */
72-
public Builder setCertificateAuthority(CertificateAuthority certificateAuthority) {
73-
this.certificateAuthority = certificateAuthority;
74-
return this;
75-
}
76-
77-
/** The remote fulcio instance inferred from a trustedRoot. */
78-
public Builder setCertificateAuthority(SigstoreTrustedRoot trustedRoot) {
79-
this.certificateAuthority = trustedRoot.getCAs().current();
69+
/** Base url of the remote fulcio instance. */
70+
public Builder setUri(URI uri) {
71+
this.uri = uri;
8072
return this;
8173
}
8274

8375
public FulcioClient build() {
84-
return new FulcioClient(httpParams, certificateAuthority);
76+
return new FulcioClient(httpParams, uri);
8577
}
8678
}
8779

@@ -93,16 +85,11 @@ public FulcioClient build() {
9385
*/
9486
public CertPath signingCertificate(CertificateRequest request)
9587
throws InterruptedException, CertificateException {
96-
if (!certificateAuthority.isCurrent()) {
97-
throw new RuntimeException(
98-
"Certificate Authority '" + certificateAuthority.getUri() + "' is not current");
99-
}
10088
// TODO: 1. If we want to reduce the cost of creating channels/connections, we could try
10189
// to make a new connection once per batch of fulcio requests, but we're not really
10290
// at that point yet.
10391
// TODO: 2. getUri().getAuthority() is potentially prone to error if we don't get a good URI
104-
var channel =
105-
GrpcChannels.newManagedChannel(certificateAuthority.getUri().getAuthority(), httpParams);
92+
var channel = GrpcChannels.newManagedChannel(uri.getAuthority(), httpParams);
10693

10794
try {
10895
var client = CAGrpc.newBlockingStub(channel);
@@ -135,9 +122,7 @@ public CertPath signingCertificate(CertificateRequest request)
135122
if (certs.getCertificateCase() == SIGNED_CERTIFICATE_DETACHED_SCT) {
136123
throw new CertificateException("Detached SCTs are not supported");
137124
}
138-
return Certificates.trimParent(
139-
decodeCerts(certs.getSignedCertificateEmbeddedSct().getChain()),
140-
certificateAuthority.getCertPath());
125+
return decodeCerts(certs.getSignedCertificateEmbeddedSct().getChain());
141126
} finally {
142127
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
143128
}

sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,16 @@ public void verifySigningCertificate(CertPath signingCertificate)
150150
verifySct(fullCertPath);
151151
}
152152

153+
public CertPath trimTrustedParent(CertPath signingCertificate)
154+
throws FulcioVerificationException, CertificateException {
155+
for (var ca : cas) {
156+
if (Certificates.containsParent(signingCertificate, ca.getCertPath())) {
157+
return Certificates.trimParent(signingCertificate, ca.getCertPath());
158+
}
159+
}
160+
throw new FulcioVerificationException("Certificate does not chain to trusted roots");
161+
}
162+
153163
/**
154164
* Find a valid cert path that chains back up to the trusted root certs and reconstruct a
155165
* certificate path combining the provided un-trusted certs and a known set of trusted and

sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorClient.java

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,9 @@
2222
import com.google.api.client.http.HttpRequest;
2323
import com.google.api.client.http.HttpResponse;
2424
import com.google.api.client.http.HttpResponseException;
25-
import com.google.common.base.Preconditions;
2625
import dev.sigstore.http.HttpClients;
2726
import dev.sigstore.http.HttpParams;
2827
import dev.sigstore.http.ImmutableHttpParams;
29-
import dev.sigstore.trustroot.SigstoreTrustedRoot;
30-
import dev.sigstore.trustroot.TransparencyLog;
3128
import java.io.IOException;
3229
import java.net.URI;
3330
import java.util.Arrays;
@@ -42,20 +39,20 @@ public class RekorClient {
4239
public static final String REKOR_INDEX_SEARCH_PATH = "/api/v1/index/retrieve";
4340

4441
private final HttpParams httpParams;
45-
private final TransparencyLog tlog;
42+
private final URI uri;
4643

4744
public static RekorClient.Builder builder() {
4845
return new RekorClient.Builder();
4946
}
5047

51-
private RekorClient(HttpParams httpParams, TransparencyLog tlog) {
52-
this.tlog = tlog;
48+
private RekorClient(HttpParams httpParams, URI uri) {
49+
this.uri = uri;
5350
this.httpParams = httpParams;
5451
}
5552

5653
public static class Builder {
5754
private HttpParams httpParams = ImmutableHttpParams.builder().build();
58-
private TransparencyLog tlog;
55+
private URI uri = URI.create("https://rekor.sigstore.dev");
5956

6057
private Builder() {}
6158

@@ -65,21 +62,14 @@ public Builder setHttpParams(HttpParams httpParams) {
6562
return this;
6663
}
6764

68-
/** Configure the remote rekor instance to communicate with. */
69-
public Builder setTransparencyLog(TransparencyLog tlog) {
70-
this.tlog = tlog;
71-
return this;
72-
}
73-
74-
/** Configure the remote rekor instance to communicate with, inferred from a trusted root. */
75-
public Builder setTransparencyLog(SigstoreTrustedRoot trustedRoot) {
76-
this.tlog = trustedRoot.getTLogs().current();
65+
/** Base url of the remote rekor instance. */
66+
public Builder setUri(URI uri) {
67+
this.uri = uri;
7768
return this;
7869
}
7970

8071
public RekorClient build() {
81-
Preconditions.checkNotNull(tlog);
82-
return new RekorClient(httpParams, tlog);
72+
return new RekorClient(httpParams, uri);
8373
}
8474
}
8575

@@ -91,7 +81,7 @@ public RekorClient build() {
9181
*/
9282
public RekorResponse putEntry(HashedRekordRequest hashedRekordRequest)
9383
throws IOException, RekorParseException {
94-
URI rekorPutEndpoint = tlog.getBaseUrl().resolve(REKOR_ENTRIES_PATH);
84+
URI rekorPutEndpoint = uri.resolve(REKOR_ENTRIES_PATH);
9585

9686
HttpRequest req =
9787
HttpClients.newRequestFactory(httpParams)
@@ -112,7 +102,7 @@ public RekorResponse putEntry(HashedRekordRequest hashedRekordRequest)
112102
resp.parseAsString()));
113103
}
114104

115-
URI rekorEntryUri = tlog.getBaseUrl().resolve(resp.getHeaders().getLocation());
105+
URI rekorEntryUri = uri.resolve(resp.getHeaders().getLocation());
116106
String entry = resp.parseAsString();
117107
return RekorResponse.newRekorResponse(rekorEntryUri, entry);
118108
}
@@ -123,7 +113,7 @@ public Optional<RekorEntry> getEntry(HashedRekordRequest hashedRekordRequest)
123113
}
124114

125115
public Optional<RekorEntry> getEntry(String UUID) throws IOException, RekorParseException {
126-
URI getEntryURI = tlog.getBaseUrl().resolve(REKOR_ENTRIES_PATH + "/" + UUID);
116+
URI getEntryURI = uri.resolve(REKOR_ENTRIES_PATH + "/" + UUID);
127117
HttpRequest req =
128118
HttpClients.newRequestFactory(httpParams).buildGetRequest(new GenericUrl(getEntryURI));
129119
req.getHeaders().set("Accept", "application/json");
@@ -149,7 +139,7 @@ public Optional<RekorEntry> getEntry(String UUID) throws IOException, RekorParse
149139
public List<String> searchEntry(
150140
String email, String hash, String publicKeyFormat, String publicKeyContent)
151141
throws IOException {
152-
URI rekorSearchEndpoint = tlog.getBaseUrl().resolve(REKOR_INDEX_SEARCH_PATH);
142+
URI rekorSearchEndpoint = uri.resolve(REKOR_INDEX_SEARCH_PATH);
153143

154144
HashMap<String, Object> publicKeyParams = null;
155145
if (publicKeyContent != null) {

0 commit comments

Comments
 (0)