// Header.swift
// This file is part of KeePassKit.
//
// Copyright © 2019 Maxime Epain. All rights reserved.
//
// KeePassKit is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// KeePassKit is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with KeePassKit. If not, see .
import Foundation
import Binary
import Crypto
protocol Header {
subscript(_ type: OuterHeader) -> Bytes? { get }
}
enum OuterHeader: UInt8, Streamable {
case end = 0
case comment = 1
case cipherID = 2
case compressionFlags = 3
case masterSeed = 4
case transformSeed = 5
case transformRounds = 6
case initialVector = 7
case protectedStreamKey = 8
case streamStartBytes = 9
case innerRandomStreamID = 10
case kdfParameters = 11
case publicCustomData = 12
}
enum InnerHeader: UInt8, Streamable {
case end = 0
case innerRandomStreamID = 1
case innerRandomStreamKey = 2
case binary = 3
}
enum Compression: UInt32, BytesRepresentable {
case none = 0
case gzip = 1
case count = 2
}
enum RandomStream: UInt32, BytesRepresentable {
case none = 0
case arc4 = 1
case salsa20 = 2
case chacha20 = 3
case count = 4
}
extension Header {
subscript(_ type: OuterHeader) -> T? where T: BytesRepresentable {
guard let bytes = self[type] else { return nil }
return try? T(bytes)
}
func cipher(key: Bytes) throws -> Cipher {
guard
let uuid: UUID = self[.cipherID],
let iv = self[.initialVector]
else { throw KDBXError.corruptedDatabase }
switch uuid {
case AESCipher.UUID:
return try AESCipher(key: key, iv: iv)
case Twofish.UUID:
return try Twofish(key: key, iv: iv)
case ChaCha20.UUID:
return try ChaCha20(key: key, iv: iv)
default:
throw KDBXError.unsupportedCipher
}
}
func masterKey(from compositeKey: CompositeKey) throws -> Bytes {
guard let masterSeed = self[.masterSeed] else { throw KDBXError.corruptedDatabase }
let key = try compositeKey.serialize()
// Key Derivation
let derivedKey = try kdf().derive(key: key)
return masterSeed + derivedKey
}
func kdf() throws -> KeyDerivation {
if let seed: Bytes = self[.transformSeed], let rounds: UInt64 = self[.transformRounds] {
return try AESKeyDerivation(seed: seed, rounds: rounds)
}
guard
let parameters: KeyDerivationParameters = self[.kdfParameters],
let uuid: UUID = try parameters["$UUID"]?.unwrap()
else { throw KDBXError.corruptedDatabase }
switch uuid {
case Argon2.UUID:
return try Argon2(parameters: parameters)
case AESKeyDerivation.UUID:
return try AESKeyDerivation(parameters: parameters)
default:
throw KDBXError.unsupportedKeyDerivation
}
}
}