Skip to content

Commit

Permalink
Cleanup tuf from sigstore-core
Browse files Browse the repository at this point in the history
- remove tests based on copy of old prod data
- remove tuf key parsing from sigstore packages, we are using the
  verifiers in the tuf package path
- standardize ed25519 parsing on bouncy castle

Signed-off-by: Appu Goundan <[email protected]>
  • Loading branch information
loosebazooka committed Nov 14, 2024
1 parent 6d82e02 commit ef10161
Show file tree
Hide file tree
Showing 49 changed files with 12 additions and 2,644 deletions.
39 changes: 0 additions & 39 deletions fuzzing/src/main/java/fuzzing/TufKeysFuzzer.java

This file was deleted.

85 changes: 1 addition & 84 deletions sigstore-java/src/main/java/dev/sigstore/encryption/Keys.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,22 @@
*/
package dev.sigstore.encryption;

import static org.bouncycastle.jce.ECPointUtil.decodePoint;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.List;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.util.encoders.DecoderException;
Expand Down Expand Up @@ -74,6 +64,7 @@ public static PublicKey parsePublicKey(byte[] keyBytes)
"sigstore public keys must be only a single PEM encoded public key");
}
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
converter.setProvider(BouncyCastleProvider.PROVIDER_NAME);
if (keyObj instanceof SubjectPublicKeyInfo) {
PublicKey pk = converter.getPublicKey((SubjectPublicKeyInfo) keyObj);
if (!SUPPORTED_KEY_TYPES.contains(pk.getAlgorithm())) {
Expand Down Expand Up @@ -115,78 +106,4 @@ public static PublicKey parsePkcs1RsaPublicKey(byte[] contents)
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(keySpec);
}

/**
* Valid values for scheme are:
*
* <ol>
* <li><a href="https://ed25519.cr.yp.to/">ed25519</a>
* <li><a
* href="https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm">ecdsa-sha2-nistp256</a>
* </ol>
*
* @see <a
* href="https://theupdateframework.github.io/specification/latest/index.html#role-role">spec</a>
* @param contents keyBytes
* @param scheme signing scheme
* @return java {link PublicKey}
* @throws NoSuchAlgorithmException if we don't support the scheme provided
* @throws InvalidKeySpecException if the public key material is invalid
*/
public static PublicKey constructTufPublicKey(byte[] contents, String scheme)
throws NoSuchAlgorithmException, InvalidKeySpecException {
if (contents == null || contents.length == 0) {
throw new InvalidKeySpecException("key contents was empty");
}
switch (scheme) {
case "ed25519":
{
final KeyFactory kf = KeyFactory.getInstance("Ed25519");
X509EncodedKeySpec keySpec;
// tuf allows raw keys only for ed25519 (non PEM):
// https://github.com/theupdateframework/specification/blob/c51875f445d8a57efca9dadfbd5dbdece06d87e6/tuf-spec.md#key-objects--file-formats-keys
if (contents.length == 32) {
var params =
new SubjectPublicKeyInfo(
new AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519), contents);
try {
keySpec = new X509EncodedKeySpec(params.getEncoded());
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
keySpec = new X509EncodedKeySpec(contents);
}
return kf.generatePublic(keySpec);
}
case "ecdsa":
case "ecdsa-sha2-nistp256":
{
// spec for P-256 curve
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("P-256");
// create a KeyFactory with ECDSA (Elliptic Curve Diffie-Hellman) algorithm and use
// BouncyCastle as the provider
KeyFactory kf = null;
try {
kf = KeyFactory.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);
} catch (NoSuchProviderException e) {
throw new RuntimeException(e);
}

// code below just creates the public key from key contents using the curve parameters
// (spec variable)
try {
ECNamedCurveSpec params =
new ECNamedCurveSpec("P-256", spec.getCurve(), spec.getG(), spec.getN());
ECPoint point = decodePoint(params.getCurve(), contents);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
return kf.generatePublic(pubKeySpec);
} catch (IllegalArgumentException | NullPointerException ex) {
throw new InvalidKeySpecException("ecdsa key was not parseable", ex);
}
}
default:
throw new RuntimeException(scheme + " not currently supported");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@

/** Autodetection for verification algorithms based on public keys used. */
public class Verifiers {
@FunctionalInterface
public interface Supplier {
public Verifier newVerifier(PublicKey publicKey) throws NoSuchAlgorithmException;
}

/** Returns a new verifier for the provided public key to use during verification. */
public static Verifier newVerifier(PublicKey publicKey) throws NoSuchAlgorithmException {
if (publicKey.getAlgorithm().equals("RSA")) {
Expand Down
121 changes: 3 additions & 118 deletions sigstore-java/src/test/java/dev/sigstore/encryption/KeysTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,11 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;

class KeysTest {

Expand Down Expand Up @@ -73,24 +69,15 @@ void parsePublicKey_ec() throws IOException, InvalidKeySpecException, NoSuchAlgo
}

@Test
@EnabledForJreRange(max = JRE.JAVA_14)
void parsePublicKey_ed25519_withBouncyCastle()
void parsePublicKey_ed25519()
throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
PublicKey result =
Keys.parsePublicKey(Resources.toByteArray(Resources.getResource(ED25519_PUB_PATH)));
// BouncyCastle names the algorithm differently than the JDK (Ed25519 vs EdDSA)
// BouncyCastle names the algorithm differently than the JDK (Ed25519 vs EdDSA) but we
// force the converter to use BouncyCastle always.
assertEquals("Ed25519", result.getAlgorithm());
}

@Test
@EnabledForJreRange(min = JRE.JAVA_15)
void parsePublicKey_ed25519_withStdLib()
throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
PublicKey result =
Keys.parsePublicKey(Resources.toByteArray(Resources.getResource(ED25519_PUB_PATH)));
assertEquals("EdDSA", result.getAlgorithm());
}

@Test
void parsePublicKey_dsaShouldFail() {
Assertions.assertThrows(
Expand All @@ -106,108 +93,6 @@ void parseTufPublicKeyPemEncoded_sha2_nistp256()
assertEquals("ECDSA", result.getAlgorithm());
}

@Test
void parseTufPublicKey_ecdsa() throws NoSuchAlgorithmException, InvalidKeySpecException {
PublicKey key =
Keys.constructTufPublicKey(
Hex.decode(
"04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"),
"ecdsa-sha2-nistp256");
assertNotNull(key);
assertEquals("ECDSA", key.getAlgorithm());
}

@Test
void parseTufPublicKey_ecdsaBad() {
Assertions.assertThrows(
InvalidKeySpecException.class,
() -> {
Keys.constructTufPublicKey(
Hex.decode(
"04cbcdcab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"),
"ecdsa-sha2-nistp256");
});
}

@Test
@EnabledForJreRange(min = JRE.JAVA_15)
void parseTufPublicKey_ed25519_java15Plus()
throws NoSuchAlgorithmException, InvalidKeySpecException {
// {@code step crypto keypair ed25519.pub /dev/null --kty OKP --curve Ed25519}
// copy just the key part out of ed25519.pub removing PEM header and footer
// {@code echo $(copied content) | base64 -d | hexdump -v -e '/1 "%02x" '}
PublicKey key =
Keys.constructTufPublicKey(
Hex.decode(
"302a300506032b65700321008b2e369230c3b97f4627fd6a59eb054a83ec15ed929ab3d983a40ffd322a223d"),
"ed25519");
assertNotNull(key);
assertEquals("EdDSA", key.getAlgorithm());
}

@Test
@EnabledForJreRange(max = JRE.JAVA_14)
void parseTufPublicKey_ed25519_lteJava14()
throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
// {@code step crypto keypair ed25519.pub /dev/null --kty OKP --curve Ed25519}
// copy just the key part out of ed25519.pub removing PEM header and footer
// {@code echo $(copied content) | base64 -d | hexdump -v -e '/1 "%02x" '}
PublicKey key =
Keys.constructTufPublicKey(
Hex.decode(
"302a300506032b65700321008b2e369230c3b97f4627fd6a59eb054a83ec15ed929ab3d983a40ffd322a223d"),
"ed25519");
assertNotNull(key);
assertEquals("Ed25519", key.getAlgorithm());
}

@Test
@EnabledForJreRange(min = JRE.JAVA_15)
void parseTufPublicKey_ed25519_rawBytes_java15plus() throws Exception {
PublicKey key =
Keys.constructTufPublicKey(
Hex.decode("2d7218ce609f85de4b0d29d9e679cfd73e96756652f7069a0cf00acb752e5d3c"),
"ed25519");
assertNotNull(key);
assertEquals("EdDSA", key.getAlgorithm());
}

@Test
@EnabledForJreRange(max = JRE.JAVA_14)
void parseTufPublicKey_ed25519_rawBytes_lteJava14() throws Exception {
PublicKey key =
Keys.constructTufPublicKey(
Hex.decode("2d7218ce609f85de4b0d29d9e679cfd73e96756652f7069a0cf00acb752e5d3c"),
"ed25519");
assertNotNull(key);
assertEquals("Ed25519", key.getAlgorithm());
}

@Test
void parseTufPublicKey_ed25519Bad() {
Assertions.assertThrows(
InvalidKeySpecException.class,
() ->
Keys.constructTufPublicKey(
Hex.decode(
"302b300506032b65700321008b2e369230c3b97f4627fd6a59eb054a83ec15ed929ab3d983a40ffd322a223d"),
"ed25519"));
}

@Test
void parseTufPublicKey_rsa() throws NoSuchAlgorithmException, InvalidKeySpecException {
// {@code step crypto keypair ed25519.pub /dev/null --kty OKP --curve Ed25519}
// copy just the key part out of ed25519.pub removing PEM header and footer
// {@code echo $(copied content) | base64 -d | hexdump -v -e '/1 "%02x" '}
Assertions.assertThrows(
RuntimeException.class,
() ->
Keys.constructTufPublicKey(
Hex.decode(
"302a300506032b65700321008b2e369230c3b97f4627fd6a59eb054a83ec15ed929ab3d983a40ffd322a223d"),
"rsassa-pss-sha256"));
}

@Test
void parsePkixPublicKey_rsa() throws NoSuchAlgorithmException, InvalidKeySpecException {
var base64Key =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

class FileSystemTufStoreTest {

public static final String PROD_REPO = "real/prod";
public static final String REPO = "synthetic/test-template";

@Test
void newFileSystemStore_empty(@TempDir Path repoBase) throws IOException {
Expand All @@ -37,7 +37,7 @@ void newFileSystemStore_empty(@TempDir Path repoBase) throws IOException {

@Test
void newFileSystemStore_hasRepo(@TempDir Path repoBase) throws IOException {
TestResources.setupRepoFiles(PROD_REPO, repoBase, "root.json");
TestResources.setupRepoFiles(REPO, repoBase, "root.json");
FileSystemTufStore tufStore = FileSystemTufStore.newFileSystemStore(repoBase);
assertTrue(tufStore.readMeta(RootRole.ROOT, Root.class).isPresent());
}
Expand All @@ -47,15 +47,15 @@ void writeMeta(@TempDir Path repoBase) throws IOException {
FileSystemTufStore tufStore = FileSystemTufStore.newFileSystemStore(repoBase);
assertFalse(repoBase.resolve("root.json").toFile().exists());
tufStore.writeMeta(
RootRole.ROOT, TestResources.loadRoot(TestResources.UPDATER_REAL_TRUSTED_ROOT));
RootRole.ROOT, TestResources.loadRoot(TestResources.UPDATER_SYNTHETIC_TRUSTED_ROOT));
assertEquals(2, repoBase.toFile().list().length, "Expect 2: root.json plus the /targets dir.");
assertTrue(repoBase.resolve("root.json").toFile().exists());
assertTrue(repoBase.resolve("targets").toFile().isDirectory());
}

@Test
void clearMeta(@TempDir Path repoBase) throws IOException {
TestResources.setupRepoFiles(PROD_REPO, repoBase, "snapshot.json", "timestamp.json");
TestResources.setupRepoFiles(REPO, repoBase, "snapshot.json", "timestamp.json");
FileSystemTufStore tufStore = FileSystemTufStore.newFileSystemStore(repoBase);
assertTrue(repoBase.resolve("snapshot.json").toFile().exists());
assertTrue(repoBase.resolve("timestamp.json").toFile().exists());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ class PassthroughCacheMetaStoreTest {
@BeforeAll
public static void readAllMeta() throws IOException {
Path timestampResource =
Path.of(Resources.getResource("dev/sigstore/tuf/real/prod/timestamp.json").getPath());
Path.of(
Resources.getResource("dev/sigstore/tuf/synthetic/test/repository/timestamp.json")
.getPath());
timestamp = GSON.get().fromJson(Files.newBufferedReader(timestampResource), Timestamp.class);
}

Expand Down
Loading

0 comments on commit ef10161

Please sign in to comment.