X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/72a12576750f52947eb043106ba5c12c0d07decf..b1ab9ed8d0e0f1c3b66d7daa8fd5564444c56195:/libsecurity_ssl/sslViewer/sslViewer.cpp?ds=sidebyside diff --git a/libsecurity_ssl/sslViewer/sslViewer.cpp b/libsecurity_ssl/sslViewer/sslViewer.cpp new file mode 100644 index 00000000..a661ef8d --- /dev/null +++ b/libsecurity_ssl/sslViewer/sslViewer.cpp @@ -0,0 +1,1870 @@ +/* + * Copyright (c) 2006-2012 Apple Inc. All Rights Reserved. + * + * SSL viewer tool, Secure Transport. + */ + +#include "SecureTransport.h" + +#include +#include +#include +#include +#include +#include "sslAppUtils.h" +#include "printCert.h" +#include "ioSock.h" +#include "fileIo.h" + +#include +#include +#include +#include +#include +#include +#include + +#if NO_SERVER +#include +#endif + +#define DEFAULT_GETMSG "GET" +#define DEFAULT_PATH "/" +#define DEFAULT_GET_SUFFIX "HTTP/1.0\r\n\r\n" + +#define DEFAULT_HOST "store.apple.com" +#define DEFAULT_PORT 443 + +#define CFReleaseSafe(CF) { CFTypeRef _cf = (CF); if (_cf) CFRelease(_cf); } +#define CFReleaseNull(CF) { CFTypeRef _cf = (CF); \ + if (_cf) { (CF) = NULL; CFRelease(_cf); } } + +static void usageNorm(char **argv) +{ + printf("Usage: %s [hostname|-] [path] [option ...]\n", argv[0]); + printf(" %s hostname [path] [option ...]\n", argv[0]); + printf("Specifying '-' for hostname, or no args, uses default of %s.\n", + DEFAULT_HOST); + printf("Optional path argument must start with leading '/'.\n"); + printf("Options:\n"); + printf(" e Allow Expired Certs\n"); + printf(" E Allow Expired Roots\n"); + printf(" r Allow any root cert\n"); + printf(" c Display peer certs\n"); + printf(" cc Display peer SecTrust\n"); + printf(" d Display received data\n"); + printf(" S Display enabled cipher suites\n"); + printf(" 2 SSLv2 only\n"); + printf(" 3 SSLv3 only\n"); + printf(" tls10 | t TLSv1 only\n"); + printf(" tls11 TLSv1.1 only\n"); + printf(" tls12 TLSv1.2 only\n"); + printf(" L all - TLSv1.2, TLSv1.1, TLSv1, SSLv3, SSLv2 (default = TLSv1.2)\n"); + printf(" g={prot...} Specify legal protocols; prot = any combo of" + " [2|3|t|tls10|tls11|tls12]\n"); + printf(" k=keychain Contains (client|server) cert and keys. Optional.\n"); + printf(" l=loopCount Perform loopCount ops (default = 1)\n"); + printf(" P=port Default = %d\n", DEFAULT_PORT); + printf(" p Pause after each loop\n"); + printf(" q Quiet/diagnostic mode (site names and errors" + " only)\n"); + printf(" a fileName Add fileName to list of trusted roots\n"); + printf(" A fileName fileName is ONLY trusted root\n"); + printf(" Z fileName fileName is a trusted leaf cert\n"); + printf(" x Disable Cert Verification\n"); + printf(" z=password Unlock client keychain with password.\n"); + printf(" 8 Complete cert chains (default is out cert is a root)\n"); + printf(" s Silent\n"); + printf(" V Verbose\n"); + printf(" h Help\n"); + printf(" hv More, verbose help\n"); +} + +static void usageVerbose(char **argv) __attribute__((noreturn)); +static void usageVerbose(char **argv) +{ + usageNorm(argv); + printf("Obscure Usage:\n"); + printf(" u kSSLProtocolUnknown only (TLSv1)\n"); + printf(" M Manual cert verification via " + "SecTrustEvaluate\n"); + printf(" f fileBase Write Peer Certs to fileBase*\n"); + printf(" o TLSv1, SSLv3 use kSSLProtocol__X__Only\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"); + printf(" y=keychain Encryption-only cert and keys. Optional.\n"); + printf(" K Keep connected until server disconnects\n"); + printf(" n Require closure notify message in TLSv1, " + "SSLv3 mode (implies K)\n"); + printf(" R Disable resumable session support\n"); + printf(" b Non-blocking I/O\n"); + printf(" v Verify negotiated protocol equals attempted\n"); + printf(" m=[23t] Max protocol supported as specified; implies " + "v\n"); + printf(" T=[nrsj] Verify client cert state = " + "none/requested/sent/rejected\n"); + printf(" H allow hostname spoofing\n"); + printf(" F=vfyHost Verify certs with specified host name\n"); + printf(" G=getMsg Specify entire GET, POST, etc.\n"); + printf(" N Log handshake timing\n"); + printf(" 7 Pause only after first loop\n"); + exit(1); +} + +static void usage(char **argv) __attribute__((noreturn)); +static void usage(char **argv) +{ + usageNorm(argv); + exit(1); +} + +/* + * Arguments to top-level sslPing() + */ +typedef struct { + SSLProtocol tryVersion; // only used if acceptedProts NULL + // uses SSLSetProtocolVersion + char *acceptedProts; // optional, any combo of {2,3,t} + // uses SSLSetProtocolVersionEnabled + const char *hostName; // e.g., "store.apple.com" + const char *vfyHostName; // use this for cert vfy if non-NULL, + // else use hostName + unsigned short port; + const char *getMsg; // e.g., + // "GET / HTTP/1.0\r\n\r\n" + bool allowExpired; + bool allowAnyRoot; + bool allowExpiredRoot; + bool disableCertVerify; + bool manualCertVerify; + bool dumpRxData; // display server data + char cipherRestrict; // '2', 'd'. etc...; '\0' for + // no restriction + bool keepConnected; + bool requireNotify; // require closure notify + // in V3 mode + bool resumableEnable; + bool allowHostnameSpoof; + bool nonBlocking; + char *anchorFile; + char *trustedLeafFile; + bool replaceAnchors; + bool interactiveAuth; + CFArrayRef clientCerts; // optional + CFArrayRef encryptClientCerts; // optional + uint32 sessionCacheTimeout;// optional + bool disableAnonCiphers; + bool showCipherSuites; + bool quiet; // minimal stdout + bool silent; // no stdout + bool verbose; + SSLProtocol negVersion; // RETURNED + SSLCipherSuite negCipher; // RETURNED + CFArrayRef peerCerts; // mallocd & RETURNED + SecTrustRef peerTrust; // RETURNED + SSLClientCertificateState certState; // RETURNED +#if TARGET_OS_MAC && MAC_OS_X_VERSION_MAX_ALLOWED < 1060 + int authType; +#else + SSLClientAuthenticationType authType; // RETURNED +#endif + CFArrayRef dnList; // RETURNED + char *password; // optional to open clientCerts + char **argv; + Boolean sessionWasResumed; + unsigned char sessionID[MAX_SESSION_ID_LENGTH]; + size_t sessionIDLength; + CFAbsoluteTime handshakeTimeOp; // time for this op + CFAbsoluteTime handshakeTimeFirst; // time for FIRST op, not averaged + CFAbsoluteTime handshakeTimeTotal; // time for all ops except first + unsigned numHandshakes; + +} sslPingArgs; + +#include +static void sigpipe(int sig) +{ + fflush(stdin); + printf("***SIGPIPE***\n"); +} + +/* + * Snag a copy of current connection's peer certs so we can + * examine them later after the connection is closed. + * SecureTransport actually does the create and retain for us. + */ +static OSStatus copyPeerCerts( + SSLContext *ctx, + CFArrayRef *peerCerts) // mallocd & RETURNED +{ + OSStatus ortn = SSLCopyPeerCertificates(ctx, peerCerts); + if(ortn) { + printf("***Error obtaining peer certs: %s\n", + sslGetSSLErrString(ortn)); + } + return ortn; +} + +/* + * Manually evaluate session's SecTrustRef. + */ + +static OSStatus sslEvaluateTrust( + SSLContext *ctx, + bool verbose, + bool silent, + CFArrayRef *peerCerts) // fetched and retained +{ + OSStatus ortn = noErr; +#if USE_CDSA_CRYPTO + SecTrustRef secTrust = NULL; + ortn = SSLGetPeerSecTrust(ctx, &secTrust); + if(ortn) { + printf("\n***Error obtaining peer SecTrustRef: %s\n", + sslGetSSLErrString(ortn)); + return ortn; + } + if(secTrust == NULL) { + /* this is the normal case for resumed sessions, in which + * no cert evaluation is performed */ + if(!silent) { + printf("...No SecTrust available - this is a resumed session, right?\n"); + } + return noErr; + } + SecTrustResultType secTrustResult; + ortn = SecTrustEvaluate(secTrust, &secTrustResult); + if(ortn) { + printf("\n***Error on SecTrustEvaluate: %d\n", (int)ortn); + return ortn; + } + if(verbose) { + const char *res = NULL; + switch(secTrustResult) { + case kSecTrustResultInvalid: + res = "kSecTrustResultInvalid"; break; + case kSecTrustResultProceed: + res = "kSecTrustResultProceed"; break; + case kSecTrustResultConfirm: + res = "kSecTrustResultConfirm"; break; + case kSecTrustResultDeny: + res = "kSecTrustResultDeny"; break; + case kSecTrustResultUnspecified: + res = "kSecTrustResultUnspecified"; break; + case kSecTrustResultRecoverableTrustFailure: + res = "kSecTrustResultRecoverableTrustFailure"; break; + case kSecTrustResultFatalTrustFailure: + res = "kSecTrustResultFatalTrustFailure"; break; + case kSecTrustResultOtherError: + res = "kSecTrustResultOtherError"; break; + default: + res = "UNKNOWN"; break; + } + printf("\nSecTrustEvaluate(): secTrustResult %s\n", res); + } + + switch(secTrustResult) { + case kSecTrustResultUnspecified: + /* cert chain valid, no special UserTrust assignments */ + case kSecTrustResultProceed: + /* cert chain valid AND user explicitly trusts this */ + break; + default: + printf("\n***SecTrustEvaluate reported secTrustResult %d\n", + (int)secTrustResult); + ortn = errSSLXCertChainInvalid; + break; + } +#endif + + *peerCerts = NULL; + +#ifdef USE_CDSA_CRYPTO + /* one more thing - get peer certs in the form of an evidence chain */ + CSSM_TP_APPLE_EVIDENCE_INFO *dummyEv; + OSStatus thisRtn = SecTrustGetResult(secTrust, &secTrustResult, + peerCerts, &dummyEv); + if(thisRtn) { + printSslErrStr("SecTrustGetResult", thisRtn); + } +#endif + return ortn; +} + +static void sslShowEnabledCipherSuites( + SSLContextRef ctx) +{ + OSStatus status; + SSLCipherSuite *ciphers; + size_t numCiphers, totalCiphers; + unsigned int i; + + status = SSLGetNumberSupportedCiphers(ctx, &totalCiphers); + status = SSLGetNumberEnabledCiphers(ctx, &numCiphers); + ciphers = (SSLCipherSuite *)malloc(sizeof(SSLCipherSuite) * numCiphers); + status = SSLGetEnabledCiphers(ctx, ciphers, &numCiphers); + + printf(" Total enabled ciphers : %ld of %ld\n", numCiphers, totalCiphers); + + for(i=0; i", ((unsigned)c) & 0xff); + } + break; + } + + } + printf("\n"); +} + +/* + * Perform one SSL diagnostic session. Returns nonzero on error. Normally no + * output to stdout except initial "connecting to" 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 sslPing( + sslPingArgs *pargs) +{ + PeerSpec peerId; + otSocket sock = 0; + OSStatus ortn; + SSLContextRef ctx = NULL; + size_t length; + size_t actLen; + uint8_t rcvBuf[RCV_BUF_SIZE]; + CFAbsoluteTime startHandshake; + CFAbsoluteTime endHandshake; + + pargs->negVersion = kSSLProtocolUnknown; + pargs->negCipher = SSL_NULL_WITH_NULL_NULL; + pargs->peerCerts = NULL; + + /* first make sure requested server is there */ + ortn = MakeServerConnection(pargs->hostName, pargs->port, pargs->nonBlocking, + &sock, &peerId); + if(ortn) { + printf("MakeServerConnection returned %d; aborting\n", (int)ortn); + return ortn; + } + if(pargs->verbose) { + printf("...connected to server; starting SecureTransport\n"); + } + + /* + * Set up a SecureTransport session. + * First the standard calls. + */ + ortn = SSLNewContext(false, &ctx); + if(ortn) { + printSslErrStr("SSLNewContext", ortn); + goto cleanup; + } + ortn = SSLSetIOFuncs(ctx, SocketRead, SocketWrite); + if(ortn) { + printSslErrStr("SSLSetIOFuncs", ortn); + goto cleanup; + } + ortn = SSLSetConnection(ctx, (SSLConnectionRef)sock); + if(ortn) { + printSslErrStr("SSLSetConnection", ortn); + goto cleanup; + } + SSLConnectionRef getConn; + ortn = SSLGetConnection(ctx, &getConn); + if(ortn) { + printSslErrStr("SSLGetConnection", ortn); + goto cleanup; + } + if(getConn != (SSLConnectionRef)sock) { + printf("***SSLGetConnection error\n"); + ortn = paramErr; + goto cleanup; + } + if(!pargs->allowHostnameSpoof) { + /* if this isn't set, it isn't checked by AppleX509TP */ + const char *vfyHost = pargs->hostName; + if(pargs->vfyHostName) { + /* generally means we're expecting an error */ + vfyHost = pargs->vfyHostName; + } + ortn = SSLSetPeerDomainName(ctx, vfyHost, strlen(vfyHost)); + if(ortn) { + printSslErrStr("SSLSetPeerDomainName", ortn); + goto cleanup; + } + } + + /* + * SecureTransport options. + */ + if(pargs->acceptedProts) { + ortn = SSLSetProtocolVersionEnabled(ctx, kSSLProtocolAll, false); + if(ortn) { + printSslErrStr("SSLSetProtocolVersionEnabled(all off)", ortn); + goto cleanup; + } + for(const char *cp = pargs->acceptedProts; *cp; cp++) { + SSLProtocol prot; + switch(*cp) { + case '2': + prot = kSSLProtocol2; + break; + case '3': + prot = kSSLProtocol3; + break; + case 't': + prot = kTLSProtocol1; + if (cp[1] == 'l' && cp[2] == 's' && cp[3] == '1') { + cp += 3; + if (cp[1] == '1') { + cp++; + prot = kTLSProtocol11; + } + else if (cp[1] == '2') { + cp++; + prot = kTLSProtocol12; + } + } + break; + default: + usage(pargs->argv); + } + ortn = SSLSetProtocolVersionEnabled(ctx, prot, true); + if(ortn) { + printSslErrStr("SSLSetProtocolVersionEnabled", ortn); + goto cleanup; + } + } + } + else { + ortn = SSLSetProtocolVersion(ctx, pargs->tryVersion); + if(ortn) { + printSslErrStr("SSLSetProtocolVersion", ortn); + goto cleanup; + } + SSLProtocol getVers; + ortn = SSLGetProtocolVersion(ctx, &getVers); + if(ortn) { + printSslErrStr("SSLGetProtocolVersion", ortn); + goto cleanup; + } + if(getVers != pargs->tryVersion && getVers != kSSLProtocolAll) { + printf("***SSLGetProtocolVersion screwup: try %s get %s\n", + sslGetProtocolVersionString(pargs->tryVersion), + sslGetProtocolVersionString(getVers)); + ortn = paramErr; + goto cleanup; + } + } + if(pargs->resumableEnable) { + const void *rtnId = NULL; + size_t rtnIdLen = 0; + + ortn = SSLSetPeerID(ctx, &peerId, sizeof(PeerSpec)); + if(ortn) { + printSslErrStr("SSLSetPeerID", ortn); + goto cleanup; + } + /* quick test of the get fcn */ + ortn = SSLGetPeerID(ctx, &rtnId, &rtnIdLen); + if(ortn) { + printSslErrStr("SSLGetPeerID", ortn); + goto cleanup; + } + if((rtnId == NULL) || (rtnIdLen != sizeof(PeerSpec))) { + printf("***SSLGetPeerID screwup\n"); + } + else if(memcmp(&peerId, rtnId, rtnIdLen) != 0) { + printf("***SSLGetPeerID data mismatch\n"); + } + } + if(pargs->allowExpired) { + ortn = SSLSetAllowsExpiredCerts(ctx, true); + if(ortn) { + printSslErrStr("SSLSetAllowExpiredCerts", ortn); + goto cleanup; + } + } + if(pargs->allowExpiredRoot) { + ortn = SSLSetAllowsExpiredRoots(ctx, true); + if(ortn) { + printSslErrStr("SSLSetAllowsExpiredRoots", ortn); + goto cleanup; + } + } + if(pargs->disableCertVerify) { + ortn = SSLSetEnableCertVerify(ctx, false); + if(ortn) { + printSslErrStr("SSLSetEnableCertVerify", ortn); + goto cleanup; + } + } + if(pargs->allowAnyRoot) { + ortn = SSLSetAllowsAnyRoot(ctx, true); + if(ortn) { + printSslErrStr("SSLSetAllowAnyRoot", ortn); + goto cleanup; + } + } + if(pargs->cipherRestrict != '\0') { + ortn = sslSetCipherRestrictions(ctx, pargs->cipherRestrict); + if(ortn) { + goto cleanup; + } + } + if(pargs->anchorFile) { + ortn = sslAddTrustedRoot(ctx, pargs->anchorFile, pargs->replaceAnchors); + if(ortn) { + printf("***Error obtaining anchor file %s\n", pargs->anchorFile); + goto cleanup; + } + } + if(pargs->trustedLeafFile) { + SecCertificateRef leafCertRef = NULL; + CFMutableArrayRef leafCerts = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + /* sslReadAnchor is a misnomer; it just creates a SecCertificateRef from a file */ + ortn = sslReadAnchor(pargs->trustedLeafFile, &leafCertRef); + if (!ortn) { + CFArrayAppendValue(leafCerts, leafCertRef); + CFRelease(leafCertRef); + ortn = SSLSetTrustedLeafCertificates(ctx, leafCerts); + CFRelease(leafCerts); + } + if(ortn) { + goto cleanup; + } + } + if(pargs->interactiveAuth) { + /* we want to get errSSLServerAuthCompleted from SSLHandshake on server auth completion */ + SSLSetSessionOption(ctx, kSSLSessionOptionBreakOnServerAuth, true); + /* we want to get errSSLClientCertRequested from SSLHandshake on client auth request */ + SSLSetSessionOption(ctx, kSSLSessionOptionBreakOnCertRequested, true); + } + else if(pargs->clientCerts) { + CFArrayRef dummy; + if(pargs->anchorFile == NULL) { + /* assume this is a root we want to implicitly trust */ + ortn = addIdentityAsTrustedRoot(ctx, pargs->clientCerts); + if(ortn) { + goto cleanup; + } + } + ortn = SSLSetCertificate(ctx, pargs->clientCerts); + if(ortn) { + printSslErrStr("SSLSetCertificate", ortn); + goto cleanup; + } + /* quickie test for this new function */ + ortn = SSLGetCertificate(ctx, &dummy); + if(ortn) { + printSslErrStr("SSLGetCertificate", ortn); + goto cleanup; + } + if(dummy != pargs->clientCerts) { + printf("***SSLGetCertificate error\n"); + ortn = ioErr; + goto cleanup; + } + } + if(pargs->encryptClientCerts) { + if(pargs->anchorFile == NULL) { + ortn = addIdentityAsTrustedRoot(ctx, pargs->encryptClientCerts); + if(ortn) { + goto cleanup; + } + } + ortn = SSLSetEncryptionCertificate(ctx, pargs->encryptClientCerts); + if(ortn) { + printSslErrStr("SSLSetEncryptionCertificate", ortn); + goto cleanup; + } + } + if(pargs->sessionCacheTimeout) { + ortn = SSLSetSessionCacheTimeout(ctx, pargs->sessionCacheTimeout); + if(ortn) { + printSslErrStr("SSLSetSessionCacheTimeout", ortn); + goto cleanup; + } + } + if(!pargs->disableAnonCiphers) { + ortn = SSLSetAllowAnonymousCiphers(ctx, true); + 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 false; expected true\n"); + ortn = ioErr; + goto cleanup; + } + } + if(pargs->showCipherSuites) { + sslShowEnabledCipherSuites(ctx); + } + /*** end options ***/ + + if(pargs->verbose) { + printf("...starting SSL handshake\n"); + } + startHandshake = CFAbsoluteTimeGetCurrent(); + + do + { ortn = SSLHandshake(ctx); + if((ortn == errSSLWouldBlock) && !pargs->silent) { + /* keep UI responsive */ + sslOutputDot(); + } + else if(ortn == errSSLServerAuthCompleted) { + if(pargs->verbose) { + printf("...server authentication completed\n"); + } + } + else if(ortn == errSSLClientCertRequested) { + if(pargs->verbose) { + printf("...received client cert request\n"); + } + /* %%% could prompt interactively here for client cert to use; + * for now, just use the client cert passed on the command line + */ + if(pargs->clientCerts) { + CFArrayRef dummy; + if(pargs->anchorFile == NULL) { + /* assume this is a root we want to implicitly trust */ + ortn = addIdentityAsTrustedRoot(ctx, pargs->clientCerts); + if(ortn) { + goto cleanup; + } + } + if(pargs->verbose) { + printf("...setting client certificate\n"); + } + ortn = SSLSetCertificate(ctx, pargs->clientCerts); + if(ortn) { + printSslErrStr("SSLSetCertificate", ortn); + goto cleanup; + } + /* quickie test for this new function */ + ortn = SSLGetCertificate(ctx, &dummy); + if(ortn) { + printSslErrStr("SSLGetCertificate", ortn); + goto cleanup; + } + if(dummy != pargs->clientCerts) { + printf("***SSLGetCertificate error\n"); + ortn = ioErr; + goto cleanup; + } + } + else { + printf("***no client certificate specified!\n"); + } + } + } while (ortn == errSSLWouldBlock || + ortn == errSSLServerAuthCompleted || + ortn == errSSLClientCertRequested); + + endHandshake = CFAbsoluteTimeGetCurrent(); + pargs->handshakeTimeOp = endHandshake - startHandshake; + if(pargs->numHandshakes == 0) { + /* special case, this one is always way longer */ + pargs->handshakeTimeFirst = pargs->handshakeTimeOp; + } + else { + /* normal running total */ + pargs->handshakeTimeTotal += pargs->handshakeTimeOp; + } + pargs->numHandshakes++; + + /* this works even if handshake failed due to cert chain invalid */ + CFReleaseSafe(pargs->peerCerts); + if(!pargs->manualCertVerify) { + copyPeerCerts(ctx, &pargs->peerCerts); + } + else { + /* else fetched via SecTrust later */ + pargs->peerCerts = NULL; + } + + ortn = SSLCopyPeerTrust(ctx, &pargs->peerTrust); + if(ortn) { + printf("***SSLCopyPeerTrust error %d\n", (int)ortn); + pargs->peerTrust = NULL; + } + + /* ditto */ + SSLGetClientCertificateState(ctx, &pargs->certState); +#if TARGET_OS_MAC && MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 + SSLGetNegotiatedClientAuthType(ctx, &pargs->authType); +#endif + SSLGetNegotiatedCipher(ctx, &pargs->negCipher); + SSLGetNegotiatedProtocolVersion(ctx, &pargs->negVersion); + CFReleaseSafe(pargs->dnList); + SSLCopyDistinguishedNames(ctx, &pargs->dnList); + pargs->sessionIDLength = MAX_SESSION_ID_LENGTH; + SSLGetResumableSessionInfo(ctx, &pargs->sessionWasResumed, pargs->sessionID, + &pargs->sessionIDLength); + if(pargs->manualCertVerify) { + OSStatus certRtn = sslEvaluateTrust(ctx, pargs->verbose, pargs->silent, + &pargs->peerCerts); + if(certRtn && !ortn ) { + ortn = certRtn; + } + } + + if(ortn) { + if(!pargs->silent) { + printf("\n"); + } + goto cleanup; + } + + if(pargs->verbose) { + printf("...SSL handshake complete\n"); + } + + /* Write our GET request */ + length = strlen(pargs->getMsg); + ortn = SSLWrite(ctx, pargs->getMsg, length, &actLen); + if(ortn) { + printf("***SSLWrite error: %d\n", (int)ortn); + } else if((actLen > 0) && pargs->dumpRxData) { + dumpAscii((uint8_t*)pargs->getMsg, actLen); + } + + /* + * Try to snag RCV_BUF_SIZE bytes. Exit if (!keepConnected and we get any data + * at all), or (keepConnected and err != (none, wouldBlock)). + */ + while (ortn == noErr) { + actLen = 0; + if(pargs->dumpRxData) { + size_t avail = 0; + + ortn = SSLGetBufferedReadSize(ctx, &avail); + if(ortn) { + printf("***SSLGetBufferedReadSize error\n"); + break; + } + if(avail != 0) { + printf("\n%d bytes available: ", (int)avail); + } + } + ortn = SSLRead(ctx, rcvBuf, RCV_BUF_SIZE, &actLen); + if((actLen == 0) && !pargs->silent) { + sslOutputDot(); + } + if((actLen == 0) && (ortn == noErr)) { + printf("***Radar 2984932 confirmed***\n"); + } + if (ortn == errSSLWouldBlock) { + /* for this loop, these are identical */ + ortn = noErr; + } + if(ortn == errSSLServerAuthCompleted || + ortn == errSSLClientCertRequested) { + /* should never get these once the handshake is complete */ + printf("***SSLRead returned unexpected handshake error!\n"); + } + + if((actLen > 0) && pargs->dumpRxData) { + dumpAscii(rcvBuf, actLen); + } + if(ortn != noErr) { + /* connection closed by server or by error */ + break; + } + if(!pargs->keepConnected && (actLen > 0)) { + /* good enough, we connected */ + break; + } + } + if(!pargs->silent) { + printf("\n"); + } + + /* snag these again in case of renegotiate */ + SSLGetClientCertificateState(ctx, &pargs->certState); + SSLGetNegotiatedCipher(ctx, &pargs->negCipher); + SSLGetNegotiatedProtocolVersion(ctx, &pargs->negVersion); + CFReleaseSafe(pargs->dnList); + SSLCopyDistinguishedNames(ctx, &pargs->dnList); + + /* convert normal "shutdown" into zero err rtn */ + if(ortn == errSSLClosedGraceful) { + ortn = noErr; + } + if((ortn == errSSLClosedNoNotify) && !pargs->requireNotify) { + /* relaxed disconnect rules */ + ortn = noErr; + } +cleanup: + /* + * always do close, even on error - to flush outgoing write queue + */ + OSStatus cerr = SSLClose(ctx); + if(ortn == noErr) { + ortn = cerr; + } + if(sock) { + endpointShutdown(sock); + } + if(ctx) { + SSLDisposeContext(ctx); + } + return ortn; +} + +static void add_key(const void *key, const void *value, void *context) { + CFArrayAppendValue((CFMutableArrayRef)context, key); +} + +static void showInfo(CFDictionaryRef info) { + CFIndex dict_count, key_ix, key_count; + CFMutableArrayRef keys = NULL; + CFIndex maxWidth = 20; /* Maybe precompute this or grab from context? */ + + dict_count = CFDictionaryGetCount(info); + keys = CFArrayCreateMutable(kCFAllocatorDefault, dict_count, + &kCFTypeArrayCallBacks); + CFDictionaryApplyFunction(info, add_key, keys); + key_count = CFArrayGetCount(keys); + CFArraySortValues(keys, CFRangeMake(0, key_count), + (CFComparatorFunction)CFStringCompare, 0); + + for (key_ix = 0; key_ix < key_count; ++key_ix) { + CFStringRef key = (CFStringRef)CFArrayGetValueAtIndex(keys, key_ix); + CFTypeRef value = CFDictionaryGetValue(info, key); + CFMutableStringRef line = CFStringCreateMutable(NULL, 0); + + CFStringAppend(line, key); + CFIndex jx; + for (jx = CFStringGetLength(key); + jx < maxWidth; ++jx) { + CFStringAppend(line, CFSTR(" ")); + } + CFStringAppend(line, CFSTR(" : ")); + if (CFStringGetTypeID() == CFGetTypeID(value)) { + CFStringAppend(line, (CFStringRef)value); + } else if (CFDateGetTypeID() == CFGetTypeID(value)) { + CFLocaleRef lc = CFLocaleCopyCurrent(); + CFDateFormatterRef df = CFDateFormatterCreate(NULL, lc, + kCFDateFormatterFullStyle, kCFDateFormatterFullStyle); + CFDateRef date = (CFDateRef)value; + CFStringRef ds = CFDateFormatterCreateStringWithDate(NULL, df, + date); + CFStringAppend(line, ds); + CFRelease(ds); + CFRelease(df); + CFRelease(lc); + } else if (CFURLGetTypeID() == CFGetTypeID(value)) { + CFURLRef url = (CFURLRef)value; + CFStringAppend(line, CFSTR("<")); + CFStringAppend(line, CFURLGetString(url)); + CFStringAppend(line, CFSTR(">")); + } else if (CFDataGetTypeID() == CFGetTypeID(value)) { + CFDataRef v_d = (CFDataRef)value; + CFStringRef v_s = CFStringCreateFromExternalRepresentation( + kCFAllocatorDefault, v_d, kCFStringEncodingUTF8); + if (v_s) { + CFStringAppend(line, CFSTR("/")); + CFStringAppend(line, v_s); + CFStringAppend(line, CFSTR("/ ")); + CFRelease(v_s); + } + const uint8_t *bytes = CFDataGetBytePtr(v_d); + CFIndex len = CFDataGetLength(v_d); + for (jx = 0; jx < len; ++jx) { + CFStringAppendFormat(line, NULL, CFSTR("%.02X"), bytes[jx]); + } + } else { + CFStringAppendFormat(line, NULL, CFSTR("%@"), value); + } + print_line(line); + CFRelease(line); + } + CFRelease(keys); +} + +static void showPeerTrust(SecTrustRef peerTrust, bool verbose) { + CFIndex numCerts; + CFIndex i; + + if(peerTrust == NULL) { + return; + } +#if TARGET_OS_EMBEDDED + printf("\n=============== Peer Trust Properties ===============\n"); + CFArrayRef plist = SecTrustCopyProperties(peerTrust); + if (plist) { + print_plist(plist); + CFRelease(plist); + } + + printf("\n================== Peer Trust Info ==================\n"); + CFDictionaryRef info = SecTrustCopyInfo(peerTrust); + if (info && CFDictionaryGetCount(info)) { + showInfo(info); + CFRelease(info); + } + + numCerts = SecTrustGetCertificateCount(peerTrust); + for(i=0; i= 1060 + printf(" Client Auth Type : %s\n", + sslGetClientAuthTypeString(pargs.authType)); +#endif + } + if(pargs.verbose) { + printf(" Resumed Session : "); + if(pargs.sessionWasResumed) { + for(unsigned dex=0; dex maxProtocol) { + /* known not to support this attempt, relax */ + reqProtocol = maxProtocol; + } + if(reqProtocol != negProtocol) { + printf("***Expected protocol %s; negotiated %s\n", + sslGetProtocolVersionString(reqProtocol), + sslGetProtocolVersionString(negProtocol)); + return 1; + } + else { + return 0; + } +} + +static int verifyClientCertState( + bool verifyCertState, + SSLClientCertificateState expectState, + SSLClientCertificateState gotState) +{ + if(!verifyCertState) { + return 0; + } + if(expectState == gotState) { + return 0; + } + printf("***Expected clientCertState %s; got %s\n", + sslGetClientCertStateString(expectState), + sslGetClientCertStateString(gotState)); + return 1; +} + +/* + * Free everything allocated by sslPing in an sslPingArgs. + * Mainly for looping and malloc debugging. + */ +static void freePingArgs( + sslPingArgs *pargs) +{ + CFReleaseNull(pargs->peerCerts); + CFReleaseNull(pargs->peerTrust); + CFReleaseNull(pargs->dnList); + /* more, later, for client retry/identity fetch */ +} + +static SSLProtocol strToProt( + const char *c, // 2, 3, t, tls10, tls11, tls12 + char **argv) +{ + if (c == NULL) + return kSSLProtocolUnknown; + + switch(c[0]) { + case '2': + return kSSLProtocol2; + case '3': + return kSSLProtocol3; + case 't': + if (c[1] == '\0') + return kTLSProtocol1; + if (c[1] == 'l' && c[2] == 's' && c[3] == '1') { + if (c[4] == '0') + return kTLSProtocol1; + if (c[4] == '1') + return kTLSProtocol11; + if (c[4] == '2') + return kTLSProtocol12; + } + default: + usage(argv); + } + /* NOT REACHED */ + return kSSLProtocolUnknown; +} + +int main(int argc, char **argv) +{ + OSStatus err; + int arg; + char *argp; + char getMsg[300]; + char fullFileBase[100]; + int ourRtn = 0; // exit status - sum of all errors + unsigned loop; + SecKeychainRef serverKc = nil; + SecKeychainRef encryptKc = nil; + sslPingArgs pargs; + + /* user-spec'd parameters */ + char *getPath = (char *)DEFAULT_PATH; + char *fileBase = NULL; + bool displayCerts = false; + bool doSslV2 = false; + bool doSslV3 = false; + bool doTlsV1 = true; + bool doTlsV11 = true; + bool doTlsV12 = true; + bool protXOnly = false; // kSSLProtocol3Only, kTLSProtocol1Only + bool doProtUnknown = false; + unsigned loopCount = 1; + bool doPause = false; + bool pauseFirstLoop = false; + bool verifyProt = false; + SSLProtocol maxProtocol = kTLSProtocol12; // for verifying negotiated + // protocol + char *acceptedProts = NULL; + char *keyChainName = NULL; + char *encryptKeyChainName = NULL; + char *getMsgSpec = NULL; + bool vfyCertState = false; + SSLClientCertificateState expectCertState = kSSLClientCertNone; + bool displayHandshakeTimes = false; + bool completeCertChain = false; + char *dnFileBase = NULL; + + /* special case - one arg of "h" or "-h" or "hv" */ + if(argc == 2) { + if((strcmp(argv[1], "h") == 0) || (strcmp(argv[1], "-h") == 0)) { + usage(argv); + } + if(strcmp(argv[1], "hv") == 0) { + usageVerbose(argv); + } + } + + /* set up defaults */ + memset(&pargs, 0, sizeof(sslPingArgs)); + pargs.hostName = DEFAULT_HOST; + pargs.port = DEFAULT_PORT; + pargs.resumableEnable = true; + pargs.argv = argv; + + for(arg=1; arg 1) ? "errors" : "error", pargs.hostName); + } + return ourRtn; + +} + +