Skip to content

Commit

Permalink
Merge pull request #1116 from matrix-org/element_4360_additions
Browse files Browse the repository at this point in the history
Use `MXKeyProvider` for Room Last Message Encryption Key
  • Loading branch information
ismailgulek committed Jun 7, 2021
2 parents e2a2228 + 1176414 commit 32614df
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 136 deletions.
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Changes to be released in next version
*

🙌 Improvements
*
* MXRoomLastMessage: Use MXKeyProvider methods to encrypt/decrypt last message dictionary.

🐛 Bugfix
*
Expand Down
7 changes: 7 additions & 0 deletions MatrixSDK/Data/MXEventTimeline.m
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,13 @@ - (void)handleJoinedRoomSync:(MXRoomSync *)roomSync onComplete:(void (^)(void))o
if (lastUserReadReceipt)
{
timestamp = lastUserReadReceipt.ts;

MXEvent *lastEvent = roomSync.timeline.events.lastObject;
if (timestamp > lastEvent.originServerTs)
{
// we should at least decrypt the last event for fully-read rooms
timestamp = lastEvent.originServerTs;
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions MatrixSDK/Data/MXRoomLastMessage.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@

#import <Foundation/Foundation.h>

/**
Used to identify the type of data when requesting MXKeyProvider
*/
FOUNDATION_EXPORT NSString *const MXRoomLastMessageDataType;

@class MXEvent;

NS_ASSUME_NONNULL_BEGIN
Expand Down
163 changes: 30 additions & 133 deletions MatrixSDK/Data/MXRoomLastMessage.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@

#import "MXRoomLastMessage.h"
#import "MXEvent.h"
#import "MXKeyProvider.h"
#import "MXAesKeyData.h"
#import "MXAes.h"

#import <Security/Security.h>
#import <CommonCrypto/CommonCryptor.h>

NSString *const MXRoomLastMessageDataType = @"org.matrix.sdk.keychain.MXRoomLastMessage";

NSString *const kCodingKeyEventId = @"eventId";
NSString *const kCodingKeyOriginServerTs = @"originServerTs";
NSString *const kCodingKeyIsEncrypted = @"isEncrypted";
Expand Down Expand Up @@ -148,157 +153,49 @@ - (void)encodeWithCoder:(NSCoder *)coder

/**
The AES-256 key used for encrypting MXRoomSummary sensitive data.
@return the encryption key if encryption is needed. Nil otherwise.
*/
+ (NSData*)encryptionKey
- (MXAesKeyData *)encryptionKey
{
NSData *encryptionKey;

// Create a dictionary to look up the key in the keychain
NSDictionary *searchDict = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: @"org.matrix.sdk.keychain",
(__bridge id)kSecAttrAccount: @"MXRoomSummary",
(__bridge id)kSecReturnData: (__bridge id)kCFBooleanTrue,
};

// Make the search
CFDataRef foundKey = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDict, (CFTypeRef*)&foundKey);

if (status == errSecSuccess)
// It is up to the app to provide a key for additional encryption
MXKeyData * keyData = [[MXKeyProvider sharedInstance] keyDataForDataOfType:MXRoomLastMessageDataType
isMandatory:YES
expectedKeyType:kAes];
if (keyData && [keyData isKindOfClass:[MXAesKeyData class]])
{
// Use the found key
encryptionKey = (__bridge NSData*)(foundKey);
}
else if (status == errSecItemNotFound)
{
MXLogDebug(@"[MXRoomLastMessage] encryptionKey: Generate the key and store it to the keychain");

// There is not yet a key in the keychain
// Generate an AES key
NSMutableData *newEncryptionKey = [[NSMutableData alloc] initWithLength:kCCKeySizeAES256];
int retval = SecRandomCopyBytes(kSecRandomDefault, kCCKeySizeAES256, newEncryptionKey.mutableBytes);
if (retval == 0)
{
encryptionKey = [NSData dataWithData:newEncryptionKey];

// Store it to the keychain
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:searchDict];
dict[(__bridge id)kSecValueData] = encryptionKey;

status = SecItemAdd((__bridge CFDictionaryRef)dict, NULL);
if (status != errSecSuccess)
{
// TODO: The iOS 10 simulator returns the -34018 (errSecMissingEntitlement) error.
// We need to fix it but there is no issue with the app on real device nor with iOS 9 simulator.
MXLogDebug(@"[MXRoomLastMessage] encryptionKey: SecItemAdd failed. status: %i", (int)status);
}
}
else
{
MXLogDebug(@"[MXRoomLastMessage] encryptionKey: Cannot generate key. retval: %i", retval);
}
}
else
{
MXLogDebug(@"[MXRoomLastMessage] encryptionKey: Keychain failed. OSStatus: %i", (int)status);
return (MXAesKeyData *)keyData;
}

if (foundKey)
{
CFRelease(foundKey);
}

return encryptionKey;
return nil;
}

- (NSData*)encrypt:(NSData*)data
{
NSData *encryptedData;

CCCryptorRef cryptor;
CCCryptorStatus status;

NSData *key = [MXRoomLastMessage encryptionKey];

status = CCCryptorCreateWithMode(kCCDecrypt, kCCModeCTR, kCCAlgorithmAES,
ccNoPadding, NULL, key.bytes, key.length,
NULL, 0, 0, kCCModeOptionCTR_BE, &cryptor);
if (status == kCCSuccess)
{
size_t bufferLength = CCCryptorGetOutputLength(cryptor, data.length, false);
NSMutableData *buffer = [NSMutableData dataWithLength:bufferLength];

size_t outLength;
status |= CCCryptorUpdate(cryptor,
data.bytes,
data.length,
[buffer mutableBytes],
[buffer length],
&outLength);

status |= CCCryptorRelease(cryptor);

if (status == kCCSuccess)
{
encryptedData = buffer;
}
else
{
MXLogDebug(@"[MXRoomLastMessage] encrypt: CCCryptorUpdate failed. status: %i", status);
}
}
else
// Exceptions are not caught as the key is always needed if the KeyProviderDelegate
// is provided.
MXAesKeyData *aesKey = self.encryptionKey;
if (aesKey)
{
MXLogDebug(@"[MXRoomLastMessage] encrypt: CCCryptorCreateWithMode failed. status: %i", status);
return [MXAes encrypt:data aesKey:aesKey.key iv:aesKey.iv error:nil];
}

return encryptedData;
MXLogDebug(@"[MXRoomLastMessage] encryptData: no key method provided for encryption.");
return data;
}

- (NSData*)decrypt:(NSData*)encryptedData
- (NSData*)decrypt:(NSData*)data
{
NSData *data;

CCCryptorRef cryptor;
CCCryptorStatus status;

NSData *key = [MXRoomLastMessage encryptionKey];

status = CCCryptorCreateWithMode(kCCDecrypt, kCCModeCTR, kCCAlgorithmAES,
ccNoPadding, NULL, key.bytes, key.length,
NULL, 0, 0, kCCModeOptionCTR_BE, &cryptor);
if (status == kCCSuccess)
// Exceptions are not caught as the key is always needed if the KeyProviderDelegate
// is provided.
MXAesKeyData *aesKey = self.encryptionKey;
if (aesKey)
{
size_t bufferLength = CCCryptorGetOutputLength(cryptor, encryptedData.length, false);
NSMutableData *buffer = [NSMutableData dataWithLength:bufferLength];

size_t outLength;
status |= CCCryptorUpdate(cryptor,
encryptedData.bytes,
encryptedData.length,
[buffer mutableBytes],
[buffer length],
&outLength);

status |= CCCryptorRelease(cryptor);

if (status == kCCSuccess)
{
data = buffer;
}
else
{
MXLogDebug(@"[MXRoomLastMessage] decrypt: CCCryptorUpdate failed. status: %i", status);
}
return [MXAes decrypt:data aesKey:aesKey.key iv:aesKey.iv error:nil];
}
else
{
MXLogDebug(@"[MXRoomLastMessage] decrypt: CCCryptorCreateWithMode failed. status: %i", status);
}


MXLogDebug(@"[MXRoomLastMessage] decryptData: no key method provided for decryption.");
return data;
}


@end
33 changes: 31 additions & 2 deletions MatrixSDK/MXSession.m
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ - (void)handleSyncResponse:(MXSyncResponse *)syncResponse
// Make sure the last message has been decrypted
// In case of an initial sync, we save decryptions to save time. Only unread messages are decrypted.
// We need to decrypt already read last message.
if (isInitialSync)
if (isInitialSync && room.summary.lastMessage.isEncrypted)
{
[self eventWithEventId:room.summary.lastMessage.eventId
inRoom:room.roomId
Expand Down Expand Up @@ -2832,7 +2832,36 @@ - (void)fixRoomsSummariesLastMessageWithMaxServerPaginationCount:(NSUInteger)max

for (MXRoomSummary *summary in self.roomsSummaries)
{
if (!summary.lastMessage)
if (summary.lastMessage.isEncrypted)
{
dispatch_group_enter(dispatchGroup);
[self eventWithEventId:summary.lastMessage.eventId
inRoom:summary.roomId
success:^(MXEvent *event) {

if (event.eventType == MXEventTypeRoomEncrypted)
{
MXLogDebug(@"[MXSession] fixRoomsSummariesLastMessage: Fixing last message for room %@", summary.roomId);

[summary resetLastMessageWithMaxServerPaginationCount:maxServerPaginationCount onComplete:^{
MXLogDebug(@"[MXSession] fixRoomsSummariesLastMessage:Fixing last message operation for room %@ has complete. lastMessageEventId: %@", summary.roomId, summary.lastMessage.eventId);
dispatch_group_leave(dispatchGroup);
} failure:^(NSError *error) {
MXLogDebug(@"[MXSession] fixRoomsSummariesLastMessage: Cannot fix last message for room %@ with maxServerPaginationCount: %@", summary.roomId, @(maxServerPaginationCount));
dispatch_group_leave(dispatchGroup);
}
commit:NO];
}
else
{
dispatch_group_leave(dispatchGroup);
}

} failure:^(NSError *error) {
dispatch_group_leave(dispatchGroup);
}];
}
else if (!summary.lastMessage)
{
dispatch_group_enter(dispatchGroup);
MXLogDebug(@"[MXSession] fixRoomsSummariesLastMessage: Fixing last message for room %@", summary.roomId);
Expand Down

0 comments on commit 32614df

Please sign in to comment.