Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tuf specific key/signature handlers #847

Merged
merged 1 commit into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading