Proper serialization and cleanup
This commit is contained in:
@ -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
|
||||
}
|
||||
}
|
@ -1,11 +1,36 @@
|
||||
import Foundation
|
||||
import NIOCore
|
||||
|
||||
#if canImport(CryptoKit)
|
||||
import CryptoKit
|
||||
#else
|
||||
import Crypto
|
||||
#endif
|
||||
|
||||
/**
|
||||
An authenticated message to or from the device.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -30,13 +55,13 @@ struct Message: Equatable, Hashable {
|
||||
/**
|
||||
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.
|
||||
- Parameter data: The sequence containing the bytes.
|
||||
*/
|
||||
init<T: Sequence>(decodeFrom data: T) where T.Element == UInt8 {
|
||||
self.time = UInt32(data: data.prefix(MemoryLayout<UInt32>.size))
|
||||
self.id = UInt32(data: data.dropFirst(MemoryLayout<UInt32>.size))
|
||||
self.time = UInt32(data: Data(data.prefix(MemoryLayout<UInt32>.size)))
|
||||
self.id = UInt32(data: Data(data.dropFirst(MemoryLayout<UInt32>.size)))
|
||||
}
|
||||
|
||||
/// The byte length of an encoded message content
|
||||
@ -48,27 +73,15 @@ struct Message: Equatable, Hashable {
|
||||
var encoded: Data {
|
||||
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
|
||||
let content: Content
|
||||
extension Message {
|
||||
|
||||
/**
|
||||
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
|
||||
/// The length of a message in bytes
|
||||
static var length: Int {
|
||||
SHA256.byteCount + Content.length
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,35 +101,47 @@ struct Message: Equatable, Hashable {
|
||||
mac + content.encoded
|
||||
}
|
||||
|
||||
/// The message encoded to bytes
|
||||
var bytes: [UInt8] {
|
||||
Array(mac) + content.bytes
|
||||
/**
|
||||
Create a message from received 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)
|
||||
- Note: The data must contain exactly four bytes.
|
||||
*/
|
||||
init<T: Sequence>(data: T) where T.Element == UInt8 {
|
||||
self = data
|
||||
.reversed()
|
||||
.enumerated()
|
||||
.map { UInt32($0.element) << ($0.offset * 8) }
|
||||
.reduce(0, +)
|
||||
}
|
||||
/**
|
||||
Calculate an authentication code for the message content.
|
||||
- Parameter key: The key to use to sign the content.
|
||||
- Returns: The new message signed with the key.
|
||||
*/
|
||||
func authenticate(using key: SymmetricKey) -> Message {
|
||||
let mac = HMAC<SHA256>.authenticationCode(for: encoded, using: key)
|
||||
return .init(mac: Data(mac.map { $0 }), content: self)
|
||||
}
|
||||
|
||||
/// The value encoded to a big-endian representation
|
||||
var encoded: Data {
|
||||
.init(bytes)
|
||||
}
|
||||
|
||||
/// The value encoded to a big-endian byte array
|
||||
var bytes: [UInt8] {
|
||||
(0..<4).reversed().map {
|
||||
UInt8((self >> ($0*8)) & 0xFF)
|
||||
}
|
||||
}
|
||||
/**
|
||||
Calculate an authentication code for the message content and convert everything to data.
|
||||
- Parameter key: The key to use to sign the content.
|
||||
- Returns: The new message signed with the key, serialized to bytes.
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
|
18
Sesame/API/UInt32+Extensions.swift
Normal file
18
Sesame/API/UInt32+Extensions.swift
Normal 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))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user