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

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

@ -11,11 +11,19 @@ let package = Package(
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"),
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,10 +1,12 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book
import Foundation
import Crypto
public func serializeV1(msg: Message) throws -> Data {
public func serializeV1(msg: MessageP) -> Data {
let MESSAGE_CAPACITY: Int = try countBytes(msg: msg)
let MESSAGE_CAPACITY: Int = countBytes(msg: msg)
var serializedData: Data = Data(count: MESSAGE_CAPACITY)
@ -36,8 +38,9 @@ public func serializeV1(msg: Message) throws -> Data {
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
serializedData[index..<(index + 4)] = UInt32(field.key.count).data
serializedData[(index + 4)..<(index + 8)] = UInt32(field.value.count).data
index += 8
@ -57,19 +60,32 @@ public func serializeV1(msg: Message) throws -> Data {
// Add STOP FIELDS
serializedData[index..<(index + 8)] = UInt64(0).data
print(UInt64(0).data.base64EncodedString())
index += 8
serializedData[index..<serializedData.count] = Data(msg.signature)
if serializedData.count % 8 != 0 {
print(serializedData.count % 8)
print(MESSAGE_CAPACITY % 8)
}
return serializedData
}
public func derializeV1(serializedData: Data) throws -> Message {
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
@ -107,18 +123,21 @@ public func derializeV1(serializedData: Data) throws -> Message {
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
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
}
@ -135,20 +154,18 @@ public func derializeV1(serializedData: Data) throws -> Message {
let paddingBytes = (8 - (index % 8)) % 8
// Skip padding bytes
index += paddingBytes
index += paddingBytes + 8
let signature = serializedData[index..<serializedData.count].map { value in
return value
}
// Sanity check signature with signatureType
if signature.count != signBytes {
throw DeserializationError.UNMATCHING_SIGNATURE_TYPE
}
return Message(
return SignedMessage(
version: version,
messageType: messageType,
devType: devType,
@ -162,21 +179,15 @@ public func derializeV1(serializedData: Data) throws -> Message {
)
}
public func countBytes(msg: Message) throws -> Int {
public func countBytes(msg: MessageP) -> 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
let INITIAL_CAPACITY_BYTES: Int = 56
let FIELD_HEADER_CAPACITY_BYTES: Int = 8
var fieldReveservedCapacity: Int = 0
@ -184,10 +195,9 @@ public func countBytes(msg: Message) throws -> Int {
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)
fieldReveservedCapacity += (8 - (fieldReveservedCapacity % 8)) % 8 + 8
return (INITIAL_CAPACITY_BYTES + fieldReveservedCapacity + SIGNATURE_CAPACITY_BYTES)
return INITIAL_CAPACITY_BYTES + fieldReveservedCapacity
}

View File

@ -1,6 +1,40 @@
import Foundation
public struct Message{
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
@ -28,7 +62,7 @@ public struct Message{
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 += "\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"

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,4 +1,13 @@
# 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 |

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,13 +1,12 @@
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(
let msg = SignedMessage(
version: 1,
messageType: .KEEPALIVE,
devType: .EDGE_SENSOR,
@ -23,8 +22,6 @@ import Testing
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(
@ -38,16 +35,18 @@ import Testing
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)
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,
@ -60,28 +59,62 @@ import Testing
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)
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())
let data = try serializeV1(msg: msg)
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)")
print(bytes.count % 8 == 0)
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)
let msg2 = try derializeV1(serializedData: data)
#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"))
}