From 1c912ff9aad8d12ef121d1c34f1dd738581859ca Mon Sep 17 00:00:00 2001 From: Paul Flynn Date: Wed, 23 Oct 2024 17:23:51 -0400 Subject: [PATCH] Make `toData` methods public and add Geofence testing (#10) Changed the `toData` methods to public to ensure accessibility across different modules. Introduced geofence-related structures like `Coordinate3D`, `Geofence3D`, and their respective test cases to validate geofence functionalities. --- OpenTDFKit/BinaryParser.swift | 4 +- OpenTDFKit/NanoTDF.swift | 16 ++- OpenTDFKitTests/KASWebsocketTests.swift | 141 +++++++++++++++++++++++- 3 files changed, 152 insertions(+), 9 deletions(-) diff --git a/OpenTDFKit/BinaryParser.swift b/OpenTDFKit/BinaryParser.swift index d5b00b7..33d0cfa 100644 --- a/OpenTDFKit/BinaryParser.swift +++ b/OpenTDFKit/BinaryParser.swift @@ -82,7 +82,7 @@ public class BinaryParser { // if no policy added then no read // Note 3.4.2.3.2 Body for Embedded Policy states Minimum Length is 1 if contentLength == 0 { - return EmbeddedPolicyBody(length: 1, body: Data([0x00]), keyAccess: nil) + return EmbeddedPolicyBody(body: Data([0x00]), keyAccess: nil) } guard let plaintextCiphertext = read(length: Int(contentLength)) else { @@ -92,7 +92,7 @@ public class BinaryParser { // Policy Key Access let keyAccess = policyType == .embeddedEncryptedWithPolicyKeyAccess ? readPolicyKeyAccess(bindingMode: bindingMode) : nil - return EmbeddedPolicyBody(length: plaintextCiphertext.count, body: plaintextCiphertext, keyAccess: keyAccess) + return EmbeddedPolicyBody(body: plaintextCiphertext, keyAccess: keyAccess) } func readEccAndBindingMode() -> PolicyBindingConfig? { diff --git a/OpenTDFKit/NanoTDF.swift b/OpenTDFKit/NanoTDF.swift index a6c821e..51c5602 100644 --- a/OpenTDFKit/NanoTDF.swift +++ b/OpenTDFKit/NanoTDF.swift @@ -159,7 +159,7 @@ public struct ResourceLocator: Sendable { self.body = body } - func toData() -> Data { + public func toData() -> Data { var data = Data() data.append(protocolEnum.rawValue) if let bodyData = body.data(using: .utf8) { @@ -191,7 +191,7 @@ public struct Policy: Sendable { self.binding = binding } - func toData() -> Data { + public func toData() -> Data { var data = Data() data.append(type.rawValue) switch type { @@ -212,13 +212,19 @@ public struct Policy: Sendable { } public struct EmbeddedPolicyBody: Sendable { - public let length: Int public let body: Data public let keyAccess: PolicyKeyAccess? - func toData() -> Data { + public init(body: Data, keyAccess: PolicyKeyAccess? = nil) { + self.body = body + self.keyAccess = keyAccess + } + + public func toData() -> Data { var data = Data() - data.append(UInt8(body.count)) // length + let bodyLength = UInt16(body.count) + data.append(UInt8((bodyLength >> 8) & 0xFF)) // length high byte + data.append(UInt8(bodyLength & 0xFF)) // length low byte data.append(body) if let keyAccess { data.append(keyAccess.toData()) diff --git a/OpenTDFKitTests/KASWebsocketTests.swift b/OpenTDFKitTests/KASWebsocketTests.swift index 57a379d..22c7b34 100644 --- a/OpenTDFKitTests/KASWebsocketTests.swift +++ b/OpenTDFKitTests/KASWebsocketTests.swift @@ -3,11 +3,114 @@ import CryptoKit import XCTest final class KASWebsocketTests: XCTestCase { + func testGeofence() throws { + measure(metrics: [XCTCPUMetric()]) { + let nanoTDFManager = NanoTDFManager() +// let webSocket = KASWebSocket(kasUrl: URL(string: "wss://kas.arkavo.net")!, token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c") + let webSocket = KASWebSocket(kasUrl: URL(string: "ws://localhost:8080")!, token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MjkzMDE2NDAsImF1ZCI6WyJrYXMuYXJrYXZvLm5ldCJdLCJpc3MiOiJBcmthdm8gQXBwIiwic3ViIjoiN0RQNnhIR3VqTHV2RnBmOUc4ekNLb0hmMmd1bWVzenZVZ3p6am5UeXJZOTIiLCJleHAiOjE3MjkzMDUyNDB9.R_B3RuguYq-k_d-au8BOBqAvIIuDweU1XOXwft5ohbY") + let plaintext = "Keep this message secret".data(using: .utf8)! + webSocket.setRewrapCallback { identifier, symmetricKey in +// defer { +// print("END setRewrapCallback") +// } +// print("BEGIN setRewrapCallback") +// print("Received Rewrapped identifier: \(identifier.hexEncodedString())") +// print("Received Rewrapped Symmetric key: \(String(describing: symmetricKey))") + let nanoTDF = nanoTDFManager.getNanoTDF(withIdentifier: identifier) + nanoTDFManager.removeNanoTDF(withIdentifier: identifier) + if symmetricKey == nil { + // DENY + return + } + let payload = nanoTDF?.payload + let rawIV = payload?.iv + // Pad the IV + let paddedIV = CryptoHelper.adjustNonce(rawIV!, to: 12) + let authTag = payload?.mac + let ciphertext = payload?.ciphertext + // Create AES-GCM SealedBox + do { +// print("Symmetric key (first 4 bytes): \(symmetricKey!.withUnsafeBytes { Data($0.prefix(4)).hexEncodedString() })") +// print("Raw IV: \(rawIV!.hexEncodedString())") +// print("Padded IV: \(paddedIV.hexEncodedString())") +// print("Ciphertext length: \(ciphertext!.count)") +// print("Auth tag: \(authTag!.hexEncodedString())") + let sealedBox = try AES.GCM.SealedBox(nonce: AES.GCM.Nonce(data: paddedIV), + ciphertext: ciphertext!, + tag: authTag!) +// print("SealedBox created successfully") + let dplaintext = try AES.GCM.open(sealedBox, using: symmetricKey!) +// print("plaintext: \(dplaintext)") + // print("Decryption successful") + XCTAssertEqual(plaintext, dplaintext) + } catch { + print("Error decryption nanoTDF payload: \(error)") + } + } + webSocket.setKASPublicKeyCallback { publicKey in + let kasRL = ResourceLocator(protocolEnum: .http, body: "localhost:8080") + let kasMetadata = KasMetadata(resourceLocator: kasRL!, publicKey: publicKey, curve: .secp256r1) + // geofence smart contract + let geofenceContractResourceLocator = ResourceLocator(protocolEnum: .sharedResourceDirectory, body: "5H6sLwXKBv3cdm5VVRxrvA8p5cux2Rrni5CQ4GRyYKo4b9B4") + guard let geofenceContractResourceLocator else { + print("Error creating geofence contract resource locator") + return + } + let geofence = Geofence3D(minLatitude: 374300000, + maxLatitude: 374310000, + minLongitude: -1221020000, + maxLongitude: -1221010000, + minAltitude: 0, + maxAltitude: 2000) + var policyBodyData = Data() + policyBodyData.append(geofenceContractResourceLocator.toData()) + policyBodyData.append(geofence.toData()) + print("Policy body data: \(policyBodyData.hexEncodedString())") + let embeddedPolicy = EmbeddedPolicyBody(body: policyBodyData) + var policy = Policy(type: .embeddedPlaintext, body: embeddedPolicy, remote: nil, binding: nil) + + let coordinate = Coordinate3D(latitude: 374305556, // 37.4305556 degrees + longitude: -1221018056, // -122.1018056 degrees + altitude: 1000) // 1000 meters + print("Coordinate: \(coordinate)") + + do { + var i = 0 + while i < 2000 { + i += 1 + // create + let nanoTDF = try createNanoTDF(kas: kasMetadata, policy: &policy, plaintext: plaintext) + // print("Encryption successful") + // store nanoTDF + let id = nanoTDF.header.ephemeralPublicKey + nanoTDFManager.addNanoTDF(nanoTDF, withIdentifier: id) + webSocket.sendRewrapMessage(header: nanoTDF.header) + } + + } catch { + print("Error creating nanoTDF: \(error)") + } + } + webSocket.connect() + webSocket.sendPublicKey() + webSocket.sendKASKeyMessage() + // wait + Thread.sleep(forTimeInterval: 0.5) + print("+++++++++++++++++", nanoTDFManager.getCount()) + while !nanoTDFManager.isEmpty() { + Thread.sleep(forTimeInterval: 0.1) + print("+++++++++++++++++", nanoTDFManager.getCount()) + } + // Optionally, disconnect when done or needed + webSocket.disconnect() + } + } + func testEncryptDecrypt() throws { measure(metrics: [XCTCPUMetric()]) { let nanoTDFManager = NanoTDFManager() - let webSocket = KASWebSocket(kasUrl: URL(string: "wss://kas.arkavo.net")!, token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c") -// let webSocket = KASWebSocket(kasUrl: URL(string: "ws://localhost:8080")!, token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c") +// let webSocket = KASWebSocket(kasUrl: URL(string: "wss://kas.arkavo.net")!, token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c") + let webSocket = KASWebSocket(kasUrl: URL(string: "ws://localhost:8080")!, token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c") let plaintext = "Keep this message secret".data(using: .utf8)! webSocket.setRewrapCallback { identifier, symmetricKey in // defer { @@ -178,3 +281,37 @@ final class KASWebsocketTests: XCTestCase { return nanoTDF.header } } + +public struct Coordinate3D: Codable, Sendable { + public let latitude: Int64 + public let longitude: Int64 + public let altitude: Int64 + + public func toData() -> Data { + var data = Data() + data.append(contentsOf: withUnsafeBytes(of: latitude.bigEndian) { Data($0) }) + data.append(contentsOf: withUnsafeBytes(of: longitude.bigEndian) { Data($0) }) + data.append(contentsOf: withUnsafeBytes(of: altitude.bigEndian) { Data($0) }) + return data + } +} + +public struct Geofence3D: Codable, Sendable { + public let minLatitude: Int64 + public let maxLatitude: Int64 + public let minLongitude: Int64 + public let maxLongitude: Int64 + public let minAltitude: Int64 + public let maxAltitude: Int64 + + public func toData() -> Data { + var data = Data() + data.append(contentsOf: withUnsafeBytes(of: minLatitude.bigEndian) { Data($0) }) + data.append(contentsOf: withUnsafeBytes(of: maxLatitude.bigEndian) { Data($0) }) + data.append(contentsOf: withUnsafeBytes(of: minLongitude.bigEndian) { Data($0) }) + data.append(contentsOf: withUnsafeBytes(of: maxLongitude.bigEndian) { Data($0) }) + data.append(contentsOf: withUnsafeBytes(of: minAltitude.bigEndian) { Data($0) }) + data.append(contentsOf: withUnsafeBytes(of: maxAltitude.bigEndian) { Data($0) }) + return data + } +}