Database.swift 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. // Database.swift
  2. // This file is part of KeePassKit.
  3. //
  4. // Copyright © 2019 Maxime Epain. All rights reserved.
  5. //
  6. // KeePassKit is free software: you can redistribute it and/or modify
  7. // it under the terms of the GNU General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // KeePassKit is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU General Public License
  17. // along with KeePassKit. If not, see <https://www.gnu.org/licenses/>.
  18. import Foundation
  19. import Binary
  20. import Crypto
  21. public let FileSignature: UInt32 = 0x9AA2D903
  22. public let FileFormat: UInt32 = 0xB54BFB65
  23. public class Database {
  24. let header: Header
  25. public private(set) var root: Group!
  26. public required init(from input: Input, compositeKey: CompositeKey) throws {
  27. header = try input.read()
  28. let data = try input.read() as Bytes
  29. let key = try header.masterKey(from: compositeKey)
  30. let cipher: Cipher
  31. if header.cipher.contains(.aes) {
  32. cipher = try AESCipher(key: key, iv: header.initialVector)
  33. } else if header.cipher.contains(.twofish) {
  34. cipher = try Twofish(key: key, iv: header.initialVector)
  35. } else {
  36. throw KDBError.unsupportedCipher
  37. }
  38. let content = try cipher.decrypt(data: data)
  39. guard SHA256.hash(content) == header.contentHash else {
  40. throw KDBError.invalidKey
  41. }
  42. let stream = Input(bytes: content)
  43. self.root = try tree(from: stream)
  44. }
  45. public convenience init(from file: URL, compositeKey: CompositeKey) throws {
  46. let bytes = try Bytes(contentsOf: file)
  47. let stream = Input(bytes: bytes)
  48. guard
  49. try stream.read() == FileSignature,
  50. try stream.read() == FileFormat
  51. else { throw KDBError.invalidFileFormat }
  52. try self.init(from: stream, compositeKey: compositeKey)
  53. }
  54. }
  55. extension Database: Writable {
  56. public func write(to output: Output) throws {
  57. try output.write(header)
  58. fatalError()
  59. }
  60. }
  61. extension Database {
  62. private func tree(from input: Input) throws -> Group {
  63. let groups: [Group] = try input.read(maxLenght: Int(header.groups))
  64. let entries: [Entry] = try input.read(maxLenght: Int(header.entries))
  65. let root = Group()
  66. for (i, group) in groups.enumerated() {
  67. guard let level1: UInt16 = group[.groupLevel] else { throw KDBError.corruptedDatabase }
  68. if level1 == 0 {
  69. root.childs.append(group)
  70. continue
  71. }
  72. for (j, parent) in groups[0..<i].enumerated().reversed() {
  73. guard let level2: UInt16 = parent[.groupLevel] else { throw KDBError.corruptedDatabase }
  74. if level2 < level1 {
  75. guard (level1 - level2) == 1 else { throw KDBError.corruptedDatabase }
  76. parent.childs.append(group)
  77. break
  78. }
  79. guard j > 0 else { throw KDBError.corruptedDatabase }
  80. }
  81. }
  82. for entry in entries {
  83. guard let groupID: UInt32 = entry[.groupID] else { throw KDBError.corruptedDatabase }
  84. let group = try groups.first(where: .groupID, { $0 == groupID }) ?? root
  85. group.entries.append(entry)
  86. }
  87. return root
  88. }
  89. }