Skip to content

Commit 9bebb37

Browse files
authored
Add interfaces for sigstore trusted_root (#430)
This should allow us to use it as the source for all validation materials from TUF. Signed-off-by: Appu Goundan <[email protected]>
1 parent 5e126a4 commit 9bebb37

File tree

12 files changed

+570
-6
lines changed

12 files changed

+570
-6
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2023 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.proto;
17+
18+
import com.google.protobuf.Timestamp;
19+
import dev.sigstore.encryption.certificates.Certificates;
20+
import dev.sigstore.proto.common.v1.X509Certificate;
21+
import java.security.cert.CertPath;
22+
import java.security.cert.Certificate;
23+
import java.security.cert.CertificateException;
24+
import java.security.cert.CertificateFactory;
25+
import java.time.Instant;
26+
import java.util.ArrayList;
27+
import java.util.List;
28+
29+
public class ProtoMutators {
30+
31+
public static CertPath toCertPath(List<X509Certificate> certificates)
32+
throws CertificateException {
33+
CertificateFactory cf = CertificateFactory.getInstance("X.509");
34+
List<Certificate> converted = new ArrayList<>(certificates.size());
35+
for (var cert : certificates) {
36+
converted.add(Certificates.fromDer(cert.getRawBytes().toByteArray()));
37+
}
38+
return cf.generateCertPath(converted);
39+
}
40+
41+
public static Instant toInstant(Timestamp timestamp) {
42+
return Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos());
43+
}
44+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2023 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.trustroot;
17+
18+
import dev.sigstore.proto.ProtoMutators;
19+
import java.net.URI;
20+
import java.security.cert.CertPath;
21+
import java.security.cert.CertificateException;
22+
import org.immutables.value.Value.Immutable;
23+
24+
@Immutable
25+
public interface CertificateAuthority {
26+
CertPath getCertPath();
27+
28+
URI getUri();
29+
30+
ValidFor getValidFor();
31+
32+
Subject getSubject();
33+
34+
static CertificateAuthority from(dev.sigstore.proto.trustroot.v1.CertificateAuthority proto)
35+
throws CertificateException {
36+
return ImmutableCertificateAuthority.builder()
37+
.certPath(ProtoMutators.toCertPath(proto.getCertChain().getCertificatesList()))
38+
.validFor(ValidFor.from(proto.getValidFor()))
39+
.uri(URI.create(proto.getUri()))
40+
.subject(Subject.from(proto.getSubject()))
41+
.build();
42+
}
43+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2023 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.trustroot;
17+
18+
import org.immutables.value.Value.Immutable;
19+
20+
@Immutable
21+
public interface LogId {
22+
byte[] getKeyId();
23+
24+
static LogId from(dev.sigstore.proto.common.v1.LogId proto) {
25+
return ImmutableLogId.builder().keyId(proto.getKeyId().toByteArray()).build();
26+
}
27+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2023 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.trustroot;
17+
18+
import dev.sigstore.encryption.Keys;
19+
import java.security.NoSuchAlgorithmException;
20+
import java.security.spec.InvalidKeySpecException;
21+
import org.immutables.value.Value.Immutable;
22+
23+
@Immutable
24+
public interface PublicKey {
25+
byte[] getRawBytes();
26+
27+
String getKeyDetails();
28+
29+
ValidFor getValidFor();
30+
31+
static PublicKey from(dev.sigstore.proto.common.v1.PublicKey proto) {
32+
return ImmutablePublicKey.builder()
33+
.rawBytes(proto.getRawBytes().toByteArray())
34+
.keyDetails(proto.getKeyDetails().name())
35+
.validFor(ValidFor.from(proto.getValidFor()))
36+
.build();
37+
}
38+
39+
static java.security.PublicKey toJavaPublicKey(PublicKey publicKey)
40+
throws InvalidKeySpecException, NoSuchAlgorithmException {
41+
if (!publicKey.getKeyDetails().equals("PKIX_ECDSA_P256_SHA_256")) {
42+
throw new InvalidKeySpecException("Unsupported key algorithm: " + publicKey.getKeyDetails());
43+
}
44+
return Keys.parsePkixPublicKey(publicKey.getRawBytes(), "EC");
45+
}
46+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2023 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.trustroot;
17+
18+
import dev.sigstore.proto.trustroot.v1.TrustedRoot;
19+
import java.security.cert.CertificateException;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.stream.Collectors;
23+
import org.immutables.value.Value.Immutable;
24+
25+
@Immutable
26+
public interface SigstoreTrustedRoot {
27+
28+
List<CertificateAuthority> getCertificateAuthorities();
29+
30+
List<TransparencyLog> getTLogs();
31+
32+
List<TransparencyLog> getCTLogs();
33+
34+
static SigstoreTrustedRoot from(TrustedRoot proto) throws CertificateException {
35+
List<CertificateAuthority> certificateAuthorities =
36+
new ArrayList<>(proto.getCertificateAuthoritiesCount());
37+
for (var certAuthority : proto.getCertificateAuthoritiesList()) {
38+
certificateAuthorities.add(CertificateAuthority.from(certAuthority));
39+
}
40+
var tlogs =
41+
proto.getTlogsList().stream().map(TransparencyLog::from).collect(Collectors.toList());
42+
var ctlogs =
43+
proto.getCtlogsList().stream().map(TransparencyLog::from).collect(Collectors.toList());
44+
45+
return ImmutableSigstoreTrustedRoot.builder()
46+
.certificateAuthorities(certificateAuthorities)
47+
.tLogs(tlogs)
48+
.cTLogs(ctlogs)
49+
.build();
50+
}
51+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2023 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.trustroot;
17+
18+
import dev.sigstore.proto.common.v1.DistinguishedName;
19+
import org.immutables.value.Value.Immutable;
20+
21+
@Immutable
22+
interface Subject {
23+
String getOrganization();
24+
25+
String getCommonName();
26+
27+
static Subject from(DistinguishedName proto) {
28+
return ImmutableSubject.builder()
29+
.commonName(proto.getCommonName())
30+
.organization(proto.getOrganization())
31+
.build();
32+
}
33+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2023 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.trustroot;
17+
18+
import static org.immutables.value.Value.*;
19+
20+
import dev.sigstore.proto.trustroot.v1.TransparencyLogInstance;
21+
import java.net.URI;
22+
23+
@Immutable
24+
public interface TransparencyLog {
25+
URI getBaseUrl();
26+
27+
String getHashAlgorithm();
28+
29+
LogId getLogId();
30+
31+
PublicKey getPublicKey();
32+
33+
static TransparencyLog from(TransparencyLogInstance proto) {
34+
return ImmutableTransparencyLog.builder()
35+
.baseUrl(URI.create(proto.getBaseUrl()))
36+
.hashAlgorithm(proto.getHashAlgorithm().name())
37+
.logId(LogId.from(proto.getLogId()))
38+
.publicKey(PublicKey.from(proto.getPublicKey()))
39+
.build();
40+
}
41+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2023 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.trustroot;
17+
18+
import dev.sigstore.proto.ProtoMutators;
19+
import dev.sigstore.proto.common.v1.TimeRange;
20+
import java.time.Instant;
21+
import java.util.Optional;
22+
import org.immutables.value.Value.Immutable;
23+
24+
@Immutable
25+
public interface ValidFor {
26+
Instant getStart();
27+
28+
Optional<Instant> getEnd();
29+
30+
static ValidFor from(TimeRange proto) {
31+
return ImmutableValidFor.builder()
32+
.start(ProtoMutators.toInstant(proto.getStart()))
33+
.end(
34+
proto.hasEnd()
35+
? Optional.of(ProtoMutators.toInstant(proto.getEnd()))
36+
: Optional.empty())
37+
.build();
38+
}
39+
}

sigstore-java/src/main/java/dev/sigstore/tuf/SigstoreTufClient.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.common.io.Resources;
2121
import com.google.protobuf.util.JsonFormat;
2222
import dev.sigstore.proto.trustroot.v1.TrustedRoot;
23+
import dev.sigstore.trustroot.SigstoreTrustedRoot;
2324
import java.io.IOException;
2425
import java.net.MalformedURLException;
2526
import java.net.URL;
@@ -28,6 +29,7 @@
2829
import java.nio.file.Path;
2930
import java.security.InvalidKeyException;
3031
import java.security.NoSuchAlgorithmException;
32+
import java.security.cert.CertificateException;
3133
import java.security.spec.InvalidKeySpecException;
3234
import java.time.Duration;
3335
import java.time.Instant;
@@ -40,9 +42,9 @@ public class SigstoreTufClient {
4042

4143
@VisibleForTesting static final String TRUST_ROOT_FILENAME = "trusted_root.json";
4244

43-
private Updater updater;
45+
private final Updater updater;
4446
private Instant lastUpdate;
45-
private TrustedRoot sigstoreTrustedRoot;
47+
private SigstoreTrustedRoot sigstoreTrustedRoot;
4648
private final Duration cacheValidity;
4749

4850
@VisibleForTesting
@@ -117,7 +119,8 @@ public SigstoreTufClient build() throws IOException {
117119
* defined on the client.
118120
*/
119121
public void update()
120-
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
122+
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException,
123+
CertificateException {
121124
if (lastUpdate == null
122125
|| Duration.between(lastUpdate, Instant.now()).compareTo(cacheValidity) > 0) {
123126
this.forceUpdate();
@@ -126,7 +129,8 @@ public void update()
126129

127130
/** Force an update, ignoring any cache validity. */
128131
public void forceUpdate()
129-
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
132+
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException,
133+
CertificateException {
130134
updater.update();
131135
lastUpdate = Instant.now();
132136
var trustedRootBuilder = TrustedRoot.newBuilder();
@@ -135,10 +139,10 @@ public void forceUpdate()
135139
new String(
136140
updater.getLocalStore().getTargetFile(TRUST_ROOT_FILENAME), StandardCharsets.UTF_8),
137141
trustedRootBuilder);
138-
sigstoreTrustedRoot = trustedRootBuilder.build();
142+
sigstoreTrustedRoot = SigstoreTrustedRoot.from(trustedRootBuilder.build());
139143
}
140144

141-
public TrustedRoot getSigstoreTrustedRoot() {
145+
public SigstoreTrustedRoot getSigstoreTrustedRoot() {
142146
return sigstoreTrustedRoot;
143147
}
144148
}

0 commit comments

Comments
 (0)