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.
		
		
		
		
		
			
		
			
				
	
	
		
			564 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			564 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Swift
		
	
| //
 | |
| //  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
 | |
| //
 | |
| 
 | |
| import Foundation
 | |
| import HKDFKit
 | |
| import SignalCoreKit
 | |
| 
 | |
| @objc
 | |
| public class SecretSessionKnownSenderError: NSObject, CustomNSError {
 | |
| 
 | |
|     @objc
 | |
|     public static let kSenderRecipientIdKey = "kSenderRecipientIdKey"
 | |
| 
 | |
|     @objc
 | |
|     public static let kSenderDeviceIdKey = "kSenderDeviceIdKey"
 | |
| 
 | |
|     public let senderRecipientId: String
 | |
|     public let senderDeviceId: UInt32
 | |
|     public let underlyingError: Error
 | |
| 
 | |
|     init(senderRecipientId: String, senderDeviceId: UInt32, underlyingError: Error) {
 | |
|         self.senderRecipientId = senderRecipientId
 | |
|         self.senderDeviceId = senderDeviceId
 | |
|         self.underlyingError = underlyingError
 | |
|     }
 | |
| 
 | |
|     public var errorUserInfo: [String: Any] {
 | |
|         return [
 | |
|             type(of: self).kSenderRecipientIdKey: self.senderRecipientId,
 | |
|             type(of: self).kSenderDeviceIdKey: self.senderDeviceId,
 | |
|             NSUnderlyingErrorKey: (underlyingError as NSError)
 | |
|         ]
 | |
|     }
 | |
| }
 | |
| 
 | |
| @objc
 | |
| public enum SMKSecretSessionCipherError: Int, Error {
 | |
|     case selfSentMessage
 | |
| }
 | |
| 
 | |
| // MARK: -
 | |
| 
 | |
| private class SMKSecretKeySpec: NSObject {
 | |
| 
 | |
|     @objc public let keyData: Data
 | |
|     @objc public let algorithm: String
 | |
| 
 | |
|     init(keyData: Data, algorithm: String) {
 | |
|         self.keyData = keyData
 | |
|         self.algorithm = algorithm
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: -
 | |
| 
 | |
| private class SMKEphemeralKeys: NSObject {
 | |
| 
 | |
|     @objc public let chainKey: Data
 | |
|     @objc public let cipherKey: SMKSecretKeySpec
 | |
|     @objc public let macKey: SMKSecretKeySpec
 | |
| 
 | |
|     init(chainKey: Data, cipherKey: Data, macKey: Data) {
 | |
|         self.chainKey = chainKey
 | |
|         self.cipherKey = SMKSecretKeySpec(keyData: cipherKey, algorithm: "AES")
 | |
|         self.macKey = SMKSecretKeySpec(keyData: macKey, algorithm: "HmacSHA256")
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: -
 | |
| 
 | |
| private class SMKStaticKeys: NSObject {
 | |
| 
 | |
|     @objc public let cipherKey: SMKSecretKeySpec
 | |
|     @objc public let macKey: SMKSecretKeySpec
 | |
| 
 | |
|     init(cipherKey: Data, macKey: Data) {
 | |
|         self.cipherKey = SMKSecretKeySpec(keyData: cipherKey, algorithm: "AES")
 | |
|         self.macKey = SMKSecretKeySpec(keyData: macKey, algorithm: "HmacSHA256")
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: -
 | |
| 
 | |
| @objc
 | |
| public class SMKDecryptResult: NSObject {
 | |
| 
 | |
|     @objc public let senderRecipientId: String
 | |
|     @objc public let senderDeviceId: Int
 | |
|     @objc public let paddedPayload: Data
 | |
|     @objc public let messageType: SMKMessageType
 | |
| 
 | |
|     init(senderRecipientId: String,
 | |
|          senderDeviceId: Int,
 | |
|          paddedPayload: Data,
 | |
|          messageType: SMKMessageType) {
 | |
|         self.senderRecipientId = senderRecipientId
 | |
|         self.senderDeviceId = senderDeviceId
 | |
|         self.paddedPayload = paddedPayload
 | |
|         self.messageType = messageType
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: -
 | |
| 
 | |
| @objc public class SMKSecretSessionCipher: NSObject {
 | |
| 
 | |
|     private let kUDPrefixString = "UnidentifiedDelivery"
 | |
| 
 | |
|     private let kSMKSecretSessionCipherMacLength: UInt = 10
 | |
| 
 | |
|     private let sessionResetImplementation: SessionRestorationProtocol!
 | |
|     private let sessionStore: SessionStore
 | |
|     private let preKeyStore: PreKeyStore
 | |
|     private let signedPreKeyStore: SignedPreKeyStore
 | |
|     private let identityStore: IdentityKeyStore
 | |
| 
 | |
|     @objc public init(sessionResetImplementation: SessionRestorationProtocol!,
 | |
|                       sessionStore: SessionStore,
 | |
|                       preKeyStore: PreKeyStore,
 | |
|                       signedPreKeyStore: SignedPreKeyStore,
 | |
|                       identityStore: IdentityKeyStore) throws {
 | |
|         self.sessionResetImplementation = sessionResetImplementation
 | |
|         self.sessionStore = sessionStore
 | |
|         self.preKeyStore = preKeyStore
 | |
|         self.signedPreKeyStore = signedPreKeyStore
 | |
|         self.identityStore = identityStore
 | |
|     }
 | |
| 
 | |
|     @objc public convenience init(sessionStore: SessionStore,
 | |
|                                   preKeyStore: PreKeyStore,
 | |
|                                   signedPreKeyStore: SignedPreKeyStore,
 | |
|                                   identityStore: IdentityKeyStore) throws {
 | |
|         try self.init(sessionResetImplementation: nil, sessionStore: sessionStore, preKeyStore: preKeyStore, signedPreKeyStore: signedPreKeyStore, identityStore: identityStore)
 | |
|     }
 | |
| 
 | |
|     // MARK: - Public
 | |
| 
 | |
|     @objc
 | |
|     public func throwswrapped_encryptMessage(recipientPublicKey: String,
 | |
|                                              deviceID: Int32,
 | |
|                                              paddedPlaintext: Data,
 | |
|                                              senderCertificate: SMKSenderCertificate,
 | |
|                                              protocolContext: Any,
 | |
|                                              useFallbackSessionCipher: Bool) throws -> Data {
 | |
|         guard recipientPublicKey.count > 0 else {
 | |
|             throw SMKError.assertionError(description: "\(SMKSecretSessionCipher.logTag) invalid recipientId")
 | |
|         }
 | |
| 
 | |
|         guard deviceID > 0 else {
 | |
|             throw SMKError.assertionError(description: "\(SMKSecretSessionCipher.logTag) invalid deviceId")
 | |
|         }
 | |
| 
 | |
|         guard let ourIdentityKeyPair = identityStore.identityKeyPair(protocolContext) else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Missing our identity key pair.")
 | |
|         }
 | |
| 
 | |
|         let encryptedMessage: CipherMessage
 | |
|         if useFallbackSessionCipher {
 | |
|             let cipher = FallBackSessionCipher(recipientPublicKey: recipientPublicKey, privateKey: try ourIdentityKeyPair.privateKey)
 | |
|             let ivAndCiphertext = cipher.encrypt(paddedPlaintext)!
 | |
|             encryptedMessage = FallbackMessage(_throws_with: ivAndCiphertext)
 | |
|         } else {
 | |
|             let cipher = SessionCipher(sessionStore: sessionStore,
 | |
|                                        preKeyStore: preKeyStore,
 | |
|                                        signedPreKeyStore: signedPreKeyStore,
 | |
|                                        identityKeyStore: identityStore,
 | |
|                                        recipientId: recipientPublicKey,
 | |
|                                        deviceId: deviceID)
 | |
|             encryptedMessage = try cipher.encryptMessage(paddedPlaintext, protocolContext: protocolContext)
 | |
|         }
 | |
| 
 | |
|         guard let encryptedMessageData = encryptedMessage.serialized() else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Could not serialize encrypted message.")
 | |
|         }
 | |
| 
 | |
|         guard let theirIdentityKeyData = Data.data(fromHex: recipientPublicKey.substring(from: recipientPublicKey.index(recipientPublicKey.startIndex, offsetBy: 2))) else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Missing their public identity key.")
 | |
|         }
 | |
| 
 | |
|         // NOTE: we don't use ECPublicKey(serializedKeyData) since the
 | |
|         // key data should not have a type byte.
 | |
|         let theirIdentityKey = try ECPublicKey(keyData: theirIdentityKeyData)
 | |
| 
 | |
|         let ephemeral = Curve25519.generateKeyPair()
 | |
| 
 | |
|         guard let prefixData = kUDPrefixString.data(using: String.Encoding.utf8) else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Could not encode prefix.")
 | |
|         }
 | |
| 
 | |
|         let ephemeralSalt = NSData.join([
 | |
|             prefixData,
 | |
|             theirIdentityKey.serialized,
 | |
|             try ephemeral.ecPublicKey().serialized
 | |
|         ])
 | |
| 
 | |
|         let ephemeralKeys = try throwswrapped_calculateEphemeralKeys(ephemeralPublicKey: theirIdentityKey,
 | |
|                                                                      ephemeralPrivateKey: ephemeral.ecPrivateKey(),
 | |
|                                                                      salt: ephemeralSalt)
 | |
| 
 | |
|         let staticKeyCipherData = try encrypt(cipherKey: ephemeralKeys.cipherKey,
 | |
|                                               macKey: ephemeralKeys.macKey,
 | |
|                                               plaintextData: ourIdentityKeyPair.ecPublicKey().serialized)
 | |
| 
 | |
|         let staticSalt = NSData.join([
 | |
|             ephemeralKeys.chainKey,
 | |
|             staticKeyCipherData
 | |
|         ])
 | |
| 
 | |
|         let staticKeys = try throwswrapped_calculateStaticKeys(staticPublicKey: theirIdentityKey,
 | |
|                                                                staticPrivateKey: ourIdentityKeyPair.ecPrivateKey(),
 | |
|                                                                salt: staticSalt)
 | |
| 
 | |
|         let messageType: SMKMessageType
 | |
|         switch encryptedMessage.cipherMessageType {
 | |
|         case .prekey:
 | |
|             messageType = .prekey
 | |
|         case .whisper:
 | |
|             messageType = .whisper
 | |
|         case .fallback:
 | |
|             messageType = .fallback
 | |
|         default:
 | |
|             throw SMKError.assertionError(description: "\(logTag) Unknown cipher message type.")
 | |
|         }
 | |
| 
 | |
|         let messageContent = SMKUnidentifiedSenderMessageContent(messageType: messageType,
 | |
|                                                                  senderCertificate: senderCertificate,
 | |
|                                                                  contentData: encryptedMessageData)
 | |
| 
 | |
|         let messageData = try encrypt(cipherKey: staticKeys.cipherKey,
 | |
|                                       macKey: staticKeys.macKey,
 | |
|                                       plaintextData: try messageContent.serialized())
 | |
| 
 | |
|         let message = SMKUnidentifiedSenderMessage(ephemeralKey: try ephemeral.ecPublicKey(),
 | |
|                                                    encryptedStatic: staticKeyCipherData,
 | |
|                                                    encryptedMessage: messageData)
 | |
| 
 | |
|         return try message.serialized()
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     public func throwswrapped_decryptMessage(certificateValidator: SMKCertificateValidator,
 | |
|                                              cipherTextData: Data,
 | |
|                                              timestamp: UInt64,
 | |
|                                              localRecipientId: String,
 | |
|                                              localDeviceId: Int32,
 | |
|                                              protocolContext: Any) throws -> SMKDecryptResult {
 | |
|         guard timestamp > 0 else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) invalid timestamp")
 | |
|         }
 | |
| 
 | |
|         guard let ourIdentityKeyPair = identityStore.identityKeyPair(protocolContext) else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Missing our identity key pair.")
 | |
|         }
 | |
| 
 | |
|         let wrapper = try SMKUnidentifiedSenderMessage.parse(dataAndPrefix: cipherTextData)
 | |
| 
 | |
|         guard let prefixData = kUDPrefixString.data(using: String.Encoding.utf8) else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Could not encode prefix.")
 | |
|         }
 | |
| 
 | |
|         let ephemeralSalt = NSData.join([
 | |
|             prefixData,
 | |
|             try ourIdentityKeyPair.ecPublicKey().serialized,
 | |
|             wrapper.ephemeralKey.serialized
 | |
|         ])
 | |
| 
 | |
|         let ephemeralKeys = try throwswrapped_calculateEphemeralKeys(ephemeralPublicKey: wrapper.ephemeralKey,
 | |
|                                                                      ephemeralPrivateKey: ourIdentityKeyPair.ecPrivateKey(),
 | |
|                                                                      salt: ephemeralSalt)
 | |
| 
 | |
|         let staticKeyBytes = try decrypt(cipherKey: ephemeralKeys.cipherKey,
 | |
|                                          macKey: ephemeralKeys.macKey,
 | |
|                                          cipherTextWithMac: wrapper.encryptedStatic)
 | |
| 
 | |
|         let staticKey = try ECPublicKey(serializedKeyData: staticKeyBytes)
 | |
| 
 | |
|         let staticSalt = NSData.join([
 | |
|             ephemeralKeys.chainKey,
 | |
|             wrapper.encryptedStatic
 | |
|         ])
 | |
| 
 | |
|         let staticKeys = try throwswrapped_calculateStaticKeys(staticPublicKey: staticKey,
 | |
|                                                                staticPrivateKey: ourIdentityKeyPair.ecPrivateKey(),
 | |
|                                                                salt: staticSalt)
 | |
| 
 | |
|         let messageBytes = try decrypt(cipherKey: staticKeys.cipherKey,
 | |
|                                        macKey: staticKeys.macKey,
 | |
|                                        cipherTextWithMac: wrapper.encryptedMessage)
 | |
| 
 | |
|         let messageContent = try SMKUnidentifiedSenderMessageContent.parse(data: messageBytes)
 | |
| 
 | |
|         let senderRecipientId = messageContent.senderCertificate.senderRecipientId
 | |
|         let senderDeviceId = messageContent.senderCertificate.senderDeviceId
 | |
| 
 | |
|         guard senderRecipientId != localRecipientId || senderDeviceId != localDeviceId else {
 | |
|             Logger.info("Discarding self-sent message")
 | |
|             throw SMKSecretSessionCipherError.selfSentMessage
 | |
|         }
 | |
| 
 | |
|         // validator.validate(content.getSenderCertificate(), timestamp);
 | |
| 
 | |
|         let wrapAsKnownSenderError = { (underlyingError: Error) in
 | |
|             return SecretSessionKnownSenderError(senderRecipientId: senderRecipientId, senderDeviceId: senderDeviceId, underlyingError: underlyingError)
 | |
|         }
 | |
| 
 | |
|         do {
 | |
|             try certificateValidator.throwswrapped_validate(senderCertificate: messageContent.senderCertificate,
 | |
|                                                             validationTime: timestamp)
 | |
|         } catch {
 | |
|             throw wrapAsKnownSenderError(error)
 | |
|         }
 | |
| 
 | |
| //        if (!MessageDigest.isEqual(content.getSenderCertificate().getKey().serialize(), staticKeyBytes)) {
 | |
| //            throw new InvalidKeyException("Sender's certificate key does not match key used in message");
 | |
| //        }
 | |
| 
 | |
| //        // NOTE: Constant time comparison.
 | |
| //        guard messageContent.senderCertificate.key.serialized.ows_constantTimeIsEqual(to: staticKeyBytes) else {
 | |
| //            let underlyingError = SMKError.assertionError(description: "\(logTag) Sender's certificate key does not match key used in message.")
 | |
| //            throw wrapAsKnownSenderError(underlyingError)
 | |
| //        }
 | |
| 
 | |
|         let paddedMessagePlaintext: Data
 | |
|         do {
 | |
|             paddedMessagePlaintext = try throwswrapped_decrypt(messageContent: messageContent, protocolContext: protocolContext)
 | |
|         } catch {
 | |
|             throw wrapAsKnownSenderError(error)
 | |
|         }
 | |
| 
 | |
|         // NOTE: We use the sender properties from the sender certificate, not from this class' properties.
 | |
|         guard senderDeviceId >= 0 && senderDeviceId <= INT_MAX else {
 | |
|             let underlyingError = SMKError.assertionError(description: "\(logTag) Invalid senderDeviceId.")
 | |
|             throw wrapAsKnownSenderError(underlyingError)
 | |
|         }
 | |
|         
 | |
|         return SMKDecryptResult(senderRecipientId: senderRecipientId,
 | |
|                                 senderDeviceId: Int(senderDeviceId),
 | |
|                                 paddedPayload: paddedMessagePlaintext,
 | |
|                                 messageType: messageContent.messageType)
 | |
|     }
 | |
| 
 | |
|     // MARK: - Encrypt
 | |
| 
 | |
|     // private EphemeralKeys calculateEphemeralKeys(ECPublicKey ephemeralPublic, ECPrivateKey ephemeralPrivate, byte[] salt)
 | |
|     // throws InvalidKeyException {
 | |
|     private func throwswrapped_calculateEphemeralKeys(ephemeralPublicKey: ECPublicKey,
 | |
|                                                       ephemeralPrivateKey: ECPrivateKey,
 | |
|                                                       salt: Data) throws -> SMKEphemeralKeys {
 | |
|         guard ephemeralPublicKey.keyData.count > 0 else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) invalid ephemeralPublicKey")
 | |
|         }
 | |
| 
 | |
|         guard ephemeralPrivateKey.keyData.count > 0 else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) invalid ephemeralPrivateKey")
 | |
|         }
 | |
| 
 | |
|         guard salt.count > 0 else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) invalid salt")
 | |
|         }
 | |
| 
 | |
|         // byte[] ephemeralSecret = Curve.calculateAgreement(ephemeralPublic, ephemeralPrivate);
 | |
|         //
 | |
|         // See:
 | |
|         // https://github.com/signalapp/libsignal-protocol-java/blob/master/java/src/main/java/org/whispersystems/libsignal/ecc/Curve.java#L30
 | |
|         let ephemeralSecret = try Curve25519.generateSharedSecret(fromPublicKey: ephemeralPublicKey.keyData, privateKey: ephemeralPrivateKey.keyData)
 | |
| 
 | |
|         // byte[] ephemeralDerived = new HKDFv3().deriveSecrets(ephemeralSecret, salt, new byte[0], 96);
 | |
|         let kEphemeralDerivedLength: UInt = 96
 | |
|         let ephemeralDerived: Data =
 | |
|             try HKDFKit.deriveKey(ephemeralSecret, info: Data(), salt: salt, outputSize: Int32(kEphemeralDerivedLength))
 | |
|         guard ephemeralDerived.count == kEphemeralDerivedLength else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) derived ephemeral has unexpected length: \(ephemeralDerived.count).")
 | |
|         }
 | |
| 
 | |
|         let ephemeralDerivedParser = OWSDataParser(data: ephemeralDerived)
 | |
|         let chainKey = try ephemeralDerivedParser.nextData(length: 32, name: "chain key")
 | |
|         let cipherKey = try ephemeralDerivedParser.nextData(length: 32, name: "cipher key")
 | |
|         let macKey = try ephemeralDerivedParser.nextData(length: 32, name: "mac key")
 | |
|         guard ephemeralDerivedParser.isEmpty else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) could not parse derived ephemeral.")
 | |
|         }
 | |
| 
 | |
|         return SMKEphemeralKeys(chainKey: chainKey, cipherKey: cipherKey, macKey: macKey)
 | |
|     }
 | |
| 
 | |
|     // private StaticKeys calculateStaticKeys(ECPublicKey staticPublic, ECPrivateKey staticPrivate, byte[] salt) throws
 | |
|     // InvalidKeyException {
 | |
|     private func throwswrapped_calculateStaticKeys(staticPublicKey: ECPublicKey,
 | |
|                                                    staticPrivateKey: ECPrivateKey,
 | |
|                                                    salt: Data) throws -> SMKStaticKeys {
 | |
|         guard staticPublicKey.keyData.count > 0 else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) invalid staticPublicKey")
 | |
|         }
 | |
|         guard staticPrivateKey.keyData.count > 0 else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) invalid staticPrivateKey")
 | |
|         }
 | |
|         guard salt.count > 0 else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) invalid salt")
 | |
|         }
 | |
| 
 | |
|         // byte[] staticSecret = Curve.calculateAgreement(staticPublic, staticPrivate);
 | |
|         //
 | |
|         // See:
 | |
|         // https://github.com/signalapp/libsignal-protocol-java/blob/master/java/src/main/java/org/whispersystems/libsignal/ecc/Curve.java#L30
 | |
|         let staticSecret = try Curve25519.generateSharedSecret(fromPublicKey: staticPublicKey.keyData, privateKey: staticPrivateKey.keyData)
 | |
| 
 | |
|         // byte[] staticDerived = new HKDFv3().deriveSecrets(staticSecret, salt, new byte[0], 96);
 | |
|         let kStaticDerivedLength: UInt = 96
 | |
|         let staticDerived: Data =
 | |
|             HKDFKit.deriveKey(staticSecret, info: Data(), salt: salt, outputSize: Int32(kStaticDerivedLength))
 | |
|         guard staticDerived.count == kStaticDerivedLength else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) could not derive static.")
 | |
|         }
 | |
| 
 | |
|         // byte[][] staticDerivedParts = ByteUtil.split(staticDerived, 32, 32, 32);
 | |
|         let staticDerivedParser = OWSDataParser(data: staticDerived)
 | |
|         _ = try staticDerivedParser.nextData(length: 32)
 | |
|         let cipherKey = try staticDerivedParser.nextData(length: 32)
 | |
|         let macKey = try staticDerivedParser.nextData(length: 32)
 | |
|         guard staticDerivedParser.isEmpty else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) invalid derived static.")
 | |
|         }
 | |
| 
 | |
|         // return new StaticKeys(staticDerivedParts[1], staticDerivedParts[2]);
 | |
|         return SMKStaticKeys(cipherKey: cipherKey, macKey: macKey)
 | |
|     }
 | |
| 
 | |
|     // private byte[] encrypt(SecretKeySpec cipherKey, SecretKeySpec macKey, byte[] plaintext) {
 | |
|     private func encrypt(cipherKey: SMKSecretKeySpec,
 | |
|                          macKey: SMKSecretKeySpec,
 | |
|                          plaintextData: Data) throws -> Data {
 | |
| 
 | |
|         // Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
 | |
|         // cipher.init(Cipher.ENCRYPT_MODE, cipherKey, new IvParameterSpec(new byte[16]));
 | |
|         // byte[] ciphertext = cipher.doFinal(plaintext);
 | |
|         guard let aesKey = OWSAES256Key(data: cipherKey.keyData) else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Invalid encryption key.")
 | |
|         }
 | |
| 
 | |
|         // NOTE: The IV is all zeroes.  This is fine since we're using a unique key.
 | |
|         let initializationVector = Data(count: Int(kAES256CTR_IVLength))
 | |
| 
 | |
|         guard let encryptionResult = Cryptography.encryptAESCTR(plaintextData: plaintextData, initializationVector: initializationVector, key: aesKey) else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Could not encrypt data.")
 | |
|         }
 | |
|         let cipherText = encryptionResult.ciphertext
 | |
| 
 | |
|         // Mac mac = Mac.getInstance("HmacSHA256");
 | |
|         // mac.init(macKey);
 | |
|         //
 | |
|         // byte[] ourFullMac = mac.doFinal(ciphertext);
 | |
|         // byte[] ourMac = ByteUtil.trim(ourFullMac, 10);
 | |
|         guard let ourMac = Cryptography.truncatedSHA256HMAC(cipherText, withHMACKey: macKey.keyData, truncation: 10) else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Could not compute HmacSHA256.")
 | |
|         }
 | |
| 
 | |
|         // return ByteUtil.combine(ciphertext, ourMac);
 | |
|         let result = NSData.join([
 | |
|             cipherText,
 | |
|             ourMac
 | |
|         ])
 | |
| 
 | |
|         return result
 | |
|     }
 | |
| 
 | |
|     // MARK: - Decrypt
 | |
| 
 | |
|     private func throwswrapped_decrypt(messageContent: SMKUnidentifiedSenderMessageContent,
 | |
|                                        protocolContext: Any) throws -> Data {
 | |
|         // NOTE: We use the sender properties from the sender certificate, not from this class' properties.
 | |
|         let senderRecipientId = messageContent.senderCertificate.senderRecipientId
 | |
|         let senderDeviceId = messageContent.senderCertificate.senderDeviceId
 | |
|         guard senderDeviceId >= 0 && senderDeviceId <= INT32_MAX else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Invalid senderDeviceId.")
 | |
|         }
 | |
| 
 | |
|         let cipherMessage: CipherMessage
 | |
|         switch (messageContent.messageType) {
 | |
|         case .whisper:
 | |
|             cipherMessage = try WhisperMessage(data: messageContent.contentData)
 | |
|         case .prekey:
 | |
|             cipherMessage = try PreKeyWhisperMessage(data: messageContent.contentData)
 | |
|         case .fallback:
 | |
|             let privateKey = try? identityStore.identityKeyPair(protocolContext)?.privateKey
 | |
|             let cipher = FallBackSessionCipher(recipientPublicKey: senderRecipientId, privateKey: privateKey)
 | |
|             let plaintext = cipher.decrypt(messageContent.contentData)!
 | |
|             return plaintext
 | |
|         }
 | |
| 
 | |
|         let cipher = LokiSessionCipher(sessionResetImplementation: sessionResetImplementation,
 | |
|                                        sessionStore: sessionStore,
 | |
|                                        preKeyStore: preKeyStore,
 | |
|                                        signedPreKeyStore: signedPreKeyStore,
 | |
|                                        identityKeyStore: identityStore,
 | |
|                                        recipientID: senderRecipientId,
 | |
|                                        deviceID: Int32(senderDeviceId))
 | |
| 
 | |
|         let plaintextData = try cipher.decrypt(cipherMessage, protocolContext: protocolContext)
 | |
|         return plaintextData
 | |
|     }
 | |
| 
 | |
|     // private byte[] decrypt(SecretKeySpec cipherKey, SecretKeySpec macKey, byte[] ciphertext) throws InvalidMacException {
 | |
|     private func decrypt(cipherKey: SMKSecretKeySpec,
 | |
|                          macKey: SMKSecretKeySpec,
 | |
|                          cipherTextWithMac: Data) throws -> Data {
 | |
| 
 | |
|         // if (ciphertext.count < 10) {
 | |
|         // throw new InvalidMacException("Ciphertext not long enough for MAC!");
 | |
|         // }
 | |
|         if (cipherTextWithMac.count < kSMKSecretSessionCipherMacLength) {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Cipher text not long enough for MAC.")
 | |
|         }
 | |
| 
 | |
|         // byte[][] ciphertextParts = ByteUtil.split(ciphertext, ciphertext.count - 10, 10);
 | |
|         let cipherTextWithMacParser = OWSDataParser(data: cipherTextWithMac)
 | |
|         let cipherTextLength = UInt(cipherTextWithMac.count) - kSMKSecretSessionCipherMacLength
 | |
|         let cipherText = try cipherTextWithMacParser.nextData(length: cipherTextLength, name: "cipher text")
 | |
|         let theirMac = try cipherTextWithMacParser.nextData(length: kSMKSecretSessionCipherMacLength, name: "their mac")
 | |
|         guard cipherTextWithMacParser.isEmpty else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Could not parse cipher text.")
 | |
|         }
 | |
| 
 | |
|         // Mac mac = Mac.getInstance("HmacSHA256");
 | |
|         // mac.init(macKey);
 | |
|         //
 | |
|         // byte[] digest = mac.doFinal(ciphertextParts[0]);
 | |
|         guard let ourFullMac = Cryptography.computeSHA256HMAC(cipherText, withHMACKey: macKey.keyData) else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) Could not compute HmacSHA256.")
 | |
|         }
 | |
| 
 | |
|         // byte[] ourMac = ByteUtil.trim(digest, 10);
 | |
|         guard ourFullMac.count >= kSMKSecretSessionCipherMacLength else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) HmacSHA256 has unexpected length.")
 | |
|         }
 | |
| 
 | |
|         let ourMac = ourFullMac[0..<kSMKSecretSessionCipherMacLength]
 | |
| 
 | |
|         // if (!MessageDigest.isEqual(ourMac, theirMac)) {
 | |
|         // throw new InvalidMacException("Bad mac!");
 | |
|         // }
 | |
|         //
 | |
|         // NOTE: Constant time comparison.
 | |
|         guard ourMac.ows_constantTimeIsEqual(to: theirMac) else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) macs do not match.")
 | |
|         }
 | |
| 
 | |
|         // Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
 | |
|         // cipher.init(Cipher.DECRYPT_MODE, cipherKey, new IvParameterSpec(new byte[16]));
 | |
|         guard let aesKey = OWSAES256Key(data: cipherKey.keyData) else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) could not parse AES256 key.")
 | |
|         }
 | |
| 
 | |
|         // NOTE: The IV is all zeroes.  This is fine since we're using a unique key.
 | |
|         let initializationVector = Data(count: Int(kAES256CTR_IVLength))
 | |
| 
 | |
|         guard let plaintext = Cryptography.decryptAESCTR(cipherText: cipherText, initializationVector: initializationVector, key: aesKey) else {
 | |
|             throw SMKError.assertionError(description: "\(logTag) could not decrypt AESGCM.")
 | |
|         }
 | |
| 
 | |
|         return plaintext
 | |
|     }
 | |
| }
 |