From: jfarrell Date: Fri, 4 Apr 2014 15:41:15 +0000 (-0400) Subject: THRIFT-2204:SSL client for the cocoa client X-Git-Url: https://source.supwisdom.com/gerrit/gitweb?a=commitdiff_plain;h=9f154150b7c52ab2d63dc978782b363a21f4dadb;p=common%2Fthrift.git THRIFT-2204:SSL client for the cocoa client Client: cocoa Patch: Olivier Van Acker Adds a SSL transport to the cocoa library --- diff --git a/lib/cocoa/src/transport/TNSStreamTransport.h b/lib/cocoa/src/transport/TNSStreamTransport.h index d7be40b2..8011fb95 100644 --- a/lib/cocoa/src/transport/TNSStreamTransport.h +++ b/lib/cocoa/src/transport/TNSStreamTransport.h @@ -21,10 +21,12 @@ #import "TTransport.h" @interface TNSStreamTransport : NSObject { - NSInputStream * mInput; - NSOutputStream * mOutput; + } +@property (nonatomic, strong) NSInputStream * mInput; +@property (nonatomic, strong) NSOutputStream * mOutput; + - (id) initWithInputStream: (NSInputStream *) input outputStream: (NSOutputStream *) output; diff --git a/lib/cocoa/src/transport/TNSStreamTransport.m b/lib/cocoa/src/transport/TNSStreamTransport.m index 265e0ba4..e2bc249e 100644 --- a/lib/cocoa/src/transport/TNSStreamTransport.m +++ b/lib/cocoa/src/transport/TNSStreamTransport.m @@ -28,8 +28,8 @@ outputStream: (NSOutputStream *) output { self = [super init]; - mInput = [input retain_stub]; - mOutput = [output retain_stub]; + self.mInput = [input retain_stub]; + self.mOutput = [output retain_stub]; return self; } @@ -45,8 +45,8 @@ - (void) dealloc { - [mInput release_stub]; - [mOutput release_stub]; + [self.mInput release_stub]; + [self.mOutput release_stub]; [super dealloc_stub]; } @@ -56,7 +56,7 @@ int got = 0; int ret = 0; while (got < len) { - ret = [mInput read: buf+off+got maxLength: len-got]; + ret = [self.mInput read: buf+off+got maxLength: len-got]; if (ret <= 0) { @throw [TTransportException exceptionWithReason: @"Cannot read. Remote side has closed."]; } @@ -71,10 +71,10 @@ int got = 0; int result = 0; while (got < length) { - result = [mOutput write: data+offset+got maxLength: length-got]; + result = [self.mOutput write: data+offset+got maxLength: length-got]; if (result == -1) { @throw [TTransportException exceptionWithReason: @"Error writing to transport output stream." - error: [mOutput streamError]]; + error: [self.mOutput streamError]]; } else if (result == 0) { @throw [TTransportException exceptionWithReason: @"End of output stream."]; } diff --git a/lib/cocoa/src/transport/TSSLSocketClient.h b/lib/cocoa/src/transport/TSSLSocketClient.h new file mode 100644 index 00000000..44de1249 --- /dev/null +++ b/lib/cocoa/src/transport/TSSLSocketClient.h @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#import +#import "TNSStreamTransport.h" + +@interface TSSLSocketClient : TNSStreamTransport +#if TARGET_OS_IPHONE || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) + +#endif +{ + NSInputStream *inputStream; + NSOutputStream *outputStream; +@private + NSString *sslHostname; + int sd; +} + +- (id) initWithHostname: (NSString *) hostname + port: (int) port; + +- (BOOL) isOpen; + +@end diff --git a/lib/cocoa/src/transport/TSSLSocketClient.m b/lib/cocoa/src/transport/TSSLSocketClient.m new file mode 100644 index 00000000..db5a2b4a --- /dev/null +++ b/lib/cocoa/src/transport/TSSLSocketClient.m @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#import +#import +#import "TSSLSocketClient.h" +#import "TSSLSocketException.h" +#import "TObjective-C.h" +#include +#include +#include + +#if !TARGET_OS_IPHONE +#import +#else +#import +#endif + +@implementation TSSLSocketClient + +- (id) initWithHostname: (NSString *) hostname + port: (int) port +{ + sslHostname = hostname; + CFReadStreamRef readStream = NULL; + CFWriteStreamRef writeStream = NULL; + + + /* create a socket structure */ + struct sockaddr_in pin; + struct hostent *hp = NULL; + for(int i = 0; i < 10; i++) { + + + + if ((hp = gethostbyname([hostname UTF8String])) == NULL) { + NSLog(@"failed to resolve hostname %@", hostname); + herror("resolv"); + if(i == 9) { + @throw [TSSLSocketException exceptionWithReason: @"failed to resolve hostname"]; + } + [NSThread sleepForTimeInterval:0.2]; + } else { + break; + } + } + + memset (&pin, 0, sizeof(pin)); + pin.sin_family = AF_INET; + pin.sin_addr.s_addr = ((struct in_addr *) (hp->h_addr))->s_addr; + pin.sin_port = htons (port); + + /* create the socket */ + if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + NSLog(@"failed to create socket for host %@:%d", hostname, port); + @throw [TSSLSocketException exceptionWithReason: @"failed to create socket"]; + } + + /* open a connection */ + if (connect (sd, (struct sockaddr *) &pin, sizeof(pin)) == -1) + { + NSLog(@"failed to create conenct to host %@:%d", hostname, port); + @throw [TSSLSocketException exceptionWithReason: @"failed to connect"]; + } + CFStreamCreatePairWithSocket(kCFAllocatorDefault, sd, &readStream, &writeStream); + + CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + + if (readStream && writeStream) { + CFReadStreamSetProperty(readStream, + kCFStreamPropertySocketSecurityLevel, + kCFStreamSocketSecurityLevelTLSv1); + + NSDictionary *settings = + [NSDictionary dictionaryWithObjectsAndKeys: + (id)kCFBooleanTrue, (id)kCFStreamSSLValidatesCertificateChain, + nil]; + + CFReadStreamSetProperty((CFReadStreamRef)readStream, + kCFStreamPropertySSLSettings, + (CFTypeRef)settings); + CFWriteStreamSetProperty((CFWriteStreamRef)writeStream, + kCFStreamPropertySSLSettings, + (CFTypeRef)settings); + + inputStream = (bridge_stub NSInputStream *)readStream; + [inputStream retain_stub]; + [inputStream setDelegate:self]; + [inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; + [inputStream open]; + + outputStream = (bridge_stub NSOutputStream *)writeStream; + [outputStream retain_stub]; + [outputStream setDelegate:self]; + [outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; + [outputStream open]; + + + CFRelease(readStream); + CFRelease(writeStream); + } + + + + self = [super initWithInputStream: inputStream outputStream: outputStream]; + + return self; +} + +#pragma mark - +#pragma mark NSStreamDelegate +- (void)stream:(NSStream *)aStream + handleEvent:(NSStreamEvent)eventCode { + switch (eventCode) { + case NSStreamEventNone: + break; + case NSStreamEventHasBytesAvailable: + break; + case NSStreamEventOpenCompleted: + break; + case NSStreamEventHasSpaceAvailable: + { + SecPolicyRef policy = SecPolicyCreateSSL(NO, (__bridge CFStringRef)(sslHostname)); + SecTrustRef trust = NULL; + CFArrayRef streamCertificatesRef = + CFBridgingRetain((__bridge id)((__bridge CFArrayRef)([aStream propertyForKey:(NSString *) kCFStreamPropertySSLPeerCertificates]))); + SecTrustCreateWithCertificates(CFBridgingRetain((__bridge id)(streamCertificatesRef)), + policy, + &trust); + + SecTrustResultType trustResultType = kSecTrustResultInvalid; + SecTrustEvaluate(trust, &trustResultType); + + BOOL proceed = NO; + switch (trustResultType) { + case kSecTrustResultProceed: + proceed = YES; + break; + case kSecTrustResultUnspecified: + NSLog(@"Trusted by OS"); + proceed = YES; + break; + case kSecTrustResultRecoverableTrustFailure: + proceed = recoverFromTrustFailure(trust); + break; + case kSecTrustResultDeny: + NSLog(@"Deny"); + break; + case kSecTrustResultFatalTrustFailure: + NSLog(@"FatalTrustFailure"); + break; + case kSecTrustResultOtherError: + NSLog(@"OtherError"); + break; + case kSecTrustResultInvalid: + NSLog(@"Invalid"); + break; + default: + NSLog(@"Default"); + break; + } + + if (trust) { + CFRelease(trust); + } + if (policy) { + CFRelease(policy); + } + if (!proceed) { + NSLog(@"Cannot trust certificate. TrustResultType: %u", trustResultType); + [aStream close]; + @throw [TSSLSocketException exceptionWithReason: @"Cannot trust certificate"]; + } + } + break; + case NSStreamEventErrorOccurred: + { + NSError *theError = [aStream streamError]; + NSLog(@"Error occured opening stream: %@", theError); +// @throw [TSSLSocketException exceptionWithReason: @"Error occured opening stream" error: theError]; + break; + } + case NSStreamEventEndEncountered: + break; + default: + break; + } +} + +bool recoverFromTrustFailure(SecTrustRef myTrust) +{ + + SecTrustResultType trustResult; + OSStatus status = SecTrustEvaluate(myTrust, &trustResult); + + CFAbsoluteTime trustTime,currentTime,timeIncrement,newTime; + CFDateRef newDate; + if (trustResult == kSecTrustResultRecoverableTrustFailure) { + trustTime = SecTrustGetVerifyTime(myTrust); + timeIncrement = 31536000; + currentTime = CFAbsoluteTimeGetCurrent(); + newTime = currentTime - timeIncrement; + if (trustTime - newTime){ + newDate = CFDateCreate(NULL, newTime); + SecTrustSetVerifyDate(myTrust, newDate); + status = SecTrustEvaluate(myTrust, &trustResult); + } + } + if (trustResult != kSecTrustResultProceed) { + NSLog(@"Certificate trust failure"); + return false; + } + return true; +} + +- (void)close +{ + if(self.mInput) { + //Close and reset inputstream + CFReadStreamSetProperty((__bridge CFReadStreamRef)(self.mInput), kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + [self.mInput setDelegate:nil]; + [self.mInput close]; + [self.mInput removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + self.mInput = nil; + } + + if(self.mOutput) { + //Close and reset outputstream + CFReadStreamSetProperty((__bridge CFReadStreamRef)(self.mOutput), kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + [self.mOutput setDelegate:nil]; + [self.mOutput close]; + [self.mOutput removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + self.mOutput = nil; + } +} + +- (BOOL) isOpen +{ + if(sd > 0) + return TRUE; + else + return FALSE; +} + +@end + diff --git a/lib/cocoa/src/transport/TSSLSocketException.h b/lib/cocoa/src/transport/TSSLSocketException.h new file mode 100644 index 00000000..c290b05a --- /dev/null +++ b/lib/cocoa/src/transport/TSSLSocketException.h @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#import "TTransportException.h" + +@interface TSSLSocketException : TTransportException + ++ (id) exceptionWithReason: (NSString *) reason + error: (NSError *) error; + ++ (id) exceptionWithReason: (NSString *) reason; + +@end diff --git a/lib/cocoa/src/transport/TSSLSocketException.m b/lib/cocoa/src/transport/TSSLSocketException.m new file mode 100644 index 00000000..99eadf50 --- /dev/null +++ b/lib/cocoa/src/transport/TSSLSocketException.m @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#import "TSSLSocketException.h" + +@implementation TSSLSocketException + ++ (id) exceptionWithReason: (NSString *) reason + error: (NSError *) error +{ + NSDictionary * userInfo = nil; + if (error != nil) { + userInfo = [NSDictionary dictionaryWithObject: error forKey: @"error"]; + } + + return [super exceptionWithName: @"TSSLSocketException" + reason: reason + userInfo: userInfo]; +} + ++ (id) exceptionWithReason: (NSString *) reason +{ + return [self exceptionWithReason: reason error: nil]; +} + +@end