mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
138 lines
6.6 KiB
Swift
138 lines
6.6 KiB
Swift
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import Foundation
|
|
import GRDB
|
|
import Sodium
|
|
import SessionUtilitiesKit
|
|
|
|
extension MessageReceiver {
|
|
internal static func decryptWithSessionProtocol(
|
|
ciphertext: Data,
|
|
using x25519KeyPair: KeyPair,
|
|
using dependencies: Dependencies = Dependencies()
|
|
) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
|
let recipientX25519PrivateKey: Bytes = x25519KeyPair.secretKey
|
|
let recipientX25519PublicKey: Bytes = x25519KeyPair.publicKey
|
|
let signatureSize: Int = dependencies[singleton: .crypto].size(.signature)
|
|
let ed25519PublicKeySize: Int = dependencies[singleton: .crypto].size(.publicKey)
|
|
|
|
// 1. ) Decrypt the message
|
|
guard
|
|
let plaintextWithMetadata = dependencies[singleton: .crypto].generate(
|
|
.openedBytes(
|
|
anonymousCipherText: Bytes(ciphertext),
|
|
recipientPublicKey: Box.PublicKey(Bytes(recipientX25519PublicKey)),
|
|
recipientSecretKey: Bytes(recipientX25519PrivateKey)
|
|
)
|
|
),
|
|
plaintextWithMetadata.count > (signatureSize + ed25519PublicKeySize)
|
|
else { throw MessageReceiverError.decryptionFailed }
|
|
|
|
// 2. ) Get the message parts
|
|
let signature = Bytes(plaintextWithMetadata[plaintextWithMetadata.count - signatureSize ..< plaintextWithMetadata.count])
|
|
let senderED25519PublicKey = Bytes(plaintextWithMetadata[plaintextWithMetadata.count - (signatureSize + ed25519PublicKeySize) ..< plaintextWithMetadata.count - signatureSize])
|
|
let plaintext = Bytes(plaintextWithMetadata[0..<plaintextWithMetadata.count - (signatureSize + ed25519PublicKeySize)])
|
|
|
|
// 3. ) Verify the signature
|
|
let verificationData = plaintext + senderED25519PublicKey + recipientX25519PublicKey
|
|
|
|
guard
|
|
dependencies[singleton: .crypto].verify(
|
|
.signature(message: verificationData, publicKey: senderED25519PublicKey, signature: signature)
|
|
)
|
|
else { throw MessageReceiverError.invalidSignature }
|
|
|
|
// 4. ) Get the sender's X25519 public key
|
|
guard
|
|
let senderX25519PublicKey = dependencies[singleton: .crypto].generate(
|
|
.x25519(ed25519PublicKey: senderED25519PublicKey)
|
|
)
|
|
else { throw MessageReceiverError.decryptionFailed }
|
|
|
|
return (Data(plaintext), SessionId(.standard, publicKey: senderX25519PublicKey).hexString)
|
|
}
|
|
|
|
internal static func decryptWithSessionBlindingProtocol(
|
|
data: Data,
|
|
isOutgoing: Bool,
|
|
otherBlindedPublicKey: String,
|
|
with openGroupPublicKey: String,
|
|
userEd25519KeyPair: KeyPair,
|
|
using dependencies: Dependencies = Dependencies()
|
|
) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
|
/// Ensure the data is at least long enough to have the required components
|
|
guard
|
|
data.count > (dependencies[singleton: .crypto].size(.nonce24) + 2),
|
|
let blindedKeyPair = dependencies[singleton: .crypto].generate(
|
|
.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, using: dependencies)
|
|
),
|
|
let otherSessionId: SessionId = try? SessionId(from: otherBlindedPublicKey),
|
|
(otherSessionId.prefix == .blinded15 || otherSessionId.prefix == .blinded25)
|
|
else { throw MessageReceiverError.decryptionFailed }
|
|
|
|
/// Step one: calculate the shared encryption key, receiving from A to B
|
|
let kA: Bytes = (isOutgoing ? blindedKeyPair.publicKey : otherSessionId.publicKey)
|
|
guard
|
|
let dec_key: Bytes = dependencies[singleton: .crypto].generate(
|
|
.sharedBlindedEncryptionKey(
|
|
secretKey: userEd25519KeyPair.secretKey,
|
|
otherBlindedPublicKey: otherSessionId.publicKey,
|
|
fromBlindedPublicKey: kA,
|
|
toBlindedPublicKey: (isOutgoing ? otherSessionId.publicKey : blindedKeyPair.publicKey),
|
|
using: dependencies
|
|
)
|
|
)
|
|
else { throw MessageReceiverError.decryptionFailed }
|
|
|
|
/// v, ct, nc = data[0], data[1:-24], data[-24:]
|
|
let version: UInt8 = data.bytes[0]
|
|
let ciphertext: Bytes = Bytes(data.bytes[1..<(data.count - dependencies[singleton: .crypto].size(.nonce24))])
|
|
let nonce: Bytes = Bytes(data.bytes[(data.count - dependencies[singleton: .crypto].size(.nonce24))..<data.count])
|
|
|
|
/// Make sure our encryption version is okay
|
|
guard version == 0 else { throw MessageReceiverError.decryptionFailed }
|
|
|
|
/// Decrypt
|
|
guard
|
|
let innerBytes: Bytes = dependencies[singleton: .crypto].generate(
|
|
.decryptedBytesAeadXChaCha20(
|
|
authenticatedCipherText: ciphertext,
|
|
secretKey: dec_key,
|
|
nonce: nonce
|
|
)
|
|
)
|
|
else { throw MessageReceiverError.decryptionFailed }
|
|
|
|
/// Ensure the length is correct
|
|
guard innerBytes.count > dependencies[singleton: .crypto].size(.publicKey) else { throw MessageReceiverError.decryptionFailed }
|
|
|
|
/// Split up: the last 32 bytes are the sender's *unblinded* ed25519 key
|
|
let plaintext: Bytes = Bytes(innerBytes[
|
|
0...(innerBytes.count - 1 - dependencies[singleton: .crypto].size(.publicKey))
|
|
])
|
|
let sender_edpk: Bytes = Bytes(innerBytes[
|
|
(innerBytes.count - dependencies[singleton: .crypto].size(.publicKey))...(innerBytes.count - 1)
|
|
])
|
|
|
|
/// Verify that the inner sender_edpk (A) yields the same outer kA we got with the message
|
|
guard
|
|
let blindingFactor: Bytes = dependencies[singleton: .crypto].generate(
|
|
.blindingFactor(serverPublicKey: openGroupPublicKey, using: dependencies)
|
|
),
|
|
let sharedSecret: Bytes = dependencies[singleton: .crypto].generate(
|
|
.combinedKeys(lhsKeyBytes: blindingFactor, rhsKeyBytes: sender_edpk)
|
|
),
|
|
kA == sharedSecret
|
|
else { throw MessageReceiverError.invalidSignature }
|
|
|
|
/// Get the sender's X25519 public key
|
|
guard
|
|
let senderSessionIdBytes: Bytes = dependencies[singleton: .crypto].generate(
|
|
.x25519(ed25519PublicKey: sender_edpk)
|
|
)
|
|
else { throw MessageReceiverError.decryptionFailed }
|
|
|
|
return (Data(plaintext), SessionId(.standard, publicKey: senderSessionIdBytes).hexString)
|
|
}
|
|
}
|