Skip to content

Commit 14c4e31

Browse files
committed
Netty FIPS test suite updates
1 parent 3c58d95 commit 14c4e31

6 files changed

Lines changed: 1871 additions & 0 deletions

File tree

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
/*
2+
* CertGen.java - Certificate generation utility using BouncyCastle
3+
*
4+
* Replaces all openssl calls in apply_netty_fips_fixes.sh.
5+
* Compiled and run in the Docker builder stage using BouncyCastle jars
6+
* from Netty's Maven dependencies.
7+
*
8+
* Commands:
9+
* pkcs8 <inFile> <outFile>
10+
* Convert PKCS#1 (RSA PRIVATE KEY) PEM to PKCS#8 (PRIVATE KEY) PEM
11+
*
12+
* genca <certOut> <keyOut> <subject>
13+
* Generate a self-signed CA certificate with RSA 2048-bit key
14+
*
15+
* signcert <caCert> <caKey> <certOut> <keyOut> <cn> <san1> [san2...]
16+
* Generate RSA 2048-bit key, create cert signed by CA with SAN extensions
17+
* Key output is PKCS#8 PEM format
18+
*
19+
* encrypt <inFile> <outFile> <password>
20+
* Convert unencrypted PEM key to PBES2/PBKDF2-SHA256/AES-256-CBC encrypted PKCS#8
21+
*/
22+
23+
import org.bouncycastle.asn1.DERNull;
24+
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
25+
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
26+
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
27+
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
28+
import org.bouncycastle.asn1.x500.X500Name;
29+
import org.bouncycastle.asn1.x509.BasicConstraints;
30+
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
31+
import org.bouncycastle.asn1.x509.Extension;
32+
import org.bouncycastle.asn1.x509.GeneralName;
33+
import org.bouncycastle.asn1.x509.GeneralNames;
34+
import org.bouncycastle.asn1.x509.KeyPurposeId;
35+
import org.bouncycastle.asn1.x509.KeyUsage;
36+
import org.bouncycastle.cert.X509CertificateHolder;
37+
import org.bouncycastle.cert.X509v3CertificateBuilder;
38+
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
39+
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
40+
import org.bouncycastle.jce.provider.BouncyCastleProvider;
41+
import org.bouncycastle.openssl.PEMKeyPair;
42+
import org.bouncycastle.openssl.PEMParser;
43+
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
44+
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
45+
import org.bouncycastle.operator.ContentSigner;
46+
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
47+
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfoBuilder;
48+
import org.bouncycastle.pkcs.jcajce.JcePKCSPBEOutputEncryptorBuilder;
49+
50+
import java.io.FileReader;
51+
import java.io.FileWriter;
52+
import java.io.IOException;
53+
import java.math.BigInteger;
54+
import java.security.KeyPair;
55+
import java.security.KeyPairGenerator;
56+
import java.security.PrivateKey;
57+
import java.security.SecureRandom;
58+
import java.security.Security;
59+
import java.security.cert.X509Certificate;
60+
import java.util.ArrayList;
61+
import java.util.Calendar;
62+
import java.util.Date;
63+
import java.util.List;
64+
65+
public class CertGen {
66+
67+
static {
68+
Security.addProvider(new BouncyCastleProvider());
69+
}
70+
71+
public static void main(String[] args) throws Exception {
72+
if (args.length < 1) {
73+
System.err.println("Usage: CertGen <command> [args...]");
74+
System.err.println("Commands: pkcs8, genca, signcert, encrypt");
75+
System.exit(1);
76+
}
77+
78+
String command = args[0];
79+
switch (command) {
80+
case "pkcs8":
81+
if (args.length != 3) {
82+
System.err.println("Usage: CertGen pkcs8 <inFile> <outFile>");
83+
System.exit(1);
84+
}
85+
convertToPkcs8(args[1], args[2]);
86+
break;
87+
88+
case "genca":
89+
if (args.length != 4) {
90+
System.err.println("Usage: CertGen genca <certOut> <keyOut> <subject>");
91+
System.exit(1);
92+
}
93+
generateCA(args[1], args[2], args[3]);
94+
break;
95+
96+
case "signcert":
97+
if (args.length < 7) {
98+
System.err.println("Usage: CertGen signcert <caCert> <caKey> <certOut> <keyOut> <cn> <san1> [san2...]");
99+
System.exit(1);
100+
}
101+
String[] sans = new String[args.length - 6];
102+
System.arraycopy(args, 6, sans, 0, sans.length);
103+
signCert(args[1], args[2], args[3], args[4], args[5], sans);
104+
break;
105+
106+
case "encrypt":
107+
if (args.length != 4) {
108+
System.err.println("Usage: CertGen encrypt <inFile> <outFile> <password>");
109+
System.exit(1);
110+
}
111+
encryptKey(args[1], args[2], args[3]);
112+
break;
113+
114+
default:
115+
System.err.println("Unknown command: " + command);
116+
System.exit(1);
117+
}
118+
}
119+
120+
/**
121+
* Convert PKCS#1 PEM key to PKCS#8 PEM key (unencrypted).
122+
*/
123+
private static void convertToPkcs8(String inFile, String outFile) throws Exception {
124+
PrivateKey key = readPrivateKey(inFile);
125+
// key.getEncoded() returns PKCS#8 encoded form
126+
writePem(outFile, key);
127+
}
128+
129+
/**
130+
* Generate a self-signed CA certificate with RSA 2048 key.
131+
* Key output is PKCS#8 PEM format.
132+
*/
133+
private static void generateCA(String certOut, String keyOut, String subject) throws Exception {
134+
KeyPair keyPair = generateRSAKeyPair();
135+
136+
X500Name issuer = new X500Name(subject);
137+
138+
Calendar cal = Calendar.getInstance();
139+
Date notBefore = cal.getTime();
140+
cal.add(Calendar.YEAR, 10);
141+
Date notAfter = cal.getTime();
142+
143+
BigInteger serial = new BigInteger(128, new SecureRandom());
144+
145+
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
146+
issuer, serial, notBefore, notAfter, issuer, keyPair.getPublic());
147+
148+
// CA extensions
149+
certBuilder.addExtension(Extension.basicConstraints, true,
150+
new BasicConstraints(true));
151+
certBuilder.addExtension(Extension.keyUsage, true,
152+
new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign));
153+
154+
ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA")
155+
.setProvider("BC").build(keyPair.getPrivate());
156+
157+
X509CertificateHolder certHolder = certBuilder.build(signer);
158+
X509Certificate cert = new JcaX509CertificateConverter()
159+
.setProvider("BC").getCertificate(certHolder);
160+
161+
writePem(certOut, cert);
162+
writePem(keyOut, keyPair.getPrivate());
163+
}
164+
165+
/**
166+
* Generate a certificate signed by a CA with SAN extensions.
167+
* Key output is PKCS#8 PEM format.
168+
*/
169+
private static void signCert(String caCertFile, String caKeyFile,
170+
String certOut, String keyOut,
171+
String cn, String[] sans) throws Exception {
172+
// Load CA cert and key
173+
X509Certificate caCert = readCertificate(caCertFile);
174+
PrivateKey caKey = readPrivateKey(caKeyFile);
175+
176+
// Generate new key pair for the certificate
177+
KeyPair keyPair = generateRSAKeyPair();
178+
179+
// Use DER encoding to preserve exact DN bytes (string round-trip
180+
// mangles emailAddress OID, breaking wolfSSL issuer/subject matching)
181+
X509CertificateHolder caCertHolder = new X509CertificateHolder(caCert.getEncoded());
182+
X500Name issuer = caCertHolder.getSubject();
183+
X500Name subject = new X500Name("CN=" + cn);
184+
185+
Calendar cal = Calendar.getInstance();
186+
Date notBefore = cal.getTime();
187+
cal.add(Calendar.YEAR, 10);
188+
Date notAfter = cal.getTime();
189+
190+
BigInteger serial = new BigInteger(128, new SecureRandom());
191+
192+
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
193+
issuer, serial, notBefore, notAfter, subject, keyPair.getPublic());
194+
195+
// Not a CA
196+
certBuilder.addExtension(Extension.basicConstraints, false,
197+
new BasicConstraints(false));
198+
199+
// Key usage
200+
certBuilder.addExtension(Extension.keyUsage, false,
201+
new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
202+
203+
// Extended key usage - server auth
204+
certBuilder.addExtension(Extension.extendedKeyUsage, false,
205+
new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth));
206+
207+
// Subject Alternative Names
208+
List<GeneralName> sanList = new ArrayList<>();
209+
for (String san : sans) {
210+
if (san.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) {
211+
// IP address
212+
sanList.add(new GeneralName(GeneralName.iPAddress, san));
213+
} else {
214+
// DNS name
215+
sanList.add(new GeneralName(GeneralName.dNSName, san));
216+
}
217+
}
218+
if (!sanList.isEmpty()) {
219+
certBuilder.addExtension(Extension.subjectAlternativeName, false,
220+
new GeneralNames(sanList.toArray(new GeneralName[0])));
221+
}
222+
223+
ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA")
224+
.setProvider("BC").build(caKey);
225+
226+
X509CertificateHolder certHolder = certBuilder.build(signer);
227+
X509Certificate cert = new JcaX509CertificateConverter()
228+
.setProvider("BC").getCertificate(certHolder);
229+
230+
writePem(certOut, cert);
231+
writePem(keyOut, keyPair.getPrivate());
232+
}
233+
234+
/**
235+
* Encrypt a PEM private key with PBES2/PBKDF2-HMAC-SHA256/AES-256-CBC.
236+
*/
237+
private static void encryptKey(String inFile, String outFile, String password) throws Exception {
238+
PrivateKey key = readPrivateKey(inFile);
239+
240+
// Build PKCS#8 encrypted private key using PBES2 with PBKDF2-HMAC-SHA256 and AES-256-CBC
241+
PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(key.getEncoded());
242+
PKCS8EncryptedPrivateKeyInfoBuilder encBuilder =
243+
new PKCS8EncryptedPrivateKeyInfoBuilder(pkInfo);
244+
245+
// Use PBES2 with PBKDF2-HMAC-SHA256 and AES-256-CBC
246+
JcePKCSPBEOutputEncryptorBuilder encryptorBuilder =
247+
new JcePKCSPBEOutputEncryptorBuilder(
248+
NISTObjectIdentifiers.id_aes256_CBC);
249+
encryptorBuilder.setProvider("BC");
250+
encryptorBuilder.setIterationCount(2048);
251+
// Set PRF to HMAC-SHA256 (default is HMAC-SHA1)
252+
encryptorBuilder.setPRF(
253+
new AlgorithmIdentifier(PKCSObjectIdentifiers.id_hmacWithSHA256, DERNull.INSTANCE));
254+
255+
org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo encryptedInfo =
256+
encBuilder.build(encryptorBuilder.build(password.toCharArray()));
257+
258+
// Write as PEM
259+
try (JcaPEMWriter writer = new JcaPEMWriter(new FileWriter(outFile))) {
260+
writer.writeObject(encryptedInfo);
261+
}
262+
}
263+
264+
// --- Helper methods ---
265+
266+
private static KeyPair generateRSAKeyPair() throws Exception {
267+
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
268+
kpg.initialize(2048, new SecureRandom());
269+
return kpg.generateKeyPair();
270+
}
271+
272+
/**
273+
* Read a private key from PEM file (handles both PKCS#1 and PKCS#8 format).
274+
*/
275+
private static PrivateKey readPrivateKey(String file) throws Exception {
276+
try (FileReader reader = new FileReader(file);
277+
PEMParser parser = new PEMParser(reader)) {
278+
279+
Object obj = parser.readObject();
280+
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
281+
282+
if (obj instanceof PEMKeyPair) {
283+
// PKCS#1: -----BEGIN RSA PRIVATE KEY-----
284+
return converter.getPrivateKey(((PEMKeyPair) obj).getPrivateKeyInfo());
285+
} else if (obj instanceof PrivateKeyInfo) {
286+
// PKCS#8: -----BEGIN PRIVATE KEY-----
287+
return converter.getPrivateKey((PrivateKeyInfo) obj);
288+
} else {
289+
throw new IllegalArgumentException(
290+
"Unknown key format in " + file + ": " +
291+
(obj != null ? obj.getClass().getName() : "null"));
292+
}
293+
}
294+
}
295+
296+
/**
297+
* Read an X.509 certificate from PEM file.
298+
*/
299+
private static X509Certificate readCertificate(String file) throws Exception {
300+
try (FileReader reader = new FileReader(file);
301+
PEMParser parser = new PEMParser(reader)) {
302+
303+
Object obj = parser.readObject();
304+
if (obj instanceof X509CertificateHolder) {
305+
return new JcaX509CertificateConverter()
306+
.setProvider("BC").getCertificate((X509CertificateHolder) obj);
307+
} else {
308+
throw new IllegalArgumentException(
309+
"Not a certificate in " + file + ": " +
310+
(obj != null ? obj.getClass().getName() : "null"));
311+
}
312+
}
313+
}
314+
315+
/**
316+
* Write an object as PEM to file.
317+
* Private keys are written in PKCS#8 format (BEGIN PRIVATE KEY).
318+
*/
319+
private static void writePem(String file, Object obj) throws IOException {
320+
try (JcaPEMWriter writer = new JcaPEMWriter(new FileWriter(file))) {
321+
if (obj instanceof PrivateKey) {
322+
// Write as PKCS#8 (BEGIN PRIVATE KEY) not PKCS#1 (BEGIN RSA PRIVATE KEY)
323+
PrivateKey pk = (PrivateKey) obj;
324+
org.bouncycastle.openssl.PKCS8Generator gen =
325+
new org.bouncycastle.openssl.jcajce.JcaPKCS8Generator(pk, null);
326+
writer.writeObject(gen);
327+
} else {
328+
writer.writeObject(obj);
329+
}
330+
}
331+
}
332+
}

0 commit comments

Comments
 (0)