Database4.swift 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. // Database4.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. import XML
  22. class Database4: Database {
  23. let outerHeader: Header<OuterHeader, UInt32>
  24. let innerHeader: Header<InnerHeader, UInt32>
  25. let document: Document
  26. required init(from input: Input, compositeKey: CompositeKey) throws {
  27. outerHeader = try input.read()
  28. let masterKey = try outerHeader.masterKey(from: compositeKey)
  29. // Get outer header bytes to verify the hash
  30. let header = input.bytes.prefix(input.offset)
  31. var content = try Database4.unhash(header: header,
  32. data: try input.read(),
  33. key: masterKey)
  34. let key = SHA256.hash( masterKey )
  35. let cipher = try outerHeader.cipher(key: key)
  36. content = try cipher.decrypt(data: content)
  37. if outerHeader[.compressionFlags] == Compression.gzip {
  38. content = try content.gunzipped()
  39. }
  40. let stream = Input(bytes: content)
  41. innerHeader = try stream.read()
  42. var options = XML.Options()
  43. options.parserSettings.shouldTrimWhitespace = false
  44. document = try XML.Document(xml: stream.remaining.data, options: options)
  45. }
  46. class func unhash(header: Bytes, data: Bytes, key: Bytes) throws -> Bytes {
  47. let stream = Input(bytes: data)
  48. let key = SHA512.hash( key + 1 )
  49. let hmacKey = HmacKey(block: .max, key: key)
  50. guard
  51. try stream.read(lenght: SHA256.Lenght) == SHA256.hash( header ),
  52. try stream.read(lenght: HMACSHA256.Lenght) == HMACSHA256.authenticate(header, key: hmacKey)
  53. else { throw KDBXError.invalidCompositeKey }
  54. var index: UInt64 = 0
  55. var content = Bytes()
  56. while stream.hasBytesAvailable {
  57. let hmac = try stream.read(lenght: HMACSHA256.Lenght)
  58. let size = try CFSwapInt32LittleToHost(stream.read())
  59. let block = try stream.read(lenght: Int(size))
  60. let hmacKey = HmacKey(block: index, key: key)
  61. let hash = HMACSHA256(key: hmacKey)
  62. hash.update(CFSwapInt64HostToLittle(index).bytes)
  63. hash.update(CFSwapInt32HostToLittle(size).bytes)
  64. hash.update(block)
  65. guard hash.final == hmac else { throw KDBXError.corruptedDatabase }
  66. content += block
  67. index += 1
  68. }
  69. return content
  70. }
  71. }
  72. extension Database4: Writable {
  73. func write(to output: Output) throws {
  74. try output.write(outerHeader)
  75. fatalError()
  76. }
  77. }
  78. func HmacKey(block index: UInt64, key: Bytes) -> Bytes {
  79. // Ensure endianess
  80. let index = CFSwapInt64LittleToHost(index)
  81. let hash = SHA512()
  82. hash.update(index.bytes)
  83. hash.update(key)
  84. return hash.final
  85. }