Database.swift 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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 let root: Group
  26. public required init(from input: Input, compositeKey: CompositeKey) throws {
  27. header = try input.read()
  28. let data: Bytes = try input.read()
  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 Root(from: stream, groups: Int(header.groups), entries: Int(header.entries))
  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. public func write(to output: Output, compositeKey: CompositeKey) throws {
  55. func groups(in group: Group) -> UInt32 {
  56. let count = UInt32(group.childs.count)
  57. return group.childs.reduce(count) { $0 + groups(in: $1) }
  58. }
  59. func entries(in group: Group) -> UInt32 {
  60. let count = UInt32(group.entries.count)
  61. return group.childs.reduce(count) { $0 + entries(in: $1) }
  62. }
  63. let key = try header.masterKey(from: compositeKey)
  64. let cipher: Cipher
  65. if header.cipher.contains(.aes) {
  66. cipher = try AESCipher(key: key, iv: header.initialVector)
  67. } else if header.cipher.contains(.twofish) {
  68. cipher = try Twofish(key: key, iv: header.initialVector)
  69. } else {
  70. throw KDBError.unsupportedCipher
  71. }
  72. let content = try Data(from: root)
  73. var header = header
  74. header.contentHash = SHA256.hash(content)
  75. header.groups = groups(in: root)
  76. header.entries = entries(in: root)
  77. try output.write(header)
  78. let data = try cipher.encrypt(data: content)
  79. try output.write(data)
  80. }
  81. }
  82. private func Root(from input: Input, groups: Int, entries: Int) throws -> Group {
  83. let groups: [Group] = try input.read(maxLenght: groups)
  84. let entries: [Entry] = try input.read(maxLenght: entries)
  85. let root = Group()
  86. for (i, group) in groups.enumerated() {
  87. guard let level1: UInt16 = group[.groupLevel] else { throw KDBError.corruptedDatabase }
  88. if level1 == 0 {
  89. root.add(group)
  90. continue
  91. }
  92. for (j, parent) in groups[0..<i].enumerated().reversed() {
  93. guard let level2: UInt16 = parent[.groupLevel] else { throw KDBError.corruptedDatabase }
  94. if level2 < level1 {
  95. guard (level1 - level2) == 1 else { throw KDBError.corruptedDatabase }
  96. parent.add(group)
  97. break
  98. }
  99. guard j > 0 else { throw KDBError.corruptedDatabase }
  100. }
  101. }
  102. for entry in entries {
  103. guard let groupID: UInt32 = entry[.groupID] else { throw KDBError.corruptedDatabase }
  104. let group = try groups.first(column: .groupID, where: { $0 == groupID }) ?? root
  105. group.add(entry)
  106. }
  107. return root
  108. }
  109. private func Data(from root: Group) throws -> Bytes {
  110. fatalError()
  111. }