From 74347fa8d6115aa85705770dab9b51b7f52f0ccd Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Mon, 1 Nov 2021 21:56:37 +0100 Subject: [PATCH] Initial commit --- .gitignore | 7 ++++ Package.swift | 20 ++++++++++ README.md | 3 ++ Sources/SwiftHawk/SwiftHawk.swift | 62 +++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 .gitignore create mode 100644 Package.swift create mode 100644 README.md create mode 100644 Sources/SwiftHawk/SwiftHawk.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb460e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..578adc9 --- /dev/null +++ b/Package.swift @@ -0,0 +1,20 @@ +// swift-tools-version:5.5 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "SwiftHawk", + platforms: [ + .macOS(.v10_15), + .tvOS(.v13), + .watchOS(.v6), + .iOS(.v13) + ], + products: [ + .library(name: "SwiftHawk", targets: ["SwiftHawk"]), + ], + targets: [ + .target(name: "SwiftHawk", dependencies: []), + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c01e0d --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# SwiftHawk + +[Hawk](https://github.com/mozilla/hawk) signature implementation for `URLRequest` diff --git a/Sources/SwiftHawk/SwiftHawk.swift b/Sources/SwiftHawk/SwiftHawk.swift new file mode 100644 index 0000000..d50e557 --- /dev/null +++ b/Sources/SwiftHawk/SwiftHawk.swift @@ -0,0 +1,62 @@ +import Foundation +import CryptoKit + +public struct HawkCredentials { + public var id: String + public var key: SymmetricKey + + public init(id: String, key: SymmetricKey) { + self.id = id + self.key = key + } + + public init(id: String, key: String) { + self.init(id: id, key: SymmetricKey(data: Data(key.utf8))) + } +} + +public extension URLRequest { + mutating func sign(credentials: HawkCredentials, hash: H.Type) { + guard let url = url else { return } + + let nonce = makeNonce() + let timestamp = Int(Date().timeIntervalSince1970) + + var path = url.path + if let query = url.query { + path += "?\(query)" + } + + let string = "hawk.1.header\n\(timestamp)\n\(nonce)\n\(httpMethod?.uppercased() ?? "GET")\n\(path)\n\(url.host ?? "")\n\(port)\n\n\n" + + var hash = HMAC(key: credentials.key) + hash.update(data: Data(string.utf8)) + let signature = Data(hash.finalize()).base64EncodedString(options: []) + + let header = "Hawk id=\"\(credentials.id)\", ts=\"\(timestamp)\", nonce=\"\(nonce)\", mac=\"\(signature)\"" + + addValue(header, forHTTPHeaderField: "Authorization") + } + + mutating func sign(credentials: HawkCredentials) { + self.sign(credentials: credentials, hash: SHA256.self) + } + + var port: Int { + if let port = url?.port { + return port + } + + switch url?.scheme { + case "http": return 80 + case "https": return 443 + default: return 0 + } + } + + func makeNonce() -> String { + SymmetricKey(size: .init(bitCount: 48)).withUnsafeBytes { ptr in + Data(ptr).base64EncodedString(options: []) + } + } +}