generate static method
Generate a CA plus a Hub server certificate into outputDir.
hosts are extra SAN DNS entries; localhost and 127.0.0.1 are always
included. commonName is the server certificate's CN, caCommonName the
CA's. caDays/serverDays set validity. If <outputDir>/server.crt
already exists, the call throws unless force is set.
Implementation
static Future<GeneratedCertificates> generate({
required String outputDir,
List<String> hosts = const [],
String commonName = 'localhost',
String caCommonName = 'OmnyShell Dev CA',
int caDays = 3650,
int serverDays = 825,
bool force = false,
}) async {
final caCert = '$outputDir/ca.crt';
final caKey = '$outputDir/ca.key';
final serverCert = '$outputDir/server.crt';
final serverKey = '$outputDir/server.key';
final serverCsr = '$outputDir/server.csr';
final serverLeaf = '$outputDir/server-leaf.crt';
final caSerial = '$outputDir/ca.srl';
if (!force && File(serverCert).existsSync()) {
throw const CertGeneratorException(
'certificates already exist in the output directory — '
'pass --force to regenerate',
);
}
await _requireOpenssl();
Directory(outputDir).createSync(recursive: true);
// Subject Alternative Names the server certificate is valid for.
final san = StringBuffer('DNS:localhost,IP:127.0.0.1');
for (final host in hosts) {
if (host.isNotEmpty) san.write(',DNS:$host');
}
// 1. Local CA.
await _openssl([
'req',
'-x509',
'-newkey',
'rsa:2048',
'-nodes',
'-keyout',
caKey,
'-out',
caCert,
'-days',
'$caDays',
'-subj',
'/CN=$caCommonName',
'-addext',
'basicConstraints=critical,CA:TRUE',
'-addext',
'keyUsage=critical,keyCertSign,cRLSign',
]);
// 2. Server key + CSR.
await _openssl([
'req',
'-newkey',
'rsa:2048',
'-nodes',
'-keyout',
serverKey,
'-out',
serverCsr,
'-subj',
'/CN=$commonName',
]);
// 3. Sign the server certificate with the CA. The extensions go in a temp
// file (bash process substitution has no Dart equivalent).
final extFile = File(
'${Directory.systemTemp.path}/omnyshell-cert-ext-$pid.cnf',
);
try {
extFile.writeAsStringSync(
'subjectAltName=$san\n'
'basicConstraints=critical,CA:FALSE\n'
'keyUsage=critical,digitalSignature,keyEncipherment\n'
'extendedKeyUsage=serverAuth\n',
);
await _openssl([
'x509',
'-req',
'-in',
serverCsr,
'-CA',
caCert,
'-CAkey',
caKey,
'-CAcreateserial',
'-out',
serverLeaf,
'-days',
'$serverDays',
'-extfile',
extFile.path,
]);
} finally {
if (extFile.existsSync()) extFile.deleteSync();
}
// 4. The Hub presents the full chain (leaf + CA) so clients can build the
// verification path.
File(serverCert).writeAsStringSync(
File(serverLeaf).readAsStringSync() + File(caCert).readAsStringSync(),
);
// 5. Clean up intermediates.
for (final path in [serverCsr, serverLeaf, caSerial]) {
final f = File(path);
if (f.existsSync()) f.deleteSync();
}
return GeneratedCertificates(
caCert: caCert,
caKey: caKey,
serverCert: serverCert,
serverKey: serverKey,
);
}