saveJceks method

Uint8List saveJceks(
  1. String storePassword, {
  2. String? keyPassword,
})

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();
}