Skip to content

Commit 12068fc

Browse files
authored
Merge pull request #847 from sigstore/add_key_parsers_to_tuf_package
Add tuf specific key/signature handlers
2 parents 014d2a8 + f4f816e commit 12068fc

File tree

10 files changed

+593
-0
lines changed

10 files changed

+593
-0
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 fuzzing;
17+
18+
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
19+
import dev.sigstore.tuf.encryption.Verifiers;
20+
import dev.sigstore.tuf.model.ImmutableKey;
21+
import dev.sigstore.tuf.model.Key;
22+
import java.io.IOException;
23+
import java.security.InvalidKeyException;
24+
import java.util.Map;
25+
26+
public class TufVerifierFuzzer {
27+
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
28+
try {
29+
String keyType = data.consumeString(10);
30+
String scheme = data.consumeString(20);
31+
String keyData = data.consumeRemainingAsString();
32+
33+
Key key =
34+
ImmutableKey.builder()
35+
.keyType(keyType)
36+
.keyVal(Map.of("public", keyData))
37+
.scheme(scheme)
38+
.build();
39+
40+
Verifiers.newVerifier(key);
41+
} catch (IOException | InvalidKeyException e) {
42+
// known exceptions
43+
}
44+
}
45+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2022 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.tuf.encryption;
17+
18+
import java.security.InvalidKeyException;
19+
import java.security.NoSuchAlgorithmException;
20+
import java.security.PublicKey;
21+
import java.security.Signature;
22+
import java.security.SignatureException;
23+
24+
/** ECDSA verifier, instantiated in {@link Verifiers}. */
25+
class EcdsaVerifier implements Verifier {
26+
27+
private final PublicKey publicKey;
28+
29+
EcdsaVerifier(PublicKey publicKey) {
30+
this.publicKey = publicKey;
31+
}
32+
33+
@Override
34+
public boolean verify(byte[] artifact, byte[] signature)
35+
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
36+
var verifier = Signature.getInstance("SHA256withECDSA");
37+
verifier.initVerify(publicKey);
38+
verifier.update(artifact);
39+
return verifier.verify(signature);
40+
}
41+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2022 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.tuf.encryption;
17+
18+
import java.security.InvalidKeyException;
19+
import java.security.NoSuchAlgorithmException;
20+
import java.security.PublicKey;
21+
import java.security.Signature;
22+
import java.security.SignatureException;
23+
24+
/** Ed25519 verifier, instantiated by {@link Verifiers}. */
25+
class Ed25519Verifier implements Verifier {
26+
27+
private final PublicKey publicKey;
28+
29+
Ed25519Verifier(PublicKey publicKey) {
30+
this.publicKey = publicKey;
31+
}
32+
33+
/** EdDSA verifiers hash implicitly for ed25519 keys. */
34+
@Override
35+
public boolean verify(byte[] artifact, byte[] signature)
36+
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
37+
var verifier = Signature.getInstance("Ed25519");
38+
verifier.initVerify(publicKey);
39+
verifier.update(artifact);
40+
return verifier.verify(signature);
41+
}
42+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2022 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.tuf.encryption;
17+
18+
import java.security.InvalidKeyException;
19+
import java.security.NoSuchAlgorithmException;
20+
import java.security.PublicKey;
21+
import java.security.Signature;
22+
import java.security.SignatureException;
23+
24+
/** RSA verifier using PSS and MGF1, instantiated by {@link Verifiers}. */
25+
class RsaPssVerifier implements Verifier {
26+
27+
private final PublicKey publicKey;
28+
29+
RsaPssVerifier(PublicKey publicKey) {
30+
this.publicKey = publicKey;
31+
}
32+
33+
@Override
34+
public boolean verify(byte[] artifact, byte[] signature)
35+
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
36+
var verifier = Signature.getInstance("SHA256withRSAandMGF1");
37+
verifier.initVerify(publicKey);
38+
verifier.update(artifact);
39+
return verifier.verify(signature);
40+
}
41+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2022 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.tuf.encryption;
17+
18+
import java.security.InvalidKeyException;
19+
import java.security.NoSuchAlgorithmException;
20+
import java.security.SignatureException;
21+
22+
/** A verifier interface specifying verification for a raw artifact (no hashing). */
23+
public interface Verifier {
24+
25+
/**
26+
* Verify an artifact. Implementations may hash the artifact with sha256 before verifying unless
27+
* they have an implicit hashing algorithm.
28+
*
29+
* @param artifact the artifact that was signed
30+
* @param signature the signature associated with the artifact
31+
* @return true if the signature is valid, false otherwise
32+
*/
33+
boolean verify(byte[] artifact, byte[] signature)
34+
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException;
35+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright 2024 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.tuf.encryption;
17+
18+
import dev.sigstore.tuf.model.Key;
19+
import java.io.IOException;
20+
import java.io.StringReader;
21+
import java.security.InvalidKeyException;
22+
import java.security.PublicKey;
23+
import java.security.Security;
24+
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
25+
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
26+
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
27+
import org.bouncycastle.crypto.params.ECKeyParameters;
28+
import org.bouncycastle.crypto.params.RSAKeyParameters;
29+
import org.bouncycastle.crypto.util.PublicKeyFactory;
30+
import org.bouncycastle.jce.provider.BouncyCastleProvider;
31+
import org.bouncycastle.openssl.PEMParser;
32+
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
33+
import org.bouncycastle.util.encoders.DecoderException;
34+
import org.bouncycastle.util.encoders.Hex;
35+
36+
public class Verifiers {
37+
38+
static {
39+
Security.addProvider(new BouncyCastleProvider());
40+
}
41+
42+
@FunctionalInterface
43+
public interface Supplier {
44+
Verifier newVerifier(Key key) throws IOException, InvalidKeyException;
45+
}
46+
47+
public static Verifier newVerifier(Key key) throws IOException, InvalidKeyException {
48+
49+
PublicKey publicKey = parsePublicKey(key);
50+
if (key.getKeyType().equals("rsa") && key.getScheme().equals("rsassa-pss-sha256")) {
51+
return new RsaPssVerifier(publicKey);
52+
}
53+
if (isEcdsaKey(key) && key.getScheme().equals("ecdsa-sha2-nistp256")) {
54+
return new EcdsaVerifier(publicKey);
55+
}
56+
if (key.getKeyType().equals("ed25519") && key.getScheme().equals("ed25519")) {
57+
return new Ed25519Verifier(publicKey);
58+
}
59+
throw new InvalidKeyException(
60+
"Unsupported tuf key type and scheme combination: "
61+
+ key.getKeyType()
62+
+ "/"
63+
+ key.getScheme());
64+
}
65+
66+
private static PublicKey parsePublicKey(Key key) throws IOException, InvalidKeyException {
67+
var keyType = key.getKeyType();
68+
if (keyType.equals("rsa") || isEcdsaKey(key)) {
69+
try (PEMParser pemParser = new PEMParser(new StringReader(key.getKeyVal().get("public")))) {
70+
var keyObj = pemParser.readObject(); // throws DecoderException
71+
if (keyObj == null) {
72+
throw new InvalidKeyException(
73+
"tuf " + key.getKeyType() + " keys must be a single PEM encoded section");
74+
}
75+
if (keyObj instanceof SubjectPublicKeyInfo) {
76+
var keyInfo = PublicKeyFactory.createKey((SubjectPublicKeyInfo) keyObj);
77+
if ((keyType.equals("rsa") && keyInfo instanceof RSAKeyParameters)
78+
|| (isEcdsaKey(key) && keyInfo instanceof ECKeyParameters)) {
79+
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
80+
return converter.getPublicKey((SubjectPublicKeyInfo) keyObj);
81+
}
82+
}
83+
throw new InvalidKeyException(
84+
"Could not parse PEM section into " + keyType + " public key");
85+
} catch (DecoderException e) {
86+
throw new InvalidKeyException("Could not parse PEM section in " + keyType + " public key");
87+
}
88+
}
89+
// tuf allows raw keys only for ed25519 (non PEM):
90+
// https://github.com/theupdateframework/specification/blob/c51875f445d8a57efca9dadfbd5dbdece06d87e6/tuf-spec.md#key-objects--file-formats-keys
91+
else if (keyType.equals("ed25519")) {
92+
byte[] keyContents;
93+
try {
94+
keyContents = Hex.decode(key.getKeyVal().get("public"));
95+
} catch (DecoderException e) {
96+
throw new InvalidKeyException("Could not parse hex encoded ed25519 public key");
97+
}
98+
var params =
99+
new SubjectPublicKeyInfo(
100+
new AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519), keyContents);
101+
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
102+
return converter.getPublicKey(params);
103+
} else {
104+
throw new InvalidKeyException("Unsupported tuf key type" + key.getKeyType());
105+
}
106+
}
107+
108+
// this is a hack to handle keytypes of ecdsa-sha2-nistp256
109+
// context: https://github.com/awslabs/tough/issues/754
110+
private static boolean isEcdsaKey(Key key) {
111+
return key.getKeyType().equals("ecdsa-sha2-nistp256") || key.getKeyType().equals("ecdsa");
112+
}
113+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2024 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.tuf.encryption;
17+
18+
import java.nio.charset.StandardCharsets;
19+
import java.security.KeyPair;
20+
import java.security.KeyPairGenerator;
21+
import java.security.Security;
22+
import java.security.Signature;
23+
import org.bouncycastle.jce.provider.BouncyCastleProvider;
24+
import org.junit.jupiter.api.Assertions;
25+
import org.junit.jupiter.api.Test;
26+
27+
class EcdsaVerifierTest {
28+
29+
private static final byte[] CONTENT = "abcdef".getBytes(StandardCharsets.UTF_8);
30+
31+
@Test
32+
public void testVerify_ECDSA() throws Exception {
33+
Security.addProvider(new BouncyCastleProvider());
34+
35+
var keyPair = genKeyPair();
36+
var signature = genSignature(keyPair);
37+
var verifier = new EcdsaVerifier(keyPair.getPublic());
38+
Assertions.assertTrue(verifier.verify(CONTENT, signature));
39+
}
40+
41+
private KeyPair genKeyPair() throws Exception {
42+
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA");
43+
keyGen.initialize(256);
44+
return keyGen.generateKeyPair();
45+
}
46+
47+
private byte[] genSignature(KeyPair keyPair) throws Exception {
48+
Signature signature = Signature.getInstance("SHA256withECDSA");
49+
signature.initSign(keyPair.getPrivate());
50+
signature.update(CONTENT);
51+
return signature.sign();
52+
}
53+
}

0 commit comments

Comments
 (0)