diff --git a/Sesame.xcodeproj/project.pbxproj b/Sesame.xcodeproj/project.pbxproj index cfa6890..3081a47 100644 --- a/Sesame.xcodeproj/project.pbxproj +++ b/Sesame.xcodeproj/project.pbxproj @@ -20,8 +20,8 @@ E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77327FF95920011CFD2 /* DeviceResponse.swift */; }; E24EE77727FF95C00011CFD2 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = E24EE77627FF95C00011CFD2 /* NIOCore */; }; 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 */; }; + E2C5C1DD281B3AC400769EF6 /* UInt32+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -38,8 +38,8 @@ E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = ""; }; E24EE77327FF95920011CFD2 /* DeviceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceResponse.swift; sourceTree = ""; }; E24EE77827FF95E00011CFD2 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; - E24EE77A280058240011CFD2 /* Message+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+Extensions.swift"; sourceTree = ""; }; E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteAPI.swift; sourceTree = ""; }; + E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt32+Extensions.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -102,7 +102,7 @@ E24EE77827FF95E00011CFD2 /* Message.swift */, 884A45CE27A5402D00D6E650 /* MessageResult.swift */, E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */, - E24EE77A280058240011CFD2 /* Message+Extensions.swift */, + E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */, ); path = API; sourceTree = ""; @@ -186,8 +186,8 @@ 884A45CF27A5402D00D6E650 /* MessageResult.swift in Sources */, 884A45B9279F48C100D6E650 /* ContentView.swift in Sources */, E2C5C1DB2806FE8900769EF6 /* RouteAPI.swift in Sources */, + E2C5C1DD281B3AC400769EF6 /* UInt32+Extensions.swift in Sources */, 884A45CD27A465F500D6E650 /* Client.swift in Sources */, - E24EE77B280058240011CFD2 /* Message+Extensions.swift in Sources */, E24EE77227FDCCC00011CFD2 /* Data+Extensions.swift in Sources */, E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */, 884A45CB27A464C000D6E650 /* SymmetricKey+Extensions.swift in Sources */, diff --git a/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate b/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate index e737ffa..dff6be4 100644 Binary files a/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate and b/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Sesame/API/Message+Extensions.swift b/Sesame/API/Message+Extensions.swift deleted file mode 100644 index 38fb2b9..0000000 --- a/Sesame/API/Message+Extensions.swift +++ /dev/null @@ -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(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.isValidAuthenticationCode(mac, authenticating: content.encoded, using: key) - } -} - -extension Message.Content { - - func authenticate(using key: SymmetricKey) -> Message { - let mac = HMAC.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.authenticationCode(for: encoded, using: key) - return Data(mac.map { $0 }) + encoded - } -} diff --git a/Sesame/API/Message.swift b/Sesame/API/Message.swift index 60ede14..4bed14a 100644 --- a/Sesame/API/Message.swift +++ b/Sesame/API/Message.swift @@ -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(decodeFrom data: T) where T.Element == UInt8 { - self.time = UInt32(data: data.prefix(MemoryLayout.size)) - self.id = UInt32(data: data.dropFirst(MemoryLayout.size)) + self.time = UInt32(data: Data(data.prefix(MemoryLayout.size))) + self.id = UInt32(data: Data(data.dropFirst(MemoryLayout.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(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.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(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.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.authenticationCode(for: encoded, using: key) + return Data(mac.map { $0 }) + encoded + } } diff --git a/Sesame/API/UInt32+Extensions.swift b/Sesame/API/UInt32+Extensions.swift new file mode 100644 index 0000000..90708ef --- /dev/null +++ b/Sesame/API/UInt32+Extensions.swift @@ -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)) + } +} diff --git a/Sesame/Data+Extensions.swift b/Sesame/Data+Extensions.swift index f36b39f..159efeb 100644 --- a/Sesame/Data+Extensions.swift +++ b/Sesame/Data+Extensions.swift @@ -40,3 +40,20 @@ extension Data { } } } + +extension Data { + + + func convert(into value: T) -> T { + withUnsafeBytes { + $0.baseAddress!.load(as: T.self) + } + } + + init(from value: T) { + var target = value + self = Swift.withUnsafeBytes(of: &target) { + Data($0) + } + } +}