PushNotificationReceiver.swift 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import Foundation
  2. import Security
  3. public struct PushNotificationReceiver: Codable, Equatable, Hashable {
  4. public let privateKeyData: Data
  5. public let publicKeyData: Data
  6. public let authentication: Data
  7. }
  8. extension PushNotificationReceiver {
  9. public init() throws {
  10. var error: Unmanaged<CFError>?
  11. guard let privateKey = SecKeyCreateRandomKey([
  12. kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
  13. kSecAttrKeySizeInBits as String: 256,
  14. ] as CFDictionary, &error) else {
  15. throw PushNotificationReceiverErrorType.creatingKeyFailed(error?.takeRetainedValue())
  16. }
  17. guard let privateKeyData = SecKeyCopyExternalRepresentation(privateKey, &error) as Data? else {
  18. throw PushNotificationReceiverErrorType.extractingPrivateKeyFailed(error?.takeRetainedValue())
  19. }
  20. guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
  21. throw PushNotificationReceiverErrorType.impossible
  22. }
  23. guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
  24. throw PushNotificationReceiverErrorType.extractingPublicKeyFailed(error?.takeRetainedValue())
  25. }
  26. var authentication = Data(count: 16)
  27. try authentication.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
  28. guard SecRandomCopyBytes(kSecRandomDefault, 16, bytes) == errSecSuccess else {
  29. throw PushNotificationReceiverErrorType.creatingRandomDataFailed(error?.takeRetainedValue())
  30. }
  31. }
  32. self.init(
  33. privateKeyData: privateKeyData,
  34. publicKeyData: publicKeyData,
  35. authentication: authentication
  36. )
  37. }
  38. }
  39. extension PushNotificationReceiver {
  40. func decrypt(payload: Data, salt: Data, serverPublicKeyData: Data) throws -> Data {
  41. var error: Unmanaged<CFError>?
  42. guard let privateKey = SecKeyCreateWithData(privateKeyData as CFData,[
  43. kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
  44. kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
  45. kSecAttrKeySizeInBits as String: 256,
  46. ] as CFDictionary, &error) else {
  47. throw PushNotificationReceiverErrorType.restoringKeyFailed(error?.takeRetainedValue())
  48. }
  49. guard let serverPublicKey = SecKeyCreateWithData(serverPublicKeyData as CFData,[
  50. kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
  51. kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
  52. kSecAttrKeySizeInBits as String: 256,
  53. ] as CFDictionary, &error) else {
  54. throw PushNotificationReceiverErrorType.creatingKeyFailed(error?.takeRetainedValue())
  55. }
  56. guard let sharedSecret = SecKeyCopyKeyExchangeResult(privateKey, .ecdhKeyExchangeStandard, serverPublicKey, [:] as CFDictionary, &error) as Data? else {
  57. throw PushNotificationReceiverErrorType.keyExhangedFailed(error?.takeRetainedValue())
  58. }
  59. // TODO: These steps are slightly different from aes128gcm
  60. let secondSaltInfo = "Content-Encoding: auth\0".data(using: .utf8)!
  61. let secondSalt = deriveKey(firstSalt: authentication, secondSalt: sharedSecret, info: secondSaltInfo, length: 32)
  62. let keyInfo = info(type: "aesgcm", clientPublicKey: publicKeyData, serverPublicKey: serverPublicKeyData)
  63. let key = deriveKey(firstSalt: salt, secondSalt: secondSalt, info: keyInfo, length: 16)
  64. let nonceInfo = info(type: "nonce", clientPublicKey: publicKeyData, serverPublicKey: serverPublicKeyData)
  65. let nonce = deriveKey(firstSalt: salt, secondSalt: secondSalt, info: nonceInfo, length: 12)
  66. let gcm = try SwiftGCM(key: key, nonce: nonce, tagSize: 16)
  67. let clearText = try gcm.decrypt(auth: nil, ciphertext: payload)
  68. guard clearText.count >= 2 else {
  69. throw PushNotificationReceiverErrorType.clearTextTooShort
  70. }
  71. let paddingLength = Int(clearText[0]) * 256 + Int(clearText[1])
  72. guard clearText.count >= 2 + paddingLength else {
  73. throw PushNotificationReceiverErrorType.clearTextTooShort
  74. }
  75. let unpadded = clearText.suffix(from: paddingLength + 2)
  76. return unpadded
  77. }
  78. private func deriveKey(firstSalt: Data, secondSalt: Data, info: Data, length: Int) -> Data {
  79. return firstSalt.withUnsafeBytes { (firstSaltBytes: UnsafePointer<UInt8>) -> Data in
  80. return secondSalt.withUnsafeBytes { (secondSaltBytes: UnsafePointer<UInt8>) -> Data in
  81. return info.withUnsafeBytes { (infoBytes: UnsafePointer<UInt8>) -> Data in
  82. // RFC5869 Extract
  83. var context = CCHmacContext()
  84. CCHmacInit(&context, CCHmacAlgorithm(kCCHmacAlgSHA256), firstSaltBytes, firstSalt.count)
  85. CCHmacUpdate(&context, secondSaltBytes, secondSalt.count)
  86. var hmac: [UInt8] = .init(repeating: 0, count: 32)
  87. CCHmacFinal(&context, &hmac)
  88. // RFC5869 Expand
  89. CCHmacInit(&context, CCHmacAlgorithm(kCCHmacAlgSHA256), &hmac, hmac.count)
  90. CCHmacUpdate(&context, infoBytes, info.count)
  91. var one: [UInt8] = [1] // Add sequence byte. We only support short keys so this is always just 1.
  92. CCHmacUpdate(&context, &one, 1)
  93. CCHmacFinal(&context, &hmac)
  94. return Data(bytes: hmac.prefix(upTo: length))
  95. }
  96. }
  97. }
  98. }
  99. private func info(type: String, clientPublicKey: Data, serverPublicKey: Data) -> Data {
  100. var info = Data()
  101. info.append("Content-Encoding: ".data(using: .utf8)!)
  102. info.append(type.data(using: .utf8)!)
  103. info.append(0)
  104. info.append("P-256".data(using: .utf8)!)
  105. info.append(0)
  106. info.append(0)
  107. info.append(65)
  108. info.append(clientPublicKey)
  109. info.append(0)
  110. info.append(65)
  111. info.append(serverPublicKey)
  112. return info
  113. }
  114. }
  115. enum PushNotificationReceiverErrorType: Error {
  116. case invalidKey
  117. case impossible
  118. case creatingKeyFailed(Error?)
  119. case restoringKeyFailed(Error?)
  120. case extractingPrivateKeyFailed(Error?)
  121. case extractingPublicKeyFailed(Error?)
  122. case creatingRandomDataFailed(Error?)
  123. case keyExhangedFailed(Error?)
  124. case clearTextTooShort
  125. }