From ad4fd555f1ee97b02fbb7da4c6968ec0a36d5e5d Mon Sep 17 00:00:00 2001 From: Christian Risi <75698846+CnF-Gris@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:31:07 +0000 Subject: [PATCH] V0.6.0 Arroyo Toad Added the capability to sign and verify P521 Signature --- LICENSE | 36 +- Package.resolved | 24 + Package.swift | 56 ++- Private/.gitkeep | 0 Private/cert.pem | 16 + Private/privateKey.pem | 7 + Private/privateKey_ASN1.pem | 8 + Private/signedMessage/Message.bin | Bin 0 -> 244 bytes README.md | 4 +- Sources/MessageUtils/Enums/DevType.swift | 6 +- Sources/MessageUtils/Enums/MessageType.swift | 14 +- Sources/MessageUtils/Enums/SignType.swift | 4 +- Sources/MessageUtils/Errors/UtilsErrors.swift | 24 +- Sources/MessageUtils/MessageUtils.swift | 416 +++++++++--------- Sources/MessageUtils/Structs/Field.swift | 16 +- Sources/MessageUtils/Structs/Location.swift | 12 +- Sources/MessageUtils/Structs/Message.swift | 106 +++-- .../Utils/CryptoUtils/Security.swift | 58 +++ .../Utils/CryptoUtils/SecurityErrors.swift | 4 + .../MessageUtils/Utils/DataCompatibleP.swift | 8 +- Sources/MessageUtils/Utils/DataToUtils.swift | 62 +-- Sources/MessageUtils/Utils/MessageBytes.md | 111 ++--- Sources/MessageUtils/Utils/MessageP.swift | 16 + Sources/MessageUtils/Utils/ToDataUtils.swift | 86 ++-- .../MessageUtilsTests/MessageUtilsTests.swift | 207 +++++---- 25 files changed, 764 insertions(+), 537 deletions(-) create mode 100644 Package.resolved create mode 100644 Private/.gitkeep create mode 100644 Private/cert.pem create mode 100644 Private/privateKey.pem create mode 100644 Private/privateKey_ASN1.pem create mode 100644 Private/signedMessage/Message.bin create mode 100644 Sources/MessageUtils/Utils/CryptoUtils/Security.swift create mode 100644 Sources/MessageUtils/Utils/CryptoUtils/SecurityErrors.swift create mode 100644 Sources/MessageUtils/Utils/MessageP.swift diff --git a/LICENSE b/LICENSE index bf03591..8c7ad89 100644 --- a/LICENSE +++ b/LICENSE @@ -1,18 +1,18 @@ - LICENSE - ======= - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, provided -that the original copyright notices appear in all copies and that both -copyright notice and this permission notice appear in supporting -documentation, and that the name of the author not be used in advertising -or publicity pertaining to distribution of the software without specific -prior written permission. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. + LICENSE + ======= + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided +that the original copyright notices appear in all copies and that both +copyright notice and this permission notice appear in supporting +documentation, and that the name of the author not be used in advertising +or publicity pertaining to distribution of the software without specific +prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..7856ee5 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,24 @@ +{ + "originHash" : "dc0be3b6ca36aebf9cc5cf891bb7e7064d6b816c6ad89f60f5647f097455aa46", + "pins" : [ + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "7faebca1ea4f9aaf0cda1cef7c43aecd2311ddf6", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "branch" : "main", + "revision" : "dc4c2c14e7ff95ee3aa8d3c2a217a248f51d3688" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift index 94df151..1346514 100644 --- a/Package.swift +++ b/Package.swift @@ -1,24 +1,32 @@ -// swift-tools-version: 6.0 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "MessageUtils", - products: [ - // Products define the executables and libraries a package produces, making them visible to other packages. - .library( - name: "MessageUtils", - targets: ["MessageUtils"]), - ], - targets: [ - // Targets are the basic building blocks of a package, defining a module or a test suite. - // Targets can depend on other targets in this package and products from dependencies. - .target( - name: "MessageUtils"), - .testTarget( - name: "MessageUtilsTests", - dependencies: ["MessageUtils"] - ), - ] -) +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "MessageUtils", + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "MessageUtils", + targets: ["MessageUtils"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-crypto.git", branch: "main"), + + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "MessageUtils", + dependencies: [ + .product(name: "Crypto", package: "swift-crypto"), + ] + ), + .testTarget( + name: "MessageUtilsTests", + dependencies: ["MessageUtils"] + ), + ] +) diff --git a/Private/.gitkeep b/Private/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Private/cert.pem b/Private/cert.pem new file mode 100644 index 0000000..e384062 --- /dev/null +++ b/Private/cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICeTCCAdqgAwIBAgIUeKyiiDJdVGH3ParIry5vn/YGnaowCgYIKoZIzj0EAwIw +TjELMAkGA1UEBhMCSVQxDTALBgNVBAgMBEJhcmkxDTALBgNVBAcMBEJhcmkxITAf +BgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDEyMDIxNjM0NDVa +Fw0yNTAxMDExNjM0NDVaME4xCzAJBgNVBAYTAklUMQ0wCwYDVQQIDARCYXJpMQ0w +CwYDVQQHDARCYXJpMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQw +gZswEAYHKoZIzj0CAQYFK4EEACMDgYYABAAAfWnGEUMElY/XIWUTPvX65HX3N5Ik +JKPdVFzDRtaTHRJKnEEvU7Z5iLAT9NpbVfCabvQXKo7LD5sjoJ1ZpSVcogDgCFCo +pmVin2ZLs5lyMtaetpVDH8m+AIlRQkkuGmkasM+OV62kzSoHl/CL4eNz1xXwqsPt +oBgvPiRFxNIE/0dz96NTMFEwHQYDVR0OBBYEFKW5mYrSXJn68diXLDjhbiEGAxJu +MB8GA1UdIwQYMBaAFKW5mYrSXJn68diXLDjhbiEGAxJuMA8GA1UdEwEB/wQFMAMB +Af8wCgYIKoZIzj0EAwIDgYwAMIGIAkIAhVgtxgnZd6KeefLjZ6Mazgr5xLDcAHyI +NsKtTw3YzT/Pztnk2ccV+NyDZyoTG72lHoPMTiB5mRSUTqORg59XQTkCQgDqHRoN +tRQlPWY3abohilRRdvYZrsoPR8FzB/M4KxT0nk10jc1wtosQ7l/XZGcKe8/k+iVs +HC5CsESzsvnp+Qslyw== +-----END CERTIFICATE----- diff --git a/Private/privateKey.pem b/Private/privateKey.pem new file mode 100644 index 0000000..56bc79b --- /dev/null +++ b/Private/privateKey.pem @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIBAQAy+3ElWUTttb9xxVDshJlGt/clGdhPkp76aJ3LJySugsnC8RRO +UracnWQi2A+XnEI1ZskzYAFUfh7G5o5ViDygBwYFK4EEACOhgYkDgYYABAAAfWnG +EUMElY/XIWUTPvX65HX3N5IkJKPdVFzDRtaTHRJKnEEvU7Z5iLAT9NpbVfCabvQX +Ko7LD5sjoJ1ZpSVcogDgCFCopmVin2ZLs5lyMtaetpVDH8m+AIlRQkkuGmkasM+O +V62kzSoHl/CL4eNz1xXwqsPtoBgvPiRFxNIE/0dz9w== +-----END EC PRIVATE KEY----- diff --git a/Private/privateKey_ASN1.pem b/Private/privateKey_ASN1.pem new file mode 100644 index 0000000..55fd5c3 --- /dev/null +++ b/Private/privateKey_ASN1.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBAQAy+3ElWUTttb9x +xVDshJlGt/clGdhPkp76aJ3LJySugsnC8RROUracnWQi2A+XnEI1ZskzYAFUfh7G +5o5ViDyhgYkDgYYABAAAfWnGEUMElY/XIWUTPvX65HX3N5IkJKPdVFzDRtaTHRJK +nEEvU7Z5iLAT9NpbVfCabvQXKo7LD5sjoJ1ZpSVcogDgCFCopmVin2ZLs5lyMtae +tpVDH8m+AIlRQkkuGmkasM+OV62kzSoHl/CL4eNz1xXwqsPtoBgvPiRFxNIE/0dz +9w== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/Private/signedMessage/Message.bin b/Private/signedMessage/Message.bin new file mode 100644 index 0000000000000000000000000000000000000000..259dc27698872dce7f9339df22b3512f997ea2e1 GIT binary patch literal 244 zcmZQ%U|`?^;#-<~%eotnIWhv-$N(e<0wNHK0VdB4XmQ>+wdb_nG#52?*27x%UJ{$$sW%6$s8^_CoGbt2 z{*plT2;qc{MSGl5d7p%x3i^HAImn@Gh2+YfGX@Nq>~9a{GWxUKHD0P78YA!cXYSH- z%|}Xqw<~Dvc3QCY+^##U9{jrB%~nl$6PjK4pf#(kGgtTY)YCf+{j23#k~Unsa+L3@ GbOr!3hf4?m literal 0 HcmV?d00001 diff --git a/README.md b/README.md index fbd190a..7829fed 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# Swift-MessageUtils - +# Swift-MessageUtils + diff --git a/Sources/MessageUtils/Enums/DevType.swift b/Sources/MessageUtils/Enums/DevType.swift index e3ee372..e776995 100644 --- a/Sources/MessageUtils/Enums/DevType.swift +++ b/Sources/MessageUtils/Enums/DevType.swift @@ -1,4 +1,4 @@ -public enum DeviceType: UInt8 { - case EDGE_SENSOR = 0 - case SCANNER_SENSOR = 1 +public enum DeviceType: UInt8 { + case EDGE_SENSOR = 0 + case SCANNER_SENSOR = 1 } \ No newline at end of file diff --git a/Sources/MessageUtils/Enums/MessageType.swift b/Sources/MessageUtils/Enums/MessageType.swift index 48ecabf..8e199f4 100644 --- a/Sources/MessageUtils/Enums/MessageType.swift +++ b/Sources/MessageUtils/Enums/MessageType.swift @@ -1,8 +1,8 @@ -public enum MessageType: UInt8 { - case KEEPALIVE = 0 - case DATA = 1 - case INFO = 2 - case WARNING = 50 - case ERROR = 100 - case CRITICAL = 255 +public enum MessageType: UInt8 { + case KEEPALIVE = 0 + case DATA = 1 + case INFO = 2 + case WARNING = 50 + case ERROR = 100 + case CRITICAL = 255 } \ No newline at end of file diff --git a/Sources/MessageUtils/Enums/SignType.swift b/Sources/MessageUtils/Enums/SignType.swift index cde819d..8aa47aa 100644 --- a/Sources/MessageUtils/Enums/SignType.swift +++ b/Sources/MessageUtils/Enums/SignType.swift @@ -1,3 +1,3 @@ -public enum SignType: UInt32 { - case P521 = 10 +public enum SignType: UInt32 { + case P521 = 10 } \ No newline at end of file diff --git a/Sources/MessageUtils/Errors/UtilsErrors.swift b/Sources/MessageUtils/Errors/UtilsErrors.swift index 975a424..223830c 100644 --- a/Sources/MessageUtils/Errors/UtilsErrors.swift +++ b/Sources/MessageUtils/Errors/UtilsErrors.swift @@ -1,13 +1,13 @@ -public enum SerializationError : Error { - - case SIGNATURE_NOT_SUPPORTED - -} - -public enum CommonError : Error { - case SIGNATURE_NOT_SUPPORTED -} - -public enum DeserializationError: Error { - case UNMATCHING_SIGNATURE_TYPE +public enum SerializationError : Error { + + case SIGNATURE_NOT_SUPPORTED + +} + +public enum CommonError : Error { + case SIGNATURE_NOT_SUPPORTED +} + +public enum DeserializationError: Error { + case UNMATCHING_SIGNATURE_TYPE } \ No newline at end of file diff --git a/Sources/MessageUtils/MessageUtils.swift b/Sources/MessageUtils/MessageUtils.swift index b7be363..d4b854c 100644 --- a/Sources/MessageUtils/MessageUtils.swift +++ b/Sources/MessageUtils/MessageUtils.swift @@ -1,203 +1,213 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book -import Foundation - -public func serializeV1(msg: Message) throws -> Data { - - let MESSAGE_CAPACITY: Int = try countBytes(msg: msg) - - var serializedData: Data = Data(count: MESSAGE_CAPACITY) - - // Serialize Header - - // UGLY: extrapolate in functions - serializedData[0] = msg.version - serializedData[1] = msg.messageType.rawValue - serializedData[2] = msg.devType.rawValue - serializedData[3] = msg.RESERVED - serializedData[4...7] = msg.signType.rawValue.data - // First 8 bytes - - serializedData[8...15] = msg.timestamp.data - // 8 Bytes - - serializedData[16...31] = msg.devID.data - // 16 bytes - - serializedData[32...39] = msg.location.x.data - // 8 Bytes - - serializedData[40...47] = msg.location.y.data - // 8 Bytes - - serializedData[48...55] = msg.location.z.data - // 8 Bytes - - var index = 56 - - for field in msg.fields { - serializedData[index...(index + 3)] = UInt32(field.key.count).data - serializedData[(index + 4)...(index + 7)] = UInt32(field.key.count).data - - index += 8 - - serializedData[index..<(index + field.key.count)] = Data(field.key) - - index += field.key.count - - serializedData[index..<(index + field.value.count)] = Data(field.value) - - index += field.value.count - } - - let paddingBytes = (8 - (index % 8)) % 8 - - serializedData[index..<(index + paddingBytes)] = Data(count: paddingBytes) - index += paddingBytes - - // Add STOP FIELDS - serializedData[index..<(index + 8)] = UInt64(0).data - print(UInt64(0).data.base64EncodedString()) - index += 8 - serializedData[index.. Message { - - // Serialize Header - - // UGLY: extrapolate in functions - let version: UInt8 = serializedData[0] - let messageType: MessageType = MessageType(rawValue: serializedData[1])! - let devType: DeviceType = DeviceType(rawValue: serializedData[2])! - let RESERVED: UInt8 = serializedData[3] - let signType: SignType = SignType(rawValue: serializedData[4...7].uint32)! - // First 8 bytes - - let signBytes = try signatureBytes(signature: signType) - - let timestamp = serializedData[8...15].timestamp - // 8 Bytes - - let devID = serializedData[16...31].uint128 - // 16 bytes - - let locationX = serializedData[32...39].uint64 - // 8 Bytes - - let locationY = serializedData[40...47].uint64 - // 8 Bytes - - let locationZ = serializedData[48...55].uint64 - // 8 Bytes - - var index = 56 - - // Deserializing Fields - var MORE_FIELDS = true - var fields: [Field] = [] - - while MORE_FIELDS { - - let nextChunk = serializedData[index..<(index + 8)] - let fieldKeyCount = serializedData[index...(index + 4)].uint32 - let fieldValueCount = serializedData[(index + 4)..<(index + 8)].uint32 - - index += 8 - - let a = nextChunk.uint64 - - if nextChunk.uint64 == 0 { - MORE_FIELDS = false - continue - } - - let key = serializedData[index..<(index + Int(fieldKeyCount))].map { value in - return value - } - index += Int(fieldKeyCount) - let value = serializedData[index..<(index + Int(fieldValueCount))].map { value in - return value - } - index += Int(fieldValueCount) - - fields.append(Field(key: key, value: value)) - - } - - let paddingBytes = (8 - (index % 8)) % 8 - - // Skip padding bytes - index += paddingBytes - - let signature = serializedData[index.. Int { - - /// - /// Author: Christian Risi - /// - /// This is computed as all the bits for the fixed fields - /// - /// In this case we have: - /// - 4 B --> Generic Info - /// - 4 B --> Timestamp - /// - 8 B --> Device ID - /// - 12 B --> Device Location - /// - 8 B --> FIELDS STOP - let INITIAL_CAPACITY_BYTES: Int = 36 - let FIELD_HEADER_CAPACITY_BYTES: Int = 4 - - var fieldReveservedCapacity: Int = 0 - - for field in msg.fields { - fieldReveservedCapacity += FIELD_HEADER_CAPACITY_BYTES + field.key.count + field.value.count - } - - // UGLY: We are assuming P521 signature - let SIGNATURE_CAPACITY_BYTES: Int = try signatureBytes(signature: msg.signType) - - return (INITIAL_CAPACITY_BYTES + fieldReveservedCapacity + SIGNATURE_CAPACITY_BYTES) - -} - -public func signatureBytes(signature: SignType) throws -> Int { - switch signature { - - case .P521: - return 132 - - default: - throw CommonError.SIGNATURE_NOT_SUPPORTED - } -} +// The Swift Programming Language +// https://docs.swift.org/swift-book + +import Foundation +import Crypto + +public func serializeV1(msg: MessageP) -> Data { + + let MESSAGE_CAPACITY: Int = countBytes(msg: msg) + + var serializedData: Data = Data(count: MESSAGE_CAPACITY) + + // Serialize Header + + // UGLY: extrapolate in functions + serializedData[0] = msg.version + serializedData[1] = msg.messageType.rawValue + serializedData[2] = msg.devType.rawValue + serializedData[3] = msg.RESERVED + serializedData[4...7] = msg.signType.rawValue.data + // First 8 bytes + + serializedData[8...15] = msg.timestamp.data + // 8 Bytes + + serializedData[16...31] = msg.devID.data + // 16 bytes + + serializedData[32...39] = msg.location.x.data + // 8 Bytes + + serializedData[40...47] = msg.location.y.data + // 8 Bytes + + serializedData[48...55] = msg.location.z.data + // 8 Bytes + + var index = 56 + + for field in msg.fields { + + serializedData[index..<(index + 4)] = UInt32(field.key.count).data + serializedData[(index + 4)..<(index + 8)] = UInt32(field.value.count).data + + index += 8 + + serializedData[index..<(index + field.key.count)] = Data(field.key) + + index += field.key.count + + serializedData[index..<(index + field.value.count)] = Data(field.value) + + index += field.value.count + } + + let paddingBytes = (8 - (index % 8)) % 8 + + serializedData[index..<(index + paddingBytes)] = Data(count: paddingBytes) + index += paddingBytes + + // Add STOP FIELDS + serializedData[index..<(index + 8)] = UInt64(0).data + + return serializedData +} + +public func signMessage(msgData: Data, signType: SignType, key: P521.Signing.PrivateKey) throws -> [UInt8] { + let signatureBytes = try signatureBytes(signature: signType) + + // UGLY We are hypothesisying that signType is P521 + let signature = try signP521(object: msgData, key: key).map { value in + return value + } + + return signature + +} + + +public func verifyMessageSignature(message: SignedMessage, key: P521.Signing.PublicKey) throws -> Bool { + // UGLY Assuming P521 Signature + + let msgData = serializeV1(msg: message) + return try verifySignatureP521(signature: Data(message.signature), object: msgData, key: key) + +} + +public func deserializeV1(serializedData: Data) throws -> SignedMessage { + + // Serialize Header + + // UGLY: extrapolate in functions + let version: UInt8 = serializedData[0] + let messageType: MessageType = MessageType(rawValue: serializedData[1])! + let devType: DeviceType = DeviceType(rawValue: serializedData[2])! + let RESERVED: UInt8 = serializedData[3] + let signType: SignType = SignType(rawValue: serializedData[4...7].uint32)! + // First 8 bytes + + let signBytes = try signatureBytes(signature: signType) + + let timestamp = serializedData[8...15].timestamp + // 8 Bytes + + let devID = serializedData[16...31].uint128 + // 16 bytes + + let locationX = serializedData[32...39].uint64 + // 8 Bytes + + let locationY = serializedData[40...47].uint64 + // 8 Bytes + + let locationZ = serializedData[48...55].uint64 + // 8 Bytes + + var index = 56 + + // Deserializing Fields + var MORE_FIELDS = true + var fields: [Field] = [] + + while MORE_FIELDS { + + let nextChunk = serializedData[index..<(index + 8)] + + let a = nextChunk.map { value in + return value + } + + if nextChunk.uint64 == 0 { + MORE_FIELDS = false + continue + } + + let fieldKeyCount = serializedData[index..<(index + 4)].uint32 + let fieldValueCount = serializedData[(index + 4)..<(index + 8)].uint32 + + index += 8 + + let key = serializedData[index..<(index + Int(fieldKeyCount))].map { value in + return value + } + index += Int(fieldKeyCount) + let value = serializedData[index..<(index + Int(fieldValueCount))].map { value in + return value + } + index += Int(fieldValueCount) + + fields.append(Field(key: key, value: value)) + + } + + let paddingBytes = (8 - (index % 8)) % 8 + + // Skip padding bytes + index += paddingBytes + 8 + + let signature = serializedData[index.. Int { + + /// + /// Author: Christian Risi + /// + /// This is computed as all the bits for the fixed fields + /// + let INITIAL_CAPACITY_BYTES: Int = 56 + let FIELD_HEADER_CAPACITY_BYTES: Int = 8 + + var fieldReveservedCapacity: Int = 0 + + for field in msg.fields { + fieldReveservedCapacity += FIELD_HEADER_CAPACITY_BYTES + field.key.count + field.value.count + } + + fieldReveservedCapacity += (8 - (fieldReveservedCapacity % 8)) % 8 + 8 + + return INITIAL_CAPACITY_BYTES + fieldReveservedCapacity + +} + +public func signatureBytes(signature: SignType) throws -> Int { + switch signature { + + case .P521: + return 132 + + default: + throw CommonError.SIGNATURE_NOT_SUPPORTED + } +} diff --git a/Sources/MessageUtils/Structs/Field.swift b/Sources/MessageUtils/Structs/Field.swift index 1bbd049..2c6a4e6 100644 --- a/Sources/MessageUtils/Structs/Field.swift +++ b/Sources/MessageUtils/Structs/Field.swift @@ -1,8 +1,8 @@ -import Foundation - -public struct Field { - - public let key: [UInt8] - public let value: [UInt8] - -} +import Foundation + +public struct Field { + + public let key: [UInt8] + public let value: [UInt8] + +} diff --git a/Sources/MessageUtils/Structs/Location.swift b/Sources/MessageUtils/Structs/Location.swift index 542d9b7..22a2fe3 100644 --- a/Sources/MessageUtils/Structs/Location.swift +++ b/Sources/MessageUtils/Structs/Location.swift @@ -1,7 +1,7 @@ -public struct Location { - - public let x: UInt64 - public let y: UInt64 - public let z: UInt64 - +public struct Location { + + public let x: UInt64 + public let y: UInt64 + public let z: UInt64 + } \ No newline at end of file diff --git a/Sources/MessageUtils/Structs/Message.swift b/Sources/MessageUtils/Structs/Message.swift index 5144b62..492e51a 100644 --- a/Sources/MessageUtils/Structs/Message.swift +++ b/Sources/MessageUtils/Structs/Message.swift @@ -1,37 +1,71 @@ -import Foundation - -public struct Message{ - - public let version: UInt8 - public let messageType: MessageType - public let devType: DeviceType - public let RESERVED: UInt8 - public let signType: SignType - - public let timestamp : Date - public let devID : UInt128 - public let location: Location - public let fields: [Field] - public let signature: [UInt8] - - - public func toString() -> String{ - var description = "" - description += "MESSAGE -------\n" - description += "V: \t\(self.version)\n" - description += "Message Type: \t\(self.messageType.rawValue)\n" - description += "Device Type: \t\(self.devType.rawValue)\n" - description += "RESERVED: \t\(self.RESERVED)\n" - description += "Signature Type: \t\(self.signType.rawValue)\n" - description += "Timestamp: \t\(self.timestamp)\n" - description += "Device ID: \t\(self.devID)\n" - description += "Location: \tX: \(self.location.x)\tY: \(self.location.y)\tZ: \(self.location.z)\n" - description += "Fields: \n" - for field in self.fields { - description += "\t\(String(data: Data(field.key), encoding: .ascii) ?? "UNABLE TO DECODE"): \(String(data: Data(field.value), encoding: .ascii) ?? "UNABLE TO DECODE")" - } - description += "Signature: \t\(self.signature)\n" - - return description - } +import Foundation + +public struct Message : MessageP{ + + public let version: UInt8 + public let messageType: MessageType + public let devType: DeviceType + public let RESERVED: UInt8 + public let signType: SignType + + public let timestamp : Date + public let devID : UInt128 + public let location: Location + public let fields: [Field] + + + public func toString() -> String{ + var description = "" + description += "MESSAGE -------\n" + description += "V: \t\(self.version)\n" + description += "Message Type: \t\(self.messageType.rawValue)\n" + description += "Device Type: \t\(self.devType.rawValue)\n" + description += "RESERVED: \t\(self.RESERVED)\n" + description += "Signature Type: \t\(self.signType.rawValue)\n" + description += "Timestamp: \t\(self.timestamp)\n" + description += "Device ID: \t\(self.devID)\n" + description += "Location: \tX: \(self.location.x)\tY: \(self.location.y)\tZ: \(self.location.z)\n" + description += "Fields: \n" + for field in self.fields { + description += "\t\(String(data: Data(field.key), encoding: .ascii) ?? "UNABLE TO DECODE"): \(String(data: Data(field.value), encoding: .ascii) ?? "UNABLE TO DECODE")\n" + } + + return description + } +} + +public struct SignedMessage : MessageP { + + public let version: UInt8 + public let messageType: MessageType + public let devType: DeviceType + public let RESERVED: UInt8 + public let signType: SignType + + public let timestamp : Date + public let devID : UInt128 + public let location: Location + public let fields: [Field] + public let signature: [UInt8] + + + public func toString() -> String{ + var description = "" + description += "MESSAGE -------\n" + description += "V: \t\(self.version)\n" + description += "Message Type: \t\(self.messageType.rawValue)\n" + description += "Device Type: \t\(self.devType.rawValue)\n" + description += "RESERVED: \t\(self.RESERVED)\n" + description += "Signature Type: \t\(self.signType.rawValue)\n" + description += "Timestamp: \t\(self.timestamp)\n" + description += "Device ID: \t\(self.devID)\n" + description += "Location: \tX: \(self.location.x)\tY: \(self.location.y)\tZ: \(self.location.z)\n" + description += "Fields: \n" + for field in self.fields { + description += "\t\(String(data: Data(field.key), encoding: .ascii) ?? "UNABLE TO DECODE"): \(String(data: Data(field.value), encoding: .ascii) ?? "UNABLE TO DECODE")\n" + } + description += "Signature: \t\(self.signature)\n" + + return description + } } \ No newline at end of file diff --git a/Sources/MessageUtils/Utils/CryptoUtils/Security.swift b/Sources/MessageUtils/Utils/CryptoUtils/Security.swift new file mode 100644 index 0000000..f331ba5 --- /dev/null +++ b/Sources/MessageUtils/Utils/CryptoUtils/Security.swift @@ -0,0 +1,58 @@ +import Crypto // Equivalent to CryptoKit (more or less) +import Foundation + + +// ------------------ +// --- Sign --------- +// ------------------ +public func signP521(object: Data, key: P521.Signing.PrivateKey)throws -> Data { + return try key.signature(for: object).rawRepresentation + +} +/* +public func sign(object: T, key: P521.Signing.PrivateKey) throws -> String { + + var _object = object + let data: Data = Data(bytes: &_object, count: MemoryLayout.stride) + +} */ + + + +// ------------------ +// --- Decrypt ------ +// ------------------ +public func verifySignatureP521(signature: Data, object: Data, key: P521.Signing.PublicKey) throws -> Bool { + + let ecdsa: P521.Signing.ECDSASignature + + do { + ecdsa = try P521.Signing.ECDSASignature(rawRepresentation: signature) + } catch { + throw SecurityError.NotDecodableError + } + + return key.isValidSignature(ecdsa, for: object) +} + + + +// ------------------ +// --- PEM 2 Key ---- +// ------------------ +public func pem2key(filePath: String) throws -> P521.Signing.PrivateKey { + + let pemURL: URL = URL(filePath: filePath) + + return try pem2key(filePem: pemURL) +} + +public func pem2key(filePem: URL) throws -> P521.Signing.PrivateKey { + + let fileString: String = try String(contentsOf: filePem, encoding: String.Encoding.utf8) + return try pem2key(pemString: fileString) +} + +public func pem2key(pemString: String) throws -> P521.Signing.PrivateKey { + return try P521.Signing.PrivateKey(pemRepresentation: pemString) +} diff --git a/Sources/MessageUtils/Utils/CryptoUtils/SecurityErrors.swift b/Sources/MessageUtils/Utils/CryptoUtils/SecurityErrors.swift new file mode 100644 index 0000000..b7e9cb7 --- /dev/null +++ b/Sources/MessageUtils/Utils/CryptoUtils/SecurityErrors.swift @@ -0,0 +1,4 @@ +enum SecurityError: Error { + case NotEncodableError + case NotDecodableError +} \ No newline at end of file diff --git a/Sources/MessageUtils/Utils/DataCompatibleP.swift b/Sources/MessageUtils/Utils/DataCompatibleP.swift index fb815c6..a5cd1a1 100644 --- a/Sources/MessageUtils/Utils/DataCompatibleP.swift +++ b/Sources/MessageUtils/Utils/DataCompatibleP.swift @@ -1,5 +1,5 @@ -import Foundation - -public protocol DataCompatibleP { - var data : Data {get} +import Foundation + +public protocol DataCompatibleP { + var data : Data {get} } \ No newline at end of file diff --git a/Sources/MessageUtils/Utils/DataToUtils.swift b/Sources/MessageUtils/Utils/DataToUtils.swift index bb7ff39..1196721 100644 --- a/Sources/MessageUtils/Utils/DataToUtils.swift +++ b/Sources/MessageUtils/Utils/DataToUtils.swift @@ -1,32 +1,32 @@ -import Foundation - -public extension Data { - - var uint8 : UInt8 { - return self.withUnsafeBytes{ $0.bindMemory(to: UInt8.self) }[0] - } - - var uint16: UInt16 { - return self.withUnsafeBytes{ $0.bindMemory(to: UInt16.self) }[0] - } - - var uint32: UInt32 { - return self.withUnsafeBytes{ $0.bindMemory(to: UInt32.self) }[0] - } - - var uint64: UInt64 { - return self.withUnsafeBytes{ $0.bindMemory(to: UInt64.self) }[0] - } - - var uint128: UInt128 { - return self.withUnsafeBytes{ $0.bindMemory(to: UInt128.self) }[0] - } - - var uint: UInt { - return self.withUnsafeBytes{ $0.bindMemory(to: UInt.self) }[0] - } - - var timestamp: Date { - return self.withUnsafeBytes{ $0.bindMemory(to: Date.self) }[0] - } +import Foundation + +public extension Data { + + var uint8 : UInt8 { + return self.withUnsafeBytes{ $0.bindMemory(to: UInt8.self) }[0] + } + + var uint16: UInt16 { + return self.withUnsafeBytes{ $0.bindMemory(to: UInt16.self) }[0] + } + + var uint32: UInt32 { + return self.withUnsafeBytes{ $0.bindMemory(to: UInt32.self) }[0] + } + + var uint64: UInt64 { + return self.withUnsafeBytes{ $0.bindMemory(to: UInt64.self) }[0] + } + + var uint128: UInt128 { + return self.withUnsafeBytes{ $0.bindMemory(to: UInt128.self) }[0] + } + + var uint: UInt { + return self.withUnsafeBytes{ $0.bindMemory(to: UInt.self) }[0] + } + + var timestamp: Date { + return self.withUnsafeBytes{ $0.bindMemory(to: Date.self) }[0] + } } \ No newline at end of file diff --git a/Sources/MessageUtils/Utils/MessageBytes.md b/Sources/MessageUtils/Utils/MessageBytes.md index 79320c9..0c9e944 100644 --- a/Sources/MessageUtils/Utils/MessageBytes.md +++ b/Sources/MessageUtils/Utils/MessageBytes.md @@ -1,52 +1,61 @@ -# Message Structure -``` -+-+ 64 - bits---+----------------+-------------+----------------+-----------------------+ -|0| Version 7 | Message Type 8 | Dev Type 8 | RESERVED 16 | Sign Type 32 | -+-+-------------+----------------+-------------+----------------+-----------------------+ -| Timestamp 64 | -+---------------------------------------------------------------------------------------+ -| DevID 128 bits | -| | -+---------------------------------------------------------------------------------------+ -| Location 192 bits | -| | -| | -+---------------------------------------------------------------------------------------+ -\ / - | Fields -----------------------------------------------------------------------------| -/ \ -+---------------------------------------------------------------------------------------+ -| 0 Padding 64 - n | -+---------------------------------------------------------------------------------------+ -| Signature up to-512 bits | -| | -| | -| | -| | -| | -| | -| | -+---------------------------------------------------------------------------------------+ -``` - -# Fields -each field can be at most 8 * 2^32 bits of length: -- 34359738368 bits -- 4294967296 Bytes - -## Key -**MUST** be a `String` - -## Value -It's up to the Application to decide whether the value is -a string or another type of datum - -``` -+-- 64 - bits--------------------------+----------------------------------------+ -| Key-Length 32 | Value-Length 32 | -+--------------------------------------+----------------------------------------+ -\ Key / - |-----------------------------------------------------------------------------| -/ Value \ -+-------------------------------------------------------------------------------+ +# Message Structure +## Possible Improvements +As for now, we are not having a max abount of bytes the message may have, +so we are relying on the ability of the receiver to dynamically allocate a buffer for the message. + +However, we could add a field to tell a PRIORI the length of the whole message. + +Probably we would use between 8 B or 24 B to put this info + +(This won't be a 2.0 version, as this is not a final document yet) +``` ++-+ 64 - bits---+----------------+-------------+----------------+-----------------------+ +|0| Version 7 | Message Type 8 | Dev Type 8 | RESERVED 16 | Sign Type 32 | ++-+-------------+----------------+-------------+----------------+-----------------------+ +| Timestamp 64 | ++---------------------------------------------------------------------------------------+ +| DevID 128 bits | +| | ++---------------------------------------------------------------------------------------+ +| Location 192 bits | +| | +| | ++---------------------------------------------------------------------------------------+ +\ / + | Fields -----------------------------------------------------------------------------| +/ \ ++---------------------------------------------------------------------------------------+ +| 0 Padding 64 - n | ++---------------------------------------------------------------------------------------+ +| Signature up to-512 bits | +| | +| | +| | +| | +| | +| | +| | ++---------------------------------------------------------------------------------------+ +``` + +# Fields +each field can be at most 8 * 2^32 bits of length: +- 34359738368 bits +- 4294967296 Bytes + +## Key +**MUST** be a `String` + +## Value +It's up to the Application to decide whether the value is +a string or another type of datum + +``` ++-- 64 - bits--------------------------+----------------------------------------+ +| Key-Length 32 | Value-Length 32 | ++--------------------------------------+----------------------------------------+ +\ Key / + |-----------------------------------------------------------------------------| +/ Value \ ++-------------------------------------------------------------------------------+ ``` \ No newline at end of file diff --git a/Sources/MessageUtils/Utils/MessageP.swift b/Sources/MessageUtils/Utils/MessageP.swift new file mode 100644 index 0000000..eaa8b2d --- /dev/null +++ b/Sources/MessageUtils/Utils/MessageP.swift @@ -0,0 +1,16 @@ +import Foundation + +public protocol MessageP { + var version: UInt8 {get} + var messageType: MessageType {get} + var devType: DeviceType {get} + var RESERVED: UInt8 {get} + var signType: SignType {get} + + var timestamp: Date {get} + var devID: UInt128 {get} + var location: Location {get} + var fields: [Field] {get} + + func toString() -> String +} diff --git a/Sources/MessageUtils/Utils/ToDataUtils.swift b/Sources/MessageUtils/Utils/ToDataUtils.swift index fdc62a4..9508ab0 100644 --- a/Sources/MessageUtils/Utils/ToDataUtils.swift +++ b/Sources/MessageUtils/Utils/ToDataUtils.swift @@ -1,43 +1,43 @@ -import Foundation - -extension UInt8: DataCompatibleP { - public var data: Data { - var obj = self - return Data(bytes: &obj, count: MemoryLayout.stride) - } -} - -extension UInt16: DataCompatibleP { - public var data: Data { - var obj = self - return Data(bytes: &obj, count: MemoryLayout.stride) - } -} - -extension UInt32: DataCompatibleP { - public var data: Data { - var obj = self - return Data(bytes: &obj, count: MemoryLayout.stride) - } -} - -extension UInt64: DataCompatibleP { - public var data: Data { - var obj = self - return Data(bytes: &obj, count: MemoryLayout.stride) - } -} - -extension UInt128: DataCompatibleP { - public var data: Data { - var obj = self - return Data(bytes: &obj, count: MemoryLayout.stride) - } -} - -extension Date: DataCompatibleP { - public var data: Data { - var obj = self - return Data(bytes: &obj, count: MemoryLayout.stride) - } -} +import Foundation + +extension UInt8: DataCompatibleP { + public var data: Data { + var obj = self + return Data(bytes: &obj, count: MemoryLayout.stride) + } +} + +extension UInt16: DataCompatibleP { + public var data: Data { + var obj = self + return Data(bytes: &obj, count: MemoryLayout.stride) + } +} + +extension UInt32: DataCompatibleP { + public var data: Data { + var obj = self + return Data(bytes: &obj, count: MemoryLayout.stride) + } +} + +extension UInt64: DataCompatibleP { + public var data: Data { + var obj = self + return Data(bytes: &obj, count: MemoryLayout.stride) + } +} + +extension UInt128: DataCompatibleP { + public var data: Data { + var obj = self + return Data(bytes: &obj, count: MemoryLayout.stride) + } +} + +extension Date: DataCompatibleP { + public var data: Data { + var obj = self + return Data(bytes: &obj, count: MemoryLayout.stride) + } +} diff --git a/Tests/MessageUtilsTests/MessageUtilsTests.swift b/Tests/MessageUtilsTests/MessageUtilsTests.swift index 039c4db..3dd712a 100644 --- a/Tests/MessageUtilsTests/MessageUtilsTests.swift +++ b/Tests/MessageUtilsTests/MessageUtilsTests.swift @@ -1,87 +1,120 @@ -import Foundation - -import Testing -@testable import MessageUtils - - - -@Test func serializeMessage() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. - let msg = Message( - version: 1, - messageType: .KEEPALIVE, - devType: .EDGE_SENSOR, - RESERVED: 0, - signType: .P521, - timestamp: Date(), - devID: 1, - location: Location(x: 10,y: 20,z: 1), - fields: [], - signature: Array(repeating: 255, count: 132) - ) - - let data = try serializeV1(msg: msg) - -} - - -@Test func serializeMessageWithField() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. - let msg = Message( - version: 1, - messageType: .KEEPALIVE, - devType: .EDGE_SENSOR, - RESERVED: 0, - signType: .P521, - timestamp: Date(), - devID: 1, - location: Location(x: 10,y: 20,z: 1), - fields: [ - Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)), - Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)) - ], - signature: Array(repeating: 255, count: 132) - ) - - let data = try serializeV1(msg: msg) - -} - -@Test func serializeDeserializeMessage() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. - let msg = Message( - version: 1, - messageType: .KEEPALIVE, - devType: .EDGE_SENSOR, - RESERVED: 0, - signType: .P521, - timestamp: Date(), - devID: 1, - location: Location(x: 10,y: 20,z: 1), - fields: [ - Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)), - Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)) - ], - signature: Array(repeating: 255, count: 132) - ) - - print(msg.toString()) - - let data = try serializeV1(msg: msg) - - let bytes = data.map { value in - return value - } - - - - print(bytes.count % 8 == 0) - - - let msg2 = try derializeV1(serializedData: data) - print(msg2.toString()) - - -} - - +import Foundation + +import Testing + +@testable import MessageUtils + +@Test func serializeMessage() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. + let msg = SignedMessage( + version: 1, + messageType: .KEEPALIVE, + devType: .EDGE_SENSOR, + RESERVED: 0, + signType: .P521, + timestamp: Date(), + devID: 1, + location: Location(x: 10, y: 20, z: 1), + fields: [], + signature: Array(repeating: 255, count: 132) + ) + + let data = try serializeV1(msg: msg) + +} +@Test func serializeMessageWithField() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. + let msg = Message( + version: 1, + messageType: .KEEPALIVE, + devType: .EDGE_SENSOR, + RESERVED: 0, + signType: .P521, + timestamp: Date(), + devID: 1, + location: Location(x: 10, y: 20, z: 1), + fields: [ + Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)), + Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)), + ] + ) + + let data = try serializeV1(msg: msg) + +} +@Test func serializeDeserializeMessage() async throws { + let keyPath = "./Private/privateKey.pem" + let key = try pem2key(filePath: keyPath) + let publicKey = key.publicKey + + // Write your test here and use APIs like `#expect(...)` to check expected conditions. + let msg = Message( + version: 1, + messageType: .KEEPALIVE, + devType: .EDGE_SENSOR, + RESERVED: 0, + signType: .P521, + timestamp: Date(), + devID: 1, + location: Location(x: 10, y: 20, z: 1), + fields: [ + Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)), + Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)), + Field(key: Array("69".utf8), value: Array("Nice".utf8)), + Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)), + Field(key: Array("Live Love".utf8), value: Array("Laugh".utf8)), + ] + ) + + print(msg.toString()) + + var data = serializeV1(msg: msg) + + let bytes = data.map { value in + return value + } + + let actualBytes = countBytes(msg: msg) + + print("Number of Bytes for the message: \(bytes.count)") + print("Number of Computed Bytes: \(actualBytes)") + + let signature = try signMessage(msgData: data, signType: msg.signType, key: key) + data.append(contentsOf: signature) + + let msg2 = try deserializeV1(serializedData: data) + let authenticMessage = try verifyMessageSignature(message: msg2, key: publicKey) + + #expect(authenticMessage, "If this is not true, then we are doing some deserialization errors") + print(msg2.toString()) + +} +@Test func serializeMessageForLaterUse() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. + + let keyPath = "./Private/privateKey.pem" + let key = try pem2key(filePath: keyPath) + + let msg = Message( + version: 1, + messageType: .KEEPALIVE, + devType: .EDGE_SENSOR, + RESERVED: 0, + signType: .P521, + timestamp: Date(), + devID: 1, + location: Location(x: 10, y: 20, z: 1), + fields: [ + Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)), + Field(key: Array("Live Love".utf8), value: Array("Laugh".utf8)), + ] + ) + + var data = try serializeV1(msg: msg) + + let signature = try signMessage(msgData: data, signType: msg.signType, key: key) + data.append(Data(signature)) + + try data.write(to: URL(filePath: "./Private/signedMessage/Message.bin")) + +}