Skip to content

Commit

Permalink
Merge pull request #1955 from smartdevicelink/bugfix/issue-1954-rpc-e…
Browse files Browse the repository at this point in the history
…ncryption-multi-frame

Support TLS max size when encrypted
  • Loading branch information
joeljfischer authored Apr 28, 2021
2 parents 85b5fa8 + 6f55654 commit 0b61d84
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 188 deletions.
101 changes: 67 additions & 34 deletions SmartDeviceLink/private/SDLProtocol.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
#import "SDLV2ProtocolHeader.h"

NSString *const SDLProtocolSecurityErrorDomain = @"com.sdl.protocol.security";

static const NSUInteger TLSMaxDataSize = 16834;
static const NSUInteger TLSMaxRPCPayloadDataToEncryptSize = 16384 /*TLS Max Record Size*/ - 5 /*TLS Record Header Size*/ - 32 /*TLS MES Auth CDE Size*/ - 256 /*TLS Max Record Padding Size*/;

#pragma mark - SDLProtocol Private Interface

Expand Down Expand Up @@ -82,7 +83,7 @@ - (instancetype)initWithTransport:(id<SDLTransportType>)transport encryptionMana
_transport.delegate = self;

_encryptionLifecycleManager = encryptionManager;

return self;
}

Expand Down Expand Up @@ -185,7 +186,7 @@ - (void)startSecureServiceWithType:(SDLServiceType)serviceType payload:(nullable

// TLS initialization succeeded. Build and send the message.
SDLProtocolMessage *message = [self sdl_createStartServiceMessageWithType:serviceType encrypted:YES payload:nil];
SDLLogD(@"TLS initialized, sending start service for message: %@", message);
SDLLogD(@"TLS initialized, sending start service with encryption for message: %@", message);
[self sdl_sendDataToTransport:message.data onService:serviceType];
}];
}
Expand Down Expand Up @@ -309,7 +310,7 @@ - (BOOL)sendRPC:(SDLRPCMessage *)message encrypted:(BOOL)encryption error:(NSErr
*error = jsonError;
return NO;
}

NSData *messagePayload = nil;
SDLLogV(@"Sending RPC: %@", message);

Expand Down Expand Up @@ -346,27 +347,7 @@ - (BOOL)sendRPC:(SDLRPCMessage *)message encrypted:(BOOL)encryption error:(NSErr
return NO;
}

// If we're trying to encrypt, try to have the security manager encrypt it. Return if it fails.
NSError *encryptError = nil;
if (encryption) {
messagePayload = [self.securityManager encryptData:rpcPayload.data withError:&encryptError];

if (encryptError != nil) {
SDLLogE(@"Error encrypting request! %@", encryptError);
}
} else {
messagePayload = rpcPayload.data;
}

// If the encryption failed, pass back the error and return false
if (!messagePayload) {
if (encryptError != nil) {
*error = encryptError;
} else {
*error = [NSError sdl_encryption_unknown];
}
return NO;
}
messagePayload = rpcPayload.data;
} break;
default: {
NSAssert(NO, @"Attempting to send an RPC based on an unknown version number: %@, message: %@", @([SDLGlobals sharedGlobals].protocolVersion.major), message);
Expand All @@ -389,14 +370,57 @@ - (BOOL)sendRPC:(SDLRPCMessage *)message encrypted:(BOOL)encryption error:(NSErr
SDLProtocolMessage *protocolMessage = [SDLProtocolMessage messageWithHeader:header andPayload:messagePayload];

// See if the message is small enough to send in one transmission. If not, break it up into smaller messages and send.
if (protocolMessage.size < [[SDLGlobals sharedGlobals] mtuSizeForServiceType:SDLServiceTypeRPC]) {
SDLLogV(@"Sending protocol message: %@", protocolMessage);
[self sdl_sendDataToTransport:protocolMessage.data onService:SDLServiceTypeRPC];
NSUInteger rpcMTUSize = [[SDLGlobals sharedGlobals] mtuSizeForServiceType:SDLServiceTypeRPC];
NSUInteger mtuSize = (encryption ? MIN(TLSMaxRPCPayloadDataToEncryptSize, rpcMTUSize) : rpcMTUSize);
NSArray<SDLProtocolMessage *> *protocolMessages = nil;
if (protocolMessage.size < mtuSize) {
protocolMessages = @[protocolMessage];
} else {
NSArray<SDLProtocolMessage *> *messages = [SDLProtocolMessageDisassembler disassemble:protocolMessage withLimit:[[SDLGlobals sharedGlobals] mtuSizeForServiceType:SDLServiceTypeRPC]];
for (SDLProtocolMessage *smallerMessage in messages) {
SDLLogV(@"Sending protocol message: %@", smallerMessage);
[self sdl_sendDataToTransport:smallerMessage.data onService:SDLServiceTypeRPC];
protocolMessages = [SDLProtocolMessageDisassembler disassemble:protocolMessage withMTULimit:mtuSize];
}

// If the message should be encrypted, encrypt the payloads
if (encryption) {
BOOL success = [self sdl_encryptProtocolMessages:protocolMessages error:error];
if (!success) {
SDLLogE(@"Error encrypting protocol messages. Messages will not be sent. Error: %@", *error);
return NO;
}
}

// Send each message
for (SDLProtocolMessage *message in protocolMessages) {
SDLLogV(@"Sending protocol message: %@", message);
[self sdl_sendDataToTransport:message.data onService:SDLServiceTypeRPC];
}

return YES;
}

/// Receives an array of `SDLProtocolMessage` and attempts to encrypt their payloads in place through the active security manager. If anything fails, it will return NO and pass back the error.
/// @param protocolMessages The array of protocol messages to encrypt.
/// @param error A passback error object if attempting to encrypt the protocol message payloads fails.
/// @returns YES if the encryption was successful, NO if it was not
- (BOOL)sdl_encryptProtocolMessages:(NSArray<SDLProtocolMessage *> *)protocolMessages error:(NSError *__autoreleasing *)error {
for (SDLProtocolMessage *message in protocolMessages) {
if (message.header.frameType == SDLFrameTypeFirst) { continue; }

// If we're trying to encrypt, try to have the security manager encrypt it. Return if it fails.
NSError *encryptError = nil;
NSData *encryptedMessagePayload = [self.securityManager encryptData:message.payload withError:&encryptError];

// If the encryption failed, pass back the error and return false
if (encryptedMessagePayload.length == 0 || encryptError != nil) {
if (encryptError != nil) {
*error = encryptError;
} else {
*error = [NSError sdl_encryption_unknown];
}

return NO;
} else {
message.payload = encryptedMessagePayload;
message.header.bytesInPayload = (UInt32)encryptedMessagePayload.length;
}
}

Expand All @@ -418,7 +442,16 @@ - (void)sendRawData:(NSData *)data withServiceType:(SDLServiceType)serviceType {
}

- (void)sendEncryptedRawData:(NSData *)data onService:(SDLServiceType)serviceType {
[self sdl_sendRawData:data onService:serviceType encryption:YES];
// Break up data larger than the max TLS size so the data can be encrypted by the security manager without failing due to the data size being too big
NSUInteger offset = 0;
do {
NSUInteger remainingDataLength = data.length - offset;
NSUInteger chunkSize = (remainingDataLength > TLSMaxDataSize) ? TLSMaxDataSize : remainingDataLength;
NSData *chunk = [NSData dataWithBytesNoCopy:(BytePtr)data.bytes + offset length:chunkSize freeWhenDone:NO];

[self sdl_sendRawData:chunk onService:serviceType encryption:YES];
offset += chunkSize;
} while (offset < data.length);
}

- (void)sdl_sendRawData:(NSData *)data onService:(SDLServiceType)service encryption:(BOOL)encryption {
Expand All @@ -444,7 +477,7 @@ - (void)sdl_sendRawData:(NSData *)data onService:(SDLServiceType)service encrypt
SDLLogV(@"Sending protocol message: %@", message);
[self sdl_sendDataToTransport:message.data onService:header.serviceType];
} else {
NSArray<SDLProtocolMessage *> *messages = [SDLProtocolMessageDisassembler disassemble:message withLimit:[[SDLGlobals sharedGlobals] mtuSizeForServiceType:service]];
NSArray<SDLProtocolMessage *> *messages = [SDLProtocolMessageDisassembler disassemble:message withMTULimit:[[SDLGlobals sharedGlobals] mtuSizeForServiceType:service]];
for (SDLProtocolMessage *smallerMessage in messages) {
SDLLogV(@"Sending protocol message: %@", smallerMessage);
[self sdl_sendDataToTransport:smallerMessage.data onService:header.serviceType];
Expand Down
2 changes: 1 addition & 1 deletion SmartDeviceLink/private/SDLProtocolMessage.m
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ - (NSString *)description {
[description appendString:self.header.description];

if (self.header.encrypted) {
[description appendString:@", Payload is encrypted - no description can be provided"];
[description appendString:@"Payload is encrypted - no description can be provided"];
return description;
}

Expand Down
19 changes: 6 additions & 13 deletions SmartDeviceLink/private/SDLProtocolMessageAssembler.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,12 @@ - (void)handleMessage:(SDLProtocolMessage *)message withCompletionHandler:(SDLMe
}

if (self.parts == nil) {
self.parts = [NSMutableDictionary new];
self.parts = [NSMutableDictionary dictionary];
}


// Determine which frame it is and save it.
// Note: frames are numbered 1,2,3, ..., 0
// Always 0 for last frame.
// Note: frames are numbered 1,2,3, ..., 0. Always 0 for last frame.
if (message.header.frameType == SDLFrameTypeFirst) {
// If it's the first-frame, extract the meta-data
self.expectedBytes = NSSwapBigIntToHost(((UInt32 *)message.payload.bytes)[0]);
Expand All @@ -51,27 +50,23 @@ - (void)handleMessage:(SDLProtocolMessage *)message withCompletionHandler:(SDLMe
self.parts[frameNumberObj] = message.payload;
}


//
// If we have all the parts, assemble it and execute the completion handler.
//
SDLProtocolMessage *assembledMessage = nil;
if (self.parts.count == self.frameCount + 1) { // +1 since we also require the first-frame

// Create the header
SDLProtocolHeader *header = message.header.copy;
header.frameType = SDLFrameTypeSingle;
header.frameData = SDLFrameInfoSingleFrame;


// Create the payload
NSMutableData *payload = [[NSMutableData alloc] init];
NSMutableData *payload = [NSMutableData data];
for (unsigned int i = 1; i < self.frameCount; i++) {
NSData *dataToAppend = [self.parts objectForKey:[NSNumber numberWithUnsignedInt:i]];
NSData *dataToAppend = self.parts[@(i)];
[payload appendData:dataToAppend];
}

// Append the last frame, it has a frame # of 0.
NSData *dataToAppend = [self.parts objectForKey:[NSNumber numberWithUnsignedInt:0]];
NSData *dataToAppend = self.parts[@0];
[payload appendData:dataToAppend];

// Validation
Expand All @@ -82,15 +77,13 @@ - (void)handleMessage:(SDLProtocolMessage *)message withCompletionHandler:(SDLMe
// Create the message.
assembledMessage = [SDLProtocolMessage messageWithHeader:header andPayload:payload];


// Execute completion handler.
if (completionHandler != nil) {
completionHandler(YES, assembledMessage);
}

// Done with this data, release it.
self.parts = nil;

} else {
// Not done, let caller know
if (completionHandler != nil) {
Expand Down
6 changes: 5 additions & 1 deletion SmartDeviceLink/private/SDLProtocolMessageDisassembler.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ NS_ASSUME_NONNULL_BEGIN

@interface SDLProtocolMessageDisassembler : NSObject

+ (NSArray<SDLProtocolMessage *> *)disassemble:(SDLProtocolMessage *)protocolMessage withLimit:(NSUInteger)mtu;
/// Use to break up a large message into a sequence of smaller messages, each of which is less than 'mtu' number of bytes total size.
///
/// @param protocolMessage The message to break up
/// @param mtu The MTU size to use to determine where to break up the message payload
+ (NSArray<SDLProtocolMessage *> *)disassemble:(SDLProtocolMessage *)protocolMessage withMTULimit:(NSUInteger)mtu;

@end

Expand Down
50 changes: 20 additions & 30 deletions SmartDeviceLink/private/SDLProtocolMessageDisassembler.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,74 +9,64 @@

@implementation SDLProtocolMessageDisassembler

+ (NSArray<SDLProtocolMessage *> *)disassemble:(SDLProtocolMessage *)protocolMessage withMTULimit:(NSUInteger)mtu {
if (protocolMessage.size < mtu) {
return @[protocolMessage];
}

// Use to break up a large message into a sequence of smaller messages,
// each of which is less than 'mtu' number of bytes total size.
+ (NSArray<SDLProtocolMessage *> *)disassemble:(SDLProtocolMessage *)incomingMessage withLimit:(NSUInteger)mtu {
// Questions:
// What message IDs does the current system use? Same messageIDs? Same CorrelationIDs?
// What gets simply copied from incoming header to created headers; and what needs adjustment?

// How big is the message header?
NSUInteger headerSize = incomingMessage.header.size;

// The size of the message is too big to send in one chunk.
// So lets break it up.
// Just how big IS this message?
NSUInteger incomingPayloadSize = (incomingMessage.data.length - headerSize);
// How big is the message header and payload?
NSUInteger headerSize = protocolMessage.header.size;
NSUInteger payloadSize = (protocolMessage.data.length - headerSize);

// How many messages do we need to create to hold that many bytes?
// Note: this does NOT count the special first message which acts as a descriptor.
NSUInteger numberOfMessagesRequired = (NSUInteger)ceil((float)incomingPayloadSize / (float)(mtu - headerSize));
// Note: This does NOT count the special first message which acts as a descriptor.
NSUInteger numberOfMessagesRequired = (NSUInteger)ceilf((float)payloadSize / (float)(mtu - headerSize));

// And how many data bytes go in each message?
NSUInteger numberOfDataBytesPerMessage = mtu - headerSize;

// Create the outgoing array to hold the messages we will create.
NSMutableArray<SDLProtocolMessage *> *outgoingMessages = [NSMutableArray arrayWithCapacity:numberOfMessagesRequired + 1];


// Create the first message
SDLProtocolHeader *firstFrameHeader = [incomingMessage.header copy];
// Create the first message, which cannot be encrypted because it needs to be exactly 8 bytes
SDLProtocolHeader *firstFrameHeader = [protocolMessage.header copy];
firstFrameHeader.frameType = SDLFrameTypeFirst;
firstFrameHeader.encrypted = NO;

UInt32 payloadData[2];
payloadData[0] = CFSwapInt32HostToBig((UInt32)incomingMessage.payload.length);
payloadData[0] = CFSwapInt32HostToBig((UInt32)protocolMessage.payload.length);
payloadData[1] = CFSwapInt32HostToBig((UInt32)numberOfMessagesRequired);
NSMutableData *firstFramePayload = [NSMutableData dataWithBytes:payloadData length:sizeof(payloadData)];

SDLProtocolMessage *firstMessage = [SDLProtocolMessage messageWithHeader:firstFrameHeader andPayload:firstFramePayload];
outgoingMessages[0] = firstMessage;

[outgoingMessages addObject:firstMessage];

// Create the middle messages (the ones carrying the actual data).
for (NSUInteger n = 0; n < numberOfMessagesRequired - 1; n++) {
// Frame # after 255 must cycle back to 1, not 0.
// A 0 signals last frame.
// Frame # after 255 must cycle back to 1, not 0. A 0 signals last frame (SDLFrameInfoConsecutiveLastFrame).
UInt8 frameNumber = (n % 255) + 1;

SDLProtocolHeader *nextFrameHeader = [incomingMessage.header copy];
SDLProtocolHeader *nextFrameHeader = [protocolMessage.header copy];
nextFrameHeader.frameType = SDLFrameTypeConsecutive;
nextFrameHeader.frameData = frameNumber;

NSUInteger offsetOfDataForThisFrame = headerSize + (n * numberOfDataBytesPerMessage);
NSData *nextFramePayload = [incomingMessage.data subdataWithRange:NSMakeRange(offsetOfDataForThisFrame, numberOfDataBytesPerMessage)];
NSData *nextFramePayload = [protocolMessage.data subdataWithRange:NSMakeRange(offsetOfDataForThisFrame, numberOfDataBytesPerMessage)];

SDLProtocolMessage *nextMessage = [SDLProtocolMessage messageWithHeader:nextFrameHeader andPayload:nextFramePayload];
outgoingMessages[n + 1] = nextMessage;
}


// Create the last message
SDLProtocolHeader *lastFrameHeader = [incomingMessage.header copy];
SDLProtocolHeader *lastFrameHeader = [protocolMessage.header copy];
lastFrameHeader.frameType = SDLFrameTypeConsecutive;
lastFrameHeader.frameData = SDLFrameInfoConsecutiveLastFrame;

NSUInteger numberOfMessagesCreatedSoFar = numberOfMessagesRequired - 1;
NSUInteger numberOfDataBytesSentSoFar = numberOfMessagesCreatedSoFar * numberOfDataBytesPerMessage;
NSUInteger numberOfDataBytesInLastMessage = incomingPayloadSize - numberOfDataBytesSentSoFar;
NSUInteger numberOfDataBytesInLastMessage = payloadSize - numberOfDataBytesSentSoFar;
NSUInteger offsetOfDataForLastFrame = headerSize + numberOfDataBytesSentSoFar;
NSData *lastFramePayload = [incomingMessage.data subdataWithRange:NSMakeRange(offsetOfDataForLastFrame, numberOfDataBytesInLastMessage)];
NSData *lastFramePayload = [protocolMessage.data subdataWithRange:NSMakeRange(offsetOfDataForLastFrame, numberOfDataBytesInLastMessage)];

SDLProtocolMessage *lastMessage = [SDLProtocolMessage messageWithHeader:lastFrameHeader andPayload:lastFramePayload];
outgoingMessages[numberOfMessagesRequired] = lastMessage;
Expand Down
2 changes: 1 addition & 1 deletion SmartDeviceLink/private/SDLV2ProtocolHeader.m
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ - (NSString *)description {
}

NSMutableString *description = [[NSMutableString alloc] init];
[description appendFormat:@"Version:%i, encrypted:%i, frameType:%@(%i), serviceType:%i, frameData:%@(%i), sessionID:%i, dataSize:%i, messageID:%i ",
[description appendFormat:@"Version:%i, encrypted:%i, frameType:%@(%i), serviceType:%i, frameData:%@(%i), sessionID:%i, dataSize:%i, messageID:%i, ",
self.version,
self.encrypted,
frameTypeString,
Expand Down
2 changes: 1 addition & 1 deletion SmartDeviceLink/public/SDLProtocolConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,5 @@ typedef NS_ENUM(UInt8, SDLFrameInfo) {
SDLFrameInfoFirstFrame = 0x00, // If frameType == First (0x02)

/// Frame in a multiple frame payload.
SDLFrameInfoConsecutiveLastFrame = 0x00 // If frametype == Consecutive (0x03)
SDLFrameInfoConsecutiveLastFrame = 0x00 // If frameType == Consecutive (0x03)
};
Loading

0 comments on commit 0b61d84

Please sign in to comment.