V0.6.0 Arroyo Toad

Added the capability to sign and verify P521 Signature
This commit is contained in:
Christian Risi 2024-12-06 11:31:07 +00:00
parent 6859e27368
commit ad4fd555f1
25 changed files with 764 additions and 537 deletions

36
LICENSE
View File

@ -1,18 +1,18 @@
LICENSE LICENSE
======= =======
Permission to use, copy, modify, and distribute this software and its Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted, provided documentation for any purpose and without fee is hereby granted, provided
that the original copyright notices appear in all copies and that both that the original copyright notices appear in all copies and that both
copyright notice and this permission notice appear in supporting copyright notice and this permission notice appear in supporting
documentation, and that the name of the author not be used in advertising documentation, and that the name of the author not be used in advertising
or publicity pertaining to distribution of the software without specific or publicity pertaining to distribution of the software without specific
prior written permission. prior written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.

24
Package.resolved Normal file
View File

@ -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
}

View File

@ -1,24 +1,32 @@
// swift-tools-version: 6.0 // swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package. // The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription import PackageDescription
let package = Package( let package = Package(
name: "MessageUtils", name: "MessageUtils",
products: [ products: [
// Products define the executables and libraries a package produces, making them visible to other packages. // Products define the executables and libraries a package produces, making them visible to other packages.
.library( .library(
name: "MessageUtils", name: "MessageUtils",
targets: ["MessageUtils"]), targets: ["MessageUtils"]),
], ],
targets: [ dependencies: [
// Targets are the basic building blocks of a package, defining a module or a test suite. .package(url: "https://github.com/apple/swift-crypto.git", branch: "main"),
// Targets can depend on other targets in this package and products from dependencies.
.target( ],
name: "MessageUtils"), targets: [
.testTarget( // Targets are the basic building blocks of a package, defining a module or a test suite.
name: "MessageUtilsTests", // Targets can depend on other targets in this package and products from dependencies.
dependencies: ["MessageUtils"] .target(
), name: "MessageUtils",
] dependencies: [
) .product(name: "Crypto", package: "swift-crypto"),
]
),
.testTarget(
name: "MessageUtilsTests",
dependencies: ["MessageUtils"]
),
]
)

0
Private/.gitkeep Normal file
View File

16
Private/cert.pem Normal file
View File

@ -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-----

7
Private/privateKey.pem Normal file
View File

@ -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-----

View File

@ -0,0 +1,8 @@
-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBAQAy+3ElWUTttb9x
xVDshJlGt/clGdhPkp76aJ3LJySugsnC8RROUracnWQi2A+XnEI1ZskzYAFUfh7G
5o5ViDyhgYkDgYYABAAAfWnGEUMElY/XIWUTPvX65HX3N5IkJKPdVFzDRtaTHRJK
nEEvU7Z5iLAT9NpbVfCabvQXKo7LD5sjoJ1ZpSVcogDgCFCopmVin2ZLs5lyMtae
tpVDH8m+AIlRQkkuGmkasM+OV62kzSoHl/CL4eNz1xXwqsPtoBgvPiRFxNIE/0dz
9w==
-----END PRIVATE KEY-----

Binary file not shown.

View File

@ -1,2 +1,2 @@
# Swift-MessageUtils # Swift-MessageUtils

View File

@ -1,4 +1,4 @@
public enum DeviceType: UInt8 { public enum DeviceType: UInt8 {
case EDGE_SENSOR = 0 case EDGE_SENSOR = 0
case SCANNER_SENSOR = 1 case SCANNER_SENSOR = 1
} }

View File

@ -1,8 +1,8 @@
public enum MessageType: UInt8 { public enum MessageType: UInt8 {
case KEEPALIVE = 0 case KEEPALIVE = 0
case DATA = 1 case DATA = 1
case INFO = 2 case INFO = 2
case WARNING = 50 case WARNING = 50
case ERROR = 100 case ERROR = 100
case CRITICAL = 255 case CRITICAL = 255
} }

View File

@ -1,3 +1,3 @@
public enum SignType: UInt32 { public enum SignType: UInt32 {
case P521 = 10 case P521 = 10
} }

View File

@ -1,13 +1,13 @@
public enum SerializationError : Error { public enum SerializationError : Error {
case SIGNATURE_NOT_SUPPORTED case SIGNATURE_NOT_SUPPORTED
} }
public enum CommonError : Error { public enum CommonError : Error {
case SIGNATURE_NOT_SUPPORTED case SIGNATURE_NOT_SUPPORTED
} }
public enum DeserializationError: Error { public enum DeserializationError: Error {
case UNMATCHING_SIGNATURE_TYPE case UNMATCHING_SIGNATURE_TYPE
} }

View File

@ -1,203 +1,213 @@
// The Swift Programming Language // The Swift Programming Language
// https://docs.swift.org/swift-book // https://docs.swift.org/swift-book
import Foundation
import Foundation
public func serializeV1(msg: Message) throws -> Data { import Crypto
let MESSAGE_CAPACITY: Int = try countBytes(msg: msg) public func serializeV1(msg: MessageP) -> Data {
var serializedData: Data = Data(count: MESSAGE_CAPACITY) let MESSAGE_CAPACITY: Int = countBytes(msg: msg)
// Serialize Header var serializedData: Data = Data(count: MESSAGE_CAPACITY)
// UGLY: extrapolate in functions // Serialize Header
serializedData[0] = msg.version
serializedData[1] = msg.messageType.rawValue // UGLY: extrapolate in functions
serializedData[2] = msg.devType.rawValue serializedData[0] = msg.version
serializedData[3] = msg.RESERVED serializedData[1] = msg.messageType.rawValue
serializedData[4...7] = msg.signType.rawValue.data serializedData[2] = msg.devType.rawValue
// First 8 bytes serializedData[3] = msg.RESERVED
serializedData[4...7] = msg.signType.rawValue.data
serializedData[8...15] = msg.timestamp.data // First 8 bytes
// 8 Bytes
serializedData[8...15] = msg.timestamp.data
serializedData[16...31] = msg.devID.data // 8 Bytes
// 16 bytes
serializedData[16...31] = msg.devID.data
serializedData[32...39] = msg.location.x.data // 16 bytes
// 8 Bytes
serializedData[32...39] = msg.location.x.data
serializedData[40...47] = msg.location.y.data // 8 Bytes
// 8 Bytes
serializedData[40...47] = msg.location.y.data
serializedData[48...55] = msg.location.z.data // 8 Bytes
// 8 Bytes
serializedData[48...55] = msg.location.z.data
var index = 56 // 8 Bytes
for field in msg.fields { var index = 56
serializedData[index...(index + 3)] = UInt32(field.key.count).data
serializedData[(index + 4)...(index + 7)] = UInt32(field.key.count).data for field in msg.fields {
index += 8 serializedData[index..<(index + 4)] = UInt32(field.key.count).data
serializedData[(index + 4)..<(index + 8)] = UInt32(field.value.count).data
serializedData[index..<(index + field.key.count)] = Data(field.key)
index += 8
index += field.key.count
serializedData[index..<(index + field.key.count)] = Data(field.key)
serializedData[index..<(index + field.value.count)] = Data(field.value)
index += field.key.count
index += field.value.count
} serializedData[index..<(index + field.value.count)] = Data(field.value)
let paddingBytes = (8 - (index % 8)) % 8 index += field.value.count
}
serializedData[index..<(index + paddingBytes)] = Data(count: paddingBytes)
index += paddingBytes let paddingBytes = (8 - (index % 8)) % 8
// Add STOP FIELDS serializedData[index..<(index + paddingBytes)] = Data(count: paddingBytes)
serializedData[index..<(index + 8)] = UInt64(0).data index += paddingBytes
print(UInt64(0).data.base64EncodedString())
index += 8 // Add STOP FIELDS
serializedData[index..<serializedData.count] = Data(msg.signature) serializedData[index..<(index + 8)] = UInt64(0).data
if serializedData.count % 8 != 0 { return serializedData
print(serializedData.count % 8) }
print(MESSAGE_CAPACITY % 8)
} public func signMessage(msgData: Data, signType: SignType, key: P521.Signing.PrivateKey) throws -> [UInt8] {
let signatureBytes = try signatureBytes(signature: signType)
return serializedData
} // UGLY We are hypothesisying that signType is P521
let signature = try signP521(object: msgData, key: key).map { value in
public func derializeV1(serializedData: Data) throws -> Message { return value
}
// Serialize Header
return signature
// 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] public func verifyMessageSignature(message: SignedMessage, key: P521.Signing.PublicKey) throws -> Bool {
let signType: SignType = SignType(rawValue: serializedData[4...7].uint32)! // UGLY Assuming P521 Signature
// First 8 bytes
let msgData = serializeV1(msg: message)
let signBytes = try signatureBytes(signature: signType) return try verifySignatureP521(signature: Data(message.signature), object: msgData, key: key)
let timestamp = serializedData[8...15].timestamp }
// 8 Bytes
public func deserializeV1(serializedData: Data) throws -> SignedMessage {
let devID = serializedData[16...31].uint128
// 16 bytes // Serialize Header
let locationX = serializedData[32...39].uint64 // UGLY: extrapolate in functions
// 8 Bytes let version: UInt8 = serializedData[0]
let messageType: MessageType = MessageType(rawValue: serializedData[1])!
let locationY = serializedData[40...47].uint64 let devType: DeviceType = DeviceType(rawValue: serializedData[2])!
// 8 Bytes let RESERVED: UInt8 = serializedData[3]
let signType: SignType = SignType(rawValue: serializedData[4...7].uint32)!
let locationZ = serializedData[48...55].uint64 // First 8 bytes
// 8 Bytes
let signBytes = try signatureBytes(signature: signType)
var index = 56
let timestamp = serializedData[8...15].timestamp
// Deserializing Fields // 8 Bytes
var MORE_FIELDS = true
var fields: [Field] = [] let devID = serializedData[16...31].uint128
// 16 bytes
while MORE_FIELDS {
let locationX = serializedData[32...39].uint64
let nextChunk = serializedData[index..<(index + 8)] // 8 Bytes
let fieldKeyCount = serializedData[index...(index + 4)].uint32
let fieldValueCount = serializedData[(index + 4)..<(index + 8)].uint32 let locationY = serializedData[40...47].uint64
// 8 Bytes
index += 8
let locationZ = serializedData[48...55].uint64
let a = nextChunk.uint64 // 8 Bytes
if nextChunk.uint64 == 0 { var index = 56
MORE_FIELDS = false
continue // Deserializing Fields
} var MORE_FIELDS = true
var fields: [Field] = []
let key = serializedData[index..<(index + Int(fieldKeyCount))].map { value in
return value while MORE_FIELDS {
}
index += Int(fieldKeyCount) let nextChunk = serializedData[index..<(index + 8)]
let value = serializedData[index..<(index + Int(fieldValueCount))].map { value in
return value let a = nextChunk.map { value in
} return value
index += Int(fieldValueCount) }
fields.append(Field(key: key, value: value)) if nextChunk.uint64 == 0 {
MORE_FIELDS = false
} continue
}
let paddingBytes = (8 - (index % 8)) % 8
let fieldKeyCount = serializedData[index..<(index + 4)].uint32
// Skip padding bytes let fieldValueCount = serializedData[(index + 4)..<(index + 8)].uint32
index += paddingBytes
index += 8
let signature = serializedData[index..<serializedData.count].map { value in
return value 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
// Sanity check signature with signatureType return value
if signature.count != signBytes { }
throw DeserializationError.UNMATCHING_SIGNATURE_TYPE index += Int(fieldValueCount)
}
fields.append(Field(key: key, value: value))
return Message(
version: version, }
messageType: messageType,
devType: devType, let paddingBytes = (8 - (index % 8)) % 8
RESERVED: RESERVED,
signType: signType, // Skip padding bytes
timestamp: timestamp, index += paddingBytes + 8
devID: devID,
location: Location(x: locationX, y: locationY, z: locationZ), let signature = serializedData[index..<serializedData.count].map { value in
fields: fields, return value
signature: signature }
)
} // Sanity check signature with signatureType
if signature.count != signBytes {
public func countBytes(msg: Message) throws -> Int { throw DeserializationError.UNMATCHING_SIGNATURE_TYPE
}
///
/// Author: Christian Risi return SignedMessage(
/// version: version,
/// This is computed as all the bits for the fixed fields messageType: messageType,
/// devType: devType,
/// In this case we have: RESERVED: RESERVED,
/// - 4 B --> Generic Info signType: signType,
/// - 4 B --> Timestamp timestamp: timestamp,
/// - 8 B --> Device ID devID: devID,
/// - 12 B --> Device Location location: Location(x: locationX, y: locationY, z: locationZ),
/// - 8 B --> FIELDS STOP fields: fields,
let INITIAL_CAPACITY_BYTES: Int = 36 signature: signature
let FIELD_HEADER_CAPACITY_BYTES: Int = 4 )
}
var fieldReveservedCapacity: Int = 0
public func countBytes(msg: MessageP) -> Int {
for field in msg.fields {
fieldReveservedCapacity += FIELD_HEADER_CAPACITY_BYTES + field.key.count + field.value.count ///
} /// Author: Christian Risi
///
// UGLY: We are assuming P521 signature /// This is computed as all the bits for the fixed fields
let SIGNATURE_CAPACITY_BYTES: Int = try signatureBytes(signature: msg.signType) ///
let INITIAL_CAPACITY_BYTES: Int = 56
return (INITIAL_CAPACITY_BYTES + fieldReveservedCapacity + SIGNATURE_CAPACITY_BYTES) let FIELD_HEADER_CAPACITY_BYTES: Int = 8
} var fieldReveservedCapacity: Int = 0
public func signatureBytes(signature: SignType) throws -> Int { for field in msg.fields {
switch signature { fieldReveservedCapacity += FIELD_HEADER_CAPACITY_BYTES + field.key.count + field.value.count
}
case .P521:
return 132 fieldReveservedCapacity += (8 - (fieldReveservedCapacity % 8)) % 8 + 8
default: return INITIAL_CAPACITY_BYTES + fieldReveservedCapacity
throw CommonError.SIGNATURE_NOT_SUPPORTED
} }
}
public func signatureBytes(signature: SignType) throws -> Int {
switch signature {
case .P521:
return 132
default:
throw CommonError.SIGNATURE_NOT_SUPPORTED
}
}

View File

@ -1,8 +1,8 @@
import Foundation import Foundation
public struct Field { public struct Field {
public let key: [UInt8] public let key: [UInt8]
public let value: [UInt8] public let value: [UInt8]
} }

View File

@ -1,7 +1,7 @@
public struct Location { public struct Location {
public let x: UInt64 public let x: UInt64
public let y: UInt64 public let y: UInt64
public let z: UInt64 public let z: UInt64
} }

View File

@ -1,37 +1,71 @@
import Foundation import Foundation
public struct Message{ public struct Message : MessageP{
public let version: UInt8 public let version: UInt8
public let messageType: MessageType public let messageType: MessageType
public let devType: DeviceType public let devType: DeviceType
public let RESERVED: UInt8 public let RESERVED: UInt8
public let signType: SignType public let signType: SignType
public let timestamp : Date public let timestamp : Date
public let devID : UInt128 public let devID : UInt128
public let location: Location public let location: Location
public let fields: [Field] public let fields: [Field]
public let signature: [UInt8]
public func toString() -> String{
public func toString() -> String{ var description = ""
var description = "" description += "MESSAGE -------\n"
description += "MESSAGE -------\n" description += "V: \t\(self.version)\n"
description += "V: \t\(self.version)\n" description += "Message Type: \t\(self.messageType.rawValue)\n"
description += "Message Type: \t\(self.messageType.rawValue)\n" description += "Device Type: \t\(self.devType.rawValue)\n"
description += "Device Type: \t\(self.devType.rawValue)\n" description += "RESERVED: \t\(self.RESERVED)\n"
description += "RESERVED: \t\(self.RESERVED)\n" description += "Signature Type: \t\(self.signType.rawValue)\n"
description += "Signature Type: \t\(self.signType.rawValue)\n" description += "Timestamp: \t\(self.timestamp)\n"
description += "Timestamp: \t\(self.timestamp)\n" description += "Device ID: \t\(self.devID)\n"
description += "Device ID: \t\(self.devID)\n" description += "Location: \tX: \(self.location.x)\tY: \(self.location.y)\tZ: \(self.location.z)\n"
description += "Location: \tX: \(self.location.x)\tY: \(self.location.y)\tZ: \(self.location.z)\n" description += "Fields: \n"
description += "Fields: \n" for field in self.fields {
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 += "\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
}
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
}
} }

View File

@ -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<Data>(for: object).rawRepresentation
}
/*
public func sign<T>(object: T, key: P521.Signing.PrivateKey) throws -> String {
var _object = object
let data: Data = Data(bytes: &_object, count: MemoryLayout<T>.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<Data>(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)
}

View File

@ -0,0 +1,4 @@
enum SecurityError: Error {
case NotEncodableError
case NotDecodableError
}

View File

@ -1,5 +1,5 @@
import Foundation import Foundation
public protocol DataCompatibleP { public protocol DataCompatibleP {
var data : Data {get} var data : Data {get}
} }

View File

@ -1,32 +1,32 @@
import Foundation import Foundation
public extension Data { public extension Data {
var uint8 : UInt8 { var uint8 : UInt8 {
return self.withUnsafeBytes{ $0.bindMemory(to: UInt8.self) }[0] return self.withUnsafeBytes{ $0.bindMemory(to: UInt8.self) }[0]
} }
var uint16: UInt16 { var uint16: UInt16 {
return self.withUnsafeBytes{ $0.bindMemory(to: UInt16.self) }[0] return self.withUnsafeBytes{ $0.bindMemory(to: UInt16.self) }[0]
} }
var uint32: UInt32 { var uint32: UInt32 {
return self.withUnsafeBytes{ $0.bindMemory(to: UInt32.self) }[0] return self.withUnsafeBytes{ $0.bindMemory(to: UInt32.self) }[0]
} }
var uint64: UInt64 { var uint64: UInt64 {
return self.withUnsafeBytes{ $0.bindMemory(to: UInt64.self) }[0] return self.withUnsafeBytes{ $0.bindMemory(to: UInt64.self) }[0]
} }
var uint128: UInt128 { var uint128: UInt128 {
return self.withUnsafeBytes{ $0.bindMemory(to: UInt128.self) }[0] return self.withUnsafeBytes{ $0.bindMemory(to: UInt128.self) }[0]
} }
var uint: UInt { var uint: UInt {
return self.withUnsafeBytes{ $0.bindMemory(to: UInt.self) }[0] return self.withUnsafeBytes{ $0.bindMemory(to: UInt.self) }[0]
} }
var timestamp: Date { var timestamp: Date {
return self.withUnsafeBytes{ $0.bindMemory(to: Date.self) }[0] return self.withUnsafeBytes{ $0.bindMemory(to: Date.self) }[0]
} }
} }

View File

@ -1,52 +1,61 @@
# Message Structure # Message Structure
``` ## Possible Improvements
+-+ 64 - bits---+----------------+-------------+----------------+-----------------------+ As for now, we are not having a max abount of bytes the message may have,
|0| Version 7 | Message Type 8 | Dev Type 8 | RESERVED 16 | Sign Type 32 | so we are relying on the ability of the receiver to dynamically allocate a buffer for the message.
+-+-------------+----------------+-------------+----------------+-----------------------+
| Timestamp 64 | However, we could add a field to tell a PRIORI the length of the whole message.
+---------------------------------------------------------------------------------------+
| DevID 128 bits | 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)
| Location 192 bits | ```
| | +-+ 64 - bits---+----------------+-------------+----------------+-----------------------+
| | |0| Version 7 | Message Type 8 | Dev Type 8 | RESERVED 16 | Sign Type 32 |
+---------------------------------------------------------------------------------------+ +-+-------------+----------------+-------------+----------------+-----------------------+
\ / | Timestamp 64 |
| Fields -----------------------------------------------------------------------------| +---------------------------------------------------------------------------------------+
/ \ | DevID 128 bits |
+---------------------------------------------------------------------------------------+ | |
| 0 Padding 64 - n | +---------------------------------------------------------------------------------------+
+---------------------------------------------------------------------------------------+ | Location 192 bits |
| Signature up to-512 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 # Fields
a string or another type of datum each field can be at most 8 * 2^32 bits of length:
- 34359738368 bits
``` - 4294967296 Bytes
+-- 64 - bits--------------------------+----------------------------------------+
| Key-Length 32 | Value-Length 32 | ## Key
+--------------------------------------+----------------------------------------+ **MUST** be a `String`
\ Key /
|-----------------------------------------------------------------------------| ## Value
/ 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 \
+-------------------------------------------------------------------------------+
``` ```

View File

@ -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
}

View File

@ -1,43 +1,43 @@
import Foundation import Foundation
extension UInt8: DataCompatibleP { extension UInt8: DataCompatibleP {
public var data: Data { public var data: Data {
var obj = self var obj = self
return Data(bytes: &obj, count: MemoryLayout<Self>.stride) return Data(bytes: &obj, count: MemoryLayout<Self>.stride)
} }
} }
extension UInt16: DataCompatibleP { extension UInt16: DataCompatibleP {
public var data: Data { public var data: Data {
var obj = self var obj = self
return Data(bytes: &obj, count: MemoryLayout<Self>.stride) return Data(bytes: &obj, count: MemoryLayout<Self>.stride)
} }
} }
extension UInt32: DataCompatibleP { extension UInt32: DataCompatibleP {
public var data: Data { public var data: Data {
var obj = self var obj = self
return Data(bytes: &obj, count: MemoryLayout<Self>.stride) return Data(bytes: &obj, count: MemoryLayout<Self>.stride)
} }
} }
extension UInt64: DataCompatibleP { extension UInt64: DataCompatibleP {
public var data: Data { public var data: Data {
var obj = self var obj = self
return Data(bytes: &obj, count: MemoryLayout<Self>.stride) return Data(bytes: &obj, count: MemoryLayout<Self>.stride)
} }
} }
extension UInt128: DataCompatibleP { extension UInt128: DataCompatibleP {
public var data: Data { public var data: Data {
var obj = self var obj = self
return Data(bytes: &obj, count: MemoryLayout<Self>.stride) return Data(bytes: &obj, count: MemoryLayout<Self>.stride)
} }
} }
extension Date: DataCompatibleP { extension Date: DataCompatibleP {
public var data: Data { public var data: Data {
var obj = self var obj = self
return Data(bytes: &obj, count: MemoryLayout<Self>.stride) return Data(bytes: &obj, count: MemoryLayout<Self>.stride)
} }
} }

View File

@ -1,87 +1,120 @@
import Foundation import Foundation
import Testing import Testing
@testable import MessageUtils
@testable import MessageUtils
@Test func serializeMessage() async throws {
@Test func serializeMessage() async throws { // Write your test here and use APIs like `#expect(...)` to check expected conditions.
// Write your test here and use APIs like `#expect(...)` to check expected conditions. let msg = SignedMessage(
let msg = Message( version: 1,
version: 1, messageType: .KEEPALIVE,
messageType: .KEEPALIVE, devType: .EDGE_SENSOR,
devType: .EDGE_SENSOR, RESERVED: 0,
RESERVED: 0, signType: .P521,
signType: .P521, timestamp: Date(),
timestamp: Date(), devID: 1,
devID: 1, location: Location(x: 10, y: 20, z: 1),
location: Location(x: 10,y: 20,z: 1), fields: [],
fields: [], signature: Array(repeating: 255, count: 132)
signature: Array(repeating: 255, count: 132) )
)
let data = try serializeV1(msg: msg)
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(
@Test func serializeMessageWithField() async throws { version: 1,
// Write your test here and use APIs like `#expect(...)` to check expected conditions. messageType: .KEEPALIVE,
let msg = Message( devType: .EDGE_SENSOR,
version: 1, RESERVED: 0,
messageType: .KEEPALIVE, signType: .P521,
devType: .EDGE_SENSOR, timestamp: Date(),
RESERVED: 0, devID: 1,
signType: .P521, location: Location(x: 10, y: 20, z: 1),
timestamp: Date(), fields: [
devID: 1, Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)),
location: Location(x: 10,y: 20,z: 1), Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)),
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)
signature: Array(repeating: 255, count: 132)
) }
@Test func serializeDeserializeMessage() async throws {
let data = try serializeV1(msg: msg) let keyPath = "./Private/privateKey.pem"
let key = try pem2key(filePath: keyPath)
} let publicKey = key.publicKey
@Test func serializeDeserializeMessage() async throws { // Write your test here and use APIs like `#expect(...)` to check expected conditions.
// Write your test here and use APIs like `#expect(...)` to check expected conditions. let msg = Message(
let msg = Message( version: 1,
version: 1, messageType: .KEEPALIVE,
messageType: .KEEPALIVE, devType: .EDGE_SENSOR,
devType: .EDGE_SENSOR, RESERVED: 0,
RESERVED: 0, signType: .P521,
signType: .P521, timestamp: Date(),
timestamp: Date(), devID: 1,
devID: 1, location: Location(x: 10, y: 20, z: 1),
location: Location(x: 10,y: 20,z: 1), fields: [
fields: [ Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)),
Field(key: Array("valueOfLife".utf8), value: Array("42".utf8)), 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)),
signature: Array(repeating: 255, count: 132) Field(key: Array("Live Love".utf8), value: Array("Laugh".utf8)),
) ]
)
print(msg.toString())
print(msg.toString())
let data = try serializeV1(msg: msg)
var data = serializeV1(msg: msg)
let bytes = data.map { value in
return value let bytes = data.map { value in
} return value
}
let actualBytes = countBytes(msg: msg)
print(bytes.count % 8 == 0)
print("Number of Bytes for the message: \(bytes.count)")
print("Number of Computed Bytes: \(actualBytes)")
let msg2 = try derializeV1(serializedData: data)
print(msg2.toString()) 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"))
}