MYCryptor.m
author snej@snej.local
Sat Apr 04 20:42:03 2009 -0700 (2009-04-04)
changeset 0 0a6527af039b
child 2 8982b8fada63
permissions -rw-r--r--
Initial checkin. Passes tests on Mac and in iPhone simulator.
snej@0
     1
//
snej@0
     2
//  Cryptor.m
snej@0
     3
//  MYCrypto
snej@0
     4
//
snej@0
     5
//  Created by Jens Alfke on 3/21/09.
snej@0
     6
//  Copyright 2009 Jens Alfke. All rights reserved.
snej@0
     7
//
snej@0
     8
snej@0
     9
#import "MYCryptor.h"
snej@0
    10
#import "MYDigest.h"
snej@0
    11
#import "Test.h"
snej@0
    12
snej@0
    13
#if USE_IPHONE_API
snej@0
    14
#import <Security/SecRandom.h>
snej@0
    15
#else
snej@0
    16
#import "MYCrypto_Private.h"
snej@0
    17
#import "MYKeychain.h"
snej@0
    18
#import <stdlib.h>
snej@0
    19
#endif
snej@0
    20
snej@0
    21
snej@0
    22
NSString* const CryptorErrorDomain = @"CCCryptor";
snej@0
    23
snej@0
    24
#if !USE_IPHONE_API
snej@0
    25
static BOOL generateRandomBytes(CSSM_CSP_HANDLE module, uint32_t lengthInBytes, void *dstBytes);
snej@0
    26
#endif
snej@0
    27
snej@0
    28
snej@0
    29
@interface MYCryptor ()
snej@0
    30
@property (readwrite, retain) NSError *error;
snej@0
    31
@end
snej@0
    32
snej@0
    33
snej@0
    34
snej@0
    35
@implementation MYCryptor
snej@0
    36
snej@0
    37
snej@0
    38
+ (NSData*) randomKeyOfLength: (size_t)length {
snej@0
    39
    NSParameterAssert(length<100000);
snej@0
    40
    uint8_t *bytes = malloc(length);
snej@0
    41
    if (!bytes) return nil;
snej@0
    42
#if USE_IPHONE_API
snej@0
    43
    BOOL ok = SecRandomCopyBytes(kSecRandomDefault, length,bytes) >= 0;
snej@0
    44
#else
snej@0
    45
    BOOL ok = generateRandomBytes([[MYKeychain defaultKeychain] CSPHandle], length, bytes);
snej@0
    46
#endif
snej@0
    47
    if (ok)
snej@0
    48
        return [NSData dataWithBytesNoCopy: bytes length: length freeWhenDone: YES];
snej@0
    49
    else {
snej@0
    50
        free(bytes);
snej@0
    51
        return nil;
snej@0
    52
    }
snej@0
    53
}
snej@0
    54
snej@0
    55
+ (NSData*) keyOfLength: (size_t)lengthInBits fromPassphrase: (NSString*)passphrase
snej@0
    56
{
snej@0
    57
    size_t lengthInBytes = (lengthInBits + 7)/8;
snej@0
    58
    MYDigest *digest = [[passphrase dataUsingEncoding: NSUTF8StringEncoding] my_SHA256Digest];
snej@0
    59
    if (lengthInBytes <= digest.length)
snej@0
    60
        return [digest.asData subdataWithRange: NSMakeRange(0,lengthInBytes)];
snej@0
    61
    else
snej@0
    62
        return nil;
snej@0
    63
}
snej@0
    64
snej@0
    65
snej@0
    66
- (id) initWithKey: (NSData*)key
snej@0
    67
         algorithm: (CCAlgorithm)algorithm
snej@0
    68
         operation: (CCOperation)op {
snej@0
    69
    self = [super init];
snej@0
    70
    if (self) {
snej@0
    71
        NSParameterAssert(key);
snej@0
    72
        _key = [key copy];
snej@0
    73
        _operation = op;
snej@0
    74
        _algorithm = algorithm;
snej@0
    75
        _options = kCCOptionPKCS7Padding;
snej@0
    76
    }
snej@0
    77
    return self;
snej@0
    78
}
snej@0
    79
snej@0
    80
- (id) initEncryptorWithKey: (NSData*)key algorithm: (CCAlgorithm)algorithm {
snej@0
    81
    return [self initWithKey: key algorithm: algorithm operation: kCCEncrypt];
snej@0
    82
}
snej@0
    83
snej@0
    84
- (id) initDecryptorWithKey: (NSData*)key algorithm: (CCAlgorithm)algorithm {
snej@0
    85
    return [self initWithKey: key algorithm: algorithm operation: kCCDecrypt];
snej@0
    86
}
snej@0
    87
snej@0
    88
- (void) dealloc
snej@0
    89
{
snej@0
    90
    if (_cryptor)
snej@0
    91
        CCCryptorRelease(_cryptor);
snej@0
    92
    [_key autorelease];
snej@0
    93
    [_output autorelease];
snej@0
    94
    [_outputStream release];
snej@0
    95
    [super dealloc];
snej@0
    96
}
snej@0
    97
snej@0
    98
snej@0
    99
@synthesize key=_key, algorithm=_algorithm, options=_options,
snej@0
   100
    outputStream=_outputStream, error=_error;
snej@0
   101
snej@0
   102
snej@0
   103
- (BOOL) _check: (CCCryptorStatus)status {
snej@0
   104
    if (status == kCCSuccess)
snej@0
   105
        return YES;
snej@0
   106
    else {
snej@0
   107
        Warn(@"MYCryptor: CCCryptor error %i", status);
snej@0
   108
        self.error = [NSError errorWithDomain: CryptorErrorDomain code: status userInfo: nil];
snej@0
   109
        return NO;
snej@0
   110
    }
snej@0
   111
}
snej@0
   112
snej@0
   113
snej@0
   114
- (BOOL) _outputBytes: (const void*)bytes length: (size_t)length {
snej@0
   115
    if (_outputStream) {
snej@0
   116
        NSInteger written = [_outputStream write: bytes maxLength: length];
snej@0
   117
        if (written < 0) {
snej@0
   118
            self.error = _outputStream.streamError;
snej@0
   119
            if (_error)
snej@0
   120
                Warn(@"MYCryptor: NSOutputStream error %@", _error);
snej@0
   121
            else
snej@0
   122
                [self _check: kMYCryptorErrorOutputStreamChoked];
snej@0
   123
            return NO;
snej@0
   124
        } else if (written < length) {
snej@0
   125
            [self _check: kMYCryptorErrorOutputStreamChoked];
snej@0
   126
            return NO;
snej@0
   127
        }
snej@0
   128
    } else if (length > 0) {
snej@0
   129
        [_output appendBytes: bytes length: length];
snej@0
   130
    }
snej@0
   131
    return YES;
snej@0
   132
}
snej@0
   133
snej@0
   134
snej@0
   135
- (BOOL) _start {
snej@0
   136
    if (!_cryptor && !_error) {
snej@0
   137
        if ([self _check: CCCryptorCreate(_operation, _algorithm, _options,
snej@0
   138
                                          _key.bytes, _key.length, NULL, &_cryptor)]) {
snej@0
   139
            _output = [[NSMutableData alloc] initWithCapacity: 1024];
snej@0
   140
        }
snej@0
   141
    }
snej@0
   142
    return !_error;
snej@0
   143
}
snej@0
   144
snej@0
   145
snej@0
   146
- (BOOL) addBytes: (const void*)bytes length: (size_t)length {
snej@0
   147
    if (length > 0) {
snej@0
   148
        NSParameterAssert(bytes!=NULL);
snej@0
   149
        if(!_error && (_cryptor || [self _start])) {
snej@0
   150
            size_t outputLength = CCCryptorGetOutputLength(_cryptor,length,false);
snej@0
   151
            void *output = malloc(outputLength);
snej@0
   152
            if ([self _check: CCCryptorUpdate(_cryptor, bytes, length,
snej@0
   153
                                              output, outputLength, &outputLength)]) {
snej@0
   154
                [self _outputBytes: output length: outputLength];
snej@0
   155
            }
snej@0
   156
            free(output);
snej@0
   157
        }
snej@0
   158
    }
snej@0
   159
    return !_error;
snej@0
   160
}
snej@0
   161
snej@0
   162
- (BOOL) addData: (NSData*)data
snej@0
   163
{
snej@0
   164
    return [self addBytes: data.bytes length: data.length];
snej@0
   165
}
snej@0
   166
snej@0
   167
- (BOOL) addString: (NSString*)str {
snej@0
   168
    return [self addData: [str dataUsingEncoding: NSUTF8StringEncoding]];
snej@0
   169
}
snej@0
   170
snej@0
   171
snej@0
   172
- (BOOL) addFromStream: (NSInputStream*)input
snej@0
   173
{
snej@0
   174
    uint8_t inputBuffer[1024];
snej@0
   175
    size_t avail;
snej@0
   176
    while (!_error && input.hasBytesAvailable) {
snej@0
   177
        avail = sizeof(inputBuffer);
snej@0
   178
        NSInteger nRead = [input read: inputBuffer maxLength: sizeof(inputBuffer)];
snej@0
   179
        if (nRead < 0) {
snej@0
   180
            self.error = input.streamError;
snej@0
   181
            return NO;
snej@0
   182
        } else if (nRead == 0) {
snej@0
   183
            break;
snej@0
   184
        } else if (![self addBytes: inputBuffer length: nRead])
snej@0
   185
            return NO;
snej@0
   186
    }
snej@0
   187
    return YES;
snej@0
   188
}
snej@0
   189
snej@0
   190
snej@0
   191
- (BOOL) finish
snej@0
   192
{
snej@0
   193
    if(!_error && (_cryptor || [self _start])) {
snej@0
   194
        size_t outputLength = 100; //CCCryptorGetOutputLength(_cryptor,1,true);
snej@0
   195
        void *output = malloc(outputLength);
snej@0
   196
        if ([self _check: CCCryptorFinal(_cryptor, output, outputLength, &outputLength)]) {
snej@0
   197
            [self _outputBytes: output length: outputLength];
snej@0
   198
        }
snej@0
   199
        free(output);
snej@0
   200
    }
snej@0
   201
    CCCryptorRelease(_cryptor);
snej@0
   202
    _cryptor = NULL;
snej@0
   203
    return !_error;
snej@0
   204
}
snej@0
   205
snej@0
   206
snej@0
   207
- (NSData*) outputData {
snej@0
   208
    if (_cryptor) [self finish];
snej@0
   209
    if(_error) {
snej@0
   210
        [_output release];
snej@0
   211
        _output = nil;
snej@0
   212
    }
snej@0
   213
    return _output;
snej@0
   214
}
snej@0
   215
snej@0
   216
- (NSString*) outputString {
snej@0
   217
    NSData *output = self.outputData;
snej@0
   218
    if (output) {
snej@0
   219
        NSString *str = [[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding];
snej@0
   220
        return [str autorelease];
snej@0
   221
    } else
snej@0
   222
        return nil;
snej@0
   223
}
snej@0
   224
snej@0
   225
snej@0
   226
// NSStream delegate method
snej@0
   227
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
snej@0
   228
    switch (eventCode) {
snej@0
   229
        case NSStreamEventHasBytesAvailable:
snej@0
   230
            [self addFromStream: (NSInputStream*)stream];
snej@0
   231
            break;
snej@0
   232
        case NSStreamEventEndEncountered:
snej@0
   233
            [self finish];
snej@0
   234
            break;
snej@0
   235
        case NSStreamEventErrorOccurred:
snej@0
   236
            if (!_error)
snej@0
   237
                self.error = stream.streamError;
snej@0
   238
            break;
snej@0
   239
        default:
snej@0
   240
            break;
snej@0
   241
    }
snej@0
   242
}
snej@0
   243
snej@0
   244
snej@0
   245
snej@0
   246
@end
snej@0
   247
snej@0
   248
snej@0
   249
snej@0
   250
snej@0
   251
#if !USE_IPHONE_API
snej@0
   252
static BOOL generateRandomBytes(CSSM_CSP_HANDLE module, uint32_t lengthInBytes, void *dstBytes) {
snej@0
   253
    // Adapted from code in Keychain.framework's KeychainUtils.m by Wade Tregaskis.
snej@0
   254
    CSSM_CC_HANDLE ccHandle;
snej@0
   255
    if (!checkcssm(CSSM_CSP_CreateRandomGenContext(module, CSSM_ALGID_APPLE_YARROW, NULL,
snej@0
   256
                                                  lengthInBytes, &ccHandle),
snej@0
   257
                   @"CSSM_CSP_CreateRandomGenContext"))
snej@0
   258
        return NO;
snej@0
   259
    CSSM_DATA data = {.Data=dstBytes, .Length=lengthInBytes};
snej@0
   260
    BOOL ok = checkcssm(CSSM_GenerateRandom(ccHandle, &data), @"CSSM_GenerateRandom");
snej@0
   261
    CSSM_DeleteContext(ccHandle);
snej@0
   262
    return ok;
snej@0
   263
}
snej@0
   264
#endif
snej@0
   265
snej@0
   266
snej@0
   267
snej@0
   268
snej@0
   269
TestCase(MYCryptor) {
snej@0
   270
    // Encryption:
snej@0
   271
    NSData *key = [MYCryptor randomKeyOfLength: kCCKeySizeAES256];
snej@0
   272
    Log(@"Key = %@",key);
snej@0
   273
    MYCryptor *enc = [[MYCryptor alloc] initEncryptorWithKey: key algorithm: kCCAlgorithmAES128];
snej@0
   274
    CAssert(enc);
snej@0
   275
    CAssert([enc addString: @"This is a test. "]);
snej@0
   276
    CAssert([enc addString: @"This is only a test."]);
snej@0
   277
    CAssertEqual(enc.error, nil);
snej@0
   278
    NSData *encrypted = enc.outputData;
snej@0
   279
    CAssertEqual(enc.error, nil);
snej@0
   280
    CAssert(encrypted.length > 0);
snej@0
   281
    [enc release];
snej@0
   282
    Log(@"Encrypted = %@", encrypted);
snej@0
   283
    
snej@0
   284
    // Decryption:
snej@0
   285
    MYCryptor *dec = [[MYCryptor alloc] initDecryptorWithKey: key algorithm: kCCAlgorithmAES128];
snej@0
   286
    CAssert(dec);
snej@0
   287
    CAssert([dec addData: encrypted]);
snej@0
   288
    NSString *decrypted = dec.outputString;
snej@0
   289
    CAssertEqual(dec.error, nil);
snej@0
   290
    [dec release];
snej@0
   291
    Log(@"Decrypted = '%@'", decrypted);
snej@0
   292
    CAssertEqual(decrypted, @"This is a test. This is only a test.");
snej@0
   293
    
snej@0
   294
    // Encryption to stream:
snej@0
   295
    key = [MYCryptor randomKeyOfLength: kCCKeySizeAES256];
snej@0
   296
    Log(@"Key = %@",key);
snej@0
   297
    enc = [[MYCryptor alloc] initEncryptorWithKey: key algorithm: kCCAlgorithmAES128];
snej@0
   298
    CAssert(enc);
snej@0
   299
    enc.outputStream = [NSOutputStream outputStreamToMemory];
snej@0
   300
    [enc.outputStream open];
snej@0
   301
    CAssert([enc addString: @"This is a test. "]);
snej@0
   302
    CAssert([enc addString: @"This is only a test."]);
snej@0
   303
    CAssert([enc finish]);
snej@0
   304
    CAssertEqual(enc.error, nil);
snej@0
   305
    encrypted = [[enc.outputStream propertyForKey: NSStreamDataWrittenToMemoryStreamKey] retain];
snej@0
   306
    CAssert(encrypted.length > 0);
snej@0
   307
    [enc release];
snej@0
   308
    Log(@"Encrypted = %@", encrypted);
snej@0
   309
    
snej@0
   310
    dec = [[MYCryptor alloc] initDecryptorWithKey: key algorithm: kCCAlgorithmAES128];
snej@0
   311
    CAssert(dec);
snej@0
   312
    dec.outputStream = [NSOutputStream outputStreamToMemory];
snej@0
   313
    [dec.outputStream open];
snej@0
   314
    CAssert([dec addData: encrypted]);
snej@0
   315
    CAssert([dec finish]);
snej@0
   316
    CAssertEqual(dec.error, nil);
snej@0
   317
    NSData *decryptedData = [dec.outputStream propertyForKey: NSStreamDataWrittenToMemoryStreamKey];
snej@0
   318
    [dec release];
snej@0
   319
    decrypted = [[NSString alloc] initWithData: decryptedData
snej@0
   320
                                                      encoding: NSUTF8StringEncoding];
snej@0
   321
    Log(@"Decrypted = '%@'", decrypted);
snej@0
   322
    CAssertEqual(decrypted, @"This is a test. This is only a test.");
snej@0
   323
    [encrypted release];
snej@0
   324
    [decrypted release];
snej@0
   325
}