From 2f30d4cc7c515491ec8d1e42da14aaefbda11316 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Fri, 12 Jun 2026 16:03:56 -0600 Subject: [PATCH] JCE: accept legacy RSA OID (1.3.14.3.2.15) in RSA KeyFactory X.509 public key decode --- .../provider/jce/WolfCryptRSAKeyFactory.java | 122 +++++++++++++++++- .../jce/test/WolfCryptRSAKeyFactoryTest.java | 74 +++++++++++ 2 files changed, 194 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptRSAKeyFactory.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptRSAKeyFactory.java index 0c765557..d3727ee6 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptRSAKeyFactory.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptRSAKeyFactory.java @@ -50,6 +50,22 @@ import com.wolfssl.wolfcrypt.WolfCryptException; */ public class WolfCryptRSAKeyFactory extends KeyFactorySpi { + /* OIW OID 1.3.14.3.2.15 content bytes. Used as the X.509 + * SubjectPublicKeyInfo algorithm OID for RSA public keys encoded by JDK + * releases prior to JDK-8146293 */ + private static final byte[] OIW_RSA_OID = { + (byte)0x2B, (byte)0x0E, (byte)0x03, (byte)0x02, (byte)0x0F + }; + + /* DER-encoded AlgorithmIdentifier for rsaEncryption + * (1.2.840.113549.1.1.1) with NULL parameters */ + private static final byte[] RSA_ALG_ID_DER = { + (byte)0x30, (byte)0x0D, (byte)0x06, (byte)0x09, + (byte)0x2A, (byte)0x86, (byte)0x48, (byte)0x86, + (byte)0xF7, (byte)0x0D, (byte)0x01, (byte)0x01, + (byte)0x01, (byte)0x05, (byte)0x00 + }; + /** * Create new WolfCryptRSAKeyFactory object. */ @@ -67,6 +83,105 @@ public class WolfCryptRSAKeyFactory extends KeyFactorySpi { () -> "[RSA KeyFactory] " + msg); } + /** + * Rewrite legacy RSA algorithm OIDs inside an X.509 + * SubjectPublicKeyInfo to the standard rsaEncryption OID + * (1.2.840.113549.1.1.1). + * + * RSA public keys encoded by JDK releases prior to JDK-8146293 use OIW + * OID 1.3.14.3.2.15 in the AlgorithmIdentifier. Native wolfCrypt only + * accepts the rsaEncryption OID, so the AlgorithmIdentifier is replaced + * before decoding. This matches SunRsaSign RSA KeyFactory behavior + * (JDK-8242897). Keys returned from this KeyFactory will re-encode using + * the standard OID. + * + * @param x509Der DER-encoded X.509 SubjectPublicKeyInfo + * + * @return DER encoding with AlgorithmIdentifier rewritten to + * rsaEncryption, or the original array if the encoding does not + * use a legacy RSA OID or cannot be parsed + */ + private byte[] normalizeLegacyRsaOid(byte[] x509Der) { + + int idx = 0; + int algIdStart = 0; + int algIdHeader = 0; + int algIdLen = 0; + int oidStart = 0; + int oidLen = 0; + int tailStart = 0; + int tailLen = 0; + byte[] contents = null; + + if (x509Der == null) { + return null; + } + + try { + /* SubjectPublicKeyInfo: SEQUENCE { AlgorithmIdentifier, + * subjectPublicKey BIT STRING }, walk to the AlgorithmIdentifier + * algorithm OID */ + if (x509Der.length < 2 || x509Der[idx] != 0x30) { + return x509Der; + } + idx += 1 + WolfCryptASN1Util.getDERLengthSize(x509Der, 1); + + /* AlgorithmIdentifier SEQUENCE */ + if (x509Der[idx] != 0x30) { + return x509Der; + } + algIdStart = idx; + algIdLen = WolfCryptASN1Util.getDERLength(x509Der, idx + 1); + algIdHeader = 1 + + WolfCryptASN1Util.getDERLengthSize(x509Der, idx + 1); + idx += algIdHeader; + + /* algorithm OBJECT IDENTIFIER */ + if (x509Der[idx] != 0x06) { + return x509Der; + } + oidLen = WolfCryptASN1Util.getDERLength(x509Der, idx + 1); + oidStart = idx + 1 + + WolfCryptASN1Util.getDERLengthSize(x509Der, idx + 1); + + /* Compare OID content bytes in place against legacy OIW OID */ + if (oidLen != OIW_RSA_OID.length || + oidStart + oidLen > x509Der.length) { + return x509Der; + } + for (int i = 0; i < oidLen; i++) { + if (x509Der[oidStart + i] != OIW_RSA_OID[i]) { + return x509Der; + } + } + + /* Rebuild SubjectPublicKeyInfo with rsaEncryption + * AlgorithmIdentifier, keeping everything after the original + * AlgorithmIdentifier (subjectPublicKey BIT STRING) as is */ + tailStart = algIdStart + algIdHeader + algIdLen; + if (tailStart < 0 || tailStart > x509Der.length) { + return x509Der; + } + tailLen = x509Der.length - tailStart; + + contents = new byte[RSA_ALG_ID_DER.length + tailLen]; + System.arraycopy(RSA_ALG_ID_DER, 0, contents, 0, + RSA_ALG_ID_DER.length); + System.arraycopy(x509Der, tailStart, contents, + RSA_ALG_ID_DER.length, tailLen); + + log("rewrote legacy RSA OID (1.3.14.3.2.15) in X509 " + + "encoding to rsaEncryption"); + + return WolfCryptASN1Util.encodeDERSequence(contents); + + } catch (IllegalArgumentException | IndexOutOfBoundsException e) { + /* Parsing errors on malformed encodings, leave to native + * wolfCrypt error handling */ + return x509Der; + } + } + /** * Generate private key object from the provided key specification. * @@ -375,6 +490,10 @@ public class WolfCryptRSAKeyFactory extends KeyFactorySpi { "X509EncodedKeySpec contains null encoded key"); } + /* Rewrite legacy RSA algorithm OIDs used by older JDK X.509 + * encodings to standard rsaEncryption OID before native decode */ + x509Der = normalizeLegacyRsaOid(x509Der); + log("decoding X509 public key, length: " + x509Der.length); /* Import X509 key into Rsa to validate DER structure */ @@ -386,8 +505,7 @@ public class WolfCryptRSAKeyFactory extends KeyFactorySpi { } catch (WolfCryptException e) { throw new InvalidKeySpecException( - "wolfCrypt error during X509 key decode: " + e.getMessage(), - e); + "wolfCrypt error during X509 key decode: " + e.getMessage(), e); } finally { if (rsa != null) { diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptRSAKeyFactoryTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptRSAKeyFactoryTest.java index 54db44af..8a38f6ed 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptRSAKeyFactoryTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptRSAKeyFactoryTest.java @@ -57,6 +57,7 @@ import com.wolfssl.provider.jce.WolfCryptProvider; import com.wolfssl.wolfcrypt.FeatureDetect; import com.wolfssl.wolfcrypt.Rsa; import com.wolfssl.wolfcrypt.test.TimedTestWatcher; +import com.wolfssl.wolfcrypt.test.Util; /** * JUnit4 test cases for WolfCryptRSAKeyFactory @@ -163,6 +164,79 @@ public class WolfCryptRSAKeyFactoryTest { } } + /* X.509 SubjectPublicKeyInfo encoding of a 512-bit RSA public key using + * legacy OIW algorithm OID 1.3.14.3.2.15, as encoded by JDK releases + * prior to JDK-8146293. Same encoding used by OpenJDK test + * TestRSAOidSupport (JDK-8242897). */ + private static final String LEGACY_OID_RSA_SPKI = + "3058300906052b0e03020f0500034b003048024100d7157c65e8f22557d8" + + "a857122cfe85bddfaba3064c21b345e2a7cdd8a6751e519ab861c5109fb8" + + "8cce45d161b9817bc0eccdc30fda69e62cc577775f2c1d66bd0203010001"; + + /* DER-encoded AlgorithmIdentifier for rsaEncryption + * (1.2.840.113549.1.1.1) with NULL parameters */ + private static final String RSA_ALG_ID_HEX = + "300d06092a864886f70d0101010500"; + + /* DER-encoded legacy OIW OID 1.3.14.3.2.15 */ + private static final String OIW_RSA_OID_HEX = "06052b0e03020f"; + + /** + * Helper method to check if haystack byte array contains needle + * byte array as a contiguous subsequence. + */ + private static boolean containsBytes(byte[] haystack, byte[] needle) { + for (int i = 0; i <= haystack.length - needle.length; i++) { + int j = 0; + while (j < needle.length && haystack[i + j] == needle[j]) { + j++; + } + if (j == needle.length) { + return true; + } + } + return false; + } + + @Test + public void testGeneratePublicFromLegacyOidX509Spec() + throws Exception { + + if (!rsaKeyFactoryAvailable()) { + return; + } + + KeyFactory kf = KeyFactory.getInstance("RSA", "wolfJCE"); + assertNotNull(kf); + + /* Decode X.509 encoding using legacy RSA algorithm OID */ + X509EncodedKeySpec spec = + new X509EncodedKeySpec(Util.h2b(LEGACY_OID_RSA_SPKI)); + PublicKey generated = kf.generatePublic(spec); + assertNotNull(generated); + assertTrue(generated instanceof RSAPublicKey); + + RSAPublicKey rsaPub = (RSAPublicKey)generated; + assertEquals("RSA", rsaPub.getAlgorithm()); + assertEquals(512, rsaPub.getModulus().bitLength()); + + /* Re-encoded key should use standard rsaEncryption OID, not + * contain the legacy OID, and be decodable again */ + byte[] encoded = rsaPub.getEncoded(); + assertNotNull(encoded); + assertTrue("Re-encoded SPKI should contain rsaEncryption " + + "AlgorithmIdentifier", + containsBytes(encoded, Util.h2b(RSA_ALG_ID_HEX))); + assertFalse("Re-encoded SPKI should not contain legacy OIW OID", + containsBytes(encoded, Util.h2b(OIW_RSA_OID_HEX))); + + RSAPublicKey rsaPub2 = (RSAPublicKey)kf.generatePublic( + new X509EncodedKeySpec(encoded)); + assertEquals(rsaPub.getModulus(), rsaPub2.getModulus()); + assertEquals(rsaPub.getPublicExponent(), + rsaPub2.getPublicExponent()); + } + @Test public void testGeneratePrivateFromPKCS8Spec() throws Exception {