Re: Security with Streams
Re: Security with Streams
- Subject: Re: Security with Streams
- From: "Gerriet M. Denkmann" <email@hidden>
- Date: Sun, 26 Jun 2016 09:44:06 +0700
> On 26 Jun 2016, at 05:12, Jens Alfke <email@hidden> wrote:
>
>
>> On Jun 18, 2016, at 2:34 AM, Gerriet M. Denkmann <email@hidden> wrote:
>>
>> 1. (important) the client really wants to know that:
>> (1a) it is talking to the right server and not to some evil entity masquerading as the real server.
>> (1b) the data it receives has not been tampered with on the way.
>
> You want an SSL (aka TLS) connection, with the server providing a certificate (the typical setup.)
>
>> 2. (less important) the server might want to know that the client connecting to it is a valid client.
>> This might help if there are thousands of fake clients overwhelming the server with fake requests.
>> But this is a kind of unlikely scenario.
>
> For that you’d need the SSL client to provide a certificate too. This is supported by the CFStream APIs.
>
>> 3. (hardly important at all) no one can read the data exchanged.
>> The data exchanged it really not sensitive.
>
> Well, you get that for free with SSL anyway :)
>
> Accomplishing (1b) and (3) is straightforward, I think. The doc-comment for NSNetService says how to enable SSL/TLS for the sockets by setting the stream properties:
Following TN2326 I created a (self signed) Certificate Authority and a Digital Identity called "MyServerId".
The server gets MyServerId from my keychain:
- (void)netService:(NSNetService *)sender didAcceptConnectionWithInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream
{
NSString *certificateName = @"MyServerId";
NSDictionary *d = @{ (__bridge id) kSecClass: (__bridge id) kSecClassIdentity,
(__bridge id) kSecReturnRef: @YES,
(__bridge id) kSecMatchSubjectWholeString: certificateName,
};
CFArrayRef copyMatchingResult;
OSStatus status = SecItemCopyMatching( (__bridge CFDictionaryRef)di, (CFTypeRef *)©MatchingResult );
if (status != errSecSuccess) ...
id mySecIdentityRef = (__bridge id)copyMatchingResult;
NSArray *certs = @[ mySecIdentityRef ];
NSDictionary *settings = @{ (__bridge NSString *)kCFStreamSSLValidatesCertificateChain: @NO,
(__bridge NSString *)kCFStreamSSLIsServer: @YES,
(__bridge NSString *)kCFStreamSSLCertificates: certs,
};
BOOL ok = [ inputStream setProperty: settings forKey: (__bridge NSString *)kCFStreamPropertySSLSettings ];
if ( !ok ) ...
... for both streams: scheduleInRunLoop, open
}
The client has a copy of MyServerId.cer (exported from my keychain) in its bundle.
- (void)netService:(NSNetService *)sender didAcceptConnectionWithInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream
{
NSDictionary *settings = @{ (__bridge NSString *)kCFStreamSSLValidatesCertificateChain: @NO };
BOOL ok = [ inputStream setProperty: settings forKey: (__bridge NSString *)kCFStreamPropertySSLSettings ];
if ( !ok ) ...
... for both streams: scheduleInRunLoop, open
}
The NSStreamDelegate of the client compares the certificate in inputStream with the certificate in its bundle:
- (void)stream:(NSStream *)aStream handleEvent: (NSStreamEvent)streamEvent
{
if ( streamEvent & NSStreamEventHasSpaceAvailable )
{
if ( firstTimeHere )
{
firstTimeHere = NO;
NSString *kSSLPeerTrust = (__bridge NSString *)kCFStreamPropertySSLPeerTrust;
id trusT = [ inputStream propertyForKey: kSSLPeerTrust ];
if ( trusT == nil ) … error badServer
SecTrustRef trust = (__bridge SecTrustRef)trusT;
SecTrustResultType trustResult; // will be: recoverable trust failure; probably because certificate is based on self signed Certificate Authority
OSStatus err = SecTrustEvaluate(trust, &trustResult);
if (err != errSecSuccess) … error badServer
if trustResult ≠ kSecTrustResultProceed, kSecTrustResultUnspecified, kSecTrustResultRecoverableTrustFailure ... error badServer
SecCertificateRef certificate = SecTrustGetCertificateAtIndex ( trust, 0 );
if ( certificate == nil ) ... error badServer
CFDataRef dac = SecCertificateCopyData( certificate );
if ( dac == nil ) … error badServer
NSData *certificateDataOfServer = (NSData *)CFBridgingRelease(dac);
NSData *realCertificateData = get data from mainBundle;
if ( ![ realCertificateData isEqualToData: certificateDataOfServer ] ) ... error badServer
// trust the server
};
// write to aStream ...
}
...
}
Absolutely not sure whether the code above is correct, but it seems to be working.
> We are now falling into the rabbit hole that is peer-to-peer trust & identity. How is your server going to identify it so that a client will know that it’s the server it expects? I don’t know whether you’ve given any thought to this; the answer affects how you’d implement this part of the app.
I have thought about this, but I am not at all sure that my thoughts are correct.
Currently (as indicated in the code above) my client has a copy of the real server certificate and compares it with the certificate obtained from its inputStream.
I am not sure whether putting the server certificate into the client is ok or a breach of security.
That is: the client will accept any server which has signed with the server certificate.
When the server does AcceptConnectionWithInputStream I get a panel:
<name of app> wants to sign using key "MyServerId" in your keychain.
Do you want to allow access to this item?
Deny / Accept.
If <name of app> is “My Real Server" I will click "Accept".
If I have foolishly installed a malicious app calling itself “My Real Server” the client will be out of luck.
Also: If the LAN the client is connected to (client runs on an iOS Device) has a malicious server app which somehow has obtained a copy of my server certificate, the client will be in trouble.
Not sure how to solve this problem.
Kind regards,
Gerriet.
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden