JCE: accept legacy RSA OID (1.3.14.3.2.15) in RSA KeyFactory X.509 public key decode
parent
48f198d6c1
commit
2f30d4cc7c
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue