Skip to content

Commit f596b30

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

2 files changed

Lines changed: 262 additions & 1 deletion

File tree

src/java/com/wolfssl/WolfSSLCertificate.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,15 @@ 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. On native
821+
* wolfSSL with the AKID encoder fix (wolfSSL PR #10370), the bytes are
822+
* wrapped in SEQUENCE { [0] keyIdentifier } per RFC 5280 4.2.1.1.
823+
* Older native wolfSSL builds without the fix emit a malformed AKID
824+
* extension that parsers reject.
825+
*
826+
* @param akid Raw key identifier bytes.
819827
*
820828
* @throws IllegalStateException if WolfSSLCertificate has been freed.
821829
* @throws WolfSSLException if invalid arguments or native JNI error occurs.
@@ -851,6 +859,10 @@ public void setAuthorityKeyId(byte[] akid)
851859
* Sets the Authority Key Identifier extension for this WolfSSLCertificate
852860
* using the issuer certificate.
853861
*
862+
* The keyIdentifier value used is the issuer SubjectKeyIdentifier
863+
* extension contents if present, otherwise the SHA-1 hash of the issuer
864+
* SubjectPublicKeyInfo.
865+
*
854866
* @param issuer Issuer certificate used to derive the Authority Key ID.
855867
*
856868
* @throws IllegalStateException if WolfSSLCertificate has been freed.

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

Lines changed: 249 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,253 @@ 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+
if (akidRaw != null) {
1040+
issuer = new WolfSSLCertificate(caCertPem,
1041+
WolfSSL.SSL_FILETYPE_PEM);
1042+
x509.setIssuerName(issuer);
1043+
}
1044+
else {
1045+
x509.setIssuerName(issuerForEx);
1046+
}
1047+
1048+
x509.setPublicKey(cliKeyPubDer, WolfSSL.RSAk,
1049+
WolfSSL.SSL_FILETYPE_ASN1);
1050+
1051+
try {
1052+
if (akidRaw != null) {
1053+
x509.setAuthorityKeyId(akidRaw);
1054+
}
1055+
else {
1056+
x509.setAuthorityKeyIdEx(issuerForEx);
1057+
}
1058+
1059+
} catch (WolfSSLException e) {
1060+
if (isNotCompiledIn(e)) {
1061+
return null;
1062+
}
1063+
throw e;
1064+
}
1065+
1066+
x509.signCert(caKeyDer, WolfSSL.RSAk,
1067+
WolfSSL.SSL_FILETYPE_ASN1, "SHA256");
1068+
1069+
byte[] der = x509.getDer();
1070+
assertNotNull("getDer() returned null after signing", der);
1071+
assertTrue("getDer() returned empty bytes", der.length > 0);
1072+
1073+
return der;
1074+
1075+
} finally {
1076+
if (subjectName != null) {
1077+
subjectName.free();
1078+
}
1079+
if (issuer != null) {
1080+
issuer.free();
1081+
}
1082+
x509.free();
1083+
}
1084+
}
1085+
1086+
/* Parse DER cert with JDK CertificateFactory and assert the AKID
1087+
* extension (OID 2.5.29.35) decodes as a well-formed SEQUENCE {
1088+
* [0] keyIdentifier OCTET STRING } whose keyId matches expectedKeyId. */
1089+
private void assertAkidExtensionMatches(byte[] derCert,
1090+
byte[] expectedKeyId) throws CertificateException {
1091+
1092+
CertificateFactory cf = CertificateFactory.getInstance("X.509");
1093+
X509Certificate jdkCert = (X509Certificate)cf.generateCertificate(
1094+
new ByteArrayInputStream(derCert));
1095+
assertNotNull("CertificateFactory rejected the DER cert " +
1096+
"(likely malformed AKID extension)", jdkCert);
1097+
1098+
/* getExtensionValue() returns the DER-encoded extnValue, which per
1099+
* X.509 is an OCTET STRING whose contents are the actual extension
1100+
* value. So the bytes look like:
1101+
* 04 LL <inner-bytes>
1102+
* For AKID, <inner-bytes> must be the AuthorityKeyIdentifier
1103+
* SEQUENCE per RFC 5280:
1104+
* 30 LL 80 KL <keyId>
1105+
*/
1106+
byte[] extWrapped = jdkCert.getExtensionValue("2.5.29.35");
1107+
assertNotNull("AKID extension (2.5.29.35) was not present in cert",
1108+
extWrapped);
1109+
1110+
/* Strip the outer OCTET STRING wrapper. */
1111+
assertTrue("AKID extension envelope too short: " + extWrapped.length,
1112+
extWrapped.length >= 2);
1113+
assertEquals("AKID extension envelope is not OCTET STRING",
1114+
0x04, extWrapped[0] & 0xFF);
1115+
int outerLen = extWrapped[1] & 0xFF;
1116+
/* Short-form length only; AKID for 20-byte keyId fits in <128. */
1117+
assertTrue("Unexpected long-form length in AKID envelope",
1118+
outerLen < 0x80);
1119+
assertEquals("AKID envelope length mismatch",
1120+
extWrapped.length - 2, outerLen);
1121+
1122+
/* Inner extension structure for a 20-byte keyId is exactly 24 bytes:
1123+
* SEQUENCE (0x30) length 22 (0x16)
1124+
* [0] (0x80) length 20 (0x14)
1125+
* <20 keyId bytes>
1126+
*/
1127+
byte[] inner = new byte[outerLen];
1128+
System.arraycopy(extWrapped, 2, inner, 0, outerLen);
1129+
assertEquals("AKID inner length (expected SEQUENCE { [0] keyId } " +
1130+
"wrapping " + expectedKeyId.length + " bytes = " +
1131+
(expectedKeyId.length + 4) + " total)",
1132+
expectedKeyId.length + 4, inner.length);
1133+
assertEquals("AKID inner not a SEQUENCE — encoder likely wrote " +
1134+
"raw keyId bytes directly into the extension OCTET STRING",
1135+
0x30, inner[0] & 0xFF);
1136+
assertEquals("AKID SEQUENCE length byte unexpected",
1137+
expectedKeyId.length + 2, inner[1] & 0xFF);
1138+
assertEquals("AKID keyIdentifier tag is not [0] context-specific",
1139+
0x80, inner[2] & 0xFF);
1140+
assertEquals("AKID keyIdentifier length byte unexpected",
1141+
expectedKeyId.length, inner[3] & 0xFF);
1142+
1143+
byte[] decodedKeyId = new byte[expectedKeyId.length];
1144+
System.arraycopy(inner, 4, decodedKeyId, 0, expectedKeyId.length);
1145+
assertArrayEquals("AKID keyIdentifier bytes do not match input",
1146+
expectedKeyId, decodedKeyId);
1147+
}
1148+
9001149
/* Quick sanity check on certificate bytes. Loads cert into new
9011150
* WolfSSLCertificate object, tries to get various elements and
9021151
* simply verify if not null / etc. */

0 commit comments

Comments
 (0)