Database4.swift 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  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 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. func write(to output: Output, compositeKey: CompositeKey) throws {
  47. fatalError()
  48. }
  49. }
  50. private func Unhash(header: Bytes, data: Bytes, key: Bytes) throws -> Bytes {
  51. let stream = Input(bytes: data)
  52. let key = SHA512.hash( key + 1 )
  53. let hmacKey = HmacKey(block: .max, key: key)
  54. guard
  55. try stream.read(lenght: SHA256.Lenght) == SHA256.hash( header ),
  56. try stream.read(lenght: HMACSHA256.Lenght) == HMACSHA256.authenticate(header, key: hmacKey)
  57. else { throw KDBXError.invalidCompositeKey }
  58. var index: UInt64 = 0
  59. var content = Bytes()
  60. while stream.hasBytesAvailable {
  61. let hmac = try stream.read(lenght: HMACSHA256.Lenght)
  62. let size = try CFSwapInt32LittleToHost(stream.read())
  63. let block = try stream.read(lenght: Int(size))
  64. let hmacKey = HmacKey(block: index, key: key)
  65. let hash = HMACSHA256(key: hmacKey)
  66. hash.update(CFSwapInt64HostToLittle(index).bytes)
  67. hash.update(CFSwapInt32HostToLittle(size).bytes)
  68. hash.update(block)
  69. guard hash.final == hmac else { throw KDBXError.corruptedDatabase }
  70. content += block
  71. index += 1
  72. }
  73. return content
  74. }
  75. func HmacKey(block index: UInt64, key: Bytes) -> Bytes {
  76. // Ensure endianess
  77. let index = CFSwapInt64LittleToHost(index)
  78. let hash = SHA512()
  79. hash.update(index.bytes)
  80. hash.update(key)
  81. return hash.final
  82. }