Skip to content

Commit 0cda250

Browse files
committed
JNI: tighten setAuthorityKeyId Javadoc and add round-trip test
1 parent 57f35cf commit 0cda250

2 files changed

Lines changed: 254 additions & 1 deletion

File tree

src/java/com/wolfssl/WolfSSLCertificate.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,12 @@ public void setSubjectKeyIdEx()
815815
* Sets the Authority Key Identifier extension for this WolfSSLCertificate,
816816
* used when generating X509v3 certificates.
817817
*
818-
* @param akid Byte array containing Authority Key Identifier.
818+
* The argument is the raw key identifier value (ex: the SHA-1 hash of the
819+
* issuer public key, or the issuer SubjectKeyIdentifier value). The bytes
820+
* must not be a pre-encoded AuthorityKeyIdentifier SEQUENCE; native
821+
* wolfSSL wraps them in SEQUENCE { [0] keyIdentifier }.
822+
*
823+
* @param akid Raw key identifier bytes.
819824
*
820825
* @throws IllegalStateException if WolfSSLCertificate has been freed.
821826
* @throws WolfSSLException if invalid arguments or native JNI error occurs.
@@ -851,6 +856,10 @@ public void setAuthorityKeyId(byte[] akid)
851856
* Sets the Authority Key Identifier extension for this WolfSSLCertificate
852857
* using the issuer certificate.
853858
*
859+
* The keyIdentifier value used is the issuer SubjectKeyIdentifier
860+
* extension contents if present, otherwise the SHA-1 hash of the issuer
861+
* SubjectPublicKeyInfo.
862+
*
854863
* @param issuer Issuer certificate used to derive the Authority Key ID.
855864
*
856865
* @throws IllegalStateException if WolfSSLCertificate has been freed.

src/test/com/wolfssl/test/WolfSSLCertificateTest.java

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
import java.time.Instant;
3333
import java.time.Duration;
3434
import java.security.cert.CertificateException;
35+
import java.security.cert.CertificateFactory;
3536
import java.security.cert.X509Certificate;
37+
import java.io.ByteArrayInputStream;
3638
import java.security.spec.PKCS8EncodedKeySpec;
3739
import java.security.spec.InvalidKeySpecException;
3840
import java.security.interfaces.RSAPrivateKey;
@@ -897,6 +899,248 @@ public void testWolfSSLCertificateExtensionSetters()
897899
x509.free();
898900
}
899901

902+
/* Round trip test for setAuthorityKeyId(byte[]) and
903+
* setAuthorityKeyIdEx(WolfSSLCertificate).
904+
*
905+
* RFC 5280 requires the extension OCTET STRING to wrap a SEQUENCE { [0]
906+
* keyIdentifier OCTET STRING }. If the encoder writes the raw key-id bytes
907+
* directly into the OCTET STRING, the resulting cert is malformed and
908+
* strict parsers will reject it.
909+
*
910+
* This test signs a cert after calling each setter, runs the DER through
911+
* java.security.cert.CertificateFactory, pulls the AKID extension via
912+
* getExtensionValue("2.5.29.35"), asserts the inner bytes start with
913+
* the expected SEQUENCE { [0] keyId }, and that the embedded keyId
914+
* matches what was supplied. */
915+
@Test
916+
public void testWolfSSLCertificateAuthorityKeyIdRoundtrip()
917+
throws WolfSSLException, WolfSSLJNIException, IOException,
918+
CertificateException {
919+
920+
Assume.assumeTrue(WolfSSL.FileSystemEnabled());
921+
922+
/* Raw 20-byte key identifier passed to setAuthorityKeyId(byte[]). */
923+
final byte[] akidRaw = new byte[] {
924+
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
925+
0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23
926+
};
927+
928+
/* SKID stamped on the issuer cert. The AKID derived by
929+
* setAuthorityKeyIdEx() must match this value exactly. */
930+
final byte[] issuerSkid = new byte[] {
931+
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
932+
0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43
933+
};
934+
935+
/* setAuthorityKeyId(byte[]) round-trip. buildSignedCertWithAkid()
936+
* returns null when the setter is NOT_COMPILED_IN at runtime
937+
* (older native wolfSSL). */
938+
byte[] derCert = buildSignedCertWithAkid(akidRaw, null);
939+
Assume.assumeTrue("AKID setter not compiled in native wolfSSL",
940+
derCert != null);
941+
942+
/* Behavioral probe: if the native encoder did not wrap the raw
943+
* keyId in SEQUENCE { [0] keyId } per RFC 5280 4.2.1.1, skip
944+
* rather than fail. The encoder bug lives in wolfSSL (see
945+
* wolfSSL PR #10370), not wolfssljni. We want the test here to run
946+
* for wolfSSL versions that are fixed, but not for broken versions. */
947+
Assume.assumeTrue("Native wolfSSL AKID encoder lacks RFC 5280 " +
948+
"wrapping fix (wolfSSL PR #10370); upgrade native wolfSSL.",
949+
akidExtensionWellFormed(derCert));
950+
951+
assertAkidExtensionMatches(derCert, akidRaw);
952+
953+
/* setAuthorityKeyIdEx(WolfSSLCertificate issuer) round-trip. */
954+
WolfSSLCertificate issuerCert =
955+
new WolfSSLCertificate(caCertPem, WolfSSL.SSL_FILETYPE_PEM);
956+
try {
957+
try {
958+
issuerCert.setSubjectKeyId(issuerSkid);
959+
} catch (WolfSSLException e) {
960+
if (isNotCompiledIn(e)) {
961+
return;
962+
}
963+
throw e;
964+
}
965+
966+
byte[] derCertEx = buildSignedCertWithAkid(null, issuerCert);
967+
if (derCertEx != null) {
968+
assertAkidExtensionMatches(derCertEx, issuerSkid);
969+
}
970+
971+
} finally {
972+
issuerCert.free();
973+
}
974+
}
975+
976+
/* Behavioral probe: returns true iff the AKID extension on this DER
977+
* cert decodes as a well-formed SEQUENCE { [0] keyId } per RFC 5280
978+
* 4.2.1.1. Returns false if the extension is missing, malformed, or
979+
* the inner content begins with anything other than the SEQUENCE tag
980+
* (which is the unfixed-encoder symptom - raw key id bytes written
981+
* directly into the OCTET STRING). Used by
982+
* testWolfSSLCertificateAuthorityKeyIdRoundtrip to skip on native
983+
* wolfSSL builds without the AKID encoder fix.
984+
*
985+
* X509Certificate.getExtensionValue() returns the DER-encoded extnValue,
986+
* an OCTET STRING wrapping the inner extension bytes:
987+
* 04 LL <inner-bytes>
988+
* For a well-formed AKID, <inner-bytes> starts with 0x30 (SEQUENCE);
989+
* for an unfixed encoder it is a raw 20-byte keyId starting with
990+
* whatever happens to be byte 0 of the keyId. */
991+
private boolean akidExtensionWellFormed(byte[] derCert)
992+
throws CertificateException {
993+
994+
CertificateFactory cf = CertificateFactory.getInstance("X.509");
995+
X509Certificate jdkCert = (X509Certificate)cf.generateCertificate(
996+
new ByteArrayInputStream(derCert));
997+
if (jdkCert == null) {
998+
return false;
999+
}
1000+
byte[] extWrapped = jdkCert.getExtensionValue("2.5.29.35");
1001+
if (extWrapped == null || extWrapped.length < 4) {
1002+
return false;
1003+
}
1004+
/* Skip outer OCTET STRING wrapper. Short-form length only, since
1005+
* realistic AKID extensions fit comfortably in <128 bytes. */
1006+
if ((extWrapped[0] & 0xFF) != 0x04) {
1007+
return false;
1008+
}
1009+
int outerLen = extWrapped[1] & 0xFF;
1010+
if (outerLen >= 0x80 || extWrapped.length != 2 + outerLen) {
1011+
return false;
1012+
}
1013+
/* Inner must start with SEQUENCE tag (0x30) for a well-formed
1014+
* AuthorityKeyIdentifier. */
1015+
return (extWrapped[2] & 0xFF) == 0x30;
1016+
}
1017+
1018+
/* Build, populate, AKID-stamp, and sign a cert. Returns the DER bytes,
1019+
* or null if the AKID setter returned NOT_COMPILED_IN at runtime (older
1020+
* native wolfSSL). Exactly one of {akidRaw, issuerForEx} must be non-null
1021+
* to select which AKID setter to exercise. */
1022+
private byte[] buildSignedCertWithAkid(byte[] akidRaw,
1023+
WolfSSLCertificate issuerForEx) throws WolfSSLException,
1024+
WolfSSLJNIException, IOException {
1025+
1026+
WolfSSLCertificate x509 = new WolfSSLCertificate();
1027+
WolfSSLX509Name subjectName = null;
1028+
WolfSSLCertificate issuer = null;
1029+
1030+
try {
1031+
Instant now = Instant.now();
1032+
x509.setNotBefore(Date.from(now));
1033+
x509.setNotAfter(Date.from(now.plus(Duration.ofDays(365))));
1034+
x509.setSerialNumber(BigInteger.valueOf(0xCAFE));
1035+
1036+
subjectName = GenerateTestSubjectName();
1037+
x509.setSubjectName(subjectName);
1038+
1039+
issuer = new WolfSSLCertificate(caCertPem,
1040+
WolfSSL.SSL_FILETYPE_PEM);
1041+
x509.setIssuerName(issuer);
1042+
1043+
x509.setPublicKey(cliKeyPubDer, WolfSSL.RSAk,
1044+
WolfSSL.SSL_FILETYPE_ASN1);
1045+
1046+
try {
1047+
if (akidRaw != null) {
1048+
x509.setAuthorityKeyId(akidRaw);
1049+
}
1050+
else {
1051+
x509.setAuthorityKeyIdEx(issuerForEx);
1052+
}
1053+
1054+
} catch (WolfSSLException e) {
1055+
if (isNotCompiledIn(e)) {
1056+
return null;
1057+
}
1058+
throw e;
1059+
}
1060+
1061+
x509.signCert(caKeyDer, WolfSSL.RSAk,
1062+
WolfSSL.SSL_FILETYPE_ASN1, "SHA256");
1063+
1064+
byte[] der = x509.getDer();
1065+
assertNotNull("getDer() returned null after signing", der);
1066+
assertTrue("getDer() returned empty bytes", der.length > 0);
1067+
1068+
return der;
1069+
1070+
} finally {
1071+
if (subjectName != null) {
1072+
subjectName.free();
1073+
}
1074+
if (issuer != null) {
1075+
issuer.free();
1076+
}
1077+
x509.free();
1078+
}
1079+
}
1080+
1081+
/* Parse DER cert with JDK CertificateFactory and assert the AKID
1082+
* extension (OID 2.5.29.35) decodes as a well-formed SEQUENCE {
1083+
* [0] keyIdentifier OCTET STRING } whose keyId matches expectedKeyId. */
1084+
private void assertAkidExtensionMatches(byte[] derCert,
1085+
byte[] expectedKeyId) throws CertificateException {
1086+
1087+
CertificateFactory cf = CertificateFactory.getInstance("X.509");
1088+
X509Certificate jdkCert = (X509Certificate)cf.generateCertificate(
1089+
new ByteArrayInputStream(derCert));
1090+
assertNotNull("CertificateFactory rejected the DER cert " +
1091+
"(likely malformed AKID extension)", jdkCert);
1092+
1093+
/* getExtensionValue() returns the DER-encoded extnValue, which per
1094+
* X.509 is an OCTET STRING whose contents are the actual extension
1095+
* value. So the bytes look like:
1096+
* 04 LL <inner-bytes>
1097+
* For AKID, <inner-bytes> must be the AuthorityKeyIdentifier
1098+
* SEQUENCE per RFC 5280:
1099+
* 30 LL 80 KL <keyId>
1100+
*/
1101+
byte[] extWrapped = jdkCert.getExtensionValue("2.5.29.35");
1102+
assertNotNull("AKID extension (2.5.29.35) was not present in cert",
1103+
extWrapped);
1104+
1105+
/* Strip the outer OCTET STRING wrapper. */
1106+
assertTrue("AKID extension envelope too short: " + extWrapped.length,
1107+
extWrapped.length >= 2);
1108+
assertEquals("AKID extension envelope is not OCTET STRING",
1109+
0x04, extWrapped[0] & 0xFF);
1110+
int outerLen = extWrapped[1] & 0xFF;
1111+
/* Short-form length only; AKID for 20-byte keyId fits in <128. */
1112+
assertTrue("Unexpected long-form length in AKID envelope",
1113+
outerLen < 0x80);
1114+
assertEquals("AKID envelope length mismatch",
1115+
extWrapped.length - 2, outerLen);
1116+
1117+
/* Inner extension structure for a 20-byte keyId is exactly 24 bytes:
1118+
* SEQUENCE (0x30) length 22 (0x16)
1119+
* [0] (0x80) length 20 (0x14)
1120+
* <20 keyId bytes>
1121+
*/
1122+
byte[] inner = new byte[outerLen];
1123+
System.arraycopy(extWrapped, 2, inner, 0, outerLen);
1124+
assertEquals("AKID inner length (expected SEQUENCE { [0] keyId } " +
1125+
"wrapping " + expectedKeyId.length + " bytes = " +
1126+
(expectedKeyId.length + 4) + " total)",
1127+
expectedKeyId.length + 4, inner.length);
1128+
assertEquals("AKID inner not a SEQUENCE — encoder likely wrote " +
1129+
"raw keyId bytes directly into the extension OCTET STRING",
1130+
0x30, inner[0] & 0xFF);
1131+
assertEquals("AKID SEQUENCE length byte unexpected",
1132+
expectedKeyId.length + 2, inner[1] & 0xFF);
1133+
assertEquals("AKID keyIdentifier tag is not [0] context-specific",
1134+
0x80, inner[2] & 0xFF);
1135+
assertEquals("AKID keyIdentifier length byte unexpected",
1136+
expectedKeyId.length, inner[3] & 0xFF);
1137+
1138+
byte[] decodedKeyId = new byte[expectedKeyId.length];
1139+
System.arraycopy(inner, 4, decodedKeyId, 0, expectedKeyId.length);
1140+
assertArrayEquals("AKID keyIdentifier bytes do not match input",
1141+
expectedKeyId, decodedKeyId);
1142+
}
1143+
9001144
/* Quick sanity check on certificate bytes. Loads cert into new
9011145
* WolfSSLCertificate object, tries to get various elements and
9021146
* simply verify if not null / etc. */

0 commit comments

Comments
 (0)