diff --git a/cmdline/src/main/java/io/opentdf/platform/Command.java b/cmdline/src/main/java/io/opentdf/platform/Command.java index 3689aa38..2fcd78b3 100644 --- a/cmdline/src/main/java/io/opentdf/platform/Command.java +++ b/cmdline/src/main/java/io/opentdf/platform/Command.java @@ -10,6 +10,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; +import java.security.Security; import java.text.ParseException; import com.google.gson.JsonSyntaxException; import io.opentdf.platform.sdk.AssertionConfig; @@ -19,6 +20,7 @@ import io.opentdf.platform.sdk.SDK; import io.opentdf.platform.sdk.SDKBuilder; import nl.altindag.ssl.SSLFactory; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import picocli.CommandLine; import picocli.CommandLine.HelpCommand; import picocli.CommandLine.Option; @@ -63,6 +65,10 @@ class Versions { + "\",\"tdfSpecVersion\":\"" + Versions.TDF_SPEC + "\"}") class Command { + static { + Security.addProvider(new BouncyCastleProvider()); + } + @Option(names = { "-V", "--version" }, versionHelp = true, description = "display version info") boolean versionInfoRequested; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java b/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java index 36110853..ee0befe0 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java @@ -1,40 +1,30 @@ package io.opentdf.platform.sdk; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.asn1.x9.ECNamedCurveTable; +import org.bouncycastle.asn1.x9.X962Parameters; +import org.bouncycastle.asn1.x9.X9ECPoint; import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.generators.HKDFBytesGenerator; import org.bouncycastle.crypto.params.HKDFParameters; -import org.bouncycastle.jcajce.provider.asymmetric.ec.KeyPairGeneratorSpi; -import org.bouncycastle.jce.ECNamedCurveTable; -import org.bouncycastle.jce.interfaces.ECPrivateKey; -import org.bouncycastle.jce.interfaces.ECPublicKey; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; -import org.bouncycastle.math.ec.ECPoint; -import org.bouncycastle.openssl.PEMException; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.util.io.pem.*; import org.bouncycastle.util.io.pem.PemReader; -import org.bouncycastle.jce.spec.ECPublicKeySpec; import javax.crypto.KeyAgreement; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.io.*; import java.security.*; import java.security.spec.*; import java.util.Objects; -// https://www.bouncycastle.org/latest_releases.html public class ECKeyPair { private static final int SHA256_BYTES = 32; - - static { - Security.addProvider(new BouncyCastleProvider()); - } - private final ECCurve curve; public enum ECAlgorithm { @@ -42,8 +32,6 @@ public enum ECAlgorithm { ECDSA } - private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider(); - private KeyPair keyPair; public ECKeyPair() { @@ -58,9 +46,9 @@ public ECKeyPair(ECCurve curve, ECAlgorithm algorithm) { // Should this just use the algorithm vs use ECDH only for ECDH and ECDSA for // everything else. if (algorithm == ECAlgorithm.ECDH) { - generator = KeyPairGeneratorSpi.getInstance(ECAlgorithm.ECDH.name(), BOUNCY_CASTLE_PROVIDER); + generator = KeyPairGenerator.getInstance(ECAlgorithm.ECDH.name()); } else { - generator = KeyPairGeneratorSpi.getInstance(ECAlgorithm.ECDSA.name(), BOUNCY_CASTLE_PROVIDER); + generator = KeyPairGenerator.getInstance(ECAlgorithm.ECDSA.name()); } } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); @@ -122,77 +110,60 @@ public int keySize() { } public byte[] compressECPublickey() { - return ((ECPublicKey) this.keyPair.getPublic()).getQ().getEncoded(true); + return getCompressedECPublicKey(this.keyPair.getPublic()); } - public static String getPEMPublicKeyFromX509Cert(String pemInX509Format) { - try { - PEMParser parser = new PEMParser(new StringReader(pemInX509Format)); - X509CertificateHolder x509CertificateHolder = (X509CertificateHolder) parser.readObject(); - parser.close(); - SubjectPublicKeyInfo publicKeyInfo = x509CertificateHolder.getSubjectPublicKeyInfo(); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER); - ECPublicKey publicKey = null; - try { - publicKey = (ECPublicKey) converter.getPublicKey(publicKeyInfo); - } catch (PEMException e) { - throw new RuntimeException(e); - } + private static byte[] getCompressedECPublicKey(PublicKey publicKey) { + SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); + X962Parameters params = X962Parameters.getInstance(publicKeyInfo.getAlgorithm().getParameters()); + if (params.isImplicitlyCA()) { + throw new IllegalArgumentException("Implicitly CA parameters are not supported."); + } - // EC public key to pem formated. - StringWriter writer = new StringWriter(); - PemWriter pemWriter = new PemWriter(writer); + org.bouncycastle.math.ec.ECCurve bcCurve = + ECNamedCurveTable.getByOID((ASN1ObjectIdentifier) params.getParameters()).getCurve(); + org.bouncycastle.math.ec.ECPoint p = + bcCurve.decodePoint(publicKeyInfo.getPublicKeyData().getOctets()); - pemWriter.writeObject(new PemObject("PUBLIC KEY", publicKey.getEncoded())); - pemWriter.flush(); - pemWriter.close(); - return writer.toString(); - } catch (IOException e) { - throw new RuntimeException(e); - } + return new X9ECPoint(p, true).getPointEncoding(); } public static byte[] compressECPublickey(String pemECPubKey) { try { - KeyFactory ecKeyFac = KeyFactory.getInstance("EC", "BC"); + KeyFactory ecKeyFac = KeyFactory.getInstance("EC"); PemReader pemReader = new PemReader(new StringReader(pemECPubKey)); PemObject pemObject = pemReader.readPemObject(); PublicKey pubKey = ecKeyFac.generatePublic(new X509EncodedKeySpec(pemObject.getContent())); - return ((ECPublicKey) pubKey).getQ().getEncoded(true); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (InvalidKeySpecException e) { - throw new RuntimeException(e); - } catch (NoSuchProviderException e) { + return getCompressedECPublicKey(pubKey); + } catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException e) { throw new RuntimeException(e); } } public static String publicKeyFromECPoint(byte[] ecPoint, String curveName) { try { + org.bouncycastle.math.ec.ECPoint point = + ECNamedCurveTable.getByName(curveName).getCurve().decodePoint(ecPoint); + java.security.spec.ECPoint jpoint = new java.security.spec.ECPoint( + point.getAffineXCoord().toBigInteger(), point.getAffineYCoord().toBigInteger()); + // Create EC Public key - ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(curveName); - ECPoint point = ecSpec.getCurve().decodePoint(ecPoint); - ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(point, ecSpec); - KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC"); - PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("EC"); + algorithmParameters.init(new ECGenParameterSpec(curveName)); + ECParameterSpec ecParameterSpec = algorithmParameters.getParameterSpec(ECParameterSpec.class); + + ECPublicKeySpec spec = new ECPublicKeySpec(jpoint, ecParameterSpec); + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + PublicKey publicKey = keyFactory.generatePublic(spec); - // EC Public keu to pem format. + // EC Public key to pem format. StringWriter writer = new StringWriter(); PemWriter pemWriter = new PemWriter(writer); pemWriter.writeObject(new PemObject("PUBLIC KEY", publicKey.getEncoded())); pemWriter.flush(); pemWriter.close(); return writer.toString(); - } catch (InvalidKeySpecException e) { - throw new RuntimeException(e); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } catch (NoSuchProviderException e) { - throw new RuntimeException(e); - } catch (IOException e) { + } catch (InvalidKeySpecException | NoSuchAlgorithmException | IOException | InvalidParameterSpecException e) { throw new RuntimeException(e); } } @@ -203,7 +174,7 @@ public static ECPublicKey publicKeyFromPem(String pemEncoding) { SubjectPublicKeyInfo publicKeyInfo = (SubjectPublicKeyInfo) parser.readObject(); parser.close(); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); return (ECPublicKey) converter.getPublicKey(publicKeyInfo); } catch (IOException e) { throw new RuntimeException(e); @@ -216,7 +187,7 @@ public static ECPrivateKey privateKeyFromPem(String pemEncoding) { PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) parser.readObject(); parser.close(); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); return (ECPrivateKey) converter.getPrivateKey(privateKeyInfo); } catch (IOException e) { throw new RuntimeException(e); @@ -225,11 +196,11 @@ public static ECPrivateKey privateKeyFromPem(String pemEncoding) { public static byte[] computeECDHKey(ECPublicKey publicKey, ECPrivateKey privateKey) { try { - KeyAgreement aKeyAgree = KeyAgreement.getInstance("ECDH", "BC"); + KeyAgreement aKeyAgree = KeyAgreement.getInstance("ECDH"); aKeyAgree.init(privateKey); aKeyAgree.doPhase(publicKey, true); return aKeyAgree.generateSecret(); - } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException e) { + } catch (NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException(e); } } @@ -250,35 +221,23 @@ public static byte[] calculateHKDF(byte[] salt, byte[] secret) { public static byte[] computeECDSASig(byte[] digest, ECPrivateKey privateKey) { try { - Signature ecdsaSign = Signature.getInstance("SHA256withECDSA", "BC"); + Signature ecdsaSign = Signature.getInstance("SHA256withECDSA"); ecdsaSign.initSign(privateKey); ecdsaSign.update(digest); return ecdsaSign.sign(); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } catch (NoSuchProviderException e) { - throw new RuntimeException(e); - } catch (InvalidKeyException e) { - throw new RuntimeException(e); - } catch (SignatureException e) { + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { throw new RuntimeException(e); } } public static Boolean verifyECDSAig(byte[] digest, byte[] signature, ECPublicKey publicKey) { try { - Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA", "BC"); + Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA"); ecdsaVerify.initVerify(publicKey); ecdsaVerify.update(digest); return ecdsaVerify.verify(signature); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } catch (NoSuchProviderException e) { - throw new RuntimeException(e); - } catch (InvalidKeyException e) { - throw new RuntimeException(e); - } catch (SignatureException e) { + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { throw new RuntimeException(e); } } -} \ No newline at end of file +} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index 3ee4ba22..a428703b 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -11,7 +11,6 @@ import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; -import org.bouncycastle.jce.interfaces.ECPublicKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,6 +21,7 @@ import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.security.*; +import java.security.interfaces.ECPublicKey; import java.text.ParseException; import java.util.*; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java b/sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java new file mode 100644 index 00000000..ee0f3187 --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java @@ -0,0 +1,18 @@ +package io.opentdf.platform.sdk; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +import java.security.Security; + +public class CryptoProviderSetupExtension implements BeforeAllCallback { + private BouncyCastleProvider securityProvider; + + @Override + public synchronized void beforeAll(ExtensionContext extensionContext) throws Exception { + if (this.securityProvider == null) { + Security.addProvider(this.securityProvider = new BouncyCastleProvider()); + } + } +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java index d454b6c6..4c9265a1 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java @@ -1,14 +1,10 @@ package io.opentdf.platform.sdk; -import org.bouncycastle.jce.interfaces.ECPrivateKey; -import org.bouncycastle.jce.interfaces.ECPublicKey; import org.junit.jupiter.api.Test; -import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.security.*; -import java.security.cert.CertificateException; -import java.security.spec.InvalidKeySpecException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.util.Arrays; import java.util.Base64; @@ -51,13 +47,13 @@ public class ECKeys { void ecPublicKeyInPemformat() { ECKeyPair keyPairA = new ECKeyPair(); - String keypairAPubicKey = keyPairA.publicKeyInPEMFormat(); + String keypairAPublicKey = keyPairA.publicKeyInPEMFormat(); String keypairAPrivateKey = keyPairA.privateKeyInPEMFormat(); - ECPublicKey publicKeyA = ECKeyPair.publicKeyFromPem(keypairAPubicKey); + ECPublicKey publicKeyA = ECKeyPair.publicKeyFromPem(keypairAPublicKey); ECPrivateKey privateKeyA = ECKeyPair.privateKeyFromPem(keypairAPrivateKey); - System.out.println(keypairAPubicKey); + System.out.println(keypairAPublicKey); System.out.println(keypairAPrivateKey); byte[] compressedKey1 = keyPairA.compressECPublickey(); @@ -83,45 +79,6 @@ void ecPublicKeyInPemformat() { System.out.println(Arrays.toString(symmetricKey1)); } - @Test - void extractPemPubKeyFromX509() throws CertificateException, IOException, NoSuchAlgorithmException, - InvalidKeySpecException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException { - String x509ECPubKey = "-----BEGIN CERTIFICATE-----\n" + - "MIIBCzCBsgIJAK3Uxk7fP5oWMAoGCCqGSM49BAMCMA4xDDAKBgNVBAMMA2thczAe\n" + - "Fw0yMzA0MjQxNzQ2MTVaFw0yNDA0MjMxNzQ2MTVaMA4xDDAKBgNVBAMMA2thczBZ\n" + - "MBMGByqGSM49AgEGCCqGSM49AwEHA0IABL//OvkSC1ji2w7AUrj27BxN3K6hhN4B\n" + - "YRb45lYoMsihIxhDmMDAZTgoaDyNJG59VrJE/yoM9KuiXV8a+82+OwwwCgYIKoZI\n" + - "zj0EAwIDSAAwRQIhAItk5SmcWSg06tnOCEqTa6UsChaycX/cmAT8PTDRnaRcAiAl\n" + - "Vr2EvlA2x5mWFE/+nDdxxzljYjLZuSDQMEI/J6u0/Q==\n" + - "-----END CERTIFICATE-----"; - String pubKey = ECKeyPair.getPEMPublicKeyFromX509Cert(x509ECPubKey); - System.out.println(pubKey); - - ECPublicKey publicKey = ECKeyPair.publicKeyFromPem(pubKey); - byte[] compressedKey = publicKey.getQ().getEncoded(true); - System.out.println(Arrays.toString(compressedKey)); - - compressedKey = ECKeyPair.compressECPublickey(pubKey); - System.out.println(Arrays.toString(compressedKey)); - System.out.println(compressedKey.length); - - ECKeyPair keyPair = new ECKeyPair(); - - String keypairPubicKey = keyPair.publicKeyInPEMFormat(); - String keypairPrivateKey = keyPair.privateKeyInPEMFormat(); - System.out.println(keypairPubicKey); - System.out.println(keypairPrivateKey); - - byte[] symmetricKey = ECKeyPair.computeECDHKey(publicKey, ECKeyPair.privateKeyFromPem(keypairPrivateKey)); - System.out.println(Arrays.toString(symmetricKey)); - - byte[] key = ECKeyPair.calculateHKDF(ECKeys.salt.getBytes(StandardCharsets.UTF_8), symmetricKey); - System.out.println(Arrays.toString(key)); - System.out.println(key.length); - - assertThat(key.length).isEqualTo(32); // SHA-256 produces a 32-byte key - } - @Test void createSymmetricKeysWithOtherCurves() { ECKeyPair pubPair = new ECKeyPair(ECCurve.SECP384R1, ECKeyPair.ECAlgorithm.ECDH); @@ -147,20 +104,20 @@ void testECDH() { byte[] symmetricKey = ECKeyPair.computeECDHKey(kasPubKey, sdkPriKey); byte[] key = ECKeyPair.calculateHKDF(ECKeys.salt.getBytes(StandardCharsets.UTF_8), symmetricKey); String encodedKey = Base64.getEncoder().encodeToString(key); - assertEquals(encodedKey, expectedKey); + assertEquals(expectedKey, encodedKey); // KAS side symmetricKey = ECKeyPair.computeECDHKey(sdkPubKey, kasPriKey); key = ECKeyPair.calculateHKDF(ECKeys.salt.getBytes(StandardCharsets.UTF_8), symmetricKey); encodedKey = Base64.getEncoder().encodeToString(key); - assertEquals(encodedKey, expectedKey); + assertEquals(expectedKey, encodedKey); byte[] ecPoint = ECKeyPair.compressECPublickey(ECKeys.sdkPublicKey); String encodeECPoint = Base64.getEncoder().encodeToString(ecPoint); - assertEquals(encodeECPoint, "Al3vx59pBnP8tRxuUFw18aK9ym6rFrxZRhpVQytUQ+Kg"); + assertEquals("Al3vx59pBnP8tRxuUFw18aK9ym6rFrxZRhpVQytUQ+Kg", encodeECPoint); String publicKey = ECKeyPair.publicKeyFromECPoint(ecPoint, - SECP256R1.name()); + SECP256R1.getCurveName()); assertArrayEquals(ECKeys.sdkPublicKey.toCharArray(), publicKey.toCharArray()); } diff --git a/sdk/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/sdk/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 00000000..7ab6a88a --- /dev/null +++ b/sdk/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +io.opentdf.platform.sdk.CryptoProviderSetupExtension diff --git a/sdk/src/test/resources/junit-platform.properties b/sdk/src/test/resources/junit-platform.properties new file mode 100644 index 00000000..25ce5c98 --- /dev/null +++ b/sdk/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.extensions.autodetection.enabled = true