@ -40,11 +40,13 @@ public final class SharedSenderKeysImplementation : NSObject {
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 "
}
}
}
@ -66,7 +68,8 @@ public final class SharedSenderKeysImplementation : NSObject {
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
return ClosedGroupRatchet ( chainKey : nextChainKey . toHexString ( ) , keyIndex : nextKeyIndex , messageKeys : [ nextMessageKey . toHexString ( ) ] )
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 .
@ -90,11 +93,12 @@ public final class SharedSenderKeysImplementation : NSObject {
}
// / - 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 func stepRatchet ( for groupPublicKey : String , senderPublicKey : String , until targetKeyIndex : UInt , using transaction : YapDatabaseReadWriteTransaction ) throws -> ClosedGroupRatchet {
private func stepRatchet ( for groupPublicKey : String , senderPublicKey : String , until targetKeyIndex : UInt , using transaction : YapDatabaseReadWriteTransaction , isRetry : Bool = false ) throws -> ClosedGroupRatchet {
#if DEBUG
assert ( ! Thread . isMainThread )
#endif
guard let ratchet = Storage . getClosedGroupRatchet ( for : groupPublicKey , senderPublicKey : senderPublicKey ) else {
let collection : Storage . ClosedGroupRatchetCollectionType = ( isRetry ) ? . old : . current
guard let ratchet = Storage . getClosedGroupRatchet ( for : groupPublicKey , senderPublicKey : senderPublicKey , from : collection ) else {
let error = RatchetingError . loadingFailed ( groupPublicKey : groupPublicKey , senderPublicKey : senderPublicKey )
print ( " [Loki] \( error . errorDescription ! ) " )
throw error
@ -109,20 +113,18 @@ public final class SharedSenderKeysImplementation : NSObject {
return ratchet
} else {
var currentKeyIndex = ratchet . keyIndex
var current = ratchet
var messageKeys : [ String ] = [ ]
var result = ratchet
while currentKeyIndex < targetKeyIndex {
do {
current = try step ( current )
messageKeys += current . messageKeys
currentKeyIndex = current . keyIndex
result = try step ( result )
currentKeyIndex = result . keyIndex
} catch {
print ( " [Loki] Couldn't step ratchet due to error: \( error ) . " )
throw error
}
}
let result = ClosedGroupRatchet ( chainKey : current . chainKey , keyIndex : current . keyIndex , messageKeys : messageKeys ) // I n c l u d e s a n y s k i p p e d m e s s a g e k e y s
Storage . setClosedGroupRatchet ( for : groupPublicKey , senderPublicKey : senderPublicKey , ratchet : result , using : transaction )
let collection: Storage . ClosedGroupRatchetCollectionType = ( isRetry ) ? . old : . current
Storage . setClosedGroupRatchet ( for : groupPublicKey , senderPublicKey : senderPublicKey , ratchet : result , in : collection , using : transaction )
return result
}
}
@ -161,30 +163,49 @@ public final class SharedSenderKeysImplementation : NSObject {
return try decrypt ( ivAndCiphertext , for : groupPublicKey , senderPublicKey : senderPublicKey , keyIndex : keyIndex , using : transaction )
}
public func decrypt ( _ ivAndCiphertext : Data , for groupPublicKey : String , senderPublicKey : String , keyIndex : UInt , using transaction : YapDatabaseReadWriteTransaction ) throws -> Data {
public func decrypt ( _ ivAndCiphertext : Data , for groupPublicKey : String , senderPublicKey : String , keyIndex : UInt , using transaction : YapDatabaseReadWriteTransaction , isRetry : Bool = false ) throws -> Data {
let ratchet : ClosedGroupRatchet
do {
ratchet = try stepRatchet ( for : groupPublicKey , senderPublicKey : senderPublicKey , until : keyIndex , using : transaction )
ratchet = try stepRatchet ( for : groupPublicKey , senderPublicKey : senderPublicKey , until : keyIndex , using : transaction , isRetry : isRetry )
} catch {
// FIXME: I t ' d b e c l e a n e r t o h a n d l e t h i s i n O W S M e s s a g e D e c r y p t e r ( w h e r e a l l t h e o t h e r d e c r y p t i o n e r r o r s a r e h a n d l e d ) , b u t t h i s w a s a l o t m o r e
// c o n v e n i e n t b e c a u s e t h e r e ' s a n e a s y w a y t o g e t t h e s e n d e r p u b l i c k e y f r o m h e r e .
if case RatchetingError . loadingFailed ( _ , _ ) = error {
ClosedGroupsProtocol . requestSenderKey ( for : groupPublicKey , senderPublicKey : senderPublicKey , using : transaction )
if ! isRetry {
return try decrypt ( ivAndCiphertext , for : groupPublicKey , senderPublicKey : senderPublicKey , keyIndex : keyIndex , using : transaction , isRetry : true )
} else {
// FIXME: I t ' d b e c l e a n e r t o h a n d l e t h i s i n O W S M e s s a g e D e c r y p t e r ( w h e r e a l l t h e o t h e r d e c r y p t i o n e r r o r s a r e h a n d l e d ) , b u t t h i s w a s a l o t m o r e
// c o n v e n i e n t b e c a u s e t h e r e ' s a n e a s y w a y t o g e t t h e s e n d e r p u b l i c k e y f r o m h e r e .
if case RatchetingError . loadingFailed ( _ , _ ) = error {
ClosedGroupsProtocol . requestSenderKey ( for : groupPublicKey , senderPublicKey : senderPublicKey , using : transaction )
}
throw error
}
throw error
}
let iv = ivAndCiphertext [ 0. . < Int ( SharedSenderKeysImplementation . ivSize ) ]
let ciphertext = ivAndCiphertext [ Int ( SharedSenderKeysImplementation . ivSize ) . . . ]
let gcm = GCM ( iv : iv . bytes , tagLength : Int ( SharedSenderKeysImplementation . gcmTagSize ) , mode : . combined )
guard let messageKey = ratchet . messageKeys . last else {
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 )
}
let aes = try AES ( key : Data ( hex : messageKey ) . bytes , blockMode : gcm , padding : . noPadding )
do {
return Data ( try aes . decrypt ( ciphertext . bytes ) )
} catch {
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 {
ClosedGroupsProtocol . requestSenderKey ( for : groupPublicKey , senderPublicKey : senderPublicKey , using : transaction )
throw error
throw error ? ? RatchetingError . generic
}
}