Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support client side encryption via SEGCrypto protocol #592

Merged
merged 8 commits into from
Aug 30, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Analytics.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ Pod::Spec.new do |s|
s.ios.deployment_target = '7.0'
s.tvos.deployment_target = '9.0'

s.framework = 'Security'

s.source_files = 'Analytics/Classes/**/*'
end
23 changes: 23 additions & 0 deletions Analytics/Classes/Crypto/SEGAES256Crypto.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// SEGAES256Crypto.h
// Analytics
//
// Copyright © 2016 Segment. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "SEGCrypto.h"

@interface SEGAES256Crypto : NSObject <SEGCrypto>

@property (nonatomic, readonly, nonnull) NSString *password;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these properties need to be public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, user will need to persist password, iv, and salt in order to properly initialize the crypto object next time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is especially true in the case where iv and salt are auto-generated.

@property (nonatomic, readonly, nonnull) NSData *salt;
@property (nonatomic, readonly, nonnull) NSData *iv;

- (instancetype _Nonnull)initWithPassword:(NSString * _Nonnull)password salt:(NSData * _Nonnull)salt iv:(NSData * _Nonnull)iv;
// Convenient shorthand. Will randomly generate salt and iv.
- (instancetype _Nonnull)initWithPassword:(NSString * _Nonnull)password;

+ (NSData * _Nonnull)randomDataOfLength:(size_t)length;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's a convenience method for people to generate salt and iv and possibly password.


@end
140 changes: 140 additions & 0 deletions Analytics/Classes/Crypto/SEGAES256Crypto.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//
// SEGAES256Crypto.m
// Analytics
//
// Copyright © 2016 Segment. All rights reserved.
//


#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonKeyDerivation.h>
#import "SEGAES256Crypto.h"
#import "SEGUtils.h"

// Implementation courtesy of http://robnapier.net/aes-commoncrypto

static NSString * const kRNCryptManagerErrorDomain = @"com.segment.crypto";

static const CCAlgorithm kAlgorithm = kCCAlgorithmAES;
static const NSUInteger kAlgorithmKeySize = kCCKeySizeAES256;
static const NSUInteger kAlgorithmBlockSize = kCCBlockSizeAES128;
static const NSUInteger kAlgorithmIVSize = kCCBlockSizeAES128;
static const NSUInteger kPBKDFSaltSize = 8;
static const NSUInteger kPBKDFRounds = 10000; // ~80ms on an iPhone 4

@implementation SEGAES256Crypto

- (instancetype)initWithPassword:(NSString *)password salt:(NSData *)salt iv:(NSData * _Nonnull)iv {
if (self = [super init]) {
_password = password;
_salt = salt;
_iv = iv;
}
return self;
}

- (instancetype)initWithPassword:(NSString *)password {
NSData *iv = [SEGAES256Crypto randomDataOfLength:kAlgorithmIVSize];
NSData *salt = [SEGAES256Crypto randomDataOfLength:kPBKDFSaltSize];
return [self initWithPassword:password salt:salt iv:iv];
}

- (NSData *)aesKey {
return [[self class] AESKeyForPassword:self.password salt:self.salt];
}

- (NSData *)encrypt:(NSData *)data {
size_t outLength;
NSMutableData *cipherData = [NSMutableData dataWithLength:data.length + kAlgorithmBlockSize];

CCCryptorStatus
result = CCCrypt(kCCEncrypt, // operation
kAlgorithm, // Algorithm
kCCOptionPKCS7Padding, // options
self.aesKey.bytes, // key
self.aesKey.length, // keylength
self.iv.bytes,// iv
data.bytes, // dataIn
data.length, // dataInLength,
cipherData.mutableBytes, // dataOut
cipherData.length, // dataOutAvailable
&outLength); // dataOutMoved

if (result == kCCSuccess) {
cipherData.length = outLength;
} else {
NSError *error = [NSError errorWithDomain:kRNCryptManagerErrorDomain
code:result
userInfo:nil];
SEGLog(@"Unable to encrypt data", error);
return nil;
}
return cipherData;
}

- (NSData *)decrypt:(NSData *)data {
size_t outLength;
NSMutableData *decryptedData = [NSMutableData dataWithLength:data.length + kAlgorithmBlockSize];

CCCryptorStatus
result = CCCrypt(kCCDecrypt, // operation
kAlgorithm, // Algorithm
kCCOptionPKCS7Padding, // options
self.aesKey.bytes, // key
self.aesKey.length, // keylength
self.iv.bytes,// iv
data.bytes, // dataIn
data.length, // dataInLength,
decryptedData.mutableBytes, // dataOut
decryptedData.length, // dataOutAvailable
&outLength); // dataOutMoved

if (result == kCCSuccess) {
decryptedData.length = outLength;
} else {
NSError *error = [NSError errorWithDomain:kRNCryptManagerErrorDomain
code:result
userInfo:nil];
SEGLog(@"Unable to encrypt data", error);
return nil;
}
return decryptedData;
}

+ (NSData *)randomDataOfLength:(size_t)length {
NSMutableData *data = [NSMutableData dataWithLength:length];

int result = SecRandomCopyBytes(kSecRandomDefault,
length,
data.mutableBytes);
if (result != kCCSuccess) {
SEGLog(@"Unable to generate random bytes: %d", result);
}

return data;
}

// Replace this with a 10,000 hash calls if you don't have CCKeyDerivationPBKDF
+ (NSData *)AESKeyForPassword:(NSString *)password
salt:(NSData *)salt {
NSMutableData *derivedKey = [NSMutableData dataWithLength:kAlgorithmKeySize];

int result = CCKeyDerivationPBKDF(kCCPBKDF2, // algorithm
password.UTF8String, // password
[password lengthOfBytesUsingEncoding:NSUTF8StringEncoding], // passwordLength
salt.bytes, // salt
salt.length, // saltLen
kCCPRFHmacAlgSHA1, // PRF
kPBKDFRounds, // rounds
derivedKey.mutableBytes, // derivedKey
derivedKey.length); // derivedKeyLen

// Do not log password here
if (result != kCCSuccess) {
SEGLog(@"Unable to create AES key for password: %d", result);
}

return derivedKey;
}

@end
15 changes: 15 additions & 0 deletions Analytics/Classes/Crypto/SEGCrypto.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// SEGCrypto.h
// Analytics
//
// Copyright © 2016 Segment. All rights reserved.
//

#import <Foundation/Foundation.h>

@protocol SEGCrypto <NSObject>

- (NSData * _Nullable)encrypt:(NSData * _Nonnull)data;
- (NSData * _Nullable)decrypt:(NSData * _Nonnull)data;

@end
1 change: 0 additions & 1 deletion Analytics/Classes/Internal/SEGAnalyticsUtils.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#import <Foundation/Foundation.h>

NSURL *SEGAnalyticsURLForFilename(NSString *filename);
NSString *GenerateUUIDString();

// Date Utils
Expand Down
18 changes: 0 additions & 18 deletions Analytics/Classes/Internal/SEGAnalyticsUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,6 @@

static BOOL kAnalyticsLoggerShowLogs = NO;

NSURL *SEGAnalyticsURLForFilename(NSString *filename)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *supportPath = [paths firstObject];
if (![[NSFileManager defaultManager] fileExistsAtPath:supportPath
isDirectory:NULL]) {
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:supportPath
withIntermediateDirectories:YES
attributes:nil
error:&error]) {
SEGLog(@"error: %@", error.localizedDescription);
}
}
return [[NSURL alloc] initFileURLWithPath:[supportPath stringByAppendingPathComponent:filename]];
}

NSString *GenerateUUIDString()
{
CFUUIDRef theUUID = CFUUIDCreate(NULL);
Expand Down
22 changes: 22 additions & 0 deletions Analytics/Classes/Internal/SEGFileStorage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// SEGFileStorage.h
// Analytics
//
// Copyright © 2016 Segment. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "SEGStorage.h"

@interface SEGFileStorage : NSObject <SEGStorage>

@property (nonatomic, strong, nullable) id<SEGCrypto> crypto;

- (instancetype _Nonnull)init;
- (instancetype _Nonnull)initWithFolder:(NSURL * _Nonnull)folderURL crypto:(id<SEGCrypto> _Nullable)crypto;

- (NSURL * _Nonnull)urlForKey:(NSString * _Nonnull)key;

+ (NSURL * _Nullable)applicationSupportDirectoryURL;

@end
Loading