WIP 보관함 형식 번호 2 (2단계 폴더) - 1단계 보관함은 열 때 번호만 올림 (검토 대기)
관리자 3a 지시 (10/21): 1단계 형식만 아는 판이 2단계 보관함을 열면 '새 판으로' 를 알리게. 열쇠가 맞은 뒤에만 vault.json 의 format 만 바꾼다 (솔트 · 감싼 열쇠는 그대로). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>xplore_p3b
parent
8fa270a62e
commit
5a5ecacb31
|
|
@ -49,7 +49,8 @@ class Vault {
|
|||
final Set<String> _folders;
|
||||
Vault._(this.dir, this._key, this._entries, [Set<String>? folders]) : _folders = folders ?? {};
|
||||
|
||||
static const format = 1;
|
||||
/// 1 = 1단계, 2 = 2단계 (보관함 안 폴더 - 관리자 지시 10/21: 1단계만 아는 판은 "새 판으로" 를 알리게)
|
||||
static const format = 2;
|
||||
static const _json = 'vault.json', _index = 'index.jjv', _ext = '.jjv', _folderFile = 'folders.jjd';
|
||||
|
||||
List<VaultEntry> get entries => List.unmodifiable(_entries);
|
||||
|
|
@ -101,18 +102,21 @@ class Vault {
|
|||
final iterations = (j['iterations'] as num).toInt();
|
||||
final kek = await Isolate.run(() => VaultCrypto.kek(password, salt, iterations));
|
||||
final key = VaultCrypto.unwrapKey(kek, base64.decode('${j['nonce']}'), base64.decode('${j['key']}'));
|
||||
return _open(dir, key);
|
||||
return _open(dir, key, j);
|
||||
}
|
||||
|
||||
/// 안전 저장소의 열쇠로 연다 ([마스터로도 열기])
|
||||
static Future<Vault> unlockWithKey(Directory dir, Uint8List key) async {
|
||||
await _readHeader(dir);
|
||||
return _open(dir, key);
|
||||
return _open(dir, key, await _readHeader(dir));
|
||||
}
|
||||
|
||||
static Future<Vault> _open(Directory dir, Uint8List key) async {
|
||||
static Future<Vault> _open(Directory dir, Uint8List key, Map<String, dynamic> header) async {
|
||||
final data = await File(p.join(dir.path, _index)).readAsBytes();
|
||||
final list = jsonDecode(utf8.decode(VaultCrypto.decryptAll(key, data))) as List;
|
||||
// 1단계 보관함은 열쇠가 맞은 뒤 형식 번호만 올린다 (감싼 열쇠 · 솔트는 그대로)
|
||||
if ((header['format'] as int) < format) {
|
||||
await _atomicWrite(File(p.join(dir.path, _json)), utf8.encode(const JsonEncoder.withIndent(' ').convert({...header, 'format': format})));
|
||||
}
|
||||
final folders = <String>{};
|
||||
final ff = File(p.join(dir.path, _folderFile));
|
||||
if (await ff.exists()) {
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ void main() {
|
|||
expect(dir.listSync().length, 2, reason: 'vault.json · index.jjv 만');
|
||||
await expectLater(Vault.create(dir, 'x', iterations: 1000), throwsA(isA<StateError>()));
|
||||
final j = File(p.join(dir.path, 'vault.json'));
|
||||
j.writeAsStringSync(j.readAsStringSync().replaceFirst('"format": 1', '"format": 2'));
|
||||
j.writeAsStringSync(j.readAsStringSync().replaceFirst('"format": 2', '"format": 3'));
|
||||
await expectLater(Vault.unlock(dir, 'pw'), throwsA(isA<VaultFormatException>()));
|
||||
});
|
||||
|
||||
|
|
@ -262,6 +262,16 @@ void main() {
|
|||
File(p.join(dir.path, 'index.jjv')).writeAsBytesSync(VaultCrypto.encryptAll(key, Uint8List.fromList(utf8.encode(jsonEncode(bad)))));
|
||||
final again = await Vault.unlock(dir, 'pw');
|
||||
expect(again.entries.last.folder, '');
|
||||
// 1단계 (형식 1) 보관함: 열리고, 열쇠가 맞은 뒤 형식 번호만 2 로 (1단계만 아는 판은 "새 판으로" 를 알림)
|
||||
final j = File(p.join(dir.path, 'vault.json'));
|
||||
final before = jsonDecode(j.readAsStringSync()) as Map;
|
||||
j.writeAsStringSync(j.readAsStringSync().replaceFirst('"format": 2', '"format": 1'));
|
||||
await expectLater(Vault.unlock(dir, 'wrong'), throwsA(isA<VaultAuthException>()));
|
||||
expect((jsonDecode(j.readAsStringSync()) as Map)['format'], 1, reason: '틀린 비밀번호면 바꾸지 않음');
|
||||
await Vault.unlock(dir, 'pw');
|
||||
final after = jsonDecode(j.readAsStringSync()) as Map;
|
||||
expect(after['format'], 2);
|
||||
expect([after['salt'], after['key'], after['nonce']], [before['salt'], before['key'], before['nonce']]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue