mirror of https://github.com/oxen-io/session-ios
Merge pull request #267 from loki-project/shared-sender-keys
Shared Sender Keys Protocol Changespull/268/head
commit
aa2757f2f8
@ -1 +1 @@
|
||||
Subproject commit c89ab147afd32d3dde1e249070dd544abcf9c1c5
|
||||
Subproject commit 06051dd2a8aeb819363f6e77de58a038c1ad705e
|
@ -0,0 +1,18 @@
|
||||
import CryptoSwift
|
||||
|
||||
enum DecryptionUtilities {
|
||||
|
||||
/// - Note: Sync. Don't call from the main thread.
|
||||
internal static func decrypt(_ ivAndCiphertext: Data, usingAESGCMWithSymmetricKey symmetricKey: Data) throws -> Data {
|
||||
if Thread.isMainThread {
|
||||
#if DEBUG
|
||||
preconditionFailure("It's illegal to call decrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.")
|
||||
#endif
|
||||
}
|
||||
let iv = ivAndCiphertext[0..<Int(EncryptionUtilities.ivSize)]
|
||||
let ciphertext = ivAndCiphertext[Int(EncryptionUtilities.ivSize)...]
|
||||
let gcm = GCM(iv: iv.bytes, tagLength: Int(EncryptionUtilities.gcmTagSize), mode: .combined)
|
||||
let aes = try AES(key: symmetricKey.bytes, blockMode: gcm, padding: .noPadding)
|
||||
return Data(try aes.decrypt(ciphertext.bytes))
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import CryptoSwift
|
||||
|
||||
internal typealias EncryptionResult = (ciphertext: Data, symmetricKey: Data, ephemeralPublicKey: Data)
|
||||
|
||||
enum EncryptionUtilities {
|
||||
internal static let gcmTagSize: UInt = 16
|
||||
internal static let ivSize: UInt = 12
|
||||
|
||||
/// - Note: Sync. Don't call from the main thread.
|
||||
internal static func encrypt(_ plaintext: Data, usingAESGCMWithSymmetricKey symmetricKey: Data) throws -> Data {
|
||||
if Thread.isMainThread {
|
||||
#if DEBUG
|
||||
preconditionFailure("It's illegal to call encrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.")
|
||||
#endif
|
||||
}
|
||||
let iv = Data.getSecureRandomData(ofSize: ivSize)!
|
||||
let gcm = GCM(iv: iv.bytes, tagLength: Int(gcmTagSize), mode: .combined)
|
||||
let aes = try AES(key: symmetricKey.bytes, blockMode: gcm, padding: .noPadding)
|
||||
let ciphertext = try aes.encrypt(plaintext.bytes)
|
||||
return iv + Data(bytes: ciphertext)
|
||||
}
|
||||
|
||||
/// - Note: Sync. Don't call from the main thread.
|
||||
internal static func encrypt(_ plaintext: Data, using hexEncodedX25519PublicKey: String) throws -> EncryptionResult {
|
||||
if Thread.isMainThread {
|
||||
#if DEBUG
|
||||
preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.")
|
||||
#endif
|
||||
}
|
||||
let x25519PublicKey = Data(hex: hexEncodedX25519PublicKey)
|
||||
let ephemeralKeyPair = Curve25519.generateKeyPair()
|
||||
let ephemeralSharedSecret = try Curve25519.generateSharedSecret(fromPublicKey: x25519PublicKey, privateKey: ephemeralKeyPair.privateKey)
|
||||
let salt = "LOKI"
|
||||
let symmetricKey = try HMAC(key: salt.bytes, variant: .sha256).authenticate(ephemeralSharedSecret.bytes)
|
||||
let ciphertext = try encrypt(plaintext, usingAESGCMWithSymmetricKey: Data(bytes: symmetricKey))
|
||||
return (ciphertext, Data(bytes: symmetricKey), ephemeralKeyPair.publicKey)
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
import CryptoSwift
|
||||
import SessionMetadataKit
|
||||
|
||||
@objc(LKClosedGroupUtilities)
|
||||
public final class ClosedGroupUtilities : NSObject {
|
||||
private static let gcmTagSize: UInt = 16
|
||||
private static let ivSize: UInt = 12
|
||||
|
||||
@objc(LKSSKDecryptionError)
|
||||
public class SSKDecryptionError : NSError { // Not called `Error` for Obj-C interoperablity
|
||||
|
||||
@objc public static let invalidGroupPublicKey = SSKDecryptionError(domain: "SSKErrorDomain", code: 1, userInfo: [ NSLocalizedDescriptionKey : "Invalid group public key." ])
|
||||
@objc public static let noData = SSKDecryptionError(domain: "SSKErrorDomain", code: 2, userInfo: [ NSLocalizedDescriptionKey : "Received an empty envelope." ])
|
||||
@objc public static let noGroupPrivateKey = SSKDecryptionError(domain: "SSKErrorDomain", code: 3, userInfo: [ NSLocalizedDescriptionKey : "Missing group private key." ])
|
||||
}
|
||||
|
||||
@objc(encryptData:usingGroupPublicKey:transaction:error:)
|
||||
public static func encrypt(data: Data, groupPublicKey: String, transaction: YapDatabaseReadWriteTransaction) throws -> Data {
|
||||
// 1. ) Encrypt the data with the user's sender key
|
||||
guard let userPublicKey = OWSIdentityManager.shared().identityKeyPair()?.hexEncodedPublicKey else {
|
||||
throw SMKError.assertionError(description: "[Loki] Couldn't find user key pair.")
|
||||
}
|
||||
let ciphertextAndKeyIndex = try SharedSenderKeysImplementation.shared.encrypt(data, forGroupWithPublicKey: groupPublicKey,
|
||||
senderPublicKey: userPublicKey, protocolContext: transaction)
|
||||
let ivAndCiphertext = ciphertextAndKeyIndex[0] as! Data
|
||||
let keyIndex = ciphertextAndKeyIndex[1] as! UInt
|
||||
let encryptedMessage = ClosedGroupCiphertextMessage(_throws_withIVAndCiphertext: ivAndCiphertext, senderPublicKey: Data(hex: userPublicKey), keyIndex: UInt32(keyIndex))
|
||||
// 2. ) Encrypt the result for the group's public key to hide the sender public key and key index
|
||||
let (ciphertext, _, ephemeralPublicKey) = try EncryptionUtilities.encrypt(encryptedMessage.serialized, using: groupPublicKey.removing05PrefixIfNeeded())
|
||||
// 3. ) Wrap the result
|
||||
return try SSKProtoClosedGroupCiphertextMessageWrapper.builder(ciphertext: ciphertext, ephemeralPublicKey: ephemeralPublicKey).build().serializedData()
|
||||
}
|
||||
|
||||
@objc(decryptEnvelope:transaction:error:)
|
||||
public static func decrypt(envelope: SSKProtoEnvelope, transaction: YapDatabaseReadWriteTransaction) throws -> [Any] {
|
||||
let (plaintext, senderPublicKey) = try decrypt(envelope: envelope, transaction: transaction)
|
||||
return [ plaintext, senderPublicKey ]
|
||||
}
|
||||
|
||||
public static func decrypt(envelope: SSKProtoEnvelope, transaction: YapDatabaseReadWriteTransaction) throws -> (plaintext: Data, senderPublicKey: String) {
|
||||
// 1. ) Check preconditions
|
||||
guard let groupPublicKey = envelope.source, SharedSenderKeysImplementation.shared.isClosedGroup(groupPublicKey) else {
|
||||
throw SSKDecryptionError.invalidGroupPublicKey
|
||||
}
|
||||
guard let data = envelope.content else {
|
||||
throw SSKDecryptionError.noData
|
||||
}
|
||||
guard let hexEncodedGroupPrivateKey = Storage.getClosedGroupPrivateKey(for: groupPublicKey) else {
|
||||
throw SSKDecryptionError.noGroupPrivateKey
|
||||
}
|
||||
let groupPrivateKey = Data(hex: hexEncodedGroupPrivateKey)
|
||||
// 2. ) Parse the wrapper
|
||||
let wrapper = try SSKProtoClosedGroupCiphertextMessageWrapper.parseData(data)
|
||||
let ivAndCiphertext = wrapper.ciphertext
|
||||
let ephemeralPublicKey = wrapper.ephemeralPublicKey
|
||||
// 3. ) Decrypt the data inside
|
||||
let ephemeralSharedSecret = try Curve25519.generateSharedSecret(fromPublicKey: ephemeralPublicKey, privateKey: groupPrivateKey)
|
||||
let salt = "LOKI"
|
||||
let symmetricKey = try HMAC(key: salt.bytes, variant: .sha256).authenticate(ephemeralSharedSecret.bytes)
|
||||
let closedGroupCiphertextMessageAsData = try DecryptionUtilities.decrypt(ivAndCiphertext, usingAESGCMWithSymmetricKey: Data(symmetricKey))
|
||||
// 4. ) Parse the closed group ciphertext message
|
||||
let closedGroupCiphertextMessage = ClosedGroupCiphertextMessage(_throws_with: closedGroupCiphertextMessageAsData)
|
||||
let senderPublicKey = closedGroupCiphertextMessage.senderPublicKey.toHexString()
|
||||
// 5. ) Use the info inside the closed group ciphertext message to decrypt the actual message content
|
||||
let plaintext = try SharedSenderKeysImplementation.shared.decrypt(closedGroupCiphertextMessage.ivAndCiphertext, forGroupWithPublicKey: groupPublicKey,
|
||||
senderPublicKey: senderPublicKey, keyIndex: UInt(closedGroupCiphertextMessage.keyIndex), protocolContext: transaction)
|
||||
// 6. ) Return
|
||||
return (plaintext, senderPublicKey)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue