/* * Copyright (c) 2008-2013 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Trivial SSL server example, using SecureTransport / OS X version. * * Written by Doug Mitchell. */ #include #include #include "sslAppUtils.h" #include "ioSock.h" #include "utilities/fileIo.h" #include #include #include #include #include #include #include #include #include #include #include "SecurityTool/print_cert.h" #if NO_SERVER #include #endif /* Set true when PR-3074739 is merged to TOT */ #define SET_DH_PARAMS_ENABLE 1 /* true when using SSLCopyPeerCertificates() per Radar 3311892 */ #define USE_COPY_PEER_CERTS 1 /* * Defaults, overridable by user. */ #define SERVER_MESSAGE "HTTP/1.0 200 OK\015\012Content-Type: text/html\015\012\015\012" \ "SecureTransport Test Server" \ "

Secure connection established.

" \ "Message from the 'sslServer' sample application.\015\012" \ "\015\012" /* For ease of debugging, pick a non-privileged port */ #define DEFAULT_PORT 1200 // #define DEFAULT_PORT 443 #define DEFAULT_HOST "localhost" #define DEFAULT_KC "certkc" static void usage(char **argv) { printf("Usage: %s [option ...]\n", argv[0]); printf("Options:\n"); printf(" P=port Port to listen on; default is %d\n", DEFAULT_PORT); printf(" k=keychain Contains server cert and keys.\n"); printf(" y=keychain Encryption-only cert and keys.\n"); printf(" e Allow Expired Certs\n"); printf(" r Allow any root cert\n"); printf(" E Allow Expired Roots\n"); printf(" x Disable Cert Verification\n"); printf(" f=fileBase Write Peer Certs to fileBase*\n"); printf(" c Display peer certs\n"); printf(" d Display received data\n"); printf(" C=cipherSuite (e=40-bit d=DES D=40-bit DES 3=3DES 4=RC4 $=40-bit RC4\n" " 2=RC2 a=AES128 A=AES256 h=DH H=Anon DH r=DHE/RSA s=DH/DSS\n" " n=RSA/NULL\n"); printf(" 2 SSLv2 only (default is best fit)\n"); printf(" 3 SSLv3 only (default is best fit)\n"); printf(" t TLSv1 only (default is best fit)\n"); printf(" o TLSv1, SSLv3 use kSSLProtocol__X__Only\n"); printf(" g={prot...} Specify legal protocols; prot = any combo of [23t]\n"); printf(" T=[nrsj] Verify client cert state = " "none/requested/sent/rejected\n"); printf(" R Disable resumable session support\n"); printf(" i=timeout Session cache timeout\n"); printf(" u=[nat] Authentication: n=never; a=always; t=try\n"); printf(" b Non-blocking I/O\n"); printf(" a fileNmae Add fileName to list of trusted roots\n"); printf(" A fileName fileName is ONLY trusted root\n"); printf(" U filename Add filename to acceptable DNList (multiple times OK)\n"); printf(" D filename Diffie-Hellman parameters from filename\n"); printf(" z=password Unlock server keychain with password.\n"); printf(" H Do SecIndentityRef search instead of specific keychain\n"); printf(" M Complete cert chain (default assumes that our identity is root)\n"); printf(" 4 Disable anonymous ciphers\n"); printf(" p Pause after each phase\n"); printf(" l[=loops] Loop, performing multiple transactions\n"); printf(" q Quiet/diagnostic mode (site names and errors only)\n"); printf(" h Help\n"); exit(1); } /* snag a copy of current connection's peer certs so we can * examine them later after the connection is closed */ static OSStatus copyPeerCerts( SSLContext *ctx, CFArrayRef *peerCerts) // mallocd & RETURNED { #if USE_COPY_PEER_CERTS OSStatus ortn = SSLCopyPeerCertificates(ctx, peerCerts); #else OSStatus ortn = SSLGetPeerCertificates(ctx, peerCerts); #endif if(ortn) { printf("***Error obtaining peer certs: %s\n", sslGetSSLErrString(ortn)); } return ortn; } /* free the cert array obtained via SSLGetPeerCertificates() */ static void freePeerCerts( CFArrayRef peerCerts) { if(peerCerts == NULL) { return; } #if USE_COPY_PEER_CERTS /* Voila! Problem fixed. */ CFRelease(peerCerts); return; #else CFIndex numCerts; SecCertificateRef certData; CFIndex i; numCerts = CFArrayGetCount(peerCerts); for(i=0; i", ((unsigned)c) & 0xff); } break; } } printf("\n"); } static void doPause(const char *prompt) { if(prompt) { printf("%s. ", prompt); } fpurge(stdin); printf("Continue (n/anything)? "); char c = getchar(); if(c == 'n') { exit(0); } } /* * Perform one SSL diagnostic server-side session. Returns nonzero on error. * Normally no output to stdout except initial "waiting for connection" message, * unless there is a really screwed up error (i.e., something not directly related * to the SSL connection). */ #define RCV_BUF_SIZE 256 static OSStatus sslServe( otSocket listenSock, unsigned short portNum, SSLProtocol tryVersion, // only used if acceptedProts NULL const char *acceptedProts, CFArrayRef serverCerts, // required char *password, // optional CFArrayRef encryptServerCerts, // optional bool allowExpired, bool allowAnyRoot, bool allowExpiredRoot, bool disableCertVerify, char *anchorFile, bool replaceAnchors, char cipherRestrict, // '2', 'd'. etc...'\0' for no // restriction SSLAuthenticate authenticate, unsigned char *dhParams, // optional D-H parameters unsigned dhParamsLen, CFArrayRef acceptableDNList, // optional bool resumableEnable, uint32_t sessionCacheTimeout,// optional bool disableAnonCiphers, bool silent, // no stdout bool pause, SSLProtocol *negVersion, // RETURNED SSLCipherSuite *negCipher, // RETURNED SSLClientCertificateState *certState, // RETURNED Boolean *sessionWasResumed, // RETURNED unsigned char *sessionID, // mallocd by caller, RETURNED size_t *sessionIDLength, // RETURNED CFArrayRef *peerCerts, // mallocd & RETURNED char **argv) { otSocket acceptSock; PeerSpec peerId; OSStatus ortn; SSLContextRef ctx = NULL; size_t length; uint8_t rcvBuf[RCV_BUF_SIZE]; const char *outMsg = SERVER_MESSAGE; *negVersion = kSSLProtocolUnknown; *negCipher = SSL_NULL_WITH_NULL_NULL; *peerCerts = NULL; #if IGNORE_SIGPIPE signal(SIGPIPE, sigpipe); #endif /* first wait for a connection */ if(!silent) { printf("Waiting for client connection on port %u...", portNum); fflush(stdout); } ortn = AcceptClientConnection(listenSock, &acceptSock, &peerId); if(ortn) { printf("AcceptClientConnection returned %d; aborting\n", (int)ortn); return ortn; } /* * Set up a SecureTransport session. * First the standard calls. */ ortn = SSLNewContext(true, &ctx); if(ortn) { printSslErrStr("SSLNewContext", ortn); goto cleanup; } ortn = SSLSetIOFuncs(ctx, SocketRead, SocketWrite); if(ortn) { printSslErrStr("SSLSetIOFuncs", ortn); goto cleanup; } ortn = SSLSetConnection(ctx, (SSLConnectionRef)(intptr_t)acceptSock); if(ortn) { printSslErrStr("SSLSetConnection", ortn); goto cleanup; } /* have to do these options befor setting server certs */ if(allowExpired) { ortn = SSLSetAllowsExpiredCerts(ctx, true); if(ortn) { printSslErrStr("SSLSetAllowExpiredCerts", ortn); goto cleanup; } } if(allowAnyRoot) { ortn = SSLSetAllowsAnyRoot(ctx, true); if(ortn) { printSslErrStr("SSLSetAllowAnyRoot", ortn); goto cleanup; } } if(anchorFile) { ortn = sslAddTrustedRoot(ctx, anchorFile, replaceAnchors); if(ortn) { printf("***Error obtaining anchor file %s\n", anchorFile); goto cleanup; } } if(serverCerts != NULL) { if(anchorFile == NULL) { /* no specific anchors, so assume we want to trust this one */ ortn = addIdentityAsTrustedRoot(ctx, serverCerts); if(ortn) { goto cleanup; } } ortn = SSLSetCertificate(ctx, serverCerts); if(ortn) { printSslErrStr("SSLSetCertificate", ortn); goto cleanup; } } if(encryptServerCerts) { ortn = SSLSetEncryptionCertificate(ctx, encryptServerCerts); if(ortn) { printSslErrStr("SSLSetEncryptionCertificate", ortn); goto cleanup; } } if(allowExpiredRoot) { ortn = SSLSetAllowsExpiredRoots(ctx, true); if(ortn) { printSslErrStr("SSLSetAllowsExpiredRoots", ortn); goto cleanup; } } if(disableCertVerify) { ortn = SSLSetEnableCertVerify(ctx, false); if(ortn) { printSslErrStr("SSLSetEnableCertVerify", ortn); goto cleanup; } } /* * SecureTransport options. */ if(acceptedProts) { ortn = SSLSetProtocolVersionEnabled(ctx, kSSLProtocolAll, false); if(ortn) { printSslErrStr("SSLSetProtocolVersionEnabled(all off)", ortn); goto cleanup; } for(const char *cp = acceptedProts; *cp; cp++) { SSLProtocol prot = kSSLProtocolUnknown; switch(*cp) { case '2': prot = kSSLProtocol2; break; case '3': prot = kSSLProtocol3; break; case 't': prot = kTLSProtocol1; break; default: usage(argv); } ortn = SSLSetProtocolVersionEnabled(ctx, prot, true); if(ortn) { printSslErrStr("SSLSetProtocolVersionEnabled", ortn); goto cleanup; } } } else { ortn = SSLSetProtocolVersion(ctx, tryVersion); if(ortn) { printSslErrStr("SSLSetProtocolVersion", ortn); goto cleanup; } } if(resumableEnable) { ortn = SSLSetPeerID(ctx, &peerId, sizeof(PeerSpec)); if(ortn) { printSslErrStr("SSLSetPeerID", ortn); goto cleanup; } } if(cipherRestrict != '\0') { ortn = sslSetCipherRestrictions(ctx, cipherRestrict); if(ortn) { goto cleanup; } } if(authenticate != kNeverAuthenticate) { ortn = SSLSetClientSideAuthenticate(ctx, authenticate); if(ortn) { printSslErrStr("SSLSetClientSideAuthenticate", ortn); goto cleanup; } } if(dhParams) { ortn = SSLSetDiffieHellmanParams(ctx, dhParams, dhParamsLen); if(ortn) { printSslErrStr("SSLSetDiffieHellmanParams", ortn); goto cleanup; } } if(sessionCacheTimeout) { ortn = SSLSetSessionCacheTimeout(ctx, sessionCacheTimeout); if(ortn) { printSslErrStr("SSLSetSessionCacheTimeout", ortn); goto cleanup; } } if(disableAnonCiphers) { ortn = SSLSetAllowAnonymousCiphers(ctx, false); if(ortn) { printSslErrStr("SSLSetAllowAnonymousCiphers", ortn); goto cleanup; } /* quickie test of the getter */ Boolean e; ortn = SSLGetAllowAnonymousCiphers(ctx, &e); if(ortn) { printSslErrStr("SSLGetAllowAnonymousCiphers", ortn); goto cleanup; } if(e) { printf("***SSLGetAllowAnonymousCiphers() returned true; expected false\n"); ortn = errSecIO; goto cleanup; } } /* XXX/cs if(acceptableDNList) { ortn = SSLSetCertificateAuthorities(ctx, acceptableDNList, TRUE); if(ortn) { printSslErrStr("SSLSetCertificateAuthorities", ortn); goto cleanup; } } */ /* end options */ if(pause) { doPause("SSLContext initialized"); } /* Perform SSL/TLS handshake */ do { ortn = SSLHandshake(ctx); if((ortn == errSSLWouldBlock) && !silent) { /* keep UI responsive */ sslOutputDot(); } } while (ortn == errSSLWouldBlock); /* this works even if handshake failed due to cert chain invalid */ copyPeerCerts(ctx, peerCerts); SSLGetClientCertificateState(ctx, certState); SSLGetNegotiatedCipher(ctx, negCipher); SSLGetNegotiatedProtocolVersion(ctx, negVersion); *sessionIDLength = MAX_SESSION_ID_LENGTH; SSLGetResumableSessionInfo(ctx, sessionWasResumed, sessionID, sessionIDLength); if(!silent) { printf("\n"); } if(ortn) { goto cleanup; } if(pause) { doPause("SSLContext handshake complete"); } /* wait for one complete line or user says they've had enough */ while(ortn == errSecSuccess) { length = sizeof(rcvBuf); ortn = SSLRead(ctx, rcvBuf, length, &length); if(length == 0) { /* keep UI responsive */ sslOutputDot(); } else { /* print what we have */ printf("client request: "); dumpAscii(rcvBuf, length); } if(pause) { /* allow user to bail */ char resp; fpurge(stdin); printf("\nMore client request (y/anything): "); resp = getchar(); if(resp != 'y') { break; } } /* poor person's line completion scan */ for(unsigned i=0; i SSLSetProtocolVersionEnabled bool quiet = false; bool resumableEnable = true; bool pause = false; char *keyChainName = NULL; char *encryptKeyChainName = NULL; int loops = 1; SSLAuthenticate authenticate = kNeverAuthenticate; bool nonBlocking = false; bool allowExpiredRoot = false; bool disableCertVerify = false; char *anchorFile = NULL; bool replaceAnchors = false; bool vfyCertState = false; SSLClientCertificateState expectCertState = kSSLClientCertNone; char *password = NULL; char *dhParamsFile = NULL; unsigned char *dhParams = NULL; unsigned dhParamsLen = 0; bool doIdSearch = false; bool completeCertChain = false; uint32_t sessionCacheTimeout = 0; bool disableAnonCiphers = false; CFMutableArrayRef acceptableDNList = NULL; for(arg=1; arg loop forever */ loops = 0; break; } else if(argp[1] != '=') { usage(argv); } loops = atoi(&argp[2]); break; default: usage(argv); } } #if NO_SERVER # if DEBUG securityd_init(NULL); # endif #endif /* get server cert and optional encryption cert as CFArrayRef */ if(keyChainName) { serverCerts = getSslCerts(keyChainName, false, completeCertChain, anchorFile, &serverKc); if(serverCerts == nil) { exit(1); } } else #if 0 if(doIdSearch) { OSStatus ortn = sslIdentityPicker(NULL, anchorFile, true, NULL, &serverCerts); if(ortn) { printf("***IdentitySearch failure; aborting.\n"); exit(1); } } if(password) { OSStatus ortn = SecKeychainUnlock(serverKc, strlen(password), password, true); if(ortn) { printf("SecKeychainUnlock returned %d\n", (int)ortn); /* oh well */ } } if(encryptKeyChainName) { encryptCerts = getSslCerts(encryptKeyChainName, true, completeCertChain, anchorFile, &encryptKc); if(encryptCerts == nil) { exit(1); } } #else (void) doIdSearch; (void) encryptKeyChainName; #endif if(protXOnly) { switch(attemptProt) { case kTLSProtocol1: attemptProt = kTLSProtocol1Only; break; case kSSLProtocol3: attemptProt = kSSLProtocol3Only; break; default: break; } } #if 0 if(dhParamsFile) { int r = cspReadFile(dhParamsFile, &dhParams, &dhParamsLen); if(r) { printf("***Error reading diffie-hellman params from %s; aborting\n", dhParamsFile); } } #else (void) dhParamsFile; #endif /* one-time only server port setup */ err = ListenForClients(portNum, nonBlocking, &listenSock); if(err) { printf("ListenForClients returned %d; aborting\n", (int)err); exit(1); } for(loopNum=1; ; loopNum++) { err = sslServe(listenSock, portNum, attemptProt, acceptedProts, serverCerts, password, encryptCerts, allowExpired, allowAnyRoot, allowExpiredRoot, disableCertVerify, anchorFile, replaceAnchors, cipherRestrict, authenticate, dhParams, dhParamsLen, acceptableDNList, resumableEnable, sessionCacheTimeout, disableAnonCiphers, quiet, pause, &negVersion, &negCipher, &certState, &sessionWasResumed, sessionID, &sessionIDLength, &peerCerts, argv); if(err) { errCount++; } if(!quiet) { SSLProtocol tryProt = attemptProt; showSSLResult(tryProt, acceptedProts, err, negVersion, negCipher, sessionWasResumed, sessionID, sessionIDLength, peerCerts, displayCerts, certState, fileBase ? fullFileBase : NULL); } errCount += verifyClientCertState(vfyCertState, expectCertState, certState); freePeerCerts(peerCerts); if(loops && (loopNum == loops)) { break; } }; endpointShutdown(listenSock); if(serverKc) { CFRelease(serverKc); } if(encryptKc) { CFRelease(encryptKc); } return errCount; }