saveJceks method
Saves the keystore to JCEKS format bytes (version 2 with JCEKS magic).
JCEKS uses a different private key protection mechanism (PBEWithMD5AndTripleDES) and includes the magic 0xCECECECE.
Implementation
Uint8List saveJceks(String storePassword, {String? keyPassword}) {
final entryList = entries.values.toList();
final body = BytesBuilder();
// JCEKS Magic: 0xCECECECE
body.add(_int32ToBytes(0xCECECECE));
// Version
body.add(_int32ToBytes(2));
// Count
body.add(_int32ToBytes(entryList.length));
for (final entry in entryList) {
if (entry is JksPrivateKeyEntry || entry is PrivateKeyEntry) {
// Tag (1) - Private Key
body.add(_int32ToBytes(1));
// Alias
_writeJavaUtf(body, entry.alias);
// Timestamp
body.add(_int64ToBytes(entry.timestamp));
// Private Key Protection (JCEKeyProtector: PBEWithMD5AndTripleDES)
// Note: JCEKS stores EncryptedPrivateKeyInfo DER structure.
final pke = entry as PrivateKeyEntry;
final kPwd = keyPassword ?? storePassword;
Uint8List? pkcs8 = pke.pkcs8PrivateKey;
if (pkcs8 == null && pke.rawPrivateKey != null) {
pkcs8 = pke.rawPrivateKey; // Simplify assumption
}
if (pkcs8 == null) {
throw KeystoreException(
'Cannot save private key entry ${entry.alias}: no private key loaded');
}
// Encrypt to EncryptedPrivateKeyInfo (DER)
final protectedKey = _jceksProtectPrivateKey(pkcs8, kPwd);
body.add(_int32ToBytes(protectedKey.length));
body.add(protectedKey);
// Certificate Chain
if (pke.certChain.isEmpty) {
body.add(_int32ToBytes(0));
} else {
body.add(_int32ToBytes(pke.certChain.length));
for (final cert in pke.certChain) {
_writeJavaUtf(body, cert.$1);
body.add(_int32ToBytes(cert.$2.length));
body.add(cert.$2);
}
}
} else if (entry is TrustedCertEntry) {
// Tag (2) - Trusted Cert
body.add(_int32ToBytes(2));
_writeJavaUtf(body, entry.alias);
body.add(_int64ToBytes(entry.timestamp));
_writeJavaUtf(body, entry.certType);
body.add(_int32ToBytes(entry.certData.length));
body.add(entry.certData);
} else {
throw KeystoreException(
'Unsupported entry type for JCEKS: ${entry.runtimeType}');
}
}
final bodyBytes = body.toBytes();
// Integrity Hash (Same logic as JKS: SHA1 of password+whitener+body)
final passwordBytes = utf16BeEncode(storePassword);
final phrase = ascii.encode("Mighty Aphrodite");
final hashInput = BytesBuilder();
hashInput.add(passwordBytes);
hashInput.add(phrase);
hashInput.add(bodyBytes);
final computedDigest = _pkiCrypto.sha1Sync(hashInput.toBytes());
final finalKeystore = BytesBuilder();
finalKeystore.add(bodyBytes);
finalKeystore.add(computedDigest);
return finalKeystore.toBytes();
}