Merge pull request #231 from cconlon/legacyRsaOidKeyFactory

Accept legacy RSA OID (1.3.14.3.2.15) in RSA KeyFactory X.509 public key decode
pull/232/head
Ruby Martin 2026-06-17 14:33:22 -06:00 committed by GitHub
commit 5f04f6ed21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 194 additions and 2 deletions

View File

@ -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) {

View File

@ -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 {