Skip to content

Commit

Permalink
Merge pull request #847 from sigstore/add_key_parsers_to_tuf_package
Browse files Browse the repository at this point in the history
Add tuf specific key/signature handlers
  • Loading branch information
loosebazooka authored Nov 14, 2024
2 parents 014d2a8 + f4f816e commit 12068fc
Show file tree
Hide file tree
Showing 10 changed files with 593 additions and 0 deletions.
45 changes: 45 additions & 0 deletions fuzzing/src/main/java/fuzzing/TufVerifierFuzzer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2023 The Sigstore Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fuzzing;

import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import dev.sigstore.tuf.encryption.Verifiers;
import dev.sigstore.tuf.model.ImmutableKey;
import dev.sigstore.tuf.model.Key;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.util.Map;

public class TufVerifierFuzzer {
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
try {
String keyType = data.consumeString(10);
String scheme = data.consumeString(20);
String keyData = data.consumeRemainingAsString();

Key key =
ImmutableKey.builder()
.keyType(keyType)
.keyVal(Map.of("public", keyData))
.scheme(scheme)
.build();

Verifiers.newVerifier(key);
} catch (IOException | InvalidKeyException e) {
// known exceptions
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2022 The Sigstore Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.sigstore.tuf.encryption;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;

/** ECDSA verifier, instantiated in {@link Verifiers}. */
class EcdsaVerifier implements Verifier {

private final PublicKey publicKey;

EcdsaVerifier(PublicKey publicKey) {
this.publicKey = publicKey;
}

@Override
public boolean verify(byte[] artifact, byte[] signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
var verifier = Signature.getInstance("SHA256withECDSA");
verifier.initVerify(publicKey);
verifier.update(artifact);
return verifier.verify(signature);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2022 The Sigstore Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.sigstore.tuf.encryption;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;

/** Ed25519 verifier, instantiated by {@link Verifiers}. */
class Ed25519Verifier implements Verifier {

private final PublicKey publicKey;

Ed25519Verifier(PublicKey publicKey) {
this.publicKey = publicKey;
}

/** EdDSA verifiers hash implicitly for ed25519 keys. */
@Override
public boolean verify(byte[] artifact, byte[] signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
var verifier = Signature.getInstance("Ed25519");
verifier.initVerify(publicKey);
verifier.update(artifact);
return verifier.verify(signature);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2022 The Sigstore Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.sigstore.tuf.encryption;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;

/** RSA verifier using PSS and MGF1, instantiated by {@link Verifiers}. */
class RsaPssVerifier implements Verifier {

private final PublicKey publicKey;

RsaPssVerifier(PublicKey publicKey) {
this.publicKey = publicKey;
}

@Override
public boolean verify(byte[] artifact, byte[] signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
var verifier = Signature.getInstance("SHA256withRSAandMGF1");
verifier.initVerify(publicKey);
verifier.update(artifact);
return verifier.verify(signature);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2022 The Sigstore Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.sigstore.tuf.encryption;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;

/** A verifier interface specifying verification for a raw artifact (no hashing). */
public interface Verifier {

/**
* Verify an artifact. Implementations may hash the artifact with sha256 before verifying unless
* they have an implicit hashing algorithm.
*
* @param artifact the artifact that was signed
* @param signature the signature associated with the artifact
* @return true if the signature is valid, false otherwise
*/
boolean verify(byte[] artifact, byte[] signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException;
}
113 changes: 113 additions & 0 deletions sigstore-java/src/main/java/dev/sigstore/tuf/encryption/Verifiers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2024 The Sigstore Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.sigstore.tuf.encryption;

import dev.sigstore.tuf.model.Key;
import java.io.IOException;
import java.io.StringReader;
import java.security.InvalidKeyException;
import java.security.PublicKey;
import java.security.Security;
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.util.encoders.DecoderException;
import org.bouncycastle.util.encoders.Hex;

public class Verifiers {

static {
Security.addProvider(new BouncyCastleProvider());
}

@FunctionalInterface
public interface Supplier {
Verifier newVerifier(Key key) throws IOException, InvalidKeyException;
}

public static Verifier newVerifier(Key key) throws IOException, InvalidKeyException {

PublicKey publicKey = parsePublicKey(key);
if (key.getKeyType().equals("rsa") && key.getScheme().equals("rsassa-pss-sha256")) {
return new RsaPssVerifier(publicKey);
}
if (isEcdsaKey(key) && key.getScheme().equals("ecdsa-sha2-nistp256")) {
return new EcdsaVerifier(publicKey);
}
if (key.getKeyType().equals("ed25519") && key.getScheme().equals("ed25519")) {
return new Ed25519Verifier(publicKey);
}
throw new InvalidKeyException(
"Unsupported tuf key type and scheme combination: "
+ key.getKeyType()
+ "/"
+ key.getScheme());
}

private static PublicKey parsePublicKey(Key key) throws IOException, InvalidKeyException {
var keyType = key.getKeyType();
if (keyType.equals("rsa") || isEcdsaKey(key)) {
try (PEMParser pemParser = new PEMParser(new StringReader(key.getKeyVal().get("public")))) {
var keyObj = pemParser.readObject(); // throws DecoderException
if (keyObj == null) {
throw new InvalidKeyException(
"tuf " + key.getKeyType() + " keys must be a single PEM encoded section");
}
if (keyObj instanceof SubjectPublicKeyInfo) {
var keyInfo = PublicKeyFactory.createKey((SubjectPublicKeyInfo) keyObj);
if ((keyType.equals("rsa") && keyInfo instanceof RSAKeyParameters)
|| (isEcdsaKey(key) && keyInfo instanceof ECKeyParameters)) {
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
return converter.getPublicKey((SubjectPublicKeyInfo) keyObj);
}
}
throw new InvalidKeyException(
"Could not parse PEM section into " + keyType + " public key");
} catch (DecoderException e) {
throw new InvalidKeyException("Could not parse PEM section in " + keyType + " public key");
}
}
// tuf allows raw keys only for ed25519 (non PEM):
// https://github.com/theupdateframework/specification/blob/c51875f445d8a57efca9dadfbd5dbdece06d87e6/tuf-spec.md#key-objects--file-formats-keys
else if (keyType.equals("ed25519")) {
byte[] keyContents;
try {
keyContents = Hex.decode(key.getKeyVal().get("public"));
} catch (DecoderException e) {
throw new InvalidKeyException("Could not parse hex encoded ed25519 public key");
}
var params =
new SubjectPublicKeyInfo(
new AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519), keyContents);
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
return converter.getPublicKey(params);
} else {
throw new InvalidKeyException("Unsupported tuf key type" + key.getKeyType());
}
}

// this is a hack to handle keytypes of ecdsa-sha2-nistp256
// context: https://github.com/awslabs/tough/issues/754
private static boolean isEcdsaKey(Key key) {
return key.getKeyType().equals("ecdsa-sha2-nistp256") || key.getKeyType().equals("ecdsa");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2024 The Sigstore Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.sigstore.tuf.encryption;

import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Security;
import java.security.Signature;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class EcdsaVerifierTest {

private static final byte[] CONTENT = "abcdef".getBytes(StandardCharsets.UTF_8);

@Test
public void testVerify_ECDSA() throws Exception {
Security.addProvider(new BouncyCastleProvider());

var keyPair = genKeyPair();
var signature = genSignature(keyPair);
var verifier = new EcdsaVerifier(keyPair.getPublic());
Assertions.assertTrue(verifier.verify(CONTENT, signature));
}

private KeyPair genKeyPair() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA");
keyGen.initialize(256);
return keyGen.generateKeyPair();
}

private byte[] genSignature(KeyPair keyPair) throws Exception {
Signature signature = Signature.getInstance("SHA256withECDSA");
signature.initSign(keyPair.getPrivate());
signature.update(CONTENT);
return signature.sign();
}
}
Loading

0 comments on commit 12068fc

Please sign in to comment.