load static method
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);
}