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 d7be40b..8011fb9 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 <TTransport> {
- 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 265e0ba..e2bc249 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 0000000..44de124
--- /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 <Foundation/Foundation.h>
+#import "TNSStreamTransport.h"
+
+@interface TSSLSocketClient : TNSStreamTransport
+#if TARGET_OS_IPHONE || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
+<NSStreamDelegate>
+#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 0000000..db5a2b4
--- /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 <Foundation/Foundation.h>
+#import <CoreFoundation/CoreFoundation.h>
+#import "TSSLSocketClient.h"
+#import "TSSLSocketException.h"
+#import "TObjective-C.h"
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#if !TARGET_OS_IPHONE
+#import <CoreServices/CoreServices.h>
+#else
+#import <CFNetwork/CFNetwork.h>
+#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 0000000..c290b05
--- /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 0000000..99eadf5
--- /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