-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Start fanout implementation * Add ack * Parse fanout message * Update fanout message parsing * FanoutClient for publishing messages * Add fanout public key * Unsafe fanout signature verification * Fix signature length check * Verify fanout expiration date * Support meta keys * Add connection id * Refactor upgrade syntax * Fix value signature * Mark discardable * Stub fanout token tests * Add algo stubs * Stub signatures * Stub fanout verify * Indent * Verify ecdsa signatures * Deps * Deps * Update ECDSA api * Deps * Update demo * Add justfile * Deps * Add back unsafe verify * Use swift-crypto * Deps * Cleanup crypto * Cleanup fanout verification * Fix uppercase * Fix bytes
- Loading branch information
1 parent
21b00fe
commit 4b9b48e
Showing
16 changed files
with
615 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
build: | ||
swift build -c debug --triple wasm32-unknown-wasi | ||
|
||
demo: build | ||
fastly compute serve --skip-build --file ./.build/debug/ComputeDemo.wasm |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
// | ||
// Crypto.swift | ||
// | ||
// | ||
// Created by Andrew Barba on 2/8/23. | ||
// | ||
|
||
import Crypto | ||
|
||
public enum Crypto {} | ||
|
||
// MARK: - Hashing | ||
|
||
extension Crypto { | ||
|
||
public static func hash<T>(_ input: String, using hash: T.Type) -> T.Digest where T: HashFunction { | ||
return T.hash(data: Data(input.utf8)) | ||
} | ||
|
||
public static func hash<T>(_ input: [UInt8], using hash: T.Type) -> T.Digest where T: HashFunction { | ||
return T.hash(data: Data(input)) | ||
} | ||
|
||
public static func hash<T>(_ input: Data, using hash: T.Type) -> T.Digest where T: HashFunction { | ||
return T.hash(data: input) | ||
} | ||
|
||
public static func sha256(_ input: String) -> SHA256.Digest { | ||
return hash(input, using: SHA256.self) | ||
} | ||
|
||
public static func sha256(_ input: [UInt8]) -> SHA256.Digest { | ||
return hash(input, using: SHA256.self) | ||
} | ||
|
||
public static func sha256(_ input: Data) -> SHA256.Digest { | ||
return hash(input, using: SHA256.self) | ||
} | ||
|
||
public static func sha384(_ input: String) -> SHA384.Digest { | ||
return hash(input, using: SHA384.self) | ||
} | ||
|
||
public static func sha384(_ input: [UInt8]) -> SHA384.Digest { | ||
return hash(input, using: SHA384.self) | ||
} | ||
|
||
public static func sha384(_ input: Data) -> SHA384.Digest { | ||
return hash(input, using: SHA384.self) | ||
} | ||
|
||
public static func sha512(_ input: String) -> SHA512.Digest { | ||
return hash(input, using: SHA512.self) | ||
} | ||
|
||
public static func sha512(_ input: [UInt8]) -> SHA512.Digest { | ||
return hash(input, using: SHA512.self) | ||
} | ||
|
||
public static func sha512(_ input: Data) -> SHA512.Digest { | ||
return hash(input, using: SHA512.self) | ||
} | ||
} | ||
|
||
// MARK: - HMAC | ||
|
||
extension Crypto { | ||
public enum Auth { | ||
public enum Hash { | ||
case sha256 | ||
case sha384 | ||
case sha512 | ||
} | ||
|
||
public static func code(for input: String, secret: String, using hash: Hash) -> Data { | ||
let data = Data(input.utf8) | ||
let key = SymmetricKey(data: Data(secret.utf8)) | ||
switch hash { | ||
case .sha256: | ||
return HMAC<SHA256>.authenticationCode(for: data, using: key).data | ||
case .sha384: | ||
return HMAC<SHA384>.authenticationCode(for: data, using: key).data | ||
case .sha512: | ||
return HMAC<SHA512>.authenticationCode(for: data, using: key).data | ||
} | ||
} | ||
|
||
public static func verify(_ input: String, signature: Data, secret: String, using hash: Hash) -> Bool { | ||
let computed = code(for: input, secret: secret, using: hash) | ||
return computed.toHexString() == signature.toHexString() | ||
} | ||
} | ||
} | ||
|
||
// MARK: - ECDSA | ||
|
||
extension Crypto { | ||
public enum ECDSA { | ||
public enum Algorithm { | ||
case p256 | ||
case p384 | ||
case p521 | ||
} | ||
|
||
public static func signature(for input: String, secret: String, using algorithm: Algorithm) throws -> Data { | ||
switch algorithm { | ||
case .p256: | ||
let pk = try P256.Signing.PrivateKey(pemRepresentation: secret) | ||
return try pk.signature(for: Crypto.sha256(input)).rawRepresentation | ||
case .p384: | ||
let pk = try P384.Signing.PrivateKey(pemRepresentation: secret) | ||
return try pk.signature(for: Crypto.sha384(input)).rawRepresentation | ||
case .p521: | ||
let pk = try P521.Signing.PrivateKey(pemRepresentation: secret) | ||
return try pk.signature(for: Crypto.sha512(input)).rawRepresentation | ||
} | ||
} | ||
|
||
public static func verify(_ input: String, signature: Data, key: String, using algorithm: Algorithm) throws -> Bool { | ||
switch algorithm { | ||
case .p256: | ||
let publicKey = try P256.Signing.PublicKey(pemRepresentation: key) | ||
let ecdsaSignature = try P256.Signing.ECDSASignature(rawRepresentation: signature) | ||
return publicKey.isValidSignature(ecdsaSignature, for: Crypto.sha256(input)) | ||
case .p384: | ||
let publicKey = try P384.Signing.PublicKey(pemRepresentation: key) | ||
let ecdsaSignature = try P384.Signing.ECDSASignature(rawRepresentation: signature) | ||
return publicKey.isValidSignature(ecdsaSignature, for: Crypto.sha384(input)) | ||
case .p521: | ||
let publicKey = try P521.Signing.PublicKey(pemRepresentation: key) | ||
let ecdsaSignature = try P521.Signing.ECDSASignature(rawRepresentation: signature) | ||
return publicKey.isValidSignature(ecdsaSignature, for: Crypto.sha512(input)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
// MARK: - Utils | ||
|
||
extension DataProtocol { | ||
public var bytes: [UInt8] { | ||
return .init(self) | ||
} | ||
|
||
public var data: Data { | ||
return .init(self) | ||
} | ||
|
||
public func toHexString() -> String { | ||
return reduce("") {$0 + String(format: "%02x", $1)} | ||
} | ||
} | ||
|
||
extension Digest { | ||
public var bytes: [UInt8] { | ||
return .init(self) | ||
} | ||
|
||
public var data: Data { | ||
return .init(self) | ||
} | ||
|
||
public func toHexString() -> String { | ||
return reduce("") {$0 + String(format: "%02x", $1)} | ||
} | ||
} | ||
|
||
extension HashedAuthenticationCode { | ||
public var bytes: [UInt8] { | ||
return .init(self) | ||
} | ||
|
||
public var data: Data { | ||
return .init(self) | ||
} | ||
|
||
public func toHexString() -> String { | ||
return reduce("") {$0 + String(format: "%02x", $1)} | ||
} | ||
} | ||
|
||
extension Array where Element == UInt8 { | ||
public var data: Data { | ||
return .init(self) | ||
} | ||
|
||
public func toHexString() -> String { | ||
return reduce("") {$0 + String(format: "%02x", $1)} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// | ||
// FanoutClient.swift | ||
// | ||
// | ||
// Created by Andrew Barba on 2/1/23. | ||
// | ||
|
||
import Foundation | ||
|
||
public struct FanoutClient: Sendable { | ||
|
||
public let service: String | ||
|
||
public let hostname: String | ||
|
||
private let token: String | ||
|
||
private var publishEndpoint: String { | ||
"https://\(hostname)/service/\(service)/publish/" | ||
} | ||
|
||
public init(service: String = Fastly.Environment.serviceId, token: String, hostname: String = "api.fastly.com") { | ||
self.service = service | ||
self.token = token | ||
self.hostname = hostname | ||
} | ||
|
||
@discardableResult | ||
public func publish(_ message: PublishMessage) async throws -> FetchResponse { | ||
return try await fetch(publishEndpoint, .options( | ||
method: .post, | ||
body: .json(message), | ||
headers: ["Fastly-Key": token] | ||
)) | ||
} | ||
|
||
@discardableResult | ||
public func publish(_ content: String, to channel: String) async throws -> FetchResponse { | ||
let message = PublishMessage(items: [ | ||
.init(channel: channel, formats: .init(wsMessage: .init(content: content))) | ||
]) | ||
return try await publish(message) | ||
} | ||
|
||
@discardableResult | ||
public func publish<T: Encodable>(_ value: T, encoder: JSONEncoder = .init(), to channel: String) async throws -> FetchResponse { | ||
let content = try encoder.encode(value) | ||
return try await publish(content, to: channel) | ||
} | ||
|
||
@discardableResult | ||
public func publish(_ json: Any, to channel: String) async throws -> FetchResponse { | ||
let data = try JSONSerialization.data(withJSONObject: json) | ||
let content = String(data: data, encoding: .utf8) | ||
return try await publish(content, to: channel) | ||
} | ||
|
||
@discardableResult | ||
public func publish(_ jsonObject: [String: Any], to channel: String) async throws -> FetchResponse { | ||
let data = try JSONSerialization.data(withJSONObject: jsonObject) | ||
let content = String(data: data, encoding: .utf8) | ||
return try await publish(content, to: channel) | ||
} | ||
|
||
@discardableResult | ||
public func publish(_ jsonArray: [Any], to channel: String) async throws -> FetchResponse { | ||
let data = try JSONSerialization.data(withJSONObject: jsonArray) | ||
let content = String(data: data, encoding: .utf8) | ||
return try await publish(content, to: channel) | ||
} | ||
} | ||
|
||
extension FanoutClient { | ||
public struct PublishMessage: Codable, Sendable { | ||
public let items: [PublishMessageItem] | ||
|
||
public init(items: [PublishMessageItem]) { | ||
self.items = items | ||
} | ||
} | ||
|
||
public struct PublishMessageItem: Codable, Sendable { | ||
public let channel: String | ||
public let formats: PublishMessageItemFormats | ||
|
||
public init(channel: String, formats: PublishMessageItemFormats) { | ||
self.channel = channel | ||
self.formats = formats | ||
} | ||
} | ||
|
||
public struct PublishMessageItemFormats: Codable, Sendable { | ||
enum CodingKeys: String, CodingKey { | ||
case wsMessage = "ws-message" | ||
} | ||
public let wsMessage: PublishMessageItemContent | ||
|
||
public init(wsMessage: PublishMessageItemContent) { | ||
self.wsMessage = wsMessage | ||
} | ||
} | ||
|
||
public struct PublishMessageItemContent: Codable, Sendable { | ||
public let content: String | ||
|
||
public init(content: String) { | ||
self.content = content | ||
} | ||
} | ||
} |
Oops, something went wrong.