load static method

JksKeyStore load(
  1. Uint8List bytes, {
  2. String storePassword = '',
})

Loads a JKS keystore from bytes.

Implementation

static JksKeyStore load(Uint8List bytes, {String storePassword = ''}) {
  final entries = <String, KeystoreEntry>{};
  final data = ByteData.sublistView(bytes);
  int offset = 0;

  // Magic
  if (data.getUint32(offset) != 0xFEEDFEED) {
    throw Exception('Invalid JKS magic number');
  }
  offset += 4;

  // Version
  final version = data.getUint32(offset);
  if (version != 2) {
    throw Exception('Unsupported JKS version: $version');
  }
  offset += 4;

  // Count
  final count = data.getUint32(offset);
  offset += 4;

  for (int i = 0; i < count; i++) {
    // Tag
    final tag = data.getUint32(offset);
    offset += 4;

    // Alias
    final aliasLen = data.getUint16(offset);
    offset += 2;
    final aliasBytes = bytes.sublist(offset, offset + aliasLen);
    final alias = utf8.decode(aliasBytes);
    offset += aliasLen;

    // Timestamp
    final timestamp = _readUint64BigEndian(data, offset);
    offset += 8;

    if (tag == 1) {
      // Private Key
      // Certificate Chain
      final chainLen = data.getUint32(offset);
      offset += 4;

      final certChain = <(String, Uint8List)>[];
      for (int j = 0; j < chainLen; j++) {
        // Cert Type
        final certTypeLen = data.getUint16(offset);
        offset += 2;
        final certTypeBytes = bytes.sublist(offset, offset + certTypeLen);
        final certType = utf8.decode(certTypeBytes);
        offset += certTypeLen;

        // Cert Data
        final certLen = data.getUint32(offset);
        offset += 4;
        final certData = bytes.sublist(offset, offset + certLen);
        offset += certLen;

        certChain.add((certType, certData));
      }

      // Private Key Bytes (Encrypted)
      final keyLen = data.getUint32(offset);
      offset += 4;
      final keyBytes = bytes.sublist(offset, offset + keyLen);
      offset += keyLen;

      entries[alias] = JksPrivateKeyEntry(
        alias: alias,
        timestamp: timestamp,
        storeType: 'jks',
        certChain: certChain,
        encryptedData: keyBytes,
      );
    } else if (tag == 2) {
      // Trusted Cert
      // Cert Type
      final certTypeLen = data.getUint16(offset);
      offset += 2;
      final certTypeBytes = bytes.sublist(offset, offset + certTypeLen);
      final certType = utf8.decode(certTypeBytes);
      offset += certTypeLen;

      // Cert Data
      final certLen = data.getUint32(offset);
      offset += 4;
      final certData = bytes.sublist(offset, offset + certLen);
      offset += certLen;

      entries[alias] = TrustedCertEntry(
        alias: alias,
        timestamp: timestamp,
        storeType: 'jks',
        certType: certType,
        certData: certData,
      );
    } else {
      throw Exception('Unknown JKS tag: $tag');
    }
  }

  // Integrity Check
  if (storePassword.isNotEmpty) {
    final passwordBytes = utf16BeEncode(storePassword);
    final phrase = ascii.encode("Mighty Aphrodite");

    // Hash(password || phrase || data)
    // Data is everything before the digest.
    // The digest is the last 20 bytes of the file.
    // However, we just read 'offset' bytes so far.
    // If offset < bytes.length, the remaining bytes should be the digest.

    if (bytes.length - offset == 20) {
      final storedDigest = bytes.sublist(offset);
      // Calculate digest
      // The "data" includes the Magic, Version, Count, and all Entries.
      // Which is everything from 0 to offset.
      final dataToHash = bytes.sublist(0, offset);

      final input =
          Uint8List(passwordBytes.length + phrase.length + dataToHash.length);
      input.setAll(0, passwordBytes);
      input.setAll(passwordBytes.length, phrase);
      input.setAll(passwordBytes.length + phrase.length, dataToHash);

      final computedDigest = _pkiCrypto.sha1Sync(input);

      if (!fixedTimeEqual(computedDigest, storedDigest)) {
        // Java: Keystore was tampered with, or password was incorrect
        throw Exception('Keystore password incorrect or data corrupted');
      }
    }
  }

  return JksKeyStore._(entries);
}