Skip to content

Commit

Permalink
Add sender HMAC to MessageV2 (#218)
Browse files Browse the repository at this point in the history
* bump the proto code

* add should push entitlement

* dump the protos one more time

* update the protos again

* feat: add  to TextCodec & Composite

* feat: add HKDF key derivation and HMAC signature generation

* feat: add shouldPush parameter to MessageV2.encode() and prepareMessage() functions

* feat: add shouldPush method to the remaining content codecs

* fix: tests

* fix: remove print statement in MessageTests.swift

* fix: remove commented code

* feat: set  for all Content Types

* fix: add decoded content in prepareMessage function

* chore: remove unused dependencies

* Replace xmtp-rust-swift with libxmtp-swift (#217)

* rename module from XMTP to XMTPiOS

* more renaming

* remove xmtp-rust-swift references

* Use new V2 client from libxmtp

* building clean

* remove PublishResponse

* Use actual dep instead of local

* Update XMTP.podspec

Co-authored-by: Naomi Plasterer <[email protected]>

* implement streaming and pagination

* Update XMTP.podspec

Co-authored-by: Cameron Voell <[email protected]>

* disable file_length linter

---------

Co-authored-by: Naomi Plasterer <[email protected]>
Co-authored-by: Cameron Voell <[email protected]>

* Update for latest uniffi (#219)

* Fix example app (#220)

* Access libxmtp-swift with static binaries (#225)

* Access libxmtp-swift with static binaries

* Update libxmtp ref

* Updated reference to libxmtp-swift merge on main

* Merge pre libxmtp into main (#223)

* Add Key Material Function (#222)

* add ket material field to ios

* bump the podspec

* Update podspec LibXMTP ref

* Update libxmtp-swift refs

* Update libxmtp-swift ref

* Updated libxmtp-swift ref to tagged release

* Update XMTP.podspec version

---------

Co-authored-by: cameronvoell <[email protected]>

* Update Package.resolved with new branch and revision

* bring back the proto work

* remove the xmtp folder

* chore: refactor message encoding and decoding

* chore: update libxmtp-swift to latest version

* fix: refactor ConversationTests.swift

* potential test

* chore: update XMTP.podspec version to 0.7.9-alpha0

---------

Co-authored-by: kele-leanes <[email protected]>
Co-authored-by: Pat Nakajima <[email protected]>
Co-authored-by: Cameron Voell <[email protected]>
Co-authored-by: Cameron Voell <[email protected]>
  • Loading branch information
5 people authored Feb 2, 2024
1 parent c8e4027 commit c6a93cd
Show file tree
Hide file tree
Showing 19 changed files with 2,673 additions and 1,777 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/xmtp/libxmtp-swift",
"state" : {
"branch" : "ccbf6ac",
"revision" : "ccbf6ac71b8c5a89c3078d8dc4057123bea8a291"
"branch" : "92274fe",
"revision" : "92274fe0dde1fc7f8f716ebcffa3d252813be56d"
}
},
{
Expand Down
4 changes: 4 additions & 0 deletions Sources/XMTPiOS/Codecs/AttachmentCodec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,8 @@ public struct AttachmentCodec: ContentCodec {
public func fallback(content: Attachment) throws -> String? {
return "Can’t display “\(content.filename)”. This app doesn’t support attachments."
}

public func shouldPush(content: Attachment) throws -> Bool {
return true
}
}
4 changes: 4 additions & 0 deletions Sources/XMTPiOS/Codecs/Composite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ struct CompositeCodec: ContentCodec {
public func fallback(content: DecodedComposite) throws -> String? {
return nil
}

public func shouldPush(content: DecodedComposite) throws -> Bool {
return false
}

func toComposite(content decodedComposite: DecodedComposite) -> Composite {
var composite = Composite()
Expand Down
1 change: 1 addition & 0 deletions Sources/XMTPiOS/Codecs/ContentCodec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public protocol ContentCodec: Hashable, Equatable {
func encode(content: T, client: Client) throws -> EncodedContent
func decode(content: EncodedContent, client: Client) throws -> T
func fallback(content: T) throws -> String?
func shouldPush(content: T) throws -> Bool
}

public extension ContentCodec {
Expand Down
11 changes: 11 additions & 0 deletions Sources/XMTPiOS/Codecs/ReactionCodec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,15 @@ public struct ReactionCodec: ContentCodec {
return nil
}
}

public func shouldPush(content: Reaction) throws -> Bool {
switch content.action {
case .added:
return true
case .removed:
return false
case .unknown:
return false
}
}
}
4 changes: 4 additions & 0 deletions Sources/XMTPiOS/Codecs/ReadReceiptCodec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,8 @@ public struct ReadReceiptCodec: ContentCodec {
public func fallback(content: ReadReceipt) throws -> String? {
return nil
}

public func shouldPush(content: ReadReceipt) throws -> Bool {
return false
}
}
4 changes: 4 additions & 0 deletions Sources/XMTPiOS/Codecs/RemoteAttachmentCodec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,8 @@ public struct RemoteAttachmentCodec: ContentCodec {

return Data(parameterData)
}

public func shouldPush(content: RemoteAttachment) throws -> Bool {
return true
}
}
4 changes: 4 additions & 0 deletions Sources/XMTPiOS/Codecs/ReplyCodec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,8 @@ public struct ReplyCodec: ContentCodec {
public func fallback(content: Reply) throws -> String? {
return "Replied with “\(content.content)” to an earlier message"
}

public func shouldPush(content: Reply) throws -> Bool {
return true
}
}
4 changes: 4 additions & 0 deletions Sources/XMTPiOS/Codecs/TextCodec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ public struct TextCodec: ContentCodec {
public func fallback(content: String) throws -> String? {
return nil
}

public func shouldPush(content: String) throws -> Bool {
return true
}
}
8 changes: 6 additions & 2 deletions Sources/XMTPiOS/ConversationV2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,14 @@ public struct ConversationV2 {
}

func prepareMessage(encodedContent: EncodedContent, options: SendOptions?) async throws -> PreparedMessage {
let codec = client.codecRegistry.find(for: options?.contentType)

let message = try await MessageV2.encode(
client: client,
content: encodedContent,
topic: topic,
keyMaterial: keyMaterial
keyMaterial: keyMaterial,
codec: codec
)

let topic = options?.ephemeral == true ? ephemeralTopic : topic
Expand Down Expand Up @@ -233,7 +236,8 @@ public struct ConversationV2 {
client: client,
content: content,
topic: topic,
keyMaterial: keyMaterial
keyMaterial: keyMaterial,
codec: codec
)

let envelope = Envelope(
Expand Down
40 changes: 39 additions & 1 deletion Sources/XMTPiOS/Crypto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Foundation
public typealias CipherText = Xmtp_MessageContents_Ciphertext

enum CryptoError: Error {
case randomBytes, combinedPayload
case randomBytes, combinedPayload, keyDerivationError, hmacSignatureError
}

enum Crypto {
Expand Down Expand Up @@ -103,4 +103,42 @@ enum Crypto {
throw CryptoError.randomBytes
}
}

static func hkdfHmacKey(secret: Data, info: Data) throws -> SymmetricKey {
do {
let salt = try secureRandomBytes(count: 32)
let key = HKDF<SHA256>.deriveKey(
inputKeyMaterial: SymmetricKey(data: secret),
salt: salt,
info: info,
outputByteCount: 32)
return key
} catch {
throw CryptoError.keyDerivationError
}
}

static func generateHmacSignature(secret: Data, info: Data, message: Data) throws -> Data {
do {
let key = try hkdfHmacKey(secret: secret, info: info)
let signature = HMAC<SHA256>.authenticationCode(for: message, using: key)
return Data(signature)
} catch {
throw CryptoError.hmacSignatureError
}
}

static func exportHmacKey(key: SymmetricKey) -> Data {
var exportedData = Data(count: key.bitCount / 8)
exportedData.withUnsafeMutableBytes { buffer in
key.withUnsafeBytes { keyBuffer in
buffer.copyMemory(from: keyBuffer)
}
}
return exportedData
}

static func importHmacKey(keyData: Data) -> SymmetricKey {
return SymmetricKey(data: keyData)
}
}
24 changes: 20 additions & 4 deletions Sources/XMTPiOS/Messages/MessageV2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ import LibXMTP
typealias MessageV2 = Xmtp_MessageContents_MessageV2

enum MessageV2Error: Error {
case invalidSignature, decodeError(String)
case invalidSignature, decodeError(String), invalidData
}

extension MessageV2 {
init(headerBytes: Data, ciphertext: CipherText) {
init(headerBytes: Data, ciphertext: CipherText, senderHmac: Data, shouldPush: Bool) {
self.init()
self.headerBytes = headerBytes
self.ciphertext = ciphertext
self.senderHmac = senderHmac
self.shouldPush = shouldPush
}

static func decrypt(_ id: String, _ topic: String, _ message: MessageV2, keyMaterial: Data, client: Client) throws -> DecryptedMessage {
Expand Down Expand Up @@ -78,7 +80,7 @@ extension MessageV2 {
}
}

static func encode(client: Client, content encodedContent: EncodedContent, topic: String, keyMaterial: Data) async throws -> MessageV2 {
static func encode<Codec: ContentCodec>(client: Client, content encodedContent: EncodedContent, topic: String, keyMaterial: Data, codec: Codec) async throws -> MessageV2 {
let payload = try encodedContent.serializedData()

let date = Date()
Expand All @@ -95,10 +97,24 @@ extension MessageV2 {
let signedBytes = try signedContent.serializedData()

let ciphertext = try Crypto.encrypt(keyMaterial, signedBytes, additionalData: headerBytes)

let thirtyDayPeriodsSinceEpoch = Int(date.timeIntervalSince1970 / 60 / 60 / 24 / 30)
let info = "\(thirtyDayPeriodsSinceEpoch)-\(client.address)"
guard let infoEncoded = info.data(using: .utf8) else {
throw MessageV2Error.invalidData
}

let senderHmac = try Crypto.generateHmacSignature(secret: keyMaterial, info: infoEncoded, message: headerBytes)

let decoded = try codec.decode(content: encodedContent, client: client)
let shouldPush = try codec.shouldPush(content: decoded)


return MessageV2(
headerBytes: headerBytes,
ciphertext: ciphertext
ciphertext: ciphertext,
senderHmac: senderHmac,
shouldPush: shouldPush
)
}
}
Loading

0 comments on commit c6a93cd

Please sign in to comment.