Skip to content

Commit 58a034f

Browse files
authored
Merge pull request #1027 from sigstore/addsigningalgotosigner
Match signer chosing to algorithm registry
2 parents 6dbfb38 + 1f04a1d commit 58a034f

File tree

17 files changed

+219
-69
lines changed

17 files changed

+219
-69
lines changed

fuzzing/src/main/java/fuzzing/SignerVerifierFuzzer.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package fuzzing;
1717

1818
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
19+
import dev.sigstore.AlgorithmRegistry;
1920
import dev.sigstore.encryption.signers.Signer;
2021
import dev.sigstore.encryption.signers.Signers;
2122
import dev.sigstore.encryption.signers.Verifier;
@@ -27,10 +28,10 @@
2728
public class SignerVerifierFuzzer {
2829
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
2930
try {
30-
Integer choice = data.consumeInt(0, 1);
31+
Integer choice = data.consumeInt(0, AlgorithmRegistry.SigningAlgorithm.values().length - 1);
3132
byte[] byteArray = data.consumeRemainingAsBytes();
3233

33-
Signer signer = (choice == 1) ? Signers.newEcdsaSigner() : Signers.newRsaSigner();
34+
Signer signer = Signers.from(AlgorithmRegistry.SigningAlgorithm.values()[choice]);
3435
Verifier verifier = Verifiers.newVerifier(signer.getPublicKey());
3536

3637
byte[] signature1 = signer.sign(byteArray);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2025 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;
17+
18+
import static com.google.common.hash.Hashing.*;
19+
20+
import com.google.common.hash.HashFunction;
21+
22+
public class AlgorithmRegistry {
23+
public enum SigningAlgorithm {
24+
PKIX_RSA_PKCS1V15_2048_SHA256(HashAlgorithm.SHA2_256),
25+
PKIX_RSA_PKCS1V15_3072_SHA256(HashAlgorithm.SHA2_256),
26+
PKIX_RSA_PKCS1V15_4096_SHA256(HashAlgorithm.SHA2_256),
27+
28+
// ECDSA
29+
PKIX_ECDSA_P256_SHA_256(HashAlgorithm.SHA2_256);
30+
// TODO: PKIX_ECDSA_P384_SHA_384(HashAlgorithm.SHA2_384),
31+
// TODO: PKIX_ECDSA_P521_SHA_512(HashAlgorithm.SHA2_512);
32+
33+
private final HashAlgorithm hashAlgorithm;
34+
35+
SigningAlgorithm(HashAlgorithm hashAlgorithm) {
36+
this.hashAlgorithm = hashAlgorithm;
37+
}
38+
39+
public HashAlgorithm getHashing() {
40+
return hashAlgorithm;
41+
}
42+
}
43+
44+
public enum HashAlgorithm {
45+
SHA2_256("SHA256", 32, sha256());
46+
// TODO: SHA2_384("SHA384", 48, sha384()),
47+
// TODO: SHA2_512("SHA512", 64, sha512());
48+
49+
private final String name;
50+
private final int length;
51+
private final HashFunction hashFunction;
52+
53+
HashAlgorithm(String name, int length, HashFunction hashFunction) {
54+
this.name = name;
55+
this.length = length;
56+
this.hashFunction = hashFunction;
57+
}
58+
59+
public String toString() {
60+
return name;
61+
}
62+
63+
public int getLength() {
64+
return length;
65+
}
66+
67+
HashFunction getHashFunction() {
68+
return hashFunction;
69+
}
70+
}
71+
}

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

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import com.google.errorprone.annotations.concurrent.GuardedBy;
2525
import com.google.protobuf.ByteString;
2626
import dev.sigstore.bundle.Bundle;
27-
import dev.sigstore.bundle.Bundle.HashAlgorithm;
2827
import dev.sigstore.bundle.Bundle.MessageSignature;
2928
import dev.sigstore.bundle.ImmutableBundle;
3029
import dev.sigstore.bundle.ImmutableTimestamp;
@@ -41,7 +40,7 @@
4140
import dev.sigstore.oidc.client.OidcException;
4241
import dev.sigstore.oidc.client.OidcToken;
4342
import dev.sigstore.oidc.client.OidcTokenMatcher;
44-
import dev.sigstore.proto.common.v1.PublicKeyDetails;
43+
import dev.sigstore.proto.ProtoMutators;
4544
import dev.sigstore.proto.common.v1.X509Certificate;
4645
import dev.sigstore.proto.rekor.v2.HashedRekordRequestV002;
4746
import dev.sigstore.proto.rekor.v2.Signature;
@@ -114,6 +113,7 @@ public class KeylessSigner implements AutoCloseable {
114113
private final OidcClients oidcClients;
115114
private final List<OidcTokenMatcher> oidcIdentities;
116115
private final Signer signer;
116+
private final AlgorithmRegistry.SigningAlgorithm signingAlgorithm;
117117
private final Duration minSigningCertificateLifetime;
118118

119119
/** The code signing certificate from Fulcio. */
@@ -150,6 +150,7 @@ private KeylessSigner(
150150
OidcClients oidcClients,
151151
List<OidcTokenMatcher> oidcIdentities,
152152
Signer signer,
153+
AlgorithmRegistry.SigningAlgorithm signingAlgorithm,
153154
Duration minSigningCertificateLifetime) {
154155
this.fulcioClient = fulcioClient;
155156
this.fulcioVerifier = fulcioVerifier;
@@ -161,6 +162,7 @@ private KeylessSigner(
161162
this.oidcClients = oidcClients;
162163
this.oidcIdentities = oidcIdentities;
163164
this.signer = signer;
165+
this.signingAlgorithm = signingAlgorithm;
164166
this.minSigningCertificateLifetime = minSigningCertificateLifetime;
165167
}
166168

@@ -186,7 +188,7 @@ public static class Builder {
186188
private SigningConfigProvider signingConfigProvider;
187189
private OidcClients oidcClients;
188190
private List<OidcTokenMatcher> oidcIdentities = Collections.emptyList();
189-
private Signer signer;
191+
private AlgorithmRegistry.SigningAlgorithm signingAlgorithm;
190192
private Duration minSigningCertificateLifetime = DEFAULT_MIN_SIGNING_CERTIFICATE_LIFETIME;
191193
private boolean enableRekorV2 = false;
192194

@@ -239,8 +241,8 @@ public Builder allowedOidcIdentities(List<OidcTokenMatcher> oidcIdentities) {
239241
}
240242

241243
@CanIgnoreReturnValue
242-
public Builder signer(Signer signer) {
243-
this.signer = signer;
244+
public Builder signingAlgorithm(AlgorithmRegistry.SigningAlgorithm signingAlgorithm) {
245+
this.signingAlgorithm = signingAlgorithm;
244246
return this;
245247
}
246248

@@ -276,7 +278,7 @@ public KeylessSigner build()
276278
Preconditions.checkNotNull(signingConfigProvider);
277279
var signingConfig = signingConfigProvider.get();
278280
Preconditions.checkNotNull(oidcIdentities);
279-
Preconditions.checkNotNull(signer);
281+
Preconditions.checkNotNull(signingAlgorithm);
280282
Preconditions.checkNotNull(minSigningCertificateLifetime);
281283
var fulcioService = Service.select(signingConfig.getCas(), List.of(1));
282284
if (fulcioService.isEmpty()) {
@@ -328,6 +330,12 @@ public KeylessSigner build()
328330
oidcClients = OidcClients.from(oidcService.get());
329331
}
330332

333+
if (!signingAlgorithm.getHashing().equals(AlgorithmRegistry.HashAlgorithm.SHA2_256)) {
334+
throw new SigstoreConfigurationException("Signing algorithm must use sha256");
335+
}
336+
337+
var signer = Signers.from(signingAlgorithm);
338+
331339
return new KeylessSigner(
332340
fulcioClient,
333341
fulcioVerifier,
@@ -339,6 +347,7 @@ public KeylessSigner build()
339347
oidcClients,
340348
oidcIdentities,
341349
signer,
350+
signingAlgorithm,
342351
minSigningCertificateLifetime);
343352
}
344353

@@ -354,7 +363,7 @@ public Builder sigstorePublicDefaults() {
354363
signingConfigProvider =
355364
SigningConfigProvider.fromOrDefault(
356365
sigstoreTufClientBuilder, LegacySigningConfig.PUBLIC_GOOD);
357-
signer(Signers.newEcdsaSigner());
366+
signingAlgorithm = AlgorithmRegistry.SigningAlgorithm.PKIX_ECDSA_P256_SHA_256;
358367
minSigningCertificateLifetime(DEFAULT_MIN_SIGNING_CERTIFICATE_LIFETIME);
359368
return this;
360369
}
@@ -368,7 +377,7 @@ public Builder sigstoreStagingDefaults() {
368377
var sigstoreTufClientBuilder = SigstoreTufClient.builder().useStagingInstance();
369378
trustedRootProvider = TrustedRootProvider.from(sigstoreTufClientBuilder);
370379
signingConfigProvider = SigningConfigProvider.from(sigstoreTufClientBuilder);
371-
signer(Signers.newEcdsaSigner());
380+
signingAlgorithm = AlgorithmRegistry.SigningAlgorithm.PKIX_ECDSA_P256_SHA_256;
372381
minSigningCertificateLifetime(DEFAULT_MIN_SIGNING_CERTIFICATE_LIFETIME);
373382
return this;
374383
}
@@ -384,11 +393,20 @@ public Builder sigstoreStagingDefaults() {
384393
*/
385394
@CheckReturnValue
386395
public List<Bundle> sign(List<byte[]> artifactDigests) throws KeylessSignerException {
387-
388-
if (artifactDigests.size() == 0) {
396+
if (artifactDigests.isEmpty()) {
389397
throw new IllegalArgumentException("Require one or more digests");
390398
}
391399

400+
for (var digest : artifactDigests) {
401+
if (signingAlgorithm.getHashing().getLength() != digest.length) {
402+
throw new KeylessSignerException(
403+
"Invalid digest length: "
404+
+ digest.length
405+
+ " for signing Algorithm "
406+
+ signingAlgorithm);
407+
}
408+
}
409+
392410
var result = ImmutableList.<Bundle>builder();
393411

394412
for (var artifactDigest : artifactDigests) {
@@ -435,7 +453,7 @@ public List<Bundle> sign(List<byte[]> artifactDigests) throws KeylessSignerExcep
435453
ImmutableBundle.builder()
436454
.certPath(signingCert)
437455
.messageSignature(
438-
MessageSignature.of(HashAlgorithm.SHA2_256, artifactDigest, signature));
456+
MessageSignature.of(signingAlgorithm.getHashing(), artifactDigest, signature));
439457

440458
if (rekorV2Client != null) { // Using Rekor v2 and a TSA
441459
Preconditions.checkNotNull(
@@ -475,7 +493,7 @@ public List<Bundle> sign(List<byte[]> artifactDigests) throws KeylessSignerExcep
475493
X509Certificate.newBuilder()
476494
.setRawBytes(ByteString.copyFrom(encodedCert))
477495
.build())
478-
.setKeyDetails(PublicKeyDetails.PKIX_ECDSA_P256_SHA_256)
496+
.setKeyDetails(ProtoMutators.toPublicKeyDetails(signingAlgorithm))
479497
.build();
480498

481499
var reqSignature =
@@ -610,7 +628,7 @@ private void renewSigningCertificate()
610628
/**
611629
* Convenience wrapper around {@link #sign(List)} to sign a single digest
612630
*
613-
* @param artifactDigest sha256 digest of the artifact to sign.
631+
* @param artifactDigest sha256 digest of the artifacts to sign.
614632
* @return a keyless singing results.
615633
*/
616634
@CheckReturnValue
@@ -626,14 +644,15 @@ public Bundle sign(byte[] artifactDigest) throws KeylessSignerException {
626644
*/
627645
@CheckReturnValue
628646
public Map<Path, Bundle> signFiles(List<Path> artifacts) throws KeylessSignerException {
629-
if (artifacts.size() == 0) {
647+
if (artifacts.isEmpty()) {
630648
throw new IllegalArgumentException("Require one or more paths");
631649
}
632650
var digests = new ArrayList<byte[]>(artifacts.size());
633651
for (var artifact : artifacts) {
634652
var artifactByteSource = com.google.common.io.Files.asByteSource(artifact.toFile());
635653
try {
636-
digests.add(artifactByteSource.hash(Hashing.sha256()).asBytes());
654+
digests.add(
655+
artifactByteSource.hash(signingAlgorithm.getHashing().getHashFunction()).asBytes());
637656
} catch (IOException ex) {
638657
throw new KeylessSignerException("Failed to hash artifact " + artifact);
639658
}
@@ -656,13 +675,4 @@ public Map<Path, Bundle> signFiles(List<Path> artifacts) throws KeylessSignerExc
656675
public Bundle signFile(Path artifact) throws KeylessSignerException {
657676
return signFiles(List.of(artifact)).get(artifact);
658677
}
659-
660-
/**
661-
* Convenience wrapper around {@link #sign(List)} to accept a single file Compat - to be removed
662-
* before 1.0.0
663-
*/
664-
@Deprecated
665-
public Bundle signFile2(Path artifact) throws KeylessSignerException {
666-
return signFiles(List.of(artifact)).get(artifact);
667-
}
668678
}

sigstore-java/src/main/java/dev/sigstore/bundle/Bundle.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package dev.sigstore.bundle;
1717

1818
import com.google.common.base.Preconditions;
19+
import dev.sigstore.AlgorithmRegistry;
1920
import dev.sigstore.rekor.client.RekorEntry;
2021
import java.io.IOException;
2122
import java.io.Reader;
@@ -40,10 +41,6 @@
4041
@Immutable
4142
public abstract class Bundle {
4243

43-
public enum HashAlgorithm {
44-
SHA2_256
45-
}
46-
4744
static final String BUNDLE_V_0_1 = "application/vnd.dev.sigstore.bundle+json;version=0.1";
4845
static final String BUNDLE_V_0_2 = "application/vnd.dev.sigstore.bundle+json;version=0.2";
4946
static final String BUNDLE_V_0_3 = "application/vnd.dev.sigstore.bundle+json;version=0.3";
@@ -112,7 +109,8 @@ public interface MessageSignature {
112109
/** Signature over an artifact. */
113110
byte[] getSignature();
114111

115-
static MessageSignature of(HashAlgorithm algorithm, byte[] digest, byte[] signature) {
112+
static MessageSignature of(
113+
AlgorithmRegistry.HashAlgorithm algorithm, byte[] digest, byte[] signature) {
116114
return ImmutableMessageSignature.builder()
117115
.signature(signature)
118116
.messageDigest(
@@ -125,7 +123,7 @@ static MessageSignature of(HashAlgorithm algorithm, byte[] digest, byte[] signat
125123
public interface MessageDigest {
126124

127125
/** The algorithm used to compute the digest. */
128-
HashAlgorithm getHashAlgorithm();
126+
AlgorithmRegistry.HashAlgorithm getHashAlgorithm();
129127

130128
/**
131129
* The raw bytes of the digest computer using the hashing algorithm described by {@link

sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package dev.sigstore.bundle;
1717

1818
import com.google.protobuf.ByteString;
19+
import dev.sigstore.AlgorithmRegistry;
1920
import dev.sigstore.json.ProtoJson;
2021
import dev.sigstore.proto.ProtoMutators;
2122
import dev.sigstore.proto.common.v1.HashAlgorithm;
@@ -126,7 +127,7 @@ static Bundle readBundle(Reader jsonReader) throws BundleParseException {
126127
ImmutableMessageSignature.builder()
127128
.messageDigest(
128129
ImmutableMessageDigest.builder()
129-
.hashAlgorithm(Bundle.HashAlgorithm.SHA2_256)
130+
.hashAlgorithm(AlgorithmRegistry.HashAlgorithm.SHA2_256)
130131
.digest(
131132
protoBundle
132133
.getMessageSignature()

sigstore-java/src/main/java/dev/sigstore/bundle/BundleWriter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ static dev.sigstore.proto.bundle.v1.Bundle.Builder createBundleBuilder(Bundle bu
9494
.setMessageDigest(
9595
HashOutput.newBuilder()
9696
.setAlgorithm(
97-
ProtoMutators.from(
97+
ProtoMutators.toProtoHashAlgorithm(
9898
messageSignature.getMessageDigest().get().getHashAlgorithm()))
9999
.setDigest(
100100
ByteString.copyFrom(

sigstore-java/src/main/java/dev/sigstore/encryption/signers/EcdsaSigner.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@
1515
*/
1616
package dev.sigstore.encryption.signers;
1717

18+
import dev.sigstore.AlgorithmRegistry;
1819
import java.security.*;
1920

20-
/** ECDSA signer, use {@link Signers#newEcdsaSigner()} to instantiate}. */
21+
/** ECDSA signer, use {@link Signers} to instantiate}. */
2122
public class EcdsaSigner implements Signer {
2223

2324
private final KeyPair keyPair;
25+
private final AlgorithmRegistry.HashAlgorithm hashAlgorithm;
2426

25-
EcdsaSigner(KeyPair keyPair) {
27+
EcdsaSigner(KeyPair keyPair, AlgorithmRegistry.HashAlgorithm hashAlgorithm) {
2628
this.keyPair = keyPair;
29+
this.hashAlgorithm = hashAlgorithm;
2730
}
2831

2932
@Override
@@ -34,7 +37,7 @@ public PublicKey getPublicKey() {
3437
@Override
3538
public byte[] sign(byte[] artifact)
3639
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
37-
Signature signature = Signature.getInstance("SHA256withECDSA");
40+
Signature signature = Signature.getInstance(hashAlgorithm + "withECDSA");
3841
signature.initSign(keyPair.getPrivate());
3942
signature.update(artifact);
4043
return signature.sign();
@@ -43,8 +46,9 @@ public byte[] sign(byte[] artifact)
4346
@Override
4447
public byte[] signDigest(byte[] artifactDigest)
4548
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
46-
if (artifactDigest.length > 64) {
47-
throw new SignatureException("Artifact digest cannot be longer than 64 bytes for this mode");
49+
if (artifactDigest.length != hashAlgorithm.getLength()) {
50+
throw new SignatureException(
51+
"Artifact digest must be " + hashAlgorithm.getLength() + " bytes");
4852
}
4953
Signature signature = Signature.getInstance("NONEwithECDSA");
5054
signature.initSign(keyPair.getPrivate());

0 commit comments

Comments
 (0)