Discussion:
Security with Streams
Gerriet M. Denkmann
9 years ago
Permalink
I have a server macOS app which publishes a service via NSNetService.
And a client iOS app which finds this service via NSNetServiceBrowser.

Then the client sends commands to the server via NSOutputStream and receives data from the server via NSInputStream.

All this works fine - but there is currently no security at all.

I would like to achieve these goals:

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.

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.

3. (hardly important at all) no one can read the data exchanged.
The data exchanged it really not sensitive.

I have no experience with security.
Can anybody point me in the right direction?

Gerriet.


_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email sent to ***@ml-in.narkive.n
Jens Alfke
9 years ago
Permalink
Post by Gerriet M. Denkmann
(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.)
Post by Gerriet M. Denkmann
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.
Post by Gerriet M. Denkmann
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:

* To enable TLS on the stream, set the various TLS settings using
* kCFStreamPropertySSLSettings before calling -open. You must also specify
* kCFBooleanTrue for kCFStreamSSLIsServer in the settings dictionary along with
* a valid SecIdentityRef as the first entry of kCFStreamSSLCertificates.
*/
- (void)netService:(NSNetService *)sender didAcceptConnectionWithInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream NS_AVAILABLE(10_9, 7_0);

It doesn’t say so, but I’m guessing you need to set the kCFStreamPropertySSLSettings property on the client side too (but not IsServer or Certificates), otherwise the client will try to open a plain socket and the SSL handshake will fail.

The harder part is (1a) because it gets into the issues of “trust” and “identity”, which are quite deep and complex. Your server app will need to provide an X.509 certificate when it accepts a connection. The usual way to get these is from a certificate authority like Verisign or Comodo (or now, Let’s Encrypt), but they issue certificates for DNS domain names, and your server probably doesn’t run on a host with its own domain name, since you’re just connecting to it via a raw IP address. The other option is getting a cert for an email address, but those are marked as being for use only in signing mail messages, not for SSL.

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.

—Jens
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

T
Gerriet M. Denkmann
9 years ago
Permalink
...
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 *)&copyMatchingResult );
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.
Post by Jens Alfke
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 (Cocoa-***@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email sent
Jens Alfke
9 years ago
Permalink
Post by Gerriet M. Denkmann
Following TN2326 I created a (self signed) Certificate Authority and a Digital Identity called "MyServerId".
You probably used the Keychain Access app for this? That works fine, but you’ll probably want every instance of the server app to create its own key-pair and cert, and you don’t want the user to have to use Keychain Access. I’ve got some utility code in my MYUtilities library that will create an Identity (key pair + cert) programmatically:
https://github.com/snej/MYUtilities/blob/master/MYAnonymousIdentity.h
The MYGetOrCreateAnonymousIdentity() function returns a SecIdentityRef you can use with CFStream.
Post by Gerriet M. Denkmann
Absolutely not sure whether the code above is correct, but it seems to be working.
That was fast! This is frustrating stuff to implement. Or maybe the docs have gotten a lot better recently ;-)
Post by Gerriet M. Denkmann
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.
It’s fine; the certificate is public and intended to be shared. It’s the private key that’s sensitive. What you’re describing is called “key-pinning”: restricting a client to connect only with a server with a known public key.

If every instance of the server has its own key, then embedding a cert in the client app doesn’t work. What’s usually done is to have the app store a copy of the cert the first time it connects to the server (with the user’s approval), and then require the same cert every subsequent time it connects. (This is similar to what SSH does, where the first time you connect to a host it tells you the key is unknown and asks if you want to trust it.)

The situation you want to watch out for is where the client connects to a server it’s already connected to, but the cert’s public key doesn’t match the previous one. This is where you sound the alarm to the user — either someone’s trying to spoof the real server, or perhaps the server lost its keys and had to create a new cert (maybe its disk crashed and there wasn’t a backup of the keychain.)

—Jens
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email sent to ***@ml-in.nar
Gerriet M. Denkmann
9 years ago
Permalink
Post by Jens Alfke
Post by Gerriet M. Denkmann
Absolutely not sure whether the code above is correct, but it seems to be working.
It’s fine; the certificate is public and intended to be shared. It’s the private key that’s sensitive. What you’re describing is called “key-pinning”: restricting a client to connect only with a server with a known public key.
That is very reassuring to know. Thanks for the confirmation.
Post by Jens Alfke
That was fast! This is frustrating stuff to implement. Or maybe the docs have gotten a lot better recently ;-)
I am great, am I not? (Well, to be honest, I have been struggling with this for weeks, and also borrowed heavily from the Apple sample code TLSTool)
Post by Jens Alfke
If every instance of the server has its own key, then embedding a cert in the client app doesn’t work.
This project is for my own personal use. So there is just one server.
Post by Jens Alfke
The situation you want to watch out for is where the client connects to a server it’s already connected to, but the cert’s public key doesn’t match the previous one.
In this case the client will close the connection immediately.


Kind regards,

Gerriet.


_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in
Gerriet M. Denkmann
9 years ago
Permalink
...
Assume that an evil entity has got hold of “MyServerCertificate.cer”, but has no access to my keychain and thus to the private key of MyServerCertificate.
Could they use this certificate to open a secure stream to a client? Or do they need the private key to sign?
I am unclear to me whether you are after a client-server (i.e. all servers are under your control) or peer-to-peer (i.e. every client is a server and every server is a client)) model?
There is just one server, which is under my control.


Kind regards,

Gerriet.


_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkiv
Jens Alfke
9 years ago
Permalink
Post by Gerriet M. Denkmann
Assume that an evil entity has got hold of “MyServerCertificate.cer”, but has no access to my keychain and thus to the private key of MyServerCertificate.
Could they use this certificate to open a secure stream to a client? Or do they need the private key to sign?
— Servers don’t open connections to clients; it’s the other way around.
— There’s nothing private about a certificate. In fact, an SSL server sends its certificate out to any client that connects to it, as part of the SSL handshake.
— A certificate contains only the public key, not the private key. It can’t be used to sign anything, only to verify signatures.

—Jens
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in
Gerriet M. Denkmann
9 years ago
Permalink
Post by Jens Alfke
Post by Gerriet M. Denkmann
Assume that an evil entity has got hold of “MyServerCertificate.cer”, but has no access to my keychain and thus to the private key of MyServerCertificate.
Could they use this certificate to open a secure stream to a client? Or do they need the private key to sign?
— Servers don’t open connections to clients; it’s the other way around.
Sorry, I was speaking rather too loosely.

I meant: when the server accepts a connection from a client via
netService:didAcceptConnectionWithInputStream:outputStream:
it does:
[ inputStream setProperty: settings forKey: kCFStreamPropertySSLSettings ]

where settings has: kCFStreamSSLCertificates = array with a SecIdentityRef obtained via SecItemCopyMatching().

Could it, instead of getting the SecIdentityRef from the keychain, just use MyServerCertificate.cer instead?
Post by Jens Alfke
— A certificate contains only the public key, not the private key. It can’t be used to sign anything, only to verify signatures.
So this probably answers my question: It could not. (Correct ?).

So the evil server has to use its own EvilServerCertificate from its own keychain.
And then the client would compare the certificate it receives with MyServerCertificate.cer and notice that these are different, thus closing the connection. (Correct ?)


Kind regards,

Gerriet.


_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkiv
Jens Alfke
9 years ago
Permalink
Post by Gerriet M. Denkmann
Could it, instead of getting the SecIdentityRef from the keychain, just use MyServerCertificate.cer instead?
Nope. The server has to have a SecIdentityRef, because its side of the SSL handshake requires using the private key to sign data.
Post by Gerriet M. Denkmann
And then the client would compare the certificate it receives with MyServerCertificate.cer and notice that these are different, thus closing the connection. (Correct ?)
If you’ve programmed the client to do that, yes.

—Jens
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net
Continue reading on narkive:
Loading...