From baf0b02677ccfb435b7d5b78edfb5774139a348b Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 11 May 2026 15:42:13 -0400 Subject: [PATCH 01/32] remove bouncycastle --- sdk/pom.xml | 2 + .../io/opentdf/platform/sdk/ECKeyPair.java | 280 ++++++------------ .../io/opentdf/platform/sdk/KASClient.java | 17 +- .../java/io/opentdf/platform/sdk/TDF.java | 15 +- .../opentdf/platform/sdk/ECKeyPairTest.java | 41 +-- .../java/io/opentdf/platform/sdk/TDFTest.java | 4 +- 6 files changed, 138 insertions(+), 221 deletions(-) diff --git a/sdk/pom.xml b/sdk/pom.xml index 64497996..f14ce1f8 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -164,10 +164,12 @@ org.bouncycastle bcpkix-jdk18on + test org.bouncycastle bcprov-jdk18on + test 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..b6be4c1e 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java @@ -1,39 +1,28 @@ package io.opentdf.platform.sdk; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.cert.X509CertificateHolder; -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.io.*; -import java.security.*; -import java.security.spec.*; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.util.Base64; 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 static final String EC_ALGORITHM = "EC"; private final ECCurve curve; @@ -42,8 +31,6 @@ public enum ECAlgorithm { ECDSA } - private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider(); - private KeyPair keyPair; public ECKeyPair() { @@ -52,27 +39,14 @@ public ECKeyPair() { public ECKeyPair(ECCurve curve, ECAlgorithm algorithm) { this.curve = Objects.requireNonNull(curve); - KeyPairGenerator generator; - + Objects.requireNonNull(algorithm); try { - // 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); - } else { - generator = KeyPairGeneratorSpi.getInstance(ECAlgorithm.ECDSA.name(), BOUNCY_CASTLE_PROVIDER); - } - } catch (NoSuchAlgorithmException e) { + KeyPairGenerator generator = KeyPairGenerator.getInstance(EC_ALGORITHM); + generator.initialize(new ECGenParameterSpec(this.curve.getCurveName())); + this.keyPair = generator.generateKeyPair(); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } - - ECGenParameterSpec spec = new ECGenParameterSpec(this.curve.getCurveName()); - try { - generator.initialize(spec); - } catch (InvalidAlgorithmParameterException e) { - throw new RuntimeException(e); - } - this.keyPair = generator.generateKeyPair(); } public ECPublicKey getPublicKey() { @@ -88,33 +62,11 @@ ECCurve getCurve() { } public String publicKeyInPEMFormat() { - StringWriter writer = new StringWriter(); - PemWriter pemWriter = new PemWriter(writer); - - try { - pemWriter.writeObject(new PemObject("PUBLIC KEY", this.keyPair.getPublic().getEncoded())); - pemWriter.flush(); - pemWriter.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - return writer.toString(); + return toPem("PUBLIC KEY", this.keyPair.getPublic().getEncoded()); } public String privateKeyInPEMFormat() { - StringWriter writer = new StringWriter(); - PemWriter pemWriter = new PemWriter(writer); - - try { - pemWriter.writeObject(new PemObject("PRIVATE KEY", this.keyPair.getPrivate().getEncoded())); - pemWriter.flush(); - pemWriter.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - return writer.toString(); + return toPem("PRIVATE KEY", this.keyPair.getPrivate().getEncoded()); } public int keySize() { @@ -122,114 +74,16 @@ public int keySize() { } public byte[] compressECPublickey() { - return ((ECPublicKey) this.keyPair.getPublic()).getQ().getEncoded(true); - } - - 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); - } - - // EC public key to pem formated. - 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 (IOException e) { - throw new RuntimeException(e); - } - } - - public static byte[] compressECPublickey(String pemECPubKey) { - try { - KeyFactory ecKeyFac = KeyFactory.getInstance("EC", "BC"); - 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) { - throw new RuntimeException(e); - } - } - - public static String publicKeyFromECPoint(byte[] ecPoint, String curveName) { - try { - // 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); - - // EC Public keu 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) { - throw new RuntimeException(e); - } - } - - public static ECPublicKey publicKeyFromPem(String pemEncoding) { - try { - PEMParser parser = new PEMParser(new StringReader(pemEncoding)); - SubjectPublicKeyInfo publicKeyInfo = (SubjectPublicKeyInfo) parser.readObject(); - parser.close(); - - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER); - return (ECPublicKey) converter.getPublicKey(publicKeyInfo); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static ECPrivateKey privateKeyFromPem(String pemEncoding) { - try { - PEMParser parser = new PEMParser(new StringReader(pemEncoding)); - PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) parser.readObject(); - parser.close(); - - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER); - return (ECPrivateKey) converter.getPrivateKey(privateKeyInfo); - } catch (IOException e) { - throw new RuntimeException(e); - } + return encodeCompressedPoint((ECPublicKey) this.keyPair.getPublic()); } 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); } } @@ -239,46 +93,78 @@ public static byte[] computeECDHKey(ECPublicKey publicKey, ECPrivateKey privateK * that is 32 bytes (256 bits) long. */ public static byte[] calculateHKDF(byte[] salt, byte[] secret) { - byte[] key = new byte[SHA256_BYTES]; - HKDFParameters params = new HKDFParameters(secret, salt, null); - - HKDFBytesGenerator hkdf = new HKDFBytesGenerator(SHA256Digest.newInstance()); - hkdf.init(params); - hkdf.generateBytes(key, 0, key.length); - return key; + try { + // RFC 5869: if salt is absent, substitute a zero-filled buffer of Hash output size. + byte[] effectiveSalt = (salt == null || salt.length == 0) ? new byte[SHA256_BYTES] : salt; + Mac hmac = Mac.getInstance("HmacSHA256"); + hmac.init(new SecretKeySpec(effectiveSalt, "HmacSHA256")); + byte[] prk = hmac.doFinal(secret); + + // HKDF-Expand with empty info and L = 32 (a single HMAC block). + hmac.init(new SecretKeySpec(prk, "HmacSHA256")); + hmac.update((byte) 0x01); + return hmac.doFinal(); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new RuntimeException(e); + } } 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 + + private static String toPem(String type, byte[] der) { + String b64 = Base64.getEncoder().encodeToString(der); + StringBuilder sb = new StringBuilder(); + sb.append("-----BEGIN ").append(type).append("-----\n"); + for (int i = 0; i < b64.length(); i += 64) { + sb.append(b64, i, Math.min(i + 64, b64.length())).append('\n'); + } + sb.append("-----END ").append(type).append("-----\n"); + return sb.toString(); + } + + private static byte[] encodeCompressedPoint(ECPublicKey publicKey) { + ECPoint w = publicKey.getW(); + ECParameterSpec params = publicKey.getParams(); + int size = (params.getCurve().getField().getFieldSize() + 7) / 8; + byte[] x = toFixedLength(w.getAffineX(), size); + byte[] result = new byte[size + 1]; + result[0] = (byte) (w.getAffineY().testBit(0) ? 0x03 : 0x02); + System.arraycopy(x, 0, result, 1, size); + return result; + } + + private static byte[] toFixedLength(BigInteger value, int length) { + byte[] bytes = value.toByteArray(); + if (bytes.length == length) { + return bytes; + } + byte[] result = new byte[length]; + if (bytes.length > length) { + // BigInteger.toByteArray() may prepend a zero sign byte; strip it. + System.arraycopy(bytes, bytes.length - length, result, 0, length); + } else { + System.arraycopy(bytes, 0, result, length - bytes.length, bytes.length); + } + return result; + } +} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 91ada8b0..ca2e93dd 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -24,8 +24,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; import java.time.Duration; import java.time.Instant; +import java.util.Base64; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -176,7 +182,16 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessi } var kasEphemeralPublicKey = response.getSessionPublicKey(); - var publicKey = ECKeyPair.publicKeyFromPem(kasEphemeralPublicKey); + ECPublicKey publicKey; + try { + byte[] der = Base64.getDecoder().decode(kasEphemeralPublicKey + .replaceAll("-----[^-]+-----", "") + .replaceAll("\\s+", "")); + publicKey = (ECPublicKey) KeyFactory.getInstance("EC") + .generatePublic(new X509EncodedKeySpec(der)); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new SDKException("error decoding KAS session public key", e); + } byte[] symKey = ECKeyPair.computeECDHKey(publicKey, ecKeyPair.getPrivateKey()); var sessionKey = ECKeyPair.calculateHKDF(GLOBAL_KEY_SALT, symKey); 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..e560f3b9 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,9 @@ import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.security.*; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; import java.text.ParseException; import java.util.*; @@ -245,7 +247,16 @@ private ECKeyWrappedKeyInfo createECWrappedKey(Config.KASInfo kasInfo, var curveName = keyType.getECCurve(); var keyPair = new ECKeyPair(curveName, ECKeyPair.ECAlgorithm.ECDH); - ECPublicKey kasPubKey = ECKeyPair.publicKeyFromPem(kasInfo.PublicKey); + ECPublicKey kasPubKey; + try { + byte[] der = Base64.getDecoder().decode(kasInfo.PublicKey + .replaceAll("-----[^-]+-----", "") + .replaceAll("\\s+", "")); + kasPubKey = (ECPublicKey) KeyFactory.getInstance("EC") + .generatePublic(new X509EncodedKeySpec(der)); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new SDKException("error decoding KAS EC public key", e); + } byte[] symmetricKey = ECKeyPair.computeECDHKey(kasPubKey, keyPair.getPrivateKey()); var sessionKey = ECKeyPair.calculateHKDF(GLOBAL_KEY_SALT, symmetricKey); 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..5cf18859 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java @@ -1,7 +1,7 @@ package io.opentdf.platform.sdk; -import org.bouncycastle.jce.interfaces.ECPrivateKey; -import org.bouncycastle.jce.interfaces.ECPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -54,17 +54,17 @@ void ecPublicKeyInPemformat() { String keypairAPubicKey = keyPairA.publicKeyInPEMFormat(); String keypairAPrivateKey = keyPairA.privateKeyInPEMFormat(); - ECPublicKey publicKeyA = ECKeyPair.publicKeyFromPem(keypairAPubicKey); - ECPrivateKey privateKeyA = ECKeyPair.privateKeyFromPem(keypairAPrivateKey); + ECPublicKey publicKeyA = PemUtils.publicKeyFromPem(keypairAPubicKey); + ECPrivateKey privateKeyA = PemUtils.privateKeyFromPem(keypairAPrivateKey); System.out.println(keypairAPubicKey); System.out.println(keypairAPrivateKey); byte[] compressedKey1 = keyPairA.compressECPublickey(); - byte[] compressedKey2 = ECKeyPair.compressECPublickey(keyPairA.publicKeyInPEMFormat()); + byte[] compressedKey2 = PemUtils.compressECPublickey(keyPairA.publicKeyInPEMFormat()); assertArrayEquals(compressedKey1, compressedKey2); - String publicKey = ECKeyPair.publicKeyFromECPoint(compressedKey1, SECP256R1.getCurveName()); + String publicKey = PemUtils.publicKeyFromECPoint(compressedKey1, SECP256R1.getCurveName()); assertEquals(keyPairA.publicKeyInPEMFormat(), publicKey); ECKeyPair keyPairB = new ECKeyPair(); @@ -74,8 +74,8 @@ void ecPublicKeyInPemformat() { System.out.println(keypairBPubicKey); System.out.println(keypairBPrivateKey); - ECPublicKey publicKeyB = ECKeyPair.publicKeyFromPem(keypairBPubicKey); - ECPrivateKey privateKeyB = ECKeyPair.privateKeyFromPem(keypairBPrivateKey); + ECPublicKey publicKeyB = PemUtils.publicKeyFromPem(keypairBPubicKey); + ECPrivateKey privateKeyB = PemUtils.privateKeyFromPem(keypairBPrivateKey); byte[] symmetricKey1 = ECKeyPair.computeECDHKey(publicKeyA, privateKeyB); byte[] symmetricKey2 = ECKeyPair.computeECDHKey(publicKeyB, privateKeyA); @@ -94,14 +94,14 @@ void extractPemPubKeyFromX509() throws CertificateException, IOException, NoSuch "zj0EAwIDSAAwRQIhAItk5SmcWSg06tnOCEqTa6UsChaycX/cmAT8PTDRnaRcAiAl\n" + "Vr2EvlA2x5mWFE/+nDdxxzljYjLZuSDQMEI/J6u0/Q==\n" + "-----END CERTIFICATE-----"; - String pubKey = ECKeyPair.getPEMPublicKeyFromX509Cert(x509ECPubKey); + String pubKey = PemUtils.getPEMPublicKeyFromX509Cert(x509ECPubKey); System.out.println(pubKey); - ECPublicKey publicKey = ECKeyPair.publicKeyFromPem(pubKey); - byte[] compressedKey = publicKey.getQ().getEncoded(true); + ECPublicKey publicKey = PemUtils.publicKeyFromPem(pubKey); + byte[] compressedKey = PemUtils.compressECPublickey(pubKey); System.out.println(Arrays.toString(compressedKey)); - compressedKey = ECKeyPair.compressECPublickey(pubKey); + compressedKey = PemUtils.compressECPublickey(pubKey); System.out.println(Arrays.toString(compressedKey)); System.out.println(compressedKey.length); @@ -112,7 +112,7 @@ void extractPemPubKeyFromX509() throws CertificateException, IOException, NoSuch System.out.println(keypairPubicKey); System.out.println(keypairPrivateKey); - byte[] symmetricKey = ECKeyPair.computeECDHKey(publicKey, ECKeyPair.privateKeyFromPem(keypairPrivateKey)); + byte[] symmetricKey = ECKeyPair.computeECDHKey(publicKey, PemUtils.privateKeyFromPem(keypairPrivateKey)); System.out.println(Arrays.toString(symmetricKey)); byte[] key = ECKeyPair.calculateHKDF(ECKeys.salt.getBytes(StandardCharsets.UTF_8), symmetricKey); @@ -138,11 +138,11 @@ void testECDH() { String expectedKey = "3KGgsptHbTsbxJtql6sHUcx255KcUhxdeJWKjmPMlcc="; // SDK side - ECPublicKey kasPubKey = ECKeyPair.publicKeyFromPem(ECKeys.kasPublicKey); - ECPrivateKey kasPriKey = ECKeyPair.privateKeyFromPem(ECKeys.kasPrivateKey); + ECPublicKey kasPubKey = PemUtils.publicKeyFromPem(ECKeys.kasPublicKey); + ECPrivateKey kasPriKey = PemUtils.privateKeyFromPem(ECKeys.kasPrivateKey); - ECPublicKey sdkPubKey = ECKeyPair.publicKeyFromPem(ECKeys.sdkPublicKey); - ECPrivateKey sdkPriKey = ECKeyPair.privateKeyFromPem(ECKeys.sdkPrivateKey); + ECPublicKey sdkPubKey = PemUtils.publicKeyFromPem(ECKeys.sdkPublicKey); + ECPrivateKey sdkPriKey = PemUtils.privateKeyFromPem(ECKeys.sdkPrivateKey); byte[] symmetricKey = ECKeyPair.computeECDHKey(kasPubKey, sdkPriKey); byte[] key = ECKeyPair.calculateHKDF(ECKeys.salt.getBytes(StandardCharsets.UTF_8), symmetricKey); @@ -155,11 +155,11 @@ void testECDH() { encodedKey = Base64.getEncoder().encodeToString(key); assertEquals(encodedKey, expectedKey); - byte[] ecPoint = ECKeyPair.compressECPublickey(ECKeys.sdkPublicKey); + byte[] ecPoint = PemUtils.compressECPublickey(ECKeys.sdkPublicKey); String encodeECPoint = Base64.getEncoder().encodeToString(ecPoint); assertEquals(encodeECPoint, "Al3vx59pBnP8tRxuUFw18aK9ym6rFrxZRhpVQytUQ+Kg"); - String publicKey = ECKeyPair.publicKeyFromECPoint(ecPoint, + String publicKey = PemUtils.publicKeyFromECPoint(ecPoint, SECP256R1.name()); assertArrayEquals(ECKeys.sdkPublicKey.toCharArray(), publicKey.toCharArray()); } @@ -169,6 +169,9 @@ void testECDSA() { String plainText = "Virtru!"; for (var curve: ECCurve.values()) { + if (!curve.isSupported()) { + continue; + } ECKeyPair keyPair = new ECKeyPair(curve, ECKeyPair.ECAlgorithm.ECDSA); byte[] signature = ECKeyPair.computeECDSASig(plainText.getBytes(), keyPair.getPrivateKey()); boolean verify = ECKeyPair.verifyECDSAig(plainText.getBytes(), signature, keyPair.getPublicKey()); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java index 0813852b..3892ced4 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -89,9 +89,9 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessio if (sessionKeyType.isEc()) { var kasPrivateKey = CryptoUtils .getPrivateKeyPEM(keypairs.get(index).getPrivate()); - var privateKey = ECKeyPair.privateKeyFromPem(kasPrivateKey); + var privateKey = PemUtils.privateKeyFromPem(kasPrivateKey); var clientEphemeralPublicKey = keyAccess.ephemeralPublicKey; - var publicKey = ECKeyPair.publicKeyFromPem(clientEphemeralPublicKey); + var publicKey = PemUtils.publicKeyFromPem(clientEphemeralPublicKey); byte[] symKey = ECKeyPair.computeECDHKey(publicKey, privateKey); var sessionKey = ECKeyPair.calculateHKDF(GLOBAL_KEY_SALT, symKey); From d0f1d766e824ed68fb3224b3f8cd437e8f52e2fa Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 11 May 2026 15:42:13 -0400 Subject: [PATCH 02/32] remove bouncycastle --- sdk/pom.xml | 2 + .../io/opentdf/platform/sdk/ECKeyPair.java | 280 ++++++------------ .../io/opentdf/platform/sdk/KASClient.java | 17 +- .../java/io/opentdf/platform/sdk/TDF.java | 15 +- .../opentdf/platform/sdk/ECKeyPairTest.java | 41 +-- .../io/opentdf/platform/sdk/PemUtils.java | 128 ++++++++ .../java/io/opentdf/platform/sdk/TDFTest.java | 4 +- 7 files changed, 266 insertions(+), 221 deletions(-) create mode 100644 sdk/src/test/java/io/opentdf/platform/sdk/PemUtils.java diff --git a/sdk/pom.xml b/sdk/pom.xml index 64497996..f14ce1f8 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -164,10 +164,12 @@ org.bouncycastle bcpkix-jdk18on + test org.bouncycastle bcprov-jdk18on + test 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..b6be4c1e 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java @@ -1,39 +1,28 @@ package io.opentdf.platform.sdk; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.cert.X509CertificateHolder; -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.io.*; -import java.security.*; -import java.security.spec.*; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.util.Base64; 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 static final String EC_ALGORITHM = "EC"; private final ECCurve curve; @@ -42,8 +31,6 @@ public enum ECAlgorithm { ECDSA } - private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider(); - private KeyPair keyPair; public ECKeyPair() { @@ -52,27 +39,14 @@ public ECKeyPair() { public ECKeyPair(ECCurve curve, ECAlgorithm algorithm) { this.curve = Objects.requireNonNull(curve); - KeyPairGenerator generator; - + Objects.requireNonNull(algorithm); try { - // 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); - } else { - generator = KeyPairGeneratorSpi.getInstance(ECAlgorithm.ECDSA.name(), BOUNCY_CASTLE_PROVIDER); - } - } catch (NoSuchAlgorithmException e) { + KeyPairGenerator generator = KeyPairGenerator.getInstance(EC_ALGORITHM); + generator.initialize(new ECGenParameterSpec(this.curve.getCurveName())); + this.keyPair = generator.generateKeyPair(); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } - - ECGenParameterSpec spec = new ECGenParameterSpec(this.curve.getCurveName()); - try { - generator.initialize(spec); - } catch (InvalidAlgorithmParameterException e) { - throw new RuntimeException(e); - } - this.keyPair = generator.generateKeyPair(); } public ECPublicKey getPublicKey() { @@ -88,33 +62,11 @@ ECCurve getCurve() { } public String publicKeyInPEMFormat() { - StringWriter writer = new StringWriter(); - PemWriter pemWriter = new PemWriter(writer); - - try { - pemWriter.writeObject(new PemObject("PUBLIC KEY", this.keyPair.getPublic().getEncoded())); - pemWriter.flush(); - pemWriter.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - return writer.toString(); + return toPem("PUBLIC KEY", this.keyPair.getPublic().getEncoded()); } public String privateKeyInPEMFormat() { - StringWriter writer = new StringWriter(); - PemWriter pemWriter = new PemWriter(writer); - - try { - pemWriter.writeObject(new PemObject("PRIVATE KEY", this.keyPair.getPrivate().getEncoded())); - pemWriter.flush(); - pemWriter.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - return writer.toString(); + return toPem("PRIVATE KEY", this.keyPair.getPrivate().getEncoded()); } public int keySize() { @@ -122,114 +74,16 @@ public int keySize() { } public byte[] compressECPublickey() { - return ((ECPublicKey) this.keyPair.getPublic()).getQ().getEncoded(true); - } - - 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); - } - - // EC public key to pem formated. - 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 (IOException e) { - throw new RuntimeException(e); - } - } - - public static byte[] compressECPublickey(String pemECPubKey) { - try { - KeyFactory ecKeyFac = KeyFactory.getInstance("EC", "BC"); - 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) { - throw new RuntimeException(e); - } - } - - public static String publicKeyFromECPoint(byte[] ecPoint, String curveName) { - try { - // 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); - - // EC Public keu 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) { - throw new RuntimeException(e); - } - } - - public static ECPublicKey publicKeyFromPem(String pemEncoding) { - try { - PEMParser parser = new PEMParser(new StringReader(pemEncoding)); - SubjectPublicKeyInfo publicKeyInfo = (SubjectPublicKeyInfo) parser.readObject(); - parser.close(); - - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER); - return (ECPublicKey) converter.getPublicKey(publicKeyInfo); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static ECPrivateKey privateKeyFromPem(String pemEncoding) { - try { - PEMParser parser = new PEMParser(new StringReader(pemEncoding)); - PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) parser.readObject(); - parser.close(); - - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER); - return (ECPrivateKey) converter.getPrivateKey(privateKeyInfo); - } catch (IOException e) { - throw new RuntimeException(e); - } + return encodeCompressedPoint((ECPublicKey) this.keyPair.getPublic()); } 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); } } @@ -239,46 +93,78 @@ public static byte[] computeECDHKey(ECPublicKey publicKey, ECPrivateKey privateK * that is 32 bytes (256 bits) long. */ public static byte[] calculateHKDF(byte[] salt, byte[] secret) { - byte[] key = new byte[SHA256_BYTES]; - HKDFParameters params = new HKDFParameters(secret, salt, null); - - HKDFBytesGenerator hkdf = new HKDFBytesGenerator(SHA256Digest.newInstance()); - hkdf.init(params); - hkdf.generateBytes(key, 0, key.length); - return key; + try { + // RFC 5869: if salt is absent, substitute a zero-filled buffer of Hash output size. + byte[] effectiveSalt = (salt == null || salt.length == 0) ? new byte[SHA256_BYTES] : salt; + Mac hmac = Mac.getInstance("HmacSHA256"); + hmac.init(new SecretKeySpec(effectiveSalt, "HmacSHA256")); + byte[] prk = hmac.doFinal(secret); + + // HKDF-Expand with empty info and L = 32 (a single HMAC block). + hmac.init(new SecretKeySpec(prk, "HmacSHA256")); + hmac.update((byte) 0x01); + return hmac.doFinal(); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new RuntimeException(e); + } } 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 + + private static String toPem(String type, byte[] der) { + String b64 = Base64.getEncoder().encodeToString(der); + StringBuilder sb = new StringBuilder(); + sb.append("-----BEGIN ").append(type).append("-----\n"); + for (int i = 0; i < b64.length(); i += 64) { + sb.append(b64, i, Math.min(i + 64, b64.length())).append('\n'); + } + sb.append("-----END ").append(type).append("-----\n"); + return sb.toString(); + } + + private static byte[] encodeCompressedPoint(ECPublicKey publicKey) { + ECPoint w = publicKey.getW(); + ECParameterSpec params = publicKey.getParams(); + int size = (params.getCurve().getField().getFieldSize() + 7) / 8; + byte[] x = toFixedLength(w.getAffineX(), size); + byte[] result = new byte[size + 1]; + result[0] = (byte) (w.getAffineY().testBit(0) ? 0x03 : 0x02); + System.arraycopy(x, 0, result, 1, size); + return result; + } + + private static byte[] toFixedLength(BigInteger value, int length) { + byte[] bytes = value.toByteArray(); + if (bytes.length == length) { + return bytes; + } + byte[] result = new byte[length]; + if (bytes.length > length) { + // BigInteger.toByteArray() may prepend a zero sign byte; strip it. + System.arraycopy(bytes, bytes.length - length, result, 0, length); + } else { + System.arraycopy(bytes, 0, result, length - bytes.length, bytes.length); + } + return result; + } +} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 91ada8b0..ca2e93dd 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -24,8 +24,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; import java.time.Duration; import java.time.Instant; +import java.util.Base64; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -176,7 +182,16 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessi } var kasEphemeralPublicKey = response.getSessionPublicKey(); - var publicKey = ECKeyPair.publicKeyFromPem(kasEphemeralPublicKey); + ECPublicKey publicKey; + try { + byte[] der = Base64.getDecoder().decode(kasEphemeralPublicKey + .replaceAll("-----[^-]+-----", "") + .replaceAll("\\s+", "")); + publicKey = (ECPublicKey) KeyFactory.getInstance("EC") + .generatePublic(new X509EncodedKeySpec(der)); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new SDKException("error decoding KAS session public key", e); + } byte[] symKey = ECKeyPair.computeECDHKey(publicKey, ecKeyPair.getPrivateKey()); var sessionKey = ECKeyPair.calculateHKDF(GLOBAL_KEY_SALT, symKey); 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..e560f3b9 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,9 @@ import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.security.*; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; import java.text.ParseException; import java.util.*; @@ -245,7 +247,16 @@ private ECKeyWrappedKeyInfo createECWrappedKey(Config.KASInfo kasInfo, var curveName = keyType.getECCurve(); var keyPair = new ECKeyPair(curveName, ECKeyPair.ECAlgorithm.ECDH); - ECPublicKey kasPubKey = ECKeyPair.publicKeyFromPem(kasInfo.PublicKey); + ECPublicKey kasPubKey; + try { + byte[] der = Base64.getDecoder().decode(kasInfo.PublicKey + .replaceAll("-----[^-]+-----", "") + .replaceAll("\\s+", "")); + kasPubKey = (ECPublicKey) KeyFactory.getInstance("EC") + .generatePublic(new X509EncodedKeySpec(der)); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new SDKException("error decoding KAS EC public key", e); + } byte[] symmetricKey = ECKeyPair.computeECDHKey(kasPubKey, keyPair.getPrivateKey()); var sessionKey = ECKeyPair.calculateHKDF(GLOBAL_KEY_SALT, symmetricKey); 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..5cf18859 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java @@ -1,7 +1,7 @@ package io.opentdf.platform.sdk; -import org.bouncycastle.jce.interfaces.ECPrivateKey; -import org.bouncycastle.jce.interfaces.ECPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -54,17 +54,17 @@ void ecPublicKeyInPemformat() { String keypairAPubicKey = keyPairA.publicKeyInPEMFormat(); String keypairAPrivateKey = keyPairA.privateKeyInPEMFormat(); - ECPublicKey publicKeyA = ECKeyPair.publicKeyFromPem(keypairAPubicKey); - ECPrivateKey privateKeyA = ECKeyPair.privateKeyFromPem(keypairAPrivateKey); + ECPublicKey publicKeyA = PemUtils.publicKeyFromPem(keypairAPubicKey); + ECPrivateKey privateKeyA = PemUtils.privateKeyFromPem(keypairAPrivateKey); System.out.println(keypairAPubicKey); System.out.println(keypairAPrivateKey); byte[] compressedKey1 = keyPairA.compressECPublickey(); - byte[] compressedKey2 = ECKeyPair.compressECPublickey(keyPairA.publicKeyInPEMFormat()); + byte[] compressedKey2 = PemUtils.compressECPublickey(keyPairA.publicKeyInPEMFormat()); assertArrayEquals(compressedKey1, compressedKey2); - String publicKey = ECKeyPair.publicKeyFromECPoint(compressedKey1, SECP256R1.getCurveName()); + String publicKey = PemUtils.publicKeyFromECPoint(compressedKey1, SECP256R1.getCurveName()); assertEquals(keyPairA.publicKeyInPEMFormat(), publicKey); ECKeyPair keyPairB = new ECKeyPair(); @@ -74,8 +74,8 @@ void ecPublicKeyInPemformat() { System.out.println(keypairBPubicKey); System.out.println(keypairBPrivateKey); - ECPublicKey publicKeyB = ECKeyPair.publicKeyFromPem(keypairBPubicKey); - ECPrivateKey privateKeyB = ECKeyPair.privateKeyFromPem(keypairBPrivateKey); + ECPublicKey publicKeyB = PemUtils.publicKeyFromPem(keypairBPubicKey); + ECPrivateKey privateKeyB = PemUtils.privateKeyFromPem(keypairBPrivateKey); byte[] symmetricKey1 = ECKeyPair.computeECDHKey(publicKeyA, privateKeyB); byte[] symmetricKey2 = ECKeyPair.computeECDHKey(publicKeyB, privateKeyA); @@ -94,14 +94,14 @@ void extractPemPubKeyFromX509() throws CertificateException, IOException, NoSuch "zj0EAwIDSAAwRQIhAItk5SmcWSg06tnOCEqTa6UsChaycX/cmAT8PTDRnaRcAiAl\n" + "Vr2EvlA2x5mWFE/+nDdxxzljYjLZuSDQMEI/J6u0/Q==\n" + "-----END CERTIFICATE-----"; - String pubKey = ECKeyPair.getPEMPublicKeyFromX509Cert(x509ECPubKey); + String pubKey = PemUtils.getPEMPublicKeyFromX509Cert(x509ECPubKey); System.out.println(pubKey); - ECPublicKey publicKey = ECKeyPair.publicKeyFromPem(pubKey); - byte[] compressedKey = publicKey.getQ().getEncoded(true); + ECPublicKey publicKey = PemUtils.publicKeyFromPem(pubKey); + byte[] compressedKey = PemUtils.compressECPublickey(pubKey); System.out.println(Arrays.toString(compressedKey)); - compressedKey = ECKeyPair.compressECPublickey(pubKey); + compressedKey = PemUtils.compressECPublickey(pubKey); System.out.println(Arrays.toString(compressedKey)); System.out.println(compressedKey.length); @@ -112,7 +112,7 @@ void extractPemPubKeyFromX509() throws CertificateException, IOException, NoSuch System.out.println(keypairPubicKey); System.out.println(keypairPrivateKey); - byte[] symmetricKey = ECKeyPair.computeECDHKey(publicKey, ECKeyPair.privateKeyFromPem(keypairPrivateKey)); + byte[] symmetricKey = ECKeyPair.computeECDHKey(publicKey, PemUtils.privateKeyFromPem(keypairPrivateKey)); System.out.println(Arrays.toString(symmetricKey)); byte[] key = ECKeyPair.calculateHKDF(ECKeys.salt.getBytes(StandardCharsets.UTF_8), symmetricKey); @@ -138,11 +138,11 @@ void testECDH() { String expectedKey = "3KGgsptHbTsbxJtql6sHUcx255KcUhxdeJWKjmPMlcc="; // SDK side - ECPublicKey kasPubKey = ECKeyPair.publicKeyFromPem(ECKeys.kasPublicKey); - ECPrivateKey kasPriKey = ECKeyPair.privateKeyFromPem(ECKeys.kasPrivateKey); + ECPublicKey kasPubKey = PemUtils.publicKeyFromPem(ECKeys.kasPublicKey); + ECPrivateKey kasPriKey = PemUtils.privateKeyFromPem(ECKeys.kasPrivateKey); - ECPublicKey sdkPubKey = ECKeyPair.publicKeyFromPem(ECKeys.sdkPublicKey); - ECPrivateKey sdkPriKey = ECKeyPair.privateKeyFromPem(ECKeys.sdkPrivateKey); + ECPublicKey sdkPubKey = PemUtils.publicKeyFromPem(ECKeys.sdkPublicKey); + ECPrivateKey sdkPriKey = PemUtils.privateKeyFromPem(ECKeys.sdkPrivateKey); byte[] symmetricKey = ECKeyPair.computeECDHKey(kasPubKey, sdkPriKey); byte[] key = ECKeyPair.calculateHKDF(ECKeys.salt.getBytes(StandardCharsets.UTF_8), symmetricKey); @@ -155,11 +155,11 @@ void testECDH() { encodedKey = Base64.getEncoder().encodeToString(key); assertEquals(encodedKey, expectedKey); - byte[] ecPoint = ECKeyPair.compressECPublickey(ECKeys.sdkPublicKey); + byte[] ecPoint = PemUtils.compressECPublickey(ECKeys.sdkPublicKey); String encodeECPoint = Base64.getEncoder().encodeToString(ecPoint); assertEquals(encodeECPoint, "Al3vx59pBnP8tRxuUFw18aK9ym6rFrxZRhpVQytUQ+Kg"); - String publicKey = ECKeyPair.publicKeyFromECPoint(ecPoint, + String publicKey = PemUtils.publicKeyFromECPoint(ecPoint, SECP256R1.name()); assertArrayEquals(ECKeys.sdkPublicKey.toCharArray(), publicKey.toCharArray()); } @@ -169,6 +169,9 @@ void testECDSA() { String plainText = "Virtru!"; for (var curve: ECCurve.values()) { + if (!curve.isSupported()) { + continue; + } ECKeyPair keyPair = new ECKeyPair(curve, ECKeyPair.ECAlgorithm.ECDSA); byte[] signature = ECKeyPair.computeECDSASig(plainText.getBytes(), keyPair.getPrivateKey()); boolean verify = ECKeyPair.verifyECDSAig(plainText.getBytes(), signature, keyPair.getPublicKey()); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/PemUtils.java b/sdk/src/test/java/io/opentdf/platform/sdk/PemUtils.java new file mode 100644 index 00000000..e782a24a --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/PemUtils.java @@ -0,0 +1,128 @@ +package io.opentdf.platform.sdk; + +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +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.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import org.bouncycastle.util.io.pem.PemWriter; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Security; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; + +/** + * BouncyCastle-flavored PEM/X.509 helpers used only by tests. Kept here so the SDK + * production classpath does not have a BouncyCastle dependency. The implementations + * use BouncyCastle internally but expose standard JCA key interfaces, so callers can + * pass results directly to JCA-based APIs. + */ +final class PemUtils { + + private static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider(); + + static { + Security.addProvider(PROVIDER); + } + + private PemUtils() { + } + + static ECPublicKey publicKeyFromPem(String pemEncoding) { + try { + PEMParser parser = new PEMParser(new StringReader(pemEncoding)); + SubjectPublicKeyInfo publicKeyInfo = (SubjectPublicKeyInfo) parser.readObject(); + parser.close(); + + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(PROVIDER); + return (ECPublicKey) converter.getPublicKey(publicKeyInfo); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static ECPrivateKey privateKeyFromPem(String pemEncoding) { + try { + PEMParser parser = new PEMParser(new StringReader(pemEncoding)); + PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) parser.readObject(); + parser.close(); + + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(PROVIDER); + return (ECPrivateKey) converter.getPrivateKey(privateKeyInfo); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + 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(PROVIDER); + ECPublicKey publicKey; + try { + publicKey = (ECPublicKey) converter.getPublicKey(publicKeyInfo); + } catch (PEMException e) { + throw new RuntimeException(e); + } + + 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 (IOException e) { + throw new RuntimeException(e); + } + } + + static byte[] compressECPublickey(String pemECPubKey) { + try { + KeyFactory ecKeyFac = KeyFactory.getInstance("EC", PROVIDER); + PemReader pemReader = new PemReader(new StringReader(pemECPubKey)); + PemObject pemObject = pemReader.readPemObject(); + PublicKey pubKey = ecKeyFac.generatePublic(new X509EncodedKeySpec(pemObject.getContent())); + return ((org.bouncycastle.jce.interfaces.ECPublicKey) pubKey).getQ().getEncoded(true); + } catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException e) { + throw new RuntimeException(e); + } + } + + static String publicKeyFromECPoint(byte[] ecPoint, String curveName) { + try { + ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(curveName); + ECPoint point = ecSpec.getCurve().decodePoint(ecPoint); + ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(point, ecSpec); + KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", PROVIDER); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + 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 | NoSuchAlgorithmException | IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java index 0813852b..3892ced4 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -89,9 +89,9 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessio if (sessionKeyType.isEc()) { var kasPrivateKey = CryptoUtils .getPrivateKeyPEM(keypairs.get(index).getPrivate()); - var privateKey = ECKeyPair.privateKeyFromPem(kasPrivateKey); + var privateKey = PemUtils.privateKeyFromPem(kasPrivateKey); var clientEphemeralPublicKey = keyAccess.ephemeralPublicKey; - var publicKey = ECKeyPair.publicKeyFromPem(clientEphemeralPublicKey); + var publicKey = PemUtils.publicKeyFromPem(clientEphemeralPublicKey); byte[] symKey = ECKeyPair.computeECDHKey(publicKey, privateKey); var sessionKey = ECKeyPair.calculateHKDF(GLOBAL_KEY_SALT, symKey); From a7991caa66b468f7c019afbec70a6761d088fd93 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 11 May 2026 15:55:40 -0400 Subject: [PATCH 03/32] we do not need this --- sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java | 5 ++--- sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java | 3 +-- sdk/src/main/java/io/opentdf/platform/sdk/TDF.java | 2 +- .../test/java/io/opentdf/platform/sdk/ECKeyPairTest.java | 6 +++--- 4 files changed, 7 insertions(+), 9 deletions(-) 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 b6be4c1e..91bd0b9d 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java @@ -34,12 +34,11 @@ public enum ECAlgorithm { private KeyPair keyPair; public ECKeyPair() { - this(ECCurve.SECP256R1, ECAlgorithm.ECDH); + this(ECCurve.SECP256R1); } - public ECKeyPair(ECCurve curve, ECAlgorithm algorithm) { + public ECKeyPair(ECCurve curve) { this.curve = Objects.requireNonNull(curve); - Objects.requireNonNull(algorithm); try { KeyPairGenerator generator = KeyPairGenerator.getInstance(EC_ALGORITHM); generator.initialize(new ECGenParameterSpec(this.curve.getCurveName())); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index ca2e93dd..e834ff61 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -17,7 +17,6 @@ import io.opentdf.platform.kas.PublicKeyResponse; import io.opentdf.platform.kas.RewrapRequest; import io.opentdf.platform.kas.RewrapResponse; -import io.opentdf.platform.sdk.Config.KASInfo; import io.opentdf.platform.sdk.SDK.KasBadRequestException; import okhttp3.OkHttpClient; @@ -127,7 +126,7 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessi ECKeyPair ecKeyPair = null; if (sessionKeyType.isEc()) { var curve = sessionKeyType.getECCurve(); - ecKeyPair = new ECKeyPair(curve, ECKeyPair.ECAlgorithm.ECDH); + ecKeyPair = new ECKeyPair(curve); clientPublicKey = ecKeyPair.publicKeyInPEMFormat(); } else { // Initialize the RSA key pair only once and reuse it for future unwrap operations 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 e560f3b9..bfc4ac59 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -245,7 +245,7 @@ private Manifest.KeyAccess createKeyAccess(Config.TDFConfig tdfConfig, Config.KA private ECKeyWrappedKeyInfo createECWrappedKey(Config.KASInfo kasInfo, byte[] symKey, KeyType keyType) { var curveName = keyType.getECCurve(); - var keyPair = new ECKeyPair(curveName, ECKeyPair.ECAlgorithm.ECDH); + var keyPair = new ECKeyPair(curveName); ECPublicKey kasPubKey; try { 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 5cf18859..aa79a509 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java @@ -124,8 +124,8 @@ void extractPemPubKeyFromX509() throws CertificateException, IOException, NoSuch @Test void createSymmetricKeysWithOtherCurves() { - ECKeyPair pubPair = new ECKeyPair(ECCurve.SECP384R1, ECKeyPair.ECAlgorithm.ECDH); - ECKeyPair keyPair = new ECKeyPair(ECCurve.SECP384R1, ECKeyPair.ECAlgorithm.ECDH); + ECKeyPair pubPair = new ECKeyPair(ECCurve.SECP384R1); + ECKeyPair keyPair = new ECKeyPair(ECCurve.SECP384R1); byte[] sharedSecret = ECKeyPair.computeECDHKey(pubPair.getPublicKey(), keyPair.getPrivateKey()); byte[] encryptionKey = ECKeyPair.calculateHKDF(ECKeys.salt.getBytes(StandardCharsets.UTF_8), sharedSecret); @@ -172,7 +172,7 @@ void testECDSA() { if (!curve.isSupported()) { continue; } - ECKeyPair keyPair = new ECKeyPair(curve, ECKeyPair.ECAlgorithm.ECDSA); + ECKeyPair keyPair = new ECKeyPair(curve); byte[] signature = ECKeyPair.computeECDSASig(plainText.getBytes(), keyPair.getPrivateKey()); boolean verify = ECKeyPair.verifyECDSAig(plainText.getBytes(), signature, keyPair.getPublicKey()); assertEquals(verify, true); From 21fd9fbb0e7df0a3cca4d6c2b5cf61538f704c73 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 11 May 2026 16:05:34 -0400 Subject: [PATCH 04/32] centralize PEM parsing logic --- .../main/java/io/opentdf/platform/sdk/ECKeyPair.java | 10 ++++++++++ .../main/java/io/opentdf/platform/sdk/KASClient.java | 6 +----- sdk/src/main/java/io/opentdf/platform/sdk/TDF.java | 6 +----- 3 files changed, 12 insertions(+), 10 deletions(-) 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 91bd0b9d..f2b5d73a 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java @@ -6,6 +6,7 @@ import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; @@ -16,6 +17,8 @@ import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.Objects; @@ -48,6 +51,13 @@ public ECKeyPair(ECCurve curve) { } } + static ECPublicKey publicKeyFromPem(String pem) throws InvalidKeySpecException, NoSuchAlgorithmException { + String pemData = pem.replaceAll("-----(BEGIN|END) [A-Z ]+-----", "").replaceAll("\\s", ""); + byte[] der = Base64.getDecoder().decode(pemData); + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + return (ECPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(der)); + } + public ECPublicKey getPublicKey() { return (ECPublicKey) this.keyPair.getPublic(); } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index e834ff61..542aca8a 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -183,11 +183,7 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessi var kasEphemeralPublicKey = response.getSessionPublicKey(); ECPublicKey publicKey; try { - byte[] der = Base64.getDecoder().decode(kasEphemeralPublicKey - .replaceAll("-----[^-]+-----", "") - .replaceAll("\\s+", "")); - publicKey = (ECPublicKey) KeyFactory.getInstance("EC") - .generatePublic(new X509EncodedKeySpec(der)); + publicKey = ECKeyPair.publicKeyFromPem(kasEphemeralPublicKey); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new SDKException("error decoding KAS session public key", e); } 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 bfc4ac59..96b29f13 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -249,11 +249,7 @@ private ECKeyWrappedKeyInfo createECWrappedKey(Config.KASInfo kasInfo, ECPublicKey kasPubKey; try { - byte[] der = Base64.getDecoder().decode(kasInfo.PublicKey - .replaceAll("-----[^-]+-----", "") - .replaceAll("\\s+", "")); - kasPubKey = (ECPublicKey) KeyFactory.getInstance("EC") - .generatePublic(new X509EncodedKeySpec(der)); + kasPubKey = ECKeyPair.publicKeyFromPem(kasInfo.PublicKey); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new SDKException("error decoding KAS EC public key", e); } From dcb5d17d184e68974926a4fe2da015f51b11d696 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 11 May 2026 16:12:33 -0400 Subject: [PATCH 05/32] remove unused imports --- sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 542aca8a..dec68a33 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -23,14 +23,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; import java.time.Duration; import java.time.Instant; -import java.util.Base64; import java.util.Collections; import java.util.Date; import java.util.HashMap; From 9c793766535c52f3810088a15a06a032fcd63e9b Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 11 May 2026 16:13:05 -0400 Subject: [PATCH 06/32] Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f2b5d73a..efd5e97e 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java @@ -118,7 +118,7 @@ public static byte[] calculateHKDF(byte[] salt, byte[] secret) { } } - public static byte[] computeECDSASig(byte[] digest, ECPrivateKey privateKey) { + public static byte[] computeECDSASig(byte[] data, ECPrivateKey privateKey) { try { Signature ecdsaSign = Signature.getInstance("SHA256withECDSA"); ecdsaSign.initSign(privateKey); From c689eb8d35ca6ed720ab704059e2696580c915b1 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 11 May 2026 16:14:57 -0400 Subject: [PATCH 07/32] extract constant --- sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 f2b5d73a..c813171d 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java @@ -97,6 +97,7 @@ public static byte[] computeECDHKey(ECPublicKey publicKey, ECPrivateKey privateK } } + private static String HMAC_SHA_256 = "HmacSHA256"; /** * Returns a HKDF key derived from the provided salt and secret * that is 32 bytes (256 bits) long. @@ -105,12 +106,12 @@ public static byte[] calculateHKDF(byte[] salt, byte[] secret) { try { // RFC 5869: if salt is absent, substitute a zero-filled buffer of Hash output size. byte[] effectiveSalt = (salt == null || salt.length == 0) ? new byte[SHA256_BYTES] : salt; - Mac hmac = Mac.getInstance("HmacSHA256"); - hmac.init(new SecretKeySpec(effectiveSalt, "HmacSHA256")); + Mac hmac = Mac.getInstance(HMAC_SHA_256); + hmac.init(new SecretKeySpec(effectiveSalt, HMAC_SHA_256)); byte[] prk = hmac.doFinal(secret); // HKDF-Expand with empty info and L = 32 (a single HMAC block). - hmac.init(new SecretKeySpec(prk, "HmacSHA256")); + hmac.init(new SecretKeySpec(prk, HMAC_SHA_256)); hmac.update((byte) 0x01); return hmac.doFinal(); } catch (NoSuchAlgorithmException | InvalidKeyException e) { From df105d008cb9f8026a5144ea910dab34eaeb876e Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 11 May 2026 16:26:13 -0400 Subject: [PATCH 08/32] rename --- sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8fbe5509..d2254184 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java @@ -123,7 +123,7 @@ public static byte[] computeECDSASig(byte[] data, ECPrivateKey privateKey) { try { Signature ecdsaSign = Signature.getInstance("SHA256withECDSA"); ecdsaSign.initSign(privateKey); - ecdsaSign.update(digest); + ecdsaSign.update(data); return ecdsaSign.sign(); } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { throw new RuntimeException(e); From 8247667f15daaf64768188eedfe810cc53a4ba11 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Thu, 7 May 2026 11:39:10 -0400 Subject: [PATCH 09/32] feat(sdk): replace ayza libraries with TrustProvider on JCA Remove the three io.github.hakky54:ayza* dependencies and replace their TLS trust-material role with an SDK-owned TrustProvider built on provider-agnostic JCA APIs (CertificateFactory, KeyStore, TrustManagerFactory, SSLContext). This works under any registered crypto provider, including BC-FIPS, and avoids hardcoded provider names. - Add TrustProvider and package-private CompositeX509ExtendedTrustManager for combining JVM default + custom trust material. - SDKBuilder: replace SSLFactory field with SSLSocketFactory + X509TrustManager. sslFactory(SSLFactory) becomes sslFactory(SSLSocketFactory); add sslFactory(SSLSocketFactory, X509TrustManager) for callers that have a matching trust manager. sslFactoryFromDirectory / sslFactoryFromKeyStore signatures and semantics are preserved, now backed by TrustProvider internally. - TokenSource takes SSLSocketFactory directly. - Command.java --insecure path uses TrustProvider.insecure(). - SDKBuilderTest reworked to drop nl.altindag imports and use TrustProvider + standard JCA. Co-Authored-By: Claude Opus 4.7 --- .../java/io/opentdf/platform/Command.java | 8 +- pom.xml | 34 --- sdk/pom.xml | 12 - .../CompositeX509ExtendedTrustManager.java | 122 ++++++++ .../io/opentdf/platform/sdk/SDKBuilder.java | 97 ++++--- .../io/opentdf/platform/sdk/TokenSource.java | 18 +- .../opentdf/platform/sdk/TrustProvider.java | 274 ++++++++++++++++++ .../opentdf/platform/sdk/SDKBuilderTest.java | 34 ++- 8 files changed, 490 insertions(+), 109 deletions(-) create mode 100644 sdk/src/main/java/io/opentdf/platform/sdk/CompositeX509ExtendedTrustManager.java create mode 100644 sdk/src/main/java/io/opentdf/platform/sdk/TrustProvider.java diff --git a/cmdline/src/main/java/io/opentdf/platform/Command.java b/cmdline/src/main/java/io/opentdf/platform/Command.java index 3689aa38..5f81f86c 100644 --- a/cmdline/src/main/java/io/opentdf/platform/Command.java +++ b/cmdline/src/main/java/io/opentdf/platform/Command.java @@ -18,7 +18,7 @@ import io.opentdf.platform.sdk.KeyType; import io.opentdf.platform.sdk.SDK; import io.opentdf.platform.sdk.SDKBuilder; -import nl.altindag.ssl.SSLFactory; +import io.opentdf.platform.sdk.TrustProvider; import picocli.CommandLine; import picocli.CommandLine.HelpCommand; import picocli.CommandLine.Option; @@ -262,10 +262,8 @@ void encrypt( private SDK buildSDK() { SDKBuilder builder = new SDKBuilder(); if (insecure) { - SSLFactory sslFactory = SSLFactory.builder() - .withUnsafeTrustMaterial() // Trust all certificates - .build(); - builder.sslFactory(sslFactory); + // Trust all certificates + builder.sslFactory(TrustProvider.insecure().getSslSocketFactory()); } return builder.platformEndpoint(platformEndpoint) diff --git a/pom.xml b/pom.xml index 7ccd85d1..5ddcd589 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,6 @@ 1.75.0 4.29.2 1.82 - 10.0.0 1.18.3 0.8.13 @@ -78,39 +77,6 @@ 3.4 provided - - io.github.hakky54 - ayza-for-pem - ${ayza.version} - - - org.slf4j - slf4j-api - - - - - io.github.hakky54 - ayza - ${ayza.version} - - - org.slf4j - slf4j-api - - - - - io.github.hakky54 - ayza-for-netty - ${ayza.version} - - - org.slf4j - slf4j-api - - - io.grpc grpc-netty-shaded diff --git a/sdk/pom.xml b/sdk/pom.xml index f14ce1f8..351ec211 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -31,18 +31,6 @@ oauth2-oidc-sdk 11.10.1 - - io.github.hakky54 - ayza-for-pem - - - io.github.hakky54 - ayza - - - io.github.hakky54 - ayza-for-netty - com.google.code.gson diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/CompositeX509ExtendedTrustManager.java b/sdk/src/main/java/io/opentdf/platform/sdk/CompositeX509ExtendedTrustManager.java new file mode 100644 index 00000000..14a117f5 --- /dev/null +++ b/sdk/src/main/java/io/opentdf/platform/sdk/CompositeX509ExtendedTrustManager.java @@ -0,0 +1,122 @@ +package io.opentdf.platform.sdk; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedTrustManager; +import java.net.Socket; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +final class CompositeX509ExtendedTrustManager extends X509ExtendedTrustManager { + + private final List delegates; + private final X509Certificate[] acceptedIssuers; + + CompositeX509ExtendedTrustManager(List delegates) { + if (delegates == null || delegates.isEmpty()) { + throw new IllegalArgumentException("at least one trust manager is required"); + } + this.delegates = Collections.unmodifiableList(new ArrayList<>(delegates)); + Set issuers = new LinkedHashSet<>(); + for (X509ExtendedTrustManager tm : this.delegates) { + X509Certificate[] tmIssuers = tm.getAcceptedIssuers(); + if (tmIssuers != null) { + Collections.addAll(issuers, tmIssuers); + } + } + this.acceptedIssuers = issuers.toArray(new X509Certificate[0]); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + CertificateException last = null; + for (X509ExtendedTrustManager tm : delegates) { + try { + tm.checkClientTrusted(chain, authType); + return; + } catch (CertificateException e) { + last = e; + } + } + throw last; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + CertificateException last = null; + for (X509ExtendedTrustManager tm : delegates) { + try { + tm.checkClientTrusted(chain, authType, socket); + return; + } catch (CertificateException e) { + last = e; + } + } + throw last; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { + CertificateException last = null; + for (X509ExtendedTrustManager tm : delegates) { + try { + tm.checkClientTrusted(chain, authType, engine); + return; + } catch (CertificateException e) { + last = e; + } + } + throw last; + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + CertificateException last = null; + for (X509ExtendedTrustManager tm : delegates) { + try { + tm.checkServerTrusted(chain, authType); + return; + } catch (CertificateException e) { + last = e; + } + } + throw last; + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + CertificateException last = null; + for (X509ExtendedTrustManager tm : delegates) { + try { + tm.checkServerTrusted(chain, authType, socket); + return; + } catch (CertificateException e) { + last = e; + } + } + throw last; + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { + CertificateException last = null; + for (X509ExtendedTrustManager tm : delegates) { + try { + tm.checkServerTrusted(chain, authType, engine); + return; + } catch (CertificateException e) { + last = e; + } + } + throw last; + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return acceptedIssuers.clone(); + } +} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index bb350b66..9b2f75e8 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -34,22 +34,17 @@ import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationResponse; import io.opentdf.platform.wellknownconfiguration.WellKnownServiceClient; import io.opentdf.platform.wellknownconfiguration.WellKnownServiceClientInterface; -import nl.altindag.ssl.SSLFactory; -import nl.altindag.ssl.pem.util.PemUtils; import okhttp3.OkHttpClient; import okhttp3.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; +import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; -import javax.net.ssl.X509ExtendedTrustManager; -import java.io.File; -import java.io.FileInputStream; +import javax.net.ssl.X509TrustManager; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; @@ -63,7 +58,8 @@ public class SDKBuilder { private String platformEndpoint = null; private ClientAuthentication clientAuth = null; private Boolean usePlainText; - private SSLFactory sslFactory; + private SSLSocketFactory sslSocketFactory; + private X509TrustManager trustManager; private AuthorizationGrant authzGrant; private ProtocolType protocolType = ProtocolType.CONNECT; private SrtSigner srtSigner; @@ -80,42 +76,61 @@ public static SDKBuilder newBuilder() { return builder; } - public SDKBuilder sslFactory(SSLFactory sslFactory) { - this.sslFactory = sslFactory; + /** + * Configure the SDK to use the supplied {@link SSLSocketFactory} for outbound TLS connections. + * Callers using this overload bring their own pre-built socket factory; cert-chain trust + * material is whatever the supplied factory was built with. For full PKIX validation under a + * matching {@link X509TrustManager}, use {@link #sslFactoryFromDirectory(String)} or + * {@link #sslFactoryFromKeyStore(String, String)} which build both via {@link TrustProvider}. + */ + public SDKBuilder sslFactory(SSLSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + this.trustManager = null; + return this; + } + + /** + * Configure the SDK to use the supplied {@link SSLSocketFactory} together with a matching + * {@link X509TrustManager}. The trust manager is used by OkHttp for certificate pinning and + * cleartext-fallback decisions; supply this overload when the caller has a trust manager that + * matches the socket factory's trust material (e.g. both built from a {@link TrustProvider}). + */ + public SDKBuilder sslFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager) { + this.sslSocketFactory = sslSocketFactory; + this.trustManager = trustManager; return this; } /** * Add SSL Context with trusted certs from certDirPath - * + * * @param certsDirPath Path to a directory containing .pem or .crt trusted certs */ public SDKBuilder sslFactoryFromDirectory(String certsDirPath) throws Exception { - File certsDir = new File(certsDirPath); - File[] certFiles = certsDir.listFiles((dir, name) -> name.endsWith(".pem") || name.endsWith(".crt")); - logger.info("Loading certificates from: " + certsDir.getAbsolutePath()); - List certStreams = new ArrayList<>(certFiles.length); - for (File certFile : certFiles) { - certStreams.add(new FileInputStream(certFile)); - } - X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial(certStreams.toArray(new InputStream[0])); - this.sslFactory = SSLFactory.builder().withDefaultTrustMaterial().withSystemTrustMaterial() - .withTrustMaterial(trustManager).build(); + logger.info("Loading certificates from: {}", certsDirPath); + TrustProvider provider = TrustProvider.fromDirectory(certsDirPath); + this.sslSocketFactory = provider.getSslSocketFactory(); + this.trustManager = provider.getTrustManager(); return this; } /** * Add SSL Context with default system trust material + certs contained in a * Java keystore - * + * * @param keystorePath Path to keystore * @param keystorePassword Password to keystore */ public SDKBuilder sslFactoryFromKeyStore(String keystorePath, String keystorePassword) { - this.sslFactory = SSLFactory.builder().withDefaultTrustMaterial().withSystemTrustMaterial() - .withTrustMaterial(Path.of(keystorePath), - keystorePassword == null ? "".toCharArray() : keystorePassword.toCharArray()) - .build(); + try { + TrustProvider provider = TrustProvider.fromKeyStore( + Path.of(keystorePath), + keystorePassword == null ? "".toCharArray() : keystorePassword.toCharArray()); + this.sslSocketFactory = provider.getSslSocketFactory(); + this.trustManager = provider.getTrustManager(); + } catch (IOException | java.security.GeneralSecurityException e) { + throw new SDKException("failed to load keystore from " + keystorePath, e); + } return this; } @@ -223,8 +238,8 @@ private Interceptor getAuthInterceptor(RSAKey rsaKey) { OIDCProviderMetadata providerMetadata; try { providerMetadata = OIDCProviderMetadata.resolve(issuer, httpRequest -> { - if (sslFactory != null) { - httpRequest.setSSLSocketFactory(sslFactory.getSslSocketFactory()); + if (sslSocketFactory != null) { + httpRequest.setSSLSocketFactory(sslSocketFactory); } }); } catch (IOException | GeneralException e) { @@ -234,7 +249,7 @@ private Interceptor getAuthInterceptor(RSAKey rsaKey) { if (this.authzGrant == null) { this.authzGrant = new ClientCredentialsGrant(); } - var ts = new TokenSource(clientAuth, rsaKey, providerMetadata.getTokenEndpointURI(), this.authzGrant, sslFactory); + var ts = new TokenSource(clientAuth, rsaKey, providerMetadata.getTokenEndpointURI(), this.authzGrant, sslSocketFactory); return new AuthInterceptor(ts); } @@ -344,7 +359,7 @@ public SDK.KAS kas() { return new ServicesAndInternals( authInterceptor, - sslFactory == null ? null : sslFactory.getTrustManager().orElse(null), + trustManager, services, client, srtSignerToUse); @@ -378,6 +393,7 @@ private ProtocolClient getProtocolClient(String endpoint, OkHttpClient httpClien return new ProtocolClient(new ConnectOkHttpClient(httpClient), protocolClientConfig); } + @SuppressWarnings("deprecation") private OkHttpClient getHttpClient() { // using a single http client is apparently the best practice, subject to everyone wanting to // have the same protocols @@ -387,17 +403,24 @@ private OkHttpClient getHttpClient() { // expect HTTP/2, and Connect protocol can communicate with gRPC servers over HTTP/2 httpClient.protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)); } - if (sslFactory != null) { - var trustManager = sslFactory.getTrustManager(); - if (trustManager.isEmpty()) { - throw new SDKException("SSL factory must have a trust manager"); + if (sslSocketFactory != null) { + if (trustManager != null) { + httpClient.sslSocketFactory(sslSocketFactory, trustManager); + } else { + // Caller supplied an SSLSocketFactory without a matching trust manager (e.g. via + // sslFactory(SSLSocketFactory)). Falls back to OkHttp's reflection-based platform + // default trust manager — only the SSLSocketFactory governs the actual handshake. + httpClient.sslSocketFactory(sslSocketFactory); } - httpClient.sslSocketFactory(sslFactory.getSslSocketFactory(), trustManager.get()); } return httpClient.build(); } - SSLFactory getSslFactory() { - return this.sslFactory; + SSLSocketFactory getSslFactory() { + return this.sslSocketFactory; + } + + X509TrustManager getTrustManager() { + return this.trustManager; } } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java b/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java index 97e02a0c..587e2009 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java @@ -14,10 +14,10 @@ import com.nimbusds.oauth2.sdk.http.HTTPRequest; import com.nimbusds.oauth2.sdk.http.HTTPResponse; import com.nimbusds.oauth2.sdk.token.AccessToken; -import nl.altindag.ssl.SSLFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.net.ssl.SSLSocketFactory; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -34,22 +34,22 @@ class TokenSource { private final RSAKey rsaKey; private final URI tokenEndpointURI; private final AuthorizationGrant authzGrant; - private final SSLFactory sslFactory; + private final SSLSocketFactory sslSocketFactory; private static final Logger logger = LoggerFactory.getLogger(TokenSource.class); /** * Constructs a new TokenSource with the specified client authentication and RSA key. * - * @param clientAuth the client authentication to be used by the interceptor - * @param rsaKey the RSA key to be used by the interceptor - * @param sslFactory Optional SSLFactory for Requests + * @param clientAuth the client authentication to be used by the interceptor + * @param rsaKey the RSA key to be used by the interceptor + * @param sslSocketFactory Optional SSLSocketFactory for token endpoint requests */ - public TokenSource(ClientAuthentication clientAuth, RSAKey rsaKey, URI tokenEndpointURI, AuthorizationGrant authzGrant, SSLFactory sslFactory) { + public TokenSource(ClientAuthentication clientAuth, RSAKey rsaKey, URI tokenEndpointURI, AuthorizationGrant authzGrant, SSLSocketFactory sslSocketFactory) { this.clientAuth = clientAuth; this.rsaKey = rsaKey; this.tokenEndpointURI = tokenEndpointURI; - this.sslFactory = sslFactory; + this.sslSocketFactory = sslSocketFactory; this.authzGrant = authzGrant; } @@ -108,8 +108,8 @@ private synchronized AccessToken getToken() { TokenRequest tokenRequest = new TokenRequest(this.tokenEndpointURI, clientAuth, authzGrant, null); HTTPRequest httpRequest = tokenRequest.toHTTPRequest(); - if(sslFactory!=null){ - httpRequest.setSSLSocketFactory(sslFactory.getSslSocketFactory()); + if (sslSocketFactory != null) { + httpRequest.setSSLSocketFactory(sslSocketFactory); } DPoPProofFactory dpopFactory = new DefaultDPoPProofFactory(rsaKey, JWSAlgorithm.RS256); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TrustProvider.java b/sdk/src/main/java/io/opentdf/platform/sdk/TrustProvider.java new file mode 100644 index 00000000..9345ddc6 --- /dev/null +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TrustProvider.java @@ -0,0 +1,274 @@ +package io.opentdf.platform.sdk; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Builds {@link SSLSocketFactory} and {@link X509ExtendedTrustManager} instances for verifying + * X.509 certificate chains during TLS handshakes. + * + *

Implemented entirely on top of provider-agnostic JCA APIs ({@link CertificateFactory}, + * {@link KeyStore}, {@link TrustManagerFactory}, {@link SSLContext}). The actual cryptographic + * work is fulfilled by whichever {@link java.security.Provider} is registered with the JVM, + * including FIPS-mode providers. + */ +public final class TrustProvider { + + private final SSLSocketFactory sslSocketFactory; + private final X509ExtendedTrustManager trustManager; + private final SSLContext sslContext; + + private TrustProvider(SSLContext sslContext, X509ExtendedTrustManager trustManager) { + this.sslContext = sslContext; + this.trustManager = trustManager; + this.sslSocketFactory = sslContext.getSocketFactory(); + } + + public X509ExtendedTrustManager getTrustManager() { + return trustManager; + } + + public SSLContext getSslContext() { + return sslContext; + } + + public SSLSocketFactory getSslSocketFactory() { + return sslSocketFactory; + } + + public static Builder builder() { + return new Builder(); + } + + /** + * Builds a {@link TrustProvider} that trusts JVM default cacerts plus every {@code .pem} or + * {@code .crt} certificate found in the supplied directory. + */ + public static TrustProvider fromDirectory(String certsDirPath) throws IOException, GeneralSecurityException { + File certsDir = new File(certsDirPath); + File[] certFiles = certsDir.listFiles((dir, name) -> name.endsWith(".pem") || name.endsWith(".crt")); + if (certFiles == null) { + throw new IOException("not a directory or unreadable: " + certsDirPath); + } + Builder builder = builder().withDefaultTrustMaterial(); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + for (File certFile : certFiles) { + try (InputStream in = new FileInputStream(certFile)) { + Collection certs = cf.generateCertificates(in); + List x509s = new ArrayList<>(certs.size()); + for (Certificate c : certs) { + if (c instanceof X509Certificate) { + x509s.add((X509Certificate) c); + } + } + builder.withTrustMaterial(x509s.toArray(new X509Certificate[0])); + } + } + return builder.build(); + } + + /** + * Builds a {@link TrustProvider} that trusts JVM default cacerts plus the trusted-certificate + * entries in the supplied keystore. + */ + public static TrustProvider fromKeyStore(Path keystorePath, char[] password) throws IOException, GeneralSecurityException { + if (!Files.isRegularFile(keystorePath)) { + throw new IOException("keystore not found: " + keystorePath); + } + KeyStore ks; + try (InputStream in = Files.newInputStream(keystorePath)) { + ks = loadKeyStore(in, password); + } + return builder().withDefaultTrustMaterial().withTrustMaterial(ks).build(); + } + + /** + * Builds a {@link TrustProvider} that accepts every server certificate. Intended only for + * tests and {@code --insecure} CLI flows. + */ + public static TrustProvider insecure() { + try { + SSLContext ctx = SSLContext.getInstance("TLS"); + X509ExtendedTrustManager trustAll = new InsecureTrustManager(); + ctx.init(new KeyManager[0], new TrustManager[]{trustAll}, new SecureRandom()); + return new TrustProvider(ctx, trustAll); + } catch (GeneralSecurityException e) { + throw new IllegalStateException("failed to build insecure TrustProvider", e); + } + } + + private static KeyStore loadKeyStore(InputStream in, char[] password) + throws IOException, GeneralSecurityException { + // Try JKS first since it remains the JVM default; fall back to PKCS12 which is portable + // across both bcprov-jdk18on and bc-fips. We do not pin a provider; whichever provider is + // registered fulfills the request. + byte[] bytes = readAll(in); + KeyStoreException last = null; + for (String type : new String[]{KeyStore.getDefaultType(), "JKS", "PKCS12"}) { + try { + KeyStore ks = KeyStore.getInstance(type); + ks.load(new java.io.ByteArrayInputStream(bytes), password); + return ks; + } catch (KeyStoreException e) { + last = e; + } catch (IOException | NoSuchAlgorithmException | CertificateException e) { + // wrong format or wrong password — try next type + last = new KeyStoreException(e); + } + } + throw last != null ? last : new KeyStoreException("could not load keystore"); + } + + private static byte[] readAll(InputStream in) throws IOException { + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + byte[] buf = new byte[8192]; + int n; + while ((n = in.read(buf)) >= 0) { + out.write(buf, 0, n); + } + return out.toByteArray(); + } + + private static X509ExtendedTrustManager extractTrustManager(KeyStore trustStore) + throws GeneralSecurityException { + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustStore); + for (TrustManager tm : tmf.getTrustManagers()) { + if (tm instanceof X509ExtendedTrustManager) { + return (X509ExtendedTrustManager) tm; + } + } + throw new NoSuchAlgorithmException("no X509ExtendedTrustManager available from provider"); + } + + public static final class Builder { + private boolean includeDefault; + private final List keyStores = new ArrayList<>(); + private final List certificates = new ArrayList<>(); + + private Builder() { + } + + /** + * Include the JVM default cacerts (i.e. those returned by initialising a + * {@link TrustManagerFactory} with a {@code null} keystore). + */ + public Builder withDefaultTrustMaterial() { + this.includeDefault = true; + return this; + } + + public Builder withTrustMaterial(X509Certificate... certs) { + if (certs != null) { + Collections.addAll(this.certificates, certs); + } + return this; + } + + public Builder withTrustMaterial(Collection certs) { + if (certs != null) { + this.certificates.addAll(certs); + } + return this; + } + + public Builder withTrustMaterial(KeyStore keyStore) { + if (keyStore != null) { + this.keyStores.add(keyStore); + } + return this; + } + + public Builder withTrustMaterial(Path keystorePath, char[] password) throws IOException, GeneralSecurityException { + try (InputStream in = Files.newInputStream(keystorePath)) { + this.keyStores.add(loadKeyStore(in, password)); + } + return this; + } + + public TrustProvider build() { + try { + List trustManagers = new ArrayList<>(); + + if (includeDefault) { + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + for (TrustManager tm : tmf.getTrustManagers()) { + if (tm instanceof X509ExtendedTrustManager) { + trustManagers.add((X509ExtendedTrustManager) tm); + } + } + } + + for (KeyStore ks : keyStores) { + trustManagers.add(extractTrustManager(ks)); + } + + if (!certificates.isEmpty()) { + KeyStore custom = newEmptyKeyStore(); + int i = 0; + for (X509Certificate cert : certificates) { + custom.setCertificateEntry("trust-anchor-" + (i++), cert); + } + trustManagers.add(extractTrustManager(custom)); + } + + if (trustManagers.isEmpty()) { + throw new IllegalStateException("TrustProvider requires at least one source of trust material"); + } + + X509ExtendedTrustManager combined = trustManagers.size() == 1 + ? trustManagers.get(0) + : new CompositeX509ExtendedTrustManager(trustManagers); + + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(new KeyManager[0], new TrustManager[]{combined}, new SecureRandom()); + return new TrustProvider(ctx, combined); + } catch (GeneralSecurityException | IOException e) { + throw new IllegalStateException("failed to build TrustProvider", e); + } + } + + private static KeyStore newEmptyKeyStore() throws GeneralSecurityException, IOException { + // PKCS12 is supported by both stock JDK and BC (FIPS and non-FIPS). + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(null, null); + return ks; + } + } + + private static final class InsecureTrustManager extends X509ExtendedTrustManager { + private static final X509Certificate[] EMPTY = new X509Certificate[0]; + + @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { } + @Override public void checkClientTrusted(X509Certificate[] chain, String authType, java.net.Socket socket) { } + @Override public void checkClientTrusted(X509Certificate[] chain, String authType, javax.net.ssl.SSLEngine engine) { } + @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { } + @Override public void checkServerTrusted(X509Certificate[] chain, String authType, java.net.Socket socket) { } + @Override public void checkServerTrusted(X509Certificate[] chain, String authType, javax.net.ssl.SSLEngine engine) { } + @Override public X509Certificate[] getAcceptedIssuers() { return EMPTY; } + } +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java index cebc9928..9b365543 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java @@ -20,9 +20,6 @@ import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationRequest; import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationResponse; import io.opentdf.platform.wellknownconfiguration.WellKnownServiceGrpc; -import nl.altindag.ssl.SSLFactory; -import nl.altindag.ssl.pem.util.PemUtils; -import nl.altindag.ssl.util.KeyStoreUtils; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.tls.HandshakeCertificates; @@ -30,6 +27,8 @@ import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; @@ -40,6 +39,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyStore; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Base64; @@ -70,24 +70,31 @@ void testDirCertsSSLContext() throws Exception { IOUtils.write(EXAMPLE_COM_PEM, fos); fos.close(); SDKBuilder builder = SDKBuilder.newBuilder().sslFactoryFromDirectory(certDirPath.toAbsolutePath().toString()); - SSLFactory sslFactory = builder.getSslFactory(); - assertNotNull(sslFactory); - X509Certificate[] acceptedIssuers = sslFactory.getTrustManager().get().getAcceptedIssuers(); + SSLSocketFactory sslSocketFactory = builder.getSslFactory(); + assertNotNull(sslSocketFactory); + X509TrustManager trustManager = builder.getTrustManager(); + assertNotNull(trustManager); + X509Certificate[] acceptedIssuers = trustManager.getAcceptedIssuers(); assertEquals(1, Arrays.stream(acceptedIssuers).filter(x -> x.getIssuerX500Principal().getName() .equals("CN=example.com")).count()); } @Test void testKeystoreSSLContext() throws Exception { - KeyStore keystore = KeyStoreUtils.createKeyStore(); - keystore.setCertificateEntry("example.com", PemUtils.parseCertificate(EXAMPLE_COM_PEM).get(0)); + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + keystore.load(null, null); + X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509") + .generateCertificate(new ByteArrayInputStream(EXAMPLE_COM_PEM.getBytes(StandardCharsets.UTF_8))); + keystore.setCertificateEntry("example.com", cert); Path keyStorePath = Files.createTempFile("ca", "jks"); keystore.store(new FileOutputStream(keyStorePath.toAbsolutePath().toString()), "foo".toCharArray()); SDKBuilder builder = SDKBuilder.newBuilder().sslFactoryFromKeyStore(keyStorePath.toAbsolutePath().toString(), "foo"); - SSLFactory sslFactory = builder.getSslFactory(); - assertNotNull(sslFactory); - X509Certificate[] acceptedIssuers = sslFactory.getTrustManager().get().getAcceptedIssuers(); + SSLSocketFactory sslSocketFactory = builder.getSslFactory(); + assertNotNull(sslSocketFactory); + X509TrustManager trustManager = builder.getTrustManager(); + assertNotNull(trustManager); + X509Certificate[] acceptedIssuers = trustManager.getAcceptedIssuers(); assertEquals(1, Arrays.stream(acceptedIssuers).filter(x -> x.getIssuerX500Principal().getName() .equals("CN=example.com")).count()); @@ -305,8 +312,11 @@ public ServerCall.Listener interceptCall(ServerCall Date: Mon, 11 May 2026 17:13:40 -0400 Subject: [PATCH 10/32] sonarcloud --- sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java | 2 +- sdk/src/main/java/io/opentdf/platform/sdk/TDF.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) 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 d2254184..18191de3 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java @@ -97,7 +97,7 @@ public static byte[] computeECDHKey(ECPublicKey publicKey, ECPrivateKey privateK } } - private static String HMAC_SHA_256 = "HmacSHA256"; + private static final String HMAC_SHA_256 = "HmacSHA256"; /** * Returns a HKDF key derived from the provided salt and secret * that is 32 bytes (256 bits) long. 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 96b29f13..c0e28a6f 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -23,7 +23,6 @@ import java.security.*; import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; import java.text.ParseException; import java.util.*; From 6fc81f14b15515713c69912c8efdf99c598dda9f Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 11 May 2026 19:29:03 -0400 Subject: [PATCH 11/32] fix provider stuff --- .../java/io/opentdf/platform/sdk/PemUtils.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/PemUtils.java b/sdk/src/test/java/io/opentdf/platform/sdk/PemUtils.java index e782a24a..80bee5ab 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/PemUtils.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/PemUtils.java @@ -4,7 +4,6 @@ import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.jce.ECNamedCurveTable; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.math.ec.ECPoint; @@ -21,7 +20,6 @@ import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; -import java.security.Security; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; @@ -35,11 +33,6 @@ */ final class PemUtils { - private static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider(); - - static { - Security.addProvider(PROVIDER); - } private PemUtils() { } @@ -50,7 +43,7 @@ static ECPublicKey publicKeyFromPem(String pemEncoding) { SubjectPublicKeyInfo publicKeyInfo = (SubjectPublicKeyInfo) parser.readObject(); parser.close(); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(PROVIDER); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); return (ECPublicKey) converter.getPublicKey(publicKeyInfo); } catch (IOException e) { throw new RuntimeException(e); @@ -63,7 +56,7 @@ static ECPrivateKey privateKeyFromPem(String pemEncoding) { PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) parser.readObject(); parser.close(); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(PROVIDER); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); return (ECPrivateKey) converter.getPrivateKey(privateKeyInfo); } catch (IOException e) { throw new RuntimeException(e); @@ -76,7 +69,7 @@ static String getPEMPublicKeyFromX509Cert(String pemInX509Format) { X509CertificateHolder x509CertificateHolder = (X509CertificateHolder) parser.readObject(); parser.close(); SubjectPublicKeyInfo publicKeyInfo = x509CertificateHolder.getSubjectPublicKeyInfo(); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(PROVIDER); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); ECPublicKey publicKey; try { publicKey = (ECPublicKey) converter.getPublicKey(publicKeyInfo); @@ -97,7 +90,7 @@ static String getPEMPublicKeyFromX509Cert(String pemInX509Format) { static byte[] compressECPublickey(String pemECPubKey) { try { - KeyFactory ecKeyFac = KeyFactory.getInstance("EC", PROVIDER); + KeyFactory ecKeyFac = KeyFactory.getInstance("EC"); PemReader pemReader = new PemReader(new StringReader(pemECPubKey)); PemObject pemObject = pemReader.readPemObject(); PublicKey pubKey = ecKeyFac.generatePublic(new X509EncodedKeySpec(pemObject.getContent())); @@ -112,7 +105,7 @@ static String publicKeyFromECPoint(byte[] ecPoint, String curveName) { ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(curveName); ECPoint point = ecSpec.getCurve().decodePoint(ecPoint); ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(point, ecSpec); - KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", PROVIDER); + KeyFactory keyFactory = KeyFactory.getInstance("ECDSA"); PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); StringWriter writer = new StringWriter(); From 827a5c352f4959c423d041d7c39922a7e31e1093 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 11 May 2026 21:30:32 -0400 Subject: [PATCH 12/32] make sure we get the right provider in tests --- .../sdk/CryptoProviderSetupExtension.java | 26 ++++++++++++ .../opentdf/platform/sdk/ECKeyPairTest.java | 34 +++++++-------- .../sdk/{PemUtils.java => PemTestUtils.java} | 41 ++++++++++++------- .../java/io/opentdf/platform/sdk/TDFTest.java | 5 +-- .../org.junit.jupiter.api.extension.Extension | 1 + .../test/resources/junit-platform.properties | 1 + 6 files changed, 73 insertions(+), 35 deletions(-) create mode 100644 sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java rename sdk/src/test/java/io/opentdf/platform/sdk/{PemUtils.java => PemTestUtils.java} (79%) create mode 100644 sdk/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension create mode 100644 sdk/src/test/resources/junit-platform.properties 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..ce219dae --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java @@ -0,0 +1,26 @@ +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.Provider; +import java.security.Security; +import java.util.Arrays; + +public class CryptoProviderSetupExtension implements BeforeAllCallback { + private BouncyCastleProvider securityProvider; + + @Override + public synchronized void beforeAll(ExtensionContext extensionContext) { + if (this.securityProvider == null) { + var existingProviders = Security.getProviders(); + Arrays.asList(existingProviders).stream().map(Provider::getName).forEach(Security::removeProvider); + if (Security.getProviders().length != 0) { + throw new IllegalStateException("unable to remove all providers"); + } + + 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 aa79a509..185965c1 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/ECKeyPairTest.java @@ -54,17 +54,17 @@ void ecPublicKeyInPemformat() { String keypairAPubicKey = keyPairA.publicKeyInPEMFormat(); String keypairAPrivateKey = keyPairA.privateKeyInPEMFormat(); - ECPublicKey publicKeyA = PemUtils.publicKeyFromPem(keypairAPubicKey); - ECPrivateKey privateKeyA = PemUtils.privateKeyFromPem(keypairAPrivateKey); + ECPublicKey publicKeyA = PemTestUtils.publicKeyFromPem(keypairAPubicKey); + ECPrivateKey privateKeyA = PemTestUtils.privateKeyFromPem(keypairAPrivateKey); System.out.println(keypairAPubicKey); System.out.println(keypairAPrivateKey); byte[] compressedKey1 = keyPairA.compressECPublickey(); - byte[] compressedKey2 = PemUtils.compressECPublickey(keyPairA.publicKeyInPEMFormat()); + byte[] compressedKey2 = PemTestUtils.compressECPublickey(keyPairA.publicKeyInPEMFormat()); assertArrayEquals(compressedKey1, compressedKey2); - String publicKey = PemUtils.publicKeyFromECPoint(compressedKey1, SECP256R1.getCurveName()); + String publicKey = PemTestUtils.publicKeyFromECPoint(compressedKey1, SECP256R1.getCurveName()); assertEquals(keyPairA.publicKeyInPEMFormat(), publicKey); ECKeyPair keyPairB = new ECKeyPair(); @@ -74,8 +74,8 @@ void ecPublicKeyInPemformat() { System.out.println(keypairBPubicKey); System.out.println(keypairBPrivateKey); - ECPublicKey publicKeyB = PemUtils.publicKeyFromPem(keypairBPubicKey); - ECPrivateKey privateKeyB = PemUtils.privateKeyFromPem(keypairBPrivateKey); + ECPublicKey publicKeyB = PemTestUtils.publicKeyFromPem(keypairBPubicKey); + ECPrivateKey privateKeyB = PemTestUtils.privateKeyFromPem(keypairBPrivateKey); byte[] symmetricKey1 = ECKeyPair.computeECDHKey(publicKeyA, privateKeyB); byte[] symmetricKey2 = ECKeyPair.computeECDHKey(publicKeyB, privateKeyA); @@ -94,14 +94,14 @@ void extractPemPubKeyFromX509() throws CertificateException, IOException, NoSuch "zj0EAwIDSAAwRQIhAItk5SmcWSg06tnOCEqTa6UsChaycX/cmAT8PTDRnaRcAiAl\n" + "Vr2EvlA2x5mWFE/+nDdxxzljYjLZuSDQMEI/J6u0/Q==\n" + "-----END CERTIFICATE-----"; - String pubKey = PemUtils.getPEMPublicKeyFromX509Cert(x509ECPubKey); + String pubKey = PemTestUtils.getPEMPublicKeyFromX509Cert(x509ECPubKey); System.out.println(pubKey); - ECPublicKey publicKey = PemUtils.publicKeyFromPem(pubKey); - byte[] compressedKey = PemUtils.compressECPublickey(pubKey); + ECPublicKey publicKey = PemTestUtils.publicKeyFromPem(pubKey); + byte[] compressedKey = PemTestUtils.compressECPublickey(pubKey); System.out.println(Arrays.toString(compressedKey)); - compressedKey = PemUtils.compressECPublickey(pubKey); + compressedKey = PemTestUtils.compressECPublickey(pubKey); System.out.println(Arrays.toString(compressedKey)); System.out.println(compressedKey.length); @@ -112,7 +112,7 @@ void extractPemPubKeyFromX509() throws CertificateException, IOException, NoSuch System.out.println(keypairPubicKey); System.out.println(keypairPrivateKey); - byte[] symmetricKey = ECKeyPair.computeECDHKey(publicKey, PemUtils.privateKeyFromPem(keypairPrivateKey)); + byte[] symmetricKey = ECKeyPair.computeECDHKey(publicKey, PemTestUtils.privateKeyFromPem(keypairPrivateKey)); System.out.println(Arrays.toString(symmetricKey)); byte[] key = ECKeyPair.calculateHKDF(ECKeys.salt.getBytes(StandardCharsets.UTF_8), symmetricKey); @@ -138,11 +138,11 @@ void testECDH() { String expectedKey = "3KGgsptHbTsbxJtql6sHUcx255KcUhxdeJWKjmPMlcc="; // SDK side - ECPublicKey kasPubKey = PemUtils.publicKeyFromPem(ECKeys.kasPublicKey); - ECPrivateKey kasPriKey = PemUtils.privateKeyFromPem(ECKeys.kasPrivateKey); + ECPublicKey kasPubKey = PemTestUtils.publicKeyFromPem(ECKeys.kasPublicKey); + ECPrivateKey kasPriKey = PemTestUtils.privateKeyFromPem(ECKeys.kasPrivateKey); - ECPublicKey sdkPubKey = PemUtils.publicKeyFromPem(ECKeys.sdkPublicKey); - ECPrivateKey sdkPriKey = PemUtils.privateKeyFromPem(ECKeys.sdkPrivateKey); + ECPublicKey sdkPubKey = PemTestUtils.publicKeyFromPem(ECKeys.sdkPublicKey); + ECPrivateKey sdkPriKey = PemTestUtils.privateKeyFromPem(ECKeys.sdkPrivateKey); byte[] symmetricKey = ECKeyPair.computeECDHKey(kasPubKey, sdkPriKey); byte[] key = ECKeyPair.calculateHKDF(ECKeys.salt.getBytes(StandardCharsets.UTF_8), symmetricKey); @@ -155,11 +155,11 @@ void testECDH() { encodedKey = Base64.getEncoder().encodeToString(key); assertEquals(encodedKey, expectedKey); - byte[] ecPoint = PemUtils.compressECPublickey(ECKeys.sdkPublicKey); + byte[] ecPoint = PemTestUtils.compressECPublickey(ECKeys.sdkPublicKey); String encodeECPoint = Base64.getEncoder().encodeToString(ecPoint); assertEquals(encodeECPoint, "Al3vx59pBnP8tRxuUFw18aK9ym6rFrxZRhpVQytUQ+Kg"); - String publicKey = PemUtils.publicKeyFromECPoint(ecPoint, + String publicKey = PemTestUtils.publicKeyFromECPoint(ecPoint, SECP256R1.name()); assertArrayEquals(ECKeys.sdkPublicKey.toCharArray(), publicKey.toCharArray()); } diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/PemUtils.java b/sdk/src/test/java/io/opentdf/platform/sdk/PemTestUtils.java similarity index 79% rename from sdk/src/test/java/io/opentdf/platform/sdk/PemUtils.java rename to sdk/src/test/java/io/opentdf/platform/sdk/PemTestUtils.java index 80bee5ab..4f615b90 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/PemUtils.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/PemTestUtils.java @@ -20,10 +20,12 @@ import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; +import java.security.Security; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; +import java.util.Objects; /** * BouncyCastle-flavored PEM/X.509 helpers used only by tests. Kept here so the SDK @@ -31,10 +33,17 @@ * use BouncyCastle internally but expose standard JCA key interfaces, so callers can * pass results directly to JCA-based APIs. */ -final class PemUtils { +final class PemTestUtils { + private static final JcaPEMKeyConverter converter; + static { + var provider = Objects.requireNonNull( + Security.getProvider("BC"), + "BC provider must be registered"); + converter = new JcaPEMKeyConverter().setProvider(provider); + } - private PemUtils() { + private PemTestUtils() { } static ECPublicKey publicKeyFromPem(String pemEncoding) { @@ -43,7 +52,6 @@ static ECPublicKey publicKeyFromPem(String pemEncoding) { SubjectPublicKeyInfo publicKeyInfo = (SubjectPublicKeyInfo) parser.readObject(); parser.close(); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); return (ECPublicKey) converter.getPublicKey(publicKeyInfo); } catch (IOException e) { throw new RuntimeException(e); @@ -56,7 +64,6 @@ static ECPrivateKey privateKeyFromPem(String pemEncoding) { PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) parser.readObject(); parser.close(); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); return (ECPrivateKey) converter.getPrivateKey(privateKeyInfo); } catch (IOException e) { throw new RuntimeException(e); @@ -65,17 +72,7 @@ static ECPrivateKey privateKeyFromPem(String pemEncoding) { 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(); - ECPublicKey publicKey; - try { - publicKey = (ECPublicKey) converter.getPublicKey(publicKeyInfo); - } catch (PEMException e) { - throw new RuntimeException(e); - } + ECPublicKey publicKey = getEcPublicKey(pemInX509Format); StringWriter writer = new StringWriter(); PemWriter pemWriter = new PemWriter(writer); @@ -88,6 +85,20 @@ static String getPEMPublicKeyFromX509Cert(String pemInX509Format) { } } + private static ECPublicKey getEcPublicKey(String pemInX509Format) throws IOException { + PEMParser parser = new PEMParser(new StringReader(pemInX509Format)); + X509CertificateHolder x509CertificateHolder = (X509CertificateHolder) parser.readObject(); + parser.close(); + SubjectPublicKeyInfo publicKeyInfo = x509CertificateHolder.getSubjectPublicKeyInfo(); + ECPublicKey publicKey; + try { + publicKey = (ECPublicKey) converter.getPublicKey(publicKeyInfo); + } catch (PEMException e) { + throw new RuntimeException(e); + } + return publicKey; + } + static byte[] compressECPublickey(String pemECPubKey) { try { KeyFactory ecKeyFac = KeyFactory.getInstance("EC"); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java index 3892ced4..085f068f 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -10,7 +10,6 @@ import io.opentdf.platform.policy.kasregistry.KeyAccessServerRegistryServiceClient; import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersRequest; import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersResponse; -import io.opentdf.platform.sdk.Config.KASInfo; import io.opentdf.platform.sdk.TDF.Reader; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; import org.junit.jupiter.api.BeforeAll; @@ -89,9 +88,9 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessio if (sessionKeyType.isEc()) { var kasPrivateKey = CryptoUtils .getPrivateKeyPEM(keypairs.get(index).getPrivate()); - var privateKey = PemUtils.privateKeyFromPem(kasPrivateKey); + var privateKey = PemTestUtils.privateKeyFromPem(kasPrivateKey); var clientEphemeralPublicKey = keyAccess.ephemeralPublicKey; - var publicKey = PemUtils.publicKeyFromPem(clientEphemeralPublicKey); + var publicKey = PemTestUtils.publicKeyFromPem(clientEphemeralPublicKey); byte[] symKey = ECKeyPair.computeECDHKey(publicKey, privateKey); var sessionKey = ECKeyPair.calculateHKDF(GLOBAL_KEY_SALT, symKey); 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..b4c1aab4 --- /dev/null +++ b/sdk/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +io.opentdf.platform.sdk.CryptoProviderSetupExtension \ No newline at end of file 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 From d8a14d55953c22f2fda3ab5ada17fac1c9cf7305 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Tue, 12 May 2026 23:15:32 -0400 Subject: [PATCH 13/32] try this way --- pom.xml | 23 ++ sdk/pom.xml | 75 +++++-- .../java/io/opentdf/platform/sdk/AesGcm.java | 18 ++ .../java/io/opentdf/platform/sdk/TDF.java | 5 +- sdk/src/test/java.security.fips.test | 19 ++ sdk/src/test/java.security.test | 8 + .../sdk/CryptoProviderSetupExtension.java | 26 --- .../io/opentdf/platform/sdk/PemTestUtils.java | 200 ++++++++++-------- .../opentdf/platform/sdk/SDKBuilderTest.java | 10 +- .../java/io/opentdf/platform/sdk/TDFTest.java | 12 +- .../org.junit.jupiter.api.extension.Extension | 1 - .../test/resources/junit-platform.properties | 1 - 12 files changed, 254 insertions(+), 144 deletions(-) create mode 100644 sdk/src/test/java.security.fips.test create mode 100644 sdk/src/test/java.security.test delete mode 100644 sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java delete mode 100644 sdk/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension delete mode 100644 sdk/src/test/resources/junit-platform.properties diff --git a/pom.xml b/pom.xml index 5ddcd589..6ce3488c 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,9 @@ 1.75.0 4.29.2 1.82 + 2.1.2 + 2.1.11 + 2.1.23 1.18.3 0.8.13 @@ -123,6 +126,26 @@ bcprov-jdk18on ${bouncycastle.version} + + org.bouncycastle + bctls-jdk18on + ${bouncycastle.version} + + + org.bouncycastle + bc-fips + ${bc-fips.version} + + + org.bouncycastle + bcpkix-fips + ${bcpkix-fips.version} + + + org.bouncycastle + bctls-fips + ${bctls-fips.version} + @@ -148,17 +149,7 @@ 6.0.53 provided - - - org.bouncycastle - bcpkix-jdk18on - test - - - org.bouncycastle - bcprov-jdk18on - test - + org.junit.jupiter @@ -473,11 +464,69 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + -Djava.security.properties=${test.java.security.file} + + - + + non-fips + + true + + + ${project.basedir}/src/test/java.security.test + + + + org.bouncycastle + bcprov-jdk18on + runtime + + + org.bouncycastle + bcpkix-jdk18on + runtime + + + org.bouncycastle + bctls-jdk18on + runtime + + + + + fips + + false + + + ${project.basedir}/src/test/java.security.fips.test + + + + org.bouncycastle + bc-fips + runtime + + + org.bouncycastle + bcpkix-fips + runtime + + + org.bouncycastle + bctls-fips + runtime + + + + fuzz diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/AesGcm.java b/sdk/src/main/java/io/opentdf/platform/sdk/AesGcm.java index 71445f69..aa91d62b 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/AesGcm.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/AesGcm.java @@ -3,6 +3,7 @@ import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; @@ -20,10 +21,27 @@ public class AesGcm { public static final int GCM_NONCE_LENGTH = 12; // in bytes public static final int GCM_TAG_LENGTH = 16; // in bytes + public static final int GCM_KEY_SIZE_BITS = 256; + private static final String KEY_ALGORITHM = "AES"; private static final String CIPHER_TRANSFORM = "AES/GCM/NoPadding"; private final SecretKey key; + /** + *

Generate a fresh 256-bit AES key using the JCA {@link KeyGenerator}.

+ * + * @return the encoded key bytes + */ + public static byte[] generateKey() { + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); + keyGenerator.init(GCM_KEY_SIZE_BITS); + return keyGenerator.generateKey().getEncoded(); + } catch (NoSuchAlgorithmException e) { + throw new SDKException("error generating AES key", e); + } + } + /** *

Return symmetric key

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 c0e28a6f..b30460eb 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -98,8 +98,6 @@ private static byte[] tdfECKeySaltCompute() { private static final String kTDFAsZip = "zip"; private static final String kTDFZipReference = "reference"; - private static final SecureRandom sRandom = new SecureRandom(); - private static final Gson gson = new GsonBuilder().create(); static class EncryptedMetadata { @@ -162,8 +160,7 @@ private void prepareManifest(Config.TDFConfig tdfConfig, Map length) { + System.arraycopy(bytes, bytes.length - length, result, 0, length); + } else { + System.arraycopy(bytes, 0, result, length - bytes.length, bytes.length); + } + return result; + } + + private static byte[] decodePem(String pem) { + String body = pem.replaceAll("-----(BEGIN|END) [A-Z ]+-----", "").replaceAll("\\s", ""); + return Base64.getDecoder().decode(body); + } + + private static String encodePem(String type, byte[] der) { + String b64 = Base64.getEncoder().encodeToString(der); + StringBuilder sb = new StringBuilder(); + sb.append("-----BEGIN ").append(type).append("-----\n"); + for (int i = 0; i < b64.length(); i += 64) { + sb.append(b64, i, Math.min(i + 64, b64.length())).append('\n'); } + sb.append("-----END ").append(type).append("-----\n"); + return sb.toString(); } } diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java index 9b365543..ecbd4d03 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java @@ -168,11 +168,13 @@ void sdkServicesSetup(boolean useSSLPlatform, boolean useSSLIDP) throws Exceptio HeldCertificate rootCertificate = new HeldCertificate.Builder() .certificateAuthority(0) + .rsa2048() .build(); String localhost = InetAddress.getByName("localhost").getCanonicalHostName(); HeldCertificate serverCertificate = new HeldCertificate.Builder() .addSubjectAlternativeName(localhost) - .commonName("CN=localhost") + .rsa2048() + .commonName("localhost") .signedBy(rootCertificate) .build(); @@ -318,7 +320,6 @@ public ServerCall.Listener interceptCall(ServerCall ServerCall.Listener interceptCall(ServerCall Date: Wed, 13 May 2026 11:02:02 -0400 Subject: [PATCH 14/32] get ssl working --- pom.xml | 5 +++++ sdk/pom.xml | 5 +++++ .../platform/sdk/CryptoProviderSetupExtension.java | 10 +++------- .../java/io/opentdf/platform/sdk/SDKBuilderTest.java | 4 +++- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 7ccd85d1..4c1fc5cb 100644 --- a/pom.xml +++ b/pom.xml @@ -157,6 +157,11 @@ bcprov-jdk18on ${bouncycastle.version}
+ + org.bouncycastle + bctls-jdk18on + ${bouncycastle.version} + org.junit.jupiter diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java b/sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java index ce219dae..79b9c489 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/CryptoProviderSetupExtension.java @@ -1,6 +1,7 @@ package io.opentdf.platform.sdk; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; @@ -14,13 +15,8 @@ public class CryptoProviderSetupExtension implements BeforeAllCallback { @Override public synchronized void beforeAll(ExtensionContext extensionContext) { if (this.securityProvider == null) { - var existingProviders = Security.getProviders(); - Arrays.asList(existingProviders).stream().map(Provider::getName).forEach(Security::removeProvider); - if (Security.getProviders().length != 0) { - throw new IllegalStateException("unable to remove all providers"); - } - - Security.addProvider(this.securityProvider = new BouncyCastleProvider()); + Security.insertProviderAt(this.securityProvider = new BouncyCastleProvider(), 1); + Security.insertProviderAt(new BouncyCastleJsseProvider(), 2); } } } diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java index cebc9928..49f5affc 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java @@ -161,11 +161,13 @@ void sdkServicesSetup(boolean useSSLPlatform, boolean useSSLIDP) throws Exceptio HeldCertificate rootCertificate = new HeldCertificate.Builder() .certificateAuthority(0) + .rsa2048() .build(); String localhost = InetAddress.getByName("localhost").getCanonicalHostName(); HeldCertificate serverCertificate = new HeldCertificate.Builder() .addSubjectAlternativeName(localhost) - .commonName("CN=localhost") + .rsa2048() + .commonName("localhost") .signedBy(rootCertificate) .build(); From c5f067fb2301f6748e66ae9b1b988e3e19380204 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 13 May 2026 11:06:45 -0400 Subject: [PATCH 15/32] do not need this --- pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pom.xml b/pom.xml index c44d7cd5..6ce3488c 100644 --- a/pom.xml +++ b/pom.xml @@ -146,11 +146,6 @@ bctls-fips ${bctls-fips.version} - - org.bouncycastle - bctls-jdk18on - ${bouncycastle.version} - + + + + @@ -468,7 +471,7 @@ org.apache.maven.plugins maven-surefire-plugin - -Djava.security.properties=${test.java.security.file} + @{argLine} ${java.security.properties.test} @@ -477,11 +480,8 @@ non-fips - false + true - - ${project.basedir}/src/test/java.security.test - org.bouncycastle @@ -503,10 +503,10 @@ fips - true + false - ${project.basedir}/src/test/java.security.fips.test + -Djava.security.properties=${project.basedir}/src/test/java.security.fips.test diff --git a/sdk/src/test/java.security.test b/sdk/src/test/java.security.test index 191617da..4120f3ff 100644 --- a/sdk/src/test/java.security.test +++ b/sdk/src/test/java.security.test @@ -1 +1,4 @@ -# TODO: get rid of the BCt di providers, we should not need them \ No newline at end of file +# the default for these is usually SunX509 but BC doesn't +# support them. tell it to use PKIX instead which is supported by BC +ssl.KeyManagerFactory.algorithm=PKIX +ssl.TrustManagerFactory.algorithm=PKIX \ No newline at end of file From 4046710a92754d29377f17cd3e9b10e1268b5601 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 13 May 2026 18:09:26 -0400 Subject: [PATCH 28/32] just get some coverage --- .github/workflows/checks.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index f33cb3ce..4ea9020d 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -92,8 +92,7 @@ jobs: BUF_INPUT_HTTPS_USERNAME: opentdf-bot BUF_INPUT_HTTPS_PASSWORD: ${{ secrets.PERSONAL_ACCESS_TOKEN_OPENTDF }} run: | - mvn --batch-mode clean verify -P 'fips,!non-fips' && \ - mvn --batch-mode clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=opentdf_java-sdk -P coverage + mvn --batch-mode clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=opentdf_java-sdk -P 'coverage,!non-fips,fips' platform-integration: From 6efa0b40d5c9e7d16e61d4f06bdcc456d07284b6 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 13 May 2026 23:44:31 -0400 Subject: [PATCH 29/32] cleanup --- sdk/src/test/java.security.test | 4 ---- .../java/io/opentdf/platform/sdk/SDKBuilderTest.java | 9 ++------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/sdk/src/test/java.security.test b/sdk/src/test/java.security.test index 4120f3ff..e69de29b 100644 --- a/sdk/src/test/java.security.test +++ b/sdk/src/test/java.security.test @@ -1,4 +0,0 @@ -# the default for these is usually SunX509 but BC doesn't -# support them. tell it to use PKIX instead which is supported by BC -ssl.KeyManagerFactory.algorithm=PKIX -ssl.TrustManagerFactory.algorithm=PKIX \ No newline at end of file diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java index ecbd4d03..af443c66 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java @@ -168,13 +168,11 @@ void sdkServicesSetup(boolean useSSLPlatform, boolean useSSLIDP) throws Exceptio HeldCertificate rootCertificate = new HeldCertificate.Builder() .certificateAuthority(0) - .rsa2048() .build(); String localhost = InetAddress.getByName("localhost").getCanonicalHostName(); HeldCertificate serverCertificate = new HeldCertificate.Builder() .addSubjectAlternativeName(localhost) - .rsa2048() - .commonName("localhost") + .commonName("CN=localhost") .signedBy(rootCertificate) .build(); @@ -393,10 +391,7 @@ public ServerCall.Listener interceptCall(ServerCall Date: Wed, 13 May 2026 23:46:39 -0400 Subject: [PATCH 30/32] run the right stuff --- .github/workflows/checks.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 4ea9020d..78aab5ca 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -92,7 +92,8 @@ jobs: BUF_INPUT_HTTPS_USERNAME: opentdf-bot BUF_INPUT_HTTPS_PASSWORD: ${{ secrets.PERSONAL_ACCESS_TOKEN_OPENTDF }} run: | - mvn --batch-mode clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=opentdf_java-sdk -P 'coverage,!non-fips,fips' + mvn --batch mode clean verify test -P 'fips,!non-fips' && \ + mvn --batch-mode clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=opentdf_java-sdk -P 'coverage,non-fips,!fips' platform-integration: From 697029f356cbd1c16232a0e12d9f404c0be3fd63 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 13 May 2026 23:48:07 -0400 Subject: [PATCH 31/32] oops --- .github/workflows/checks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 78aab5ca..87a1a4c2 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -92,7 +92,7 @@ jobs: BUF_INPUT_HTTPS_USERNAME: opentdf-bot BUF_INPUT_HTTPS_PASSWORD: ${{ secrets.PERSONAL_ACCESS_TOKEN_OPENTDF }} run: | - mvn --batch mode clean verify test -P 'fips,!non-fips' && \ + mvn --batch-mode clean verify test -P 'fips,!non-fips' && \ mvn --batch-mode clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=opentdf_java-sdk -P 'coverage,non-fips,!fips' From ea23921be435e53ac14fca8eaafdbe3ba4f245c7 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 13 May 2026 23:51:43 -0400 Subject: [PATCH 32/32] just this --- .github/workflows/checks.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 87a1a4c2..089bf0a2 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -92,9 +92,7 @@ jobs: BUF_INPUT_HTTPS_USERNAME: opentdf-bot BUF_INPUT_HTTPS_PASSWORD: ${{ secrets.PERSONAL_ACCESS_TOKEN_OPENTDF }} run: | - mvn --batch-mode clean verify test -P 'fips,!non-fips' && \ - mvn --batch-mode clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=opentdf_java-sdk -P 'coverage,non-fips,!fips' - + mvn --batch-mode clean verify -P 'fips,!non-fips' platform-integration: runs-on: ubuntu-22.04