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

WIP: Code for generating ecdsa secp256r1 objects #133

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ target/
.vscode/settings.jsonbuilds
builds
.factorypath
.pregenerated-test-key*
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import net.ripe.rpki.commons.validation.objectvalidators.X509ResourceCertificateParentChildValidator;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.joda.time.DateTime;

Expand All @@ -36,7 +37,8 @@ public abstract class RpkiSignedObject implements CertificateRepositoryObject {

public static final List<String> ALLOWED_SIGNATURE_ALGORITHM_OIDS = Arrays.asList(
SHA256WITHRSA_ENCRYPTION_OID,
RSA_ENCRYPTION_OID
RSA_ENCRYPTION_OID,
X9ObjectIdentifiers.ecdsa_with_SHA256.getId()
);

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package net.ripe.rpki.commons.crypto.cms;

import com.google.common.base.Preconditions;
import net.ripe.rpki.commons.crypto.util.BouncyCastleUtil;
import net.ripe.rpki.commons.crypto.x509cert.X509CertificateBuilderHelper;
import net.ripe.rpki.commons.crypto.x509cert.X509CertificateUtil;
import org.apache.commons.lang3.Validate;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.cms.Attribute;
Expand Down Expand Up @@ -53,15 +55,30 @@ private byte[] doGenerate(X509Certificate signingCertificate, PrivateKey private
Validate.notNull(subjectKeyIdentifier, "certificate must contain SubjectKeyIdentifier extension");

RPKISignedDataGenerator generator = new RPKISignedDataGenerator();
addSignerInfo(generator, privateKey, signatureProvider, signingCertificate);
String signatureAlgorithm = null;
switch (signingCertificate.getPublicKey().getAlgorithm()) {
case "RSA":
signatureAlgorithm = X509CertificateBuilderHelper.DEFAULT_SIGNATURE_ALGORITHM;
break;
case "EC":
signatureAlgorithm = X509CertificateBuilderHelper.ECDSA_SIGNATURE_ALGORITHM;
break;
default:
Preconditions.checkArgument(false, "Not a supported public key type");
}
Preconditions.checkArgument(!(X509CertificateBuilderHelper.DEFAULT_SIGNATURE_ALGORITHM.equals(signatureAlgorithm) && X509CertificateBuilderHelper.ECDSA_SIGNATURE_PROVIDER.equals(signatureProvider)));
Preconditions.checkArgument(!(X509CertificateBuilderHelper.ECDSA_SIGNATURE_ALGORITHM.equals(signatureAlgorithm) && X509CertificateBuilderHelper.DEFAULT_SIGNATURE_PROVIDER.equals(signatureProvider)));


addSignerInfo(generator, privateKey, signatureProvider, signatureAlgorithm, signingCertificate);
generator.addCertificates(new JcaCertStore(Collections.singleton(signingCertificate)));

CMSSignedData data = generator.generate(new CMSProcessableByteArray(contentTypeOid, content), true);
return data.getEncoded();
}

private void addSignerInfo(RPKISignedDataGenerator generator, PrivateKey privateKey, String signatureProvider, X509Certificate signingCertificate) throws OperatorCreationException {
ContentSigner signer = new JcaContentSignerBuilder(X509CertificateBuilderHelper.DEFAULT_SIGNATURE_ALGORITHM).setProvider(signatureProvider).build(privateKey);
private void addSignerInfo(RPKISignedDataGenerator generator, PrivateKey privateKey, String signatureProvider, String signatureAlgorithm, X509Certificate signingCertificate) throws OperatorCreationException {
ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(signatureProvider).build(privateKey);
DigestCalculatorProvider digestProvider = BouncyCastleUtil.DIGEST_CALCULATOR_PROVIDER;
SignerInfoGenerator gen = new JcaSignerInfoGeneratorBuilder(digestProvider).setSignedAttributeGenerator(
new DefaultSignedAttributeTableGenerator(createSignedAttributes(signingCertificate.getNotBefore())) {
Expand All @@ -76,8 +93,13 @@ public AttributeTable getAttributes(Map parameters) {

private AttributeTable createSignedAttributes(Date signingTime) {
Hashtable<ASN1ObjectIdentifier, Attribute> attributes = new Hashtable<>();

Attribute signingTimeAttribute = new Attribute(CMSAttributes.signingTime, new DERSet(new Time(signingTime)));
attributes.put(CMSAttributes.signingTime, signingTimeAttribute);

Attribute binarySigningTime = new Attribute(CMSAttributes.binarySigningTime, new DERSet(new ASN1Integer(signingTime.toInstant().toEpochMilli() / 1000)));
attributes.put(CMSAttributes.binarySigningTime, binarySigningTime);

return new AttributeTable(attributes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package net.ripe.rpki.commons.crypto.util;

import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;

public class EcKeyPairFactory {
public static final String ALGORITHM = "EC";

private final String provider;

public EcKeyPairFactory(String provider) {
this.provider = provider;
}

public KeyPair generate() {
try {
final KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITHM, provider);

Check failure

Code scanning / CodeQL

Use of a potentially broken or risky cryptographic algorithm High

Cryptographic algorithm
EC
may not be secure, consider using a different algorithm.
generator.initialize(new ECGenParameterSpec("secp256r1"));
return generator.generateKeyPair();
} catch (NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
throw new KeyPairFactoryException(e);
}
}

/**
* Decodes an X.509 encoded public key.
*
* @param encoded the encoded public key.
* @return the PublicKey.
*/
public static PublicKey decodePublicKey(byte[] encoded) {
try {
return KeyFactory.getInstance(ALGORITHM).generatePublic(new X509EncodedKeySpec(encoded));
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new KeyPairFactoryException(e);
}
}

/**
* Decodes a PKCS#8 encoded private key. This is the default encoding for
* the private key getEncoded method.
*
* @param encoded the encoded data.
* @return the PrivateKey.
*/
public static PrivateKey decodePrivateKey(byte[] encoded) {
try {
return KeyFactory.getInstance(ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(encoded));
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new KeyPairFactoryException(e);
}
}

public EcKeyPairFactory withProvider(String provider) {
return new EcKeyPairFactory(provider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,17 @@ public final class X509CertificateBuilderHelper {

public static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA";

public static final String ECDSA_SIGNATURE_ALGORITHM = "SHA256withECDSA";

public static final String DEFAULT_SIGNATURE_PROVIDER = "SunRsaSign";

public static final String ECDSA_SIGNATURE_PROVIDER = "SunEC";

private static final BigInteger MAX_20_OCTETS = BigInteger.ONE.shiftLeft(160).subtract(BigInteger.ONE);

private String signatureProvider = DEFAULT_SIGNATURE_PROVIDER;
private String signatureProvider;

private String signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM;
private String signatureAlgorithm;

private BigInteger serial;

Expand Down Expand Up @@ -334,7 +338,7 @@ private void validateCertificateFields() {
Validate.notNull(publicKey, "no publicKey");
Validate.notNull(signingKeyPair, "no signingKeyPair");
Validate.notNull(validityPeriod, "no validityPeriod");
Validate.isTrue("RSA".equals(publicKey.getAlgorithm()), "publicKey algorithm is not RSA");
Validate.isTrue("RSA".equals(publicKey.getAlgorithm()) || "EC".equals(publicKey.getAlgorithm()), "publicKey algorithm is not RSA or EC");
if (!ca) {
Validate.isTrue((keyUsage & KeyUsage.keyCertSign) == 0,
"keyCertSign only allowed for ca");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.ripe.rpki.commons.validation.ValidationResult;
import org.apache.commons.lang3.ArrayUtils;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;

import java.io.ByteArrayInputStream;
import java.io.IOException;
Expand All @@ -28,6 +29,7 @@ public abstract class X509CertificateParser<T extends AbstractX509CertificateWra

private static final String[] ALLOWED_SIGNATURE_ALGORITHM_OIDS = {
PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(),
X9ObjectIdentifiers.ecdsa_with_SHA256.getId()
};

protected X509Certificate certificate;
Expand Down Expand Up @@ -73,7 +75,11 @@ public static X509GenericCertificate parseCertificate(ValidationResult result, b
}

protected void validatePublicKey() {
validateRsaPk();
if (isRsaPk(certificate.getPublicKey())) {
validateRsaPk();
} else {
validateEcPk();
}
}

void validateRsaPk() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ public class X509ResourceCertificateBuilder {
private EnumSet<IpResourceType> inheritedResourceTypes = EnumSet.noneOf(IpResourceType.class);

public X509ResourceCertificateBuilder() {
builderHelper = new X509CertificateBuilderHelper();
builderHelper = new X509CertificateBuilderHelper()
.withSignatureProvider(X509CertificateBuilderHelper.ECDSA_SIGNATURE_PROVIDER)
.withSignatureAlgorithm(X509CertificateBuilderHelper.ECDSA_SIGNATURE_ALGORITHM);
builderHelper.withResources(resources);
// https://tools.ietf.org/html/rfc6487#section-4.8.9
builderHelper.withPolicies(X509ResourceCertificate.POLICY_INFORMATION);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.ripe.rpki.commons.crypto.cms.roa;

import com.google.common.collect.ImmutableSortedSet;
import com.google.common.io.BaseEncoding;
import net.ripe.rpki.commons.crypto.rfc3779.AddressFamily;
import net.ripe.rpki.commons.crypto.util.KeyPairFactoryTest;
import org.junit.Before;
Expand Down Expand Up @@ -33,17 +34,18 @@
allPrefixes.add(TEST_IPV6_PREFIX);

subject = new RoaCmsBuilder();
subject.withCertificate(createCertificate(allPrefixes));

Check failure on line 37 in src/test/java/net/ripe/rpki/commons/crypto/cms/roa/RoaCmsBuilderTest.java

View workflow job for this annotation

GitHub Actions / Test Report

RoaCmsBuilderTest.shouldEncodeRoaIpAddressFamily

Could not initialize class net.ripe.rpki.commons.crypto.util.KeyPairFactoryTest
Raw output
java.lang.NoClassDefFoundError: Could not initialize class net.ripe.rpki.commons.crypto.util.KeyPairFactoryTest
	at net.ripe.rpki.commons.crypto.cms.roa.RoaCmsBuilderTest.setUp(RoaCmsBuilderTest.java:37)
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.RuntimeException: net.ripe.rpki.commons.crypto.x509cert.X509ResourceCertificateBuilderException: org.bouncycastle.operator.OperatorCreationException: cannot create signer: no such algorithm: SHA256WITHECDSA for provider SunRsaSign [in thread "main"]

Check failure on line 37 in src/test/java/net/ripe/rpki/commons/crypto/cms/roa/RoaCmsBuilderTest.java

View workflow job for this annotation

GitHub Actions / Test Report

RoaCmsBuilderTest.shouldEncodeRoaIpAddressFamilySequence

Could not initialize class net.ripe.rpki.commons.crypto.cms.roa.RoaCmsTest
Raw output
java.lang.NoClassDefFoundError: Could not initialize class net.ripe.rpki.commons.crypto.cms.roa.RoaCmsTest
	at net.ripe.rpki.commons.crypto.cms.roa.RoaCmsBuilderTest.setUp(RoaCmsBuilderTest.java:37)
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.NoClassDefFoundError: Could not initialize class net.ripe.rpki.commons.crypto.util.KeyPairFactoryTest [in thread "main"]
	at net.ripe.rpki.commons.crypto.cms.roa.RoaCmsBuilderTest.setUp(RoaCmsBuilderTest.java:37)

Check failure on line 37 in src/test/java/net/ripe/rpki/commons/crypto/cms/roa/RoaCmsBuilderTest.java

View workflow job for this annotation

GitHub Actions / Test Report

RoaCmsBuilderTest.shouldEncodeRoaIpAddress

Could not initialize class net.ripe.rpki.commons.crypto.cms.roa.RoaCmsTest
Raw output
java.lang.NoClassDefFoundError: Could not initialize class net.ripe.rpki.commons.crypto.cms.roa.RoaCmsTest
	at net.ripe.rpki.commons.crypto.cms.roa.RoaCmsBuilderTest.setUp(RoaCmsBuilderTest.java:37)
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.NoClassDefFoundError: Could not initialize class net.ripe.rpki.commons.crypto.util.KeyPairFactoryTest [in thread "main"]
	at net.ripe.rpki.commons.crypto.cms.roa.RoaCmsBuilderTest.setUp(RoaCmsBuilderTest.java:37)

Check failure on line 37 in src/test/java/net/ripe/rpki/commons/crypto/cms/roa/RoaCmsBuilderTest.java

View workflow job for this annotation

GitHub Actions / Test Report

RoaCmsBuilderTest.shouldGenerateRoaCms

Could not initialize class net.ripe.rpki.commons.crypto.cms.roa.RoaCmsTest
Raw output
java.lang.NoClassDefFoundError: Could not initialize class net.ripe.rpki.commons.crypto.cms.roa.RoaCmsTest
	at net.ripe.rpki.commons.crypto.cms.roa.RoaCmsBuilderTest.setUp(RoaCmsBuilderTest.java:37)
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.NoClassDefFoundError: Could not initialize class net.ripe.rpki.commons.crypto.util.KeyPairFactoryTest [in thread "main"]
	at net.ripe.rpki.commons.crypto.cms.roa.RoaCmsBuilderTest.setUp(RoaCmsBuilderTest.java:37)

Check failure on line 37 in src/test/java/net/ripe/rpki/commons/crypto/cms/roa/RoaCmsBuilderTest.java

View workflow job for this annotation

GitHub Actions / Test Report

RoaCmsBuilderTest.shouldEncodeRouteOriginAttestation

Could not initialize class net.ripe.rpki.commons.crypto.cms.roa.RoaCmsTest
Raw output
java.lang.NoClassDefFoundError: Could not initialize class net.ripe.rpki.commons.crypto.cms.roa.RoaCmsTest
	at net.ripe.rpki.commons.crypto.cms.roa.RoaCmsBuilderTest.setUp(RoaCmsBuilderTest.java:37)
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.NoClassDefFoundError: Could not initialize class net.ripe.rpki.commons.crypto.util.KeyPairFactoryTest [in thread "main"]
	at net.ripe.rpki.commons.crypto.cms.roa.RoaCmsBuilderTest.setUp(RoaCmsBuilderTest.java:37)
subject.withAsn(TEST_ASN);
subject.withPrefixes(allPrefixes);
subject.withSignatureProvider(DEFAULT_SIGNATURE_PROVIDER);
subject.withSignatureProvider(ECDSA_SIGNATURE_PROVIDER);
}

@Test
public void shouldGenerateRoaCms() {
RoaCms result = subject.build(KeyPairFactoryTest.TEST_KEY_PAIR.getPrivate());
RoaCms result = subject.build(KeyPairFactoryTest.EC_TEST_KEY_PAIR.getPrivate());
assertNotNull(result);
assertNotNull(result.getEncoded());
System.out.println(BaseEncoding.base64().encode(result.getEncoded()));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.stream.Collectors;

import static net.ripe.rpki.commons.crypto.cms.roa.RoaCmsParserTest.*;
import static net.ripe.rpki.commons.crypto.util.KeyPairFactoryTest.EC_TEST_KEY_PAIR;
import static net.ripe.rpki.commons.crypto.x509cert.X509CertificateBuilderHelper.*;
import static org.assertj.core.api.Assertions.assertThat;

Expand Down Expand Up @@ -65,9 +66,9 @@ public static RoaCms createRoaCms(List<RoaPrefix> prefixes) {
RoaCmsBuilder builder = new RoaCmsBuilder();
builder.withCertificate(createCertificate(prefixes)).withAsn(TEST_ASN);
builder.withPrefixes(prefixes);
builder.withSignatureProvider(DEFAULT_SIGNATURE_PROVIDER);
builder.withSignatureProvider(ECDSA_SIGNATURE_PROVIDER);

return builder.build(TEST_KEY_PAIR.getPrivate());
return builder.build(EC_TEST_KEY_PAIR.getPrivate());
}

// TODO: Refactor to RoaCmsObjectMother
Expand All @@ -78,7 +79,7 @@ public static RoaCms getRoaCms() {
}

public static X509ResourceCertificate createCertificate(List<RoaPrefix> prefixes){
return createCertificate(prefixes, TEST_KEY_PAIR);
return createCertificate(prefixes, EC_TEST_KEY_PAIR);
}
public static X509ResourceCertificate createCertificate(List<RoaPrefix> prefixes, KeyPair keyPair) {
IpResourceSet resources = new IpResourceSet();
Expand All @@ -90,7 +91,7 @@ public static X509ResourceCertificate createCertificate(List<RoaPrefix> prefixes
}

private static X509ResourceCertificateBuilder createCertificateBuilder(IpResourceSet resources) {
return createCertificateBuilder(resources, TEST_KEY_PAIR);
return createCertificateBuilder(resources, EC_TEST_KEY_PAIR);
}
private static X509ResourceCertificateBuilder createCertificateBuilder(IpResourceSet resources, KeyPair keyPair) {
X509ResourceCertificateBuilder builder = new X509ResourceCertificateBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,41 @@
import org.junit.Test;

import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static org.junit.Assert.*;


public class KeyPairFactoryTest {

public static final String DEFAULT_KEYPAIR_GENERATOR_PROVIDER = "SunRsaSign";
public static final String EC_KEYPAIR_GENERATOR_PROVIDER = "SunEC";
public static final String RSA_KEYPAIR_GENERATOR_PROVIDER = "SunRsaSign";

public static KeyPair TEST_KEY_PAIR = PregeneratedKeyPairFactory.getInstance().generate();
public static KeyPair SECOND_TEST_KEY_PAIR = PregeneratedKeyPairFactory.getInstance().generate();

private static final Map<String, KeyPair> cachedKeyPairs = new HashMap<String, KeyPair>();
public static KeyPair EC_TEST_KEY_PAIR = PregeneratedEcKeyPairFactory.getInstance().generate();;
public static KeyPair EC_SECOND_TEST_KEY_PAIR = PregeneratedEcKeyPairFactory.getInstance().generate();

private static final ConcurrentHashMap<String, KeyPair> cachedKeyPairs = new ConcurrentHashMap();
private static final ConcurrentHashMap<String, KeyPair> cachedEcKeyPairs = new ConcurrentHashMap();


public static KeyPair getKeyPair(String name) {
synchronized (cachedKeyPairs) {
KeyPair result = cachedKeyPairs.get(name);
if (result == null) {
result = PregeneratedKeyPairFactory.getInstance().generate();
cachedKeyPairs.put(name, result);
}
return result;
}
return cachedKeyPairs.computeIfAbsent(name, unused -> PregeneratedKeyPairFactory.getInstance().generate());
}

public static KeyPair getEcKeyPair(String name) {
return cachedEcKeyPairs.computeIfAbsent(name, unused -> PregeneratedEcKeyPairFactory.getInstance().generate());
}


@Test
public void shouldGenerateRsaKeyPairs() {
KeyPair keyPair = new KeyPairFactory(DEFAULT_KEYPAIR_GENERATOR_PROVIDER).generate();
KeyPair keyPair = new KeyPairFactory(RSA_KEYPAIR_GENERATOR_PROVIDER).generate();
assertTrue(keyPair.getPublic() instanceof RSAPublicKey);
assertTrue(keyPair.getPrivate() instanceof RSAPrivateKey);

Expand All @@ -47,6 +49,19 @@ public void shouldGenerateRsaKeyPairs() {
assertEquals(KeyPairFactory.RPKI_KEY_PAIR_SIZE, rsaPublicKey.getModulus().bitLength());
}

@Test
public void shouldGenerateEcdsaKeyPairs() {
KeyPair keyPair = new EcKeyPairFactory(EC_KEYPAIR_GENERATOR_PROVIDER).generate();
assertTrue(keyPair.getPublic() instanceof ECPublicKey);
assertTrue(keyPair.getPrivate() instanceof ECPrivateKey);

assertEquals(keyPair.getPublic(), EcKeyPairFactory.decodePublicKey(keyPair.getPublic().getEncoded()));
assertEquals(keyPair.getPrivate(), EcKeyPairFactory.decodePrivateKey(keyPair.getPrivate().getEncoded()));

ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic();
assertEquals("EC", ecPublicKey.getAlgorithm());
}

@Test(expected = RuntimeException.class)
public void shouldKeypairGenerationFailOnInvalidProvider() {
new KeyPairFactory("invalid_provider").generate();
Expand Down
Loading
Loading