Proper serialization and cleanup

This commit is contained in:
Christoph Hagen 2022-04-28 23:19:44 +02:00
parent 360833ae5f
commit 921b9237f7
6 changed files with 110 additions and 88 deletions

View File

@ -20,8 +20,8 @@
E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77327FF95920011CFD2 /* DeviceResponse.swift */; }; E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77327FF95920011CFD2 /* DeviceResponse.swift */; };
E24EE77727FF95C00011CFD2 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = E24EE77627FF95C00011CFD2 /* NIOCore */; }; E24EE77727FF95C00011CFD2 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = E24EE77627FF95C00011CFD2 /* NIOCore */; };
E24EE77927FF95E00011CFD2 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77827FF95E00011CFD2 /* Message.swift */; }; E24EE77927FF95E00011CFD2 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77827FF95E00011CFD2 /* Message.swift */; };
E24EE77B280058240011CFD2 /* Message+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77A280058240011CFD2 /* Message+Extensions.swift */; };
E2C5C1DB2806FE8900769EF6 /* RouteAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */; }; E2C5C1DB2806FE8900769EF6 /* RouteAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */; };
E2C5C1DD281B3AC400769EF6 /* UInt32+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -38,8 +38,8 @@
E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; }; E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; };
E24EE77327FF95920011CFD2 /* DeviceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceResponse.swift; sourceTree = "<group>"; }; E24EE77327FF95920011CFD2 /* DeviceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceResponse.swift; sourceTree = "<group>"; };
E24EE77827FF95E00011CFD2 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; }; E24EE77827FF95E00011CFD2 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; };
E24EE77A280058240011CFD2 /* Message+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+Extensions.swift"; sourceTree = "<group>"; };
E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteAPI.swift; sourceTree = "<group>"; }; E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteAPI.swift; sourceTree = "<group>"; };
E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt32+Extensions.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -102,7 +102,7 @@
E24EE77827FF95E00011CFD2 /* Message.swift */, E24EE77827FF95E00011CFD2 /* Message.swift */,
884A45CE27A5402D00D6E650 /* MessageResult.swift */, 884A45CE27A5402D00D6E650 /* MessageResult.swift */,
E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */, E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */,
E24EE77A280058240011CFD2 /* Message+Extensions.swift */, E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */,
); );
path = API; path = API;
sourceTree = "<group>"; sourceTree = "<group>";
@ -186,8 +186,8 @@
884A45CF27A5402D00D6E650 /* MessageResult.swift in Sources */, 884A45CF27A5402D00D6E650 /* MessageResult.swift in Sources */,
884A45B9279F48C100D6E650 /* ContentView.swift in Sources */, 884A45B9279F48C100D6E650 /* ContentView.swift in Sources */,
E2C5C1DB2806FE8900769EF6 /* RouteAPI.swift in Sources */, E2C5C1DB2806FE8900769EF6 /* RouteAPI.swift in Sources */,
E2C5C1DD281B3AC400769EF6 /* UInt32+Extensions.swift in Sources */,
884A45CD27A465F500D6E650 /* Client.swift in Sources */, 884A45CD27A465F500D6E650 /* Client.swift in Sources */,
E24EE77B280058240011CFD2 /* Message+Extensions.swift in Sources */,
E24EE77227FDCCC00011CFD2 /* Data+Extensions.swift in Sources */, E24EE77227FDCCC00011CFD2 /* Data+Extensions.swift in Sources */,
E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */, E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */,
884A45CB27A464C000D6E650 /* SymmetricKey+Extensions.swift in Sources */, 884A45CB27A464C000D6E650 /* SymmetricKey+Extensions.swift in Sources */,

View File

@ -1,38 +0,0 @@
import Foundation
#if canImport(CryptoKit)
import CryptoKit
#else
import Crypto
#endif
extension Message {
static var length: Int {
SHA256.byteCount + Content.length
}
init<T: Sequence>(decodeFrom data: T) where T.Element == UInt8 {
let count = SHA256.byteCount
self.mac = Data(data.prefix(count))
self.content = .init(decodeFrom: Array(data.dropFirst(count)))
}
func isValid(using key: SymmetricKey) -> Bool {
HMAC<SHA256>.isValidAuthenticationCode(mac, authenticating: content.encoded, using: key)
}
}
extension Message.Content {
func authenticate(using key: SymmetricKey) -> Message {
let mac = HMAC<SHA256>.authenticationCode(for: encoded, using: key)
return .init(mac: Data(mac.map { $0 }), content: self)
}
func authenticateAndSerialize(using key: SymmetricKey) -> Data {
let encoded = self.encoded
let mac = HMAC<SHA256>.authenticationCode(for: encoded, using: key)
return Data(mac.map { $0 }) + encoded
}
}

View File

@ -1,11 +1,36 @@
import Foundation import Foundation
import NIOCore import NIOCore
#if canImport(CryptoKit)
import CryptoKit
#else
import Crypto
#endif
/** /**
An authenticated message to or from the device. An authenticated message to or from the device.
*/ */
struct Message: Equatable, Hashable { struct Message: Equatable, Hashable {
/// The message authentication code for the message (32 bytes)
let mac: Data
/// The message content
let content: Content
/**
Create an authenticated message
- Parameter mac: The message authentication code
- Parameter content: The message content
*/
init(mac: Data, content: Content) {
self.mac = mac
self.content = content
}
}
extension Message {
/** /**
The message content without authentication. The message content without authentication.
*/ */
@ -30,13 +55,13 @@ struct Message: Equatable, Hashable {
/** /**
Decode message content from data. Decode message content from data.
The data consists of two `UInt32` encoded in big endian format (MSB at index 0) The data consists of two `UInt32` encoded in little endian format
- Warning: The sequence must contain at least 8 bytes, or the function will crash. - Warning: The sequence must contain at least 8 bytes, or the function will crash.
- Parameter data: The sequence containing the bytes. - Parameter data: The sequence containing the bytes.
*/ */
init<T: Sequence>(decodeFrom data: T) where T.Element == UInt8 { init<T: Sequence>(decodeFrom data: T) where T.Element == UInt8 {
self.time = UInt32(data: data.prefix(MemoryLayout<UInt32>.size)) self.time = UInt32(data: Data(data.prefix(MemoryLayout<UInt32>.size)))
self.id = UInt32(data: data.dropFirst(MemoryLayout<UInt32>.size)) self.id = UInt32(data: Data(data.dropFirst(MemoryLayout<UInt32>.size)))
} }
/// The byte length of an encoded message content /// The byte length of an encoded message content
@ -48,27 +73,15 @@ struct Message: Equatable, Hashable {
var encoded: Data { var encoded: Data {
time.encoded + id.encoded time.encoded + id.encoded
} }
/// The message content encoded to bytes
var bytes: [UInt8] {
time.bytes + id.bytes
}
} }
/// The message authentication code for the message (32 bytes) }
let mac: Data
/// The message content extension Message {
let content: Content
/** /// The length of a message in bytes
Create an authenticated message static var length: Int {
- Parameter mac: The message authentication code SHA256.byteCount + Content.length
- Parameter content: The message content
*/
init(mac: Data, content: Content) {
self.mac = mac
self.content = content
} }
/** /**
@ -88,35 +101,47 @@ struct Message: Equatable, Hashable {
mac + content.encoded mac + content.encoded
} }
/// The message encoded to bytes /**
var bytes: [UInt8] { Create a message from received bytes.
Array(mac) + content.bytes - Parameter data: The sequence of bytes
- Note: The sequence must contain at least `Message.length` bytes, or the function will crash.
*/
init<T: Sequence>(decodeFrom data: T) where T.Element == UInt8 {
let count = SHA256.byteCount
self.mac = Data(data.prefix(count))
self.content = .init(decodeFrom: Array(data.dropFirst(count)))
}
/**
Check if the message contains a valid authentication code
- Parameter key: The key used to sign the message.
- Returns: `true`, if the message is valid.
*/
func isValid(using key: SymmetricKey) -> Bool {
HMAC<SHA256>.isValidAuthenticationCode(mac, authenticating: content.encoded, using: key)
} }
} }
extension UInt32 { extension Message.Content {
/** /**
Create a value from a big-endian data representation (MSB first) Calculate an authentication code for the message content.
- Note: The data must contain exactly four bytes. - Parameter key: The key to use to sign the content.
*/ - Returns: The new message signed with the key.
init<T: Sequence>(data: T) where T.Element == UInt8 { */
self = data func authenticate(using key: SymmetricKey) -> Message {
.reversed() let mac = HMAC<SHA256>.authenticationCode(for: encoded, using: key)
.enumerated() return .init(mac: Data(mac.map { $0 }), content: self)
.map { UInt32($0.element) << ($0.offset * 8) } }
.reduce(0, +)
}
/// The value encoded to a big-endian representation /**
var encoded: Data { Calculate an authentication code for the message content and convert everything to data.
.init(bytes) - Parameter key: The key to use to sign the content.
} - Returns: The new message signed with the key, serialized to bytes.
*/
/// The value encoded to a big-endian byte array func authenticateAndSerialize(using key: SymmetricKey) -> Data {
var bytes: [UInt8] { let encoded = self.encoded
(0..<4).reversed().map { let mac = HMAC<SHA256>.authenticationCode(for: encoded, using: key)
UInt8((self >> ($0*8)) & 0xFF) return Data(mac.map { $0 }) + encoded
} }
}
} }

View File

@ -0,0 +1,18 @@
import Foundation
extension UInt32 {
/**
Create a value from a little-endian data representation (MSB first)
- Note: The data must contain exactly four bytes.
*/
init(data: Data) {
let value = data.convert(into: UInt32.zero)
self = CFSwapInt32LittleToHost(value)
}
/// The value encoded to a little-endian representation
var encoded: Data {
Data(from: CFSwapInt32HostToLittle(self))
}
}

View File

@ -40,3 +40,20 @@ extension Data {
} }
} }
} }
extension Data {
func convert<T>(into value: T) -> T {
withUnsafeBytes {
$0.baseAddress!.load(as: T.self)
}
}
init<T>(from value: T) {
var target = value
self = Swift.withUnsafeBytes(of: &target) {
Data($0)
}
}
}