maxep 5 vuotta sitten
vanhempi
commit
f7dcee5ac9
4 muutettua tiedostoa jossa 98 lisäystä ja 89 poistoa
  1. 10 34
      Sources/KDBX/Database3.swift
  2. 59 40
      Sources/KDBX/Database4.swift
  3. 1 2
      Sources/KDBX/File.swift
  4. 28 13
      Sources/KDBX/Header.swift

+ 10 - 34
Sources/KDBX/Database3.swift

@@ -24,16 +24,16 @@ import XML
 
 class Database3: Database {
 
-    typealias Header = [TLV<OuterHeader, UInt16>]
-
-    let header: Header
+    let header: Header<OuterHeader, UInt16>
 
     let document: Document
 
     required init(from input: Input, compositeKey: CompositeKey) throws {
         header = try input.read()
 
-        guard let startBytes = header[.streamStartBytes] else { throw KDBXError.corruptedDatabase }
+        guard
+            let startBytes: Bytes = header[.streamStartBytes]
+        else { throw KDBXError.corruptedDatabase }
 
         let data = try input.read() as Bytes
 
@@ -47,20 +47,20 @@ class Database3: Database {
 
         guard try stream.read(lenght: SHA256.Lenght) == startBytes else { throw KDBXError.invalidCompositeKey }
 
-        var block: UInt32 = 0
+        var index: UInt32 = 0
         var content = Bytes()
 
         while true {
-            guard try stream.read() == block else { throw KDBXError.corruptedDatabase }
-            block += 1
+            guard try stream.read() == index else { throw KDBXError.corruptedDatabase }
+            index += 1
 
             let hash = try stream.read(lenght: SHA256.Lenght)
             let size: UInt32 = try stream.read()
             guard size > 0 else { break }
 
-            let data = try stream.read(lenght: Int(size))
-            guard SHA256.hash( data ) == hash else { throw KDBXError.corruptedDatabase }
-            content += data
+            let block = try stream.read(lenght: Int(size))
+            guard SHA256.hash( block ) == hash else { throw KDBXError.corruptedDatabase }
+            content += block
         }
 
         if header[.compressionFlags] == Compression.gzip {
@@ -82,27 +82,3 @@ extension Database3: Writable {
         fatalError()
     }
 }
-
-extension Database3.Header: Readable {
-    
-    public init(from input: Input) throws {
-        var header = Database3.Header()
-
-        while true {
-            let field: TLV<OuterHeader, UInt16> = try input.read()
-            header.append(field)
-            if field.type == .end { break }
-        }
-
-        self = header
-    }
-    
-}
-
-extension Database3.Header: Header {
-
-    subscript(_ type: OuterHeader) -> Bytes? {
-        return first(where: { $0.type == type })?.value
-    }
-
-}

+ 59 - 40
Sources/KDBX/Database4.swift

@@ -23,71 +23,90 @@ import XML
 
 class Database4: Database {
 
-    struct Header {
-        let fields: [TLV<OuterHeader, UInt32>]
-        let data: Bytes
-    }
+    let outerHeader: Header<OuterHeader, UInt32>
 
-    let header: Header
+    let innerHeader: Header<InnerHeader, UInt32>
 
     let document: Document
 
     required init(from input: Input, compositeKey: CompositeKey) throws {
-        header = try input.read()
+        outerHeader = try input.read()
 
-        var key = try header.masterKey(from: compositeKey)
-        let hmacKey = SHA512.hash( UInt64.max.bytes + SHA512.hash( key + 1 ) )
-        key = SHA256.hash( key )
+        let masterKey = try outerHeader.masterKey(from: compositeKey)
 
-        let data = try input.read() as Bytes
-        let stream = Input(bytes: data)
+        // Get outer header bytes to verify the hash
+        let header = input.bytes.prefix(input.offset)
+        var content = try Database4.unhash(header: header,
+                                           data: try input.read(),
+                                           key: masterKey)
 
-        guard
-            try stream.read(lenght: SHA256.Lenght) == SHA256.hash( header.data ),
-            try stream.read(lenght: SHA256.Lenght) == HMACSHA256.authenticate(header.data, key: hmacKey)
-        else { throw KDBXError.corruptedDatabase }
+        let key = SHA256.hash( masterKey )
+        let cipher = try outerHeader.cipher(key: key)
+        content = try cipher.decrypt(data: content)
 
-        let cipher = try header.cipher(key: key)
-        let hash = try cipher.decrypt(data: data)
+        if outerHeader[.compressionFlags] == Compression.gzip {
+            content = try content.gunzipped()
+        }
 
-        fatalError()
+        let stream = Input(bytes: content)
+        innerHeader = try stream.read()
+
+        var options = XML.Options()
+        options.parserSettings.shouldTrimWhitespace = false
+
+        document = try XML.Document(xml: stream.remaining.data, options: options)
     }
 
-}
+    class func unhash(header: Bytes, data: Bytes, key: Bytes) throws -> Bytes {
+        let stream = Input(bytes: data)
 
-extension Database4: Writable {
+        let key = SHA512.hash( key + 1 )
+        let hmacKey = HmacKey(block: .max, key: key)
 
-    func write(to output: Output) throws {
-        try output.write(header)
-        fatalError()
-    }
+        guard
+            try stream.read(lenght: SHA256.Lenght) == SHA256.hash( header ),
+            try stream.read(lenght: HMACSHA256.Lenght) == HMACSHA256.authenticate(header, key: hmacKey)
+        else { throw KDBXError.invalidCompositeKey }
 
-}
+        var index: UInt64 = 0
+        var content = Bytes()
 
-extension Database4.Header: Streamable {
+        while stream.hasBytesAvailable {
 
-    init(from input: Input) throws {
-        var fields = [TLV<OuterHeader, UInt32>]()
+            let hmac = try stream.read(lenght: HMACSHA256.Lenght)
+            let size = try CFSwapInt32LittleToHost(stream.read())
+            let block = try stream.read(lenght: Int(size))
 
-        while true {
-            let field: TLV<OuterHeader, UInt32> = try input.read()
-            fields.append(field)
-            if field.type == .end { break }
+            let hmacKey = HmacKey(block: index, key: key)
+            let hash = HMACSHA256(key: hmacKey)
+            hash.update(CFSwapInt64HostToLittle(index).bytes)
+            hash.update(CFSwapInt32HostToLittle(size).bytes)
+            hash.update(block)
+
+            guard hash.final == hmac else { throw KDBXError.corruptedDatabase }
+            content += block
+            index += 1
         }
 
-        self.fields = fields
-        self.data = input.bytes.prefix(input.offset)
+        return content
     }
 
+}
+
+extension Database4: Writable {
+
     func write(to output: Output) throws {
-        try output.write(fields)
+        try output.write(outerHeader)
+        fatalError()
     }
 
 }
 
-extension Database4.Header: Header {
-
-    subscript(_ field: OuterHeader) -> Bytes? {
-        return fields.first(where: { $0.type == field })?.value
-    }
+func HmacKey(block index: UInt64, key: Bytes) -> Bytes {
+    // Ensure endianess
+    let index = CFSwapInt64LittleToHost(index)
+    let hash = SHA512()
+    hash.update(index.bytes)
+    hash.update(key)
+    return hash.final
 }

+ 1 - 2
Sources/KDBX/File.swift

@@ -64,7 +64,7 @@ public class File {
 
         guard try stream.read() == FileSignature else { throw KDBXError.invalidFileFormat }
 
-        let format = try stream.read() as UInt32
+        let format: UInt32 = try stream.read()
         guard
             format == BetaFileFormat ||
             format == FileFormat
@@ -78,7 +78,6 @@ extension File: Writable {
 
     public func write(to output: Output) throws {
         try output.write(version)
-        // `try output.write(version)` error: Protocol type 'Writable & Database' cannot conform to 'Writable' because only concrete types can conform to protocols
         try database.write(to: output)
     }
 

+ 28 - 13
Sources/KDBX/Header.swift

@@ -20,11 +20,9 @@ import Foundation
 import Binary
 import Crypto
 
-protocol Header {
-    subscript(_ type: OuterHeader) -> Bytes? { get }
-}
+typealias Header<T, L> = [TLV<T, L>] where L: BinaryInteger
 
-enum OuterHeader: UInt8, Streamable {
+enum OuterHeader: UInt8, Streamable, Endable {
     case end                 = 0
     case comment             = 1
     case cipherID            = 2
@@ -38,13 +36,17 @@ enum OuterHeader: UInt8, Streamable {
     case innerRandomStreamID = 10
     case kdfParameters       = 11
     case publicCustomData    = 12
+
+    var isAtEnd: Bool { self == .end }
 }
 
-enum InnerHeader: UInt8, Streamable {
+enum InnerHeader: UInt8, Streamable, Endable {
     case end                  = 0
     case innerRandomStreamID  = 1
     case innerRandomStreamKey = 2
     case binary               = 3
+
+    var isAtEnd: Bool { self == .end }
 }
 
 enum Compression: UInt32, BytesRepresentable {
@@ -61,17 +63,12 @@ enum RandomStream: UInt32, BytesRepresentable {
     case count      = 4
 }
 
-extension Header {
-
-    subscript<T>(_ type: OuterHeader) -> T? where T: BytesRepresentable {
-        guard let bytes = self[type] else { return nil }
-        return try? T(bytes)
-    }
+extension Array where Element: TLVProtocol, Element.`Type` == OuterHeader, Element.Value == Bytes {
 
     func cipher(key: Bytes) throws -> Cipher {
         guard
             let uuid: UUID = self[.cipherID],
-            let iv = self[.initialVector]
+            let iv: Bytes = self[.initialVector]
         else { throw KDBXError.corruptedDatabase }
 
         switch uuid {
@@ -87,7 +84,10 @@ extension Header {
     }
 
     func masterKey(from compositeKey: CompositeKey) throws -> Bytes {
-        guard let masterSeed = self[.masterSeed] else { throw KDBXError.corruptedDatabase }
+        guard
+            let masterSeed: Bytes = self[.masterSeed]
+        else { throw KDBXError.corruptedDatabase }
+
         let key = try compositeKey.serialize()
 
         // Key Derivation
@@ -117,3 +117,18 @@ extension Header {
         }
     }
 }
+
+extension Array: Readable where Element: TLVProtocol & Readable, Element.`Type`: Endable {
+
+    public init(from input: Input) throws {
+        var fields: [Element] = []
+
+        while true {
+            let field: Element = try input.read()
+            fields.append(field)
+            if field.type.isAtEnd { break }
+        }
+
+        self = fields
+    }
+}