import CryptoSwift
import PromiseKit
import SessionUtilitiesKit
public protocol SharedSenderKeysDelegate {
func requestSenderKey ( for groupPublicKey : String , senderPublicKey : String , using transaction : Any )
}
public enum SharedSenderKeys {
private static let gcmTagSize : UInt = 16
private static let ivSize : UInt = 12
// MARK: R a t c h e t i n g E r r o r
public enum RatchetingError : LocalizedError {
case loadingFailed ( groupPublicKey : String , senderPublicKey : String )
case messageKeyMissing ( targetKeyIndex : UInt , groupPublicKey : String , senderPublicKey : String )
case generic
public var errorDescription : String ? {
switch self {
case . loadingFailed ( let groupPublicKey , let senderPublicKey ) : return " Couldn't get ratchet for closed group with public key: \( groupPublicKey ) , sender public key: \( senderPublicKey ) . "
case . messageKeyMissing ( let targetKeyIndex , let groupPublicKey , let senderPublicKey ) : return " Couldn't find message key for old key index: \( targetKeyIndex ) , public key: \( groupPublicKey ) , sender public key: \( senderPublicKey ) . "
case . generic : return " An error occurred "
}
}
}
// MARK: P r i v a t e / I n t e r n a l A P I
public static func generateRatchet ( for groupPublicKey : String , senderPublicKey : String , using transaction : Any ) -> ClosedGroupRatchet {
let rootChainKey = Data . getSecureRandomData ( ofSize : 32 ) ! . toHexString ( )
let ratchet = ClosedGroupRatchet ( chainKey : rootChainKey , keyIndex : 0 , messageKeys : [ ] )
Configuration . shared . storage . setClosedGroupRatchet ( for : groupPublicKey , senderPublicKey : senderPublicKey , ratchet : ratchet , in : . current , using : transaction )
return ratchet
}
private static func step ( _ ratchet : ClosedGroupRatchet ) throws -> ClosedGroupRatchet {
let nextMessageKey = try HMAC ( key : Data ( hex : ratchet . chainKey ) . bytes , variant : . sha256 ) . authenticate ( [ UInt8 ( 1 ) ] )
let nextChainKey = try HMAC ( key : Data ( hex : ratchet . chainKey ) . bytes , variant : . sha256 ) . authenticate ( [ UInt8 ( 2 ) ] )
let nextKeyIndex = ratchet . keyIndex + 1
let messageKeys = ratchet . messageKeys + [ nextMessageKey . toHexString ( ) ]
return ClosedGroupRatchet ( chainKey : nextChainKey . toHexString ( ) , keyIndex : nextKeyIndex , messageKeys : messageKeys )
}
// / - N o t e : S y n c . D o n ' t c a l l f r o m t h e m a i n t h r e a d .
private static func stepRatchetOnce ( for groupPublicKey : String , senderPublicKey : String , using transaction : Any ) throws -> ClosedGroupRatchet {
#if DEBUG
assert ( ! Thread . isMainThread )
#endif
guard let ratchet = Configuration . shared . storage . getClosedGroupRatchet ( for : groupPublicKey , senderPublicKey : senderPublicKey , from : . current ) else {
let error = RatchetingError . loadingFailed ( groupPublicKey : groupPublicKey , senderPublicKey : senderPublicKey )
SNLog ( " \( error . errorDescription ! ) " )
throw error
}
do {
let result = try step ( ratchet )
Configuration . shared . storage . setClosedGroupRatchet ( for : groupPublicKey , senderPublicKey : senderPublicKey , ratchet : result , in : . current , using : transaction )
return result
} catch {
SNLog ( " Couldn't step ratchet due to error: \( error ) . " )
throw error
}
}
// / - N o t e : S y n c . D o n ' t c a l l f r o m t h e m a i n t h r e a d .
private static func stepRatchet ( for groupPublicKey : String , senderPublicKey : String , until targetKeyIndex : UInt , using transaction : Any , isRetry : Bool = false ) throws -> ClosedGroupRatchet {
#if DEBUG
assert ( ! Thread . isMainThread )
#endif
let collection : ClosedGroupRatchetCollectionType = ( isRetry ) ? . old : . current
guard let ratchet = Configuration . shared . storage . getClosedGroupRatchet ( for : groupPublicKey , senderPublicKey : senderPublicKey , from : collection ) else {
let error = RatchetingError . loadingFailed ( groupPublicKey : groupPublicKey , senderPublicKey : senderPublicKey )
SNLog ( " \( error . errorDescription ! ) " )
throw error
}
if targetKeyIndex < ratchet . keyIndex {
// T h e r e ' s n o n e e d t o a d v a n c e t h e r a t c h e t i f t h i s i s i n v o k e d f o r a n o l d k e y i n d e x
guard ratchet . messageKeys . count > targetKeyIndex else {
let error = RatchetingError . messageKeyMissing ( targetKeyIndex : targetKeyIndex , groupPublicKey : groupPublicKey , senderPublicKey : senderPublicKey )
SNLog ( " \( error . errorDescription ! ) " )
throw error
}
return ratchet
} else {
var currentKeyIndex = ratchet . keyIndex
var result = ratchet
while currentKeyIndex < targetKeyIndex {
do {
result = try step ( result )
currentKeyIndex = result . keyIndex
} catch {
SNLog ( " Couldn't step ratchet due to error: \( error ) . " )
throw error
}
}
let collection : ClosedGroupRatchetCollectionType = ( isRetry ) ? . old : . current
Configuration . shared . storage . setClosedGroupRatchet ( for : groupPublicKey , senderPublicKey : senderPublicKey , ratchet : result , in : collection , using : transaction )
return result
}
}
// MARK: P u b l i c A P I
public static func encrypt ( _ plaintext : Data , for groupPublicKey : String , senderPublicKey : String , using transaction : Any ) throws -> ( ivAndCiphertext : Data , keyIndex : UInt ) {
let ratchet : ClosedGroupRatchet
do {
ratchet = try stepRatchetOnce ( for : groupPublicKey , senderPublicKey : senderPublicKey , using : transaction )
} catch {
if case RatchetingError . loadingFailed ( _ , _ ) = error {
Configuration . shared . sharedSenderKeysDelegate . requestSenderKey ( for : groupPublicKey , senderPublicKey : senderPublicKey , using : transaction )
}
throw error
}
let iv = Data . getSecureRandomData ( ofSize : ivSize ) !
let gcm = GCM ( iv : iv . bytes , tagLength : Int ( gcmTagSize ) , mode : . combined )
let messageKey = ratchet . messageKeys . last !
let aes = try AES ( key : Data ( hex : messageKey ) . bytes , blockMode : gcm , padding : . noPadding )
let ciphertext = try aes . encrypt ( plaintext . bytes )
return ( ivAndCiphertext : iv + Data ( ciphertext ) , ratchet . keyIndex )
}
public static func decrypt ( _ ivAndCiphertext : Data , for groupPublicKey : String , senderPublicKey : String , keyIndex : UInt , using transaction : Any , isRetry : Bool = false ) throws -> Data {
let ratchet : ClosedGroupRatchet
do {
ratchet = try stepRatchet ( for : groupPublicKey , senderPublicKey : senderPublicKey , until : keyIndex , using : transaction , isRetry : isRetry )
} catch {
if ! isRetry {
return try decrypt ( ivAndCiphertext , for : groupPublicKey , senderPublicKey : senderPublicKey , keyIndex : keyIndex , using : transaction , isRetry : true )
} else {
if case RatchetingError . loadingFailed ( _ , _ ) = error {
Configuration . shared . sharedSenderKeysDelegate . requestSenderKey ( for : groupPublicKey , senderPublicKey : senderPublicKey , using : transaction )
}
throw error
}
}
let iv = ivAndCiphertext [ 0. . < Int ( ivSize ) ]
let ciphertext = ivAndCiphertext [ Int ( ivSize ) . . . ]
let gcm = GCM ( iv : iv . bytes , tagLength : Int ( gcmTagSize ) , mode : . combined )
let messageKeys = ratchet . messageKeys
let lastNMessageKeys : [ String ]
if messageKeys . count > 16 { // P i c k a n a r b i t r a r y n u m b e r o f m e s s a g e k e y s t o t r y ; t h i s h e l p s r e s o l v e i s s u e s c a u s e d b y m e s s a g e s a r r i v i n g o u t o f o r d e r
lastNMessageKeys = [ String ] ( messageKeys [ messageKeys . index ( messageKeys . endIndex , offsetBy : - 16 ) . . < messageKeys . endIndex ] )
} else {
lastNMessageKeys = messageKeys
}
guard ! lastNMessageKeys . isEmpty else {
throw RatchetingError . messageKeyMissing ( targetKeyIndex : keyIndex , groupPublicKey : groupPublicKey , senderPublicKey : senderPublicKey )
}
var error : Error ?
for messageKey in lastNMessageKeys . reversed ( ) { // R e v e r s e d b e c a u s e m o s t l i k e l y t h e l a s t o n e i s t h e o n e w e n e e d
let aes = try AES ( key : Data ( hex : messageKey ) . bytes , blockMode : gcm , padding : . noPadding )
do {
return Data ( try aes . decrypt ( ciphertext . bytes ) )
} catch ( let e ) {
error = e
}
}
if ! isRetry {
return try decrypt ( ivAndCiphertext , for : groupPublicKey , senderPublicKey : senderPublicKey , keyIndex : keyIndex , using : transaction , isRetry : true )
} else {
Configuration . shared . sharedSenderKeysDelegate . requestSenderKey ( for : groupPublicKey , senderPublicKey : senderPublicKey , using : transaction )
throw error ? ? RatchetingError . generic
}
}
}