Skip to content

Commit

Permalink
Merge pull request #217 from ably/fix-encrypt
Browse files Browse the repository at this point in the history
Fix message encoding and encryption.
  • Loading branch information
tcard committed Feb 17, 2016
2 parents 17576b9 + 334a4c0 commit 66b38d8
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 116 deletions.
4 changes: 2 additions & 2 deletions ably-ios/ARTBaseMessage+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ ART_ASSUME_NONNULL_BEGIN

@interface ARTBaseMessage ()

- (ARTStatus *__art_nonnull)decodeWithEncoder:(ARTDataEncoder*)encoder output:(id __art_nonnull*__art_nonnull)output;
- (ARTStatus *__art_nonnull)encodeWithEncoder:(ARTDataEncoder*)encoder output:(id __art_nonnull*__art_nonnull)output;
- (id __art_nonnull)decodeWithEncoder:(ARTDataEncoder*)encoder error:(NSError *__art_nullable*__art_nullable)error;
- (id __art_nonnull)encodeWithEncoder:(ARTDataEncoder*)encoder error:(NSError *__art_nullable*__art_nullable)error;

@end

Expand Down
28 changes: 18 additions & 10 deletions ably-ios/ARTBaseMessage.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,28 @@ - (instancetype)messageWithData:(id)data encoding:(NSString *)encoding {
return message;
}

- (ARTStatus *)decodeWithEncoder:(ARTDataEncoder*)encoder output:(id *)output {
- (id)decodeWithEncoder:(ARTDataEncoder*)encoder error:(NSError **)error {
ARTDataEncoderOutput *decoded = [encoder decode:self.data encoding:self.encoding];
*output = [self copy];
((ARTBaseMessage *)*output).data = decoded.data;
((ARTBaseMessage *)*output).encoding = decoded.encoding;
return decoded.status;
if (decoded.errorInfo && error) {
*error = [NSError errorWithDomain:ARTAblyErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: @"decoding failed",
NSLocalizedFailureReasonErrorKey: decoded.errorInfo}];
}
id ret = [self copy];
((ARTBaseMessage *)ret).data = decoded.data;
((ARTBaseMessage *)ret).encoding = decoded.encoding;
return ret;
}

- (ARTStatus *)encodeWithEncoder:(ARTDataEncoder*)encoder output:(id *)output {
- (id)encodeWithEncoder:(ARTDataEncoder*)encoder error:(NSError **)error {
ARTDataEncoderOutput *encoded = [encoder encode:self.data];
*output = [self copy];
((ARTBaseMessage *)*output).data = encoded.data;
((ARTBaseMessage *)*output).encoding = [self.encoding artAddEncoding:encoded.encoding];
return encoded.status;
if (encoded.errorInfo && error) {
*error = [NSError errorWithDomain:ARTAblyErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: @"encoding failed",
NSLocalizedFailureReasonErrorKey: encoded.errorInfo}];
}
id ret = [self copy];
((ARTBaseMessage *)ret).data = encoded.data;
((ARTBaseMessage *)ret).encoding = [NSString artAddEncoding:encoded.encoding toString:self.encoding];
return ret;
}

- (NSString *)description {
Expand Down
16 changes: 11 additions & 5 deletions ably-ios/ARTChannel.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ - (instancetype)initWithName:(NSString *)name andOptions:(ARTChannelOptions *)op
if (self = [super init]) {
_name = name;
self.options = options;
_dataEncoder = [[ARTDataEncoder alloc] initWithCipherParams:_options.cipherParams logger:logger];
NSError *error;
_dataEncoder = [[ARTDataEncoder alloc] initWithCipherParams:_options.cipherParams error:&error];
if (error != nil) {
[logger warn:@"creating ARTDataEncoder: %@", error];
_dataEncoder = [[ARTDataEncoder alloc] initWithCipherParams:nil error:nil];
}
_logger = logger;
}
return self;
Expand Down Expand Up @@ -67,10 +72,11 @@ - (ARTMessage *)encodeMessageIfNeeded:(ARTMessage *)message {
if (!self.dataEncoder) {
return message;
}
ARTStatus *status = [message encodeWithEncoder:self.dataEncoder output:&message];
if (status.state != ARTStateOk) {
[self.logger error:@"ARTChannel: error encoding data, status: %tu", status];
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"message encoding failed" userInfo:nil];
NSError *error = nil;
message = [message encodeWithEncoder:self.dataEncoder error:&error];
if (error != nil) {
[self.logger error:@"ARTChannel: error encoding data: %@", error];
[NSException raise:NSInvalidArgumentException format:@"ARTChannel: error encoding data: %@", error];
}
return message;
}
Expand Down
8 changes: 4 additions & 4 deletions ably-ios/ARTDataEncoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,23 @@ ART_ASSUME_NONNULL_BEGIN

@property (readonly, nonatomic, art_nullable) id data;
@property (readonly, nonatomic, art_nullable) NSString *encoding;
@property (readonly, nonatomic) ARTStatus *status;
@property (readonly, nonatomic, art_nullable) ARTErrorInfo *errorInfo;

- initWithData:(id __art_nullable)data encoding:(NSString *__art_nullable)encoding status:(ARTStatus *)status;
- initWithData:(id __art_nullable)data encoding:(NSString *__art_nullable)encoding errorInfo:(ARTErrorInfo *__art_nullable)errorInfo;

@end

@interface ARTDataEncoder : NSObject

- (instancetype)initWithCipherParams:(ARTCipherParams *)params logger:(ARTLog *)logger;
- (instancetype)initWithCipherParams:(ARTCipherParams *__art_nullable)params error:(NSError *__art_nullable*__art_nullable)error;
- (ARTDataEncoderOutput *)encode:(id __art_nullable)data;
- (ARTDataEncoderOutput *)decode:(id __art_nullable)data encoding:(NSString *__art_nullable)encoding;

@end

@interface NSString (ARTDataEncoder)

- (NSString *)artAddEncoding:(NSString *)encoding;
+ (NSString *)artAddEncoding:(NSString *)encoding toString:(NSString *__art_nullable)s;
- (NSString *)artLastEncoding;
- (NSString *)artRemoveLastEncoding;

Expand Down
150 changes: 84 additions & 66 deletions ably-ios/ARTDataEncoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,14 @@
#import "ARTLog.h"
#import "ARTDataEncoder.h"

@interface ARTDataEncoder ()

ART_ASSUME_NONNULL_BEGIN

@property (readonly, nonatomic) ARTLog *logger;

ART_ASSUME_NONNULL_END

@end

@implementation ARTDataEncoderOutput

- (id)initWithData:(id)data encoding:(NSString *)encoding status:(ARTStatus *)status {
- (id)initWithData:(id)data encoding:(NSString *)encoding errorInfo:(ARTErrorInfo *)errorInfo {
self = [super init];
if (self) {
_data = data;
_encoding = encoding;
_status = status;
_errorInfo = errorInfo;
}
return self;
}
Expand All @@ -38,122 +28,150 @@ @implementation ARTDataEncoder {
id<ARTChannelCipher> _cipher;
}

- (instancetype)initWithCipherParams:(ARTCipherParams *)params logger:(ARTLog *)logger {
- (instancetype)initWithCipherParams:(ARTCipherParams *)params error:(NSError **)error {
self = [super init];
if (self) {
_logger = logger;
if (params) {
_cipher = [ARTCrypto cipherWithParams:params];
if (!_cipher) {
[self.logger error:@"ARTDataEncoder failed to create cipher with name %@", params.algorithm];
if (error) {
NSString *desc = [NSString stringWithFormat:@"ARTDataEncoder failed to create cipher with name %@", params.algorithm];
*error = [NSError errorWithDomain:ARTAblyErrorDomain
code:0
userInfo:@{NSLocalizedDescriptionKey: desc}];
}
return nil;
}
}
}
return self;
}

- (ARTDataEncoderOutput *)encode:(id)data {
NSData *encoded = nil;
NSString *encoding = nil;
BOOL ok = false;

id encoded = nil;
NSData *toBase64 = nil;

if (!data) {
return [[ARTDataEncoderOutput alloc] initWithData:data encoding:nil status:[ARTStatus state:ARTStateOk]];
} else if ([data isKindOfClass:[NSData class]]) {
encoding = @"base64";
encoded = [[((NSData *)data) base64EncodedStringWithOptions:0] dataUsingEncoding:NSUTF8StringEncoding];
ok = encoded != nil;
return [[ARTDataEncoderOutput alloc] initWithData:data encoding:nil errorInfo:nil];
}

NSData *jsonEncoded = nil;
if ([data isKindOfClass:[NSArray class]] || [data isKindOfClass:[NSDictionary class]]) {
NSError *error = nil;
// Just check the error; we don't want to actually JSON-encode this. It's more like "convert to JSON-compatible data".
// We will store the result, though, because if we're encrypting, then yes, we need to use the JSON-encoded
// data before encrypting.
jsonEncoded = [NSJSONSerialization dataWithJSONObject:data options:0 error:&error];
if (error) {
return [[ARTDataEncoderOutput alloc] initWithData:data encoding:nil errorInfo:[ARTErrorInfo createWithNSError:error]];
}
encoded = data;
encoding = @"json";
} else if ([data isKindOfClass:[NSString class]]) {
encoding = @"";
encoded = [data dataUsingEncoding:NSUTF8StringEncoding];
ok = true;
} else if ([data isKindOfClass:[NSArray class]] || [data isKindOfClass:[NSDictionary class]]) {
encoding = @"json";
NSError *error = nil;
encoded = [NSJSONSerialization dataWithJSONObject:data options:0 error:&error];
ok = error == nil && encoded != nil;
}

if (!ok) {
return [[ARTDataEncoderOutput alloc] initWithData:encoded encoding:encoding status:[ARTStatus state:ARTStateError]];
encoded = data;
} else if ([data isKindOfClass:[NSData class]]) {
encoded = data;
toBase64 = data;
}

if (_cipher) {
ARTStatus *status = [_cipher encrypt:encoded output:&encoded];
if ([encoded isKindOfClass:[NSArray class]] || [encoded isKindOfClass:[NSDictionary class]]) {
encoded = jsonEncoded;
encoding = [NSString artAddEncoding:@"utf-8" toString:encoding];
} else if ([encoded isKindOfClass:[NSString class]]) {
encoded = [data dataUsingEncoding:NSUTF8StringEncoding];
encoding = [NSString artAddEncoding:@"utf-8" toString:encoding];
}
ARTStatus *status = [_cipher encrypt:encoded output:&toBase64];
if (status.state != ARTStateOk) {
return [[ARTDataEncoderOutput alloc] initWithData:encoded encoding:encoding status:status];
ARTErrorInfo *errorInfo = status.errorInfo ? status.errorInfo : [ARTErrorInfo createWithCode:0 message:@"encrypt failed"];
return [[ARTDataEncoderOutput alloc] initWithData:encoded encoding:encoding errorInfo:errorInfo];
}
encoding = [encoding artAddEncoding:[self cipherEncoding]];

// Re-encode the encrypted bytes in base64.
encoded = [[encoded base64EncodedStringWithOptions:0] dataUsingEncoding:NSUTF8StringEncoding];
if (encoded == nil) {
return [[ARTDataEncoderOutput alloc] initWithData:encoded encoding:encoding status:status];
encoding = [NSString artAddEncoding:[self cipherEncoding] toString:encoding];
}

if (toBase64 != nil) {
encoded = [[toBase64 base64EncodedStringWithOptions:0] dataUsingEncoding:NSUTF8StringEncoding];
if (!encoded) {
return [[ARTDataEncoderOutput alloc] initWithData:toBase64 encoding:encoding errorInfo:[ARTErrorInfo createWithCode:0 message:@"base64 failed"]];
}
encoding = [encoding artAddEncoding:@"base64"];
encoded = [[NSString alloc] initWithData:encoded encoding:NSUTF8StringEncoding];
encoding = [NSString artAddEncoding:@"base64" toString:encoding];
}

return [[ARTDataEncoderOutput alloc] initWithData:[[NSString alloc] initWithData:encoded encoding:NSUTF8StringEncoding]

if (encoded == nil) {
return [[ARTDataEncoderOutput alloc] initWithData:data encoding:nil errorInfo:[ARTErrorInfo createWithCode:0 message:@"must be NSString, NSData, NSArray or NSDictionary."]];
}

return [[ARTDataEncoderOutput alloc] initWithData:encoded
encoding:encoding
status:[ARTStatus state:ARTStateOk]];
errorInfo:nil];
}

- (ARTDataEncoderOutput *)decode:(id)data encoding:(NSString *)encoding {
if (!data || !encoding ) {
return [[ARTDataEncoderOutput alloc] initWithData:data encoding:encoding status:[ARTStatus state:ARTStateOk]];
return [[ARTDataEncoderOutput alloc] initWithData:data encoding:encoding errorInfo:nil];
}

BOOL ok = false;
ARTErrorInfo *errorInfo = nil;
NSArray *encodings = [encoding componentsSeparatedByString:@"/"];
NSString *outputEncoding = [NSString stringWithString:encoding];

for (NSUInteger i = [encodings count]; i > 0; i--) {
ok = false;
errorInfo = nil;
NSString *encoding = [encodings objectAtIndex:i-1];

if ([encoding isEqualToString:@"base64"]) {
if ([data isKindOfClass:[NSData class]]) { // E. g. when decrypted.
data = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
if ([data isKindOfClass:[NSString class]]) {
data = [[NSData alloc] initWithBase64EncodedString:(NSString *)data options:0];
ok = data != nil;
} else {
errorInfo = [ARTErrorInfo createWithCode:0 message:[NSString stringWithFormat:@"invalid data type for 'base64' decoding: '%@'", [data class]]];
}
} else if ([encoding isEqualToString:@""] || [encoding isEqualToString:@"utf-8"]) {
if ([data isKindOfClass:[NSData class]]) { // E. g. when decrypted.
data = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
ok = [data isKindOfClass:[NSString class]];
if (![data isKindOfClass:[NSString class]]) {
errorInfo = [ARTErrorInfo createWithCode:0 message:[NSString stringWithFormat:@"invalid data type for '%@' decoding: '%@'", encoding, [data class]]];
}
} else if ([encoding isEqualToString:@"json"]) {
if ([data isKindOfClass:[NSData class]]) { // E. g. when decrypted.
data = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
if ([data isKindOfClass:[NSString class]]) {
NSData *jsonData = [data dataUsingEncoding:NSUTF8StringEncoding];
data = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
ok = data != nil;
NSError *error;
data = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (error != nil) {
errorInfo = [ARTErrorInfo createWithNSError:error];
}
} else if (![data isKindOfClass:[NSArray class]] && ![data isKindOfClass:[NSDictionary class]]) {
errorInfo = [ARTErrorInfo createWithCode:0 message:[NSString stringWithFormat:@"invalid data type for 'json' decoding: '%@'", [data class]]];
}
} else if (_cipher && [encoding isEqualToString:[self cipherEncoding]] && [data isKindOfClass:[NSData class]]) {
ARTStatus *status = [_cipher decrypt:data output:&data];
if (i == 1) {
// Because strings have no encoding, an encoded payload whose left-most encoding
// is a cipher will be a string.
data = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (status.state != ARTStateOk) {
errorInfo = status.errorInfo ? status.errorInfo : [ARTErrorInfo createWithCode:0 message:@"decrypt failed"];
}
ok = status.state == ARTStateOk;
} else {
errorInfo = [ARTErrorInfo createWithCode:0 message:[NSString stringWithFormat:@"unknown encoding: '%@'", encoding]];
}
if (ok) {

if (errorInfo == nil) {
outputEncoding = [outputEncoding artRemoveLastEncoding];
} else {
[self.logger error:@"ARTDataDecoded failed to decode data as '%@': (%@)%@", encoding, [data class], data];
break;
}
}

return [[ARTDataEncoderOutput alloc] initWithData:data
encoding:outputEncoding
status:[ARTStatus state:(ok ? ARTStateOk : ARTStateError)]];
errorInfo:errorInfo];
}

- (NSString *)cipherEncoding {
Expand All @@ -170,8 +188,8 @@ - (NSString *)cipherEncoding {

@implementation NSString (ARTPayload)

- (NSString *)artAddEncoding:(NSString *)encoding {
return [self stringByAppendingPathComponent:encoding];
+ (NSString *)artAddEncoding:(NSString *)encoding toString:(NSString *)s {
return [(s ? s : @"") stringByAppendingPathComponent:encoding];
}

- (NSString *)artLastEncoding {
Expand Down
7 changes: 3 additions & 4 deletions ably-ios/ARTJsonEncoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,9 @@ - (ARTPresenceMessage *)presenceMessageFromDictionary:(NSDictionary *)input {
ARTPresenceMessage *message = [[ARTPresenceMessage alloc] init];
message.id = [input artString:@"id"];
message.data = [input objectForKey:@"data"];
if ([message.data isKindOfClass:[NSDictionary class]] || [message.data isKindOfClass:[NSArray class]]) {

}
message.encoding = [input artString:@"encoding"];
message.clientId = [input artString:@"clientId"];
message.timestamp = [input artDate:@"timestamp"];
Expand Down Expand Up @@ -632,10 +635,6 @@ - (void)writeData:(id)data encoding:(NSString *)encoding toDictionary:(NSMutable
if (encoding.length) {
output[@"encoding"] = encoding;
}
if ([encoding isEqualToString:@"json"]) {
NSData *jsonData = [data dataUsingEncoding:NSUTF8StringEncoding];
data = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
}
output[@"data"] = data;
}

Expand Down
Loading

0 comments on commit 66b38d8

Please sign in to comment.