diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 960129ed7..3db0917af 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -86,6 +86,8 @@ jobs: - name: test run: | + bin/appletls_pem_test + bin/appletls_test bin/curl -v -X HEAD http://example.com/ bin/curl -v -X HEAD https://example.com/ diff --git a/Makefile b/Makefile index 2b7f50464..e7406e555 100644 --- a/Makefile +++ b/Makefile @@ -377,7 +377,12 @@ hrpc_calc_server: prepare hrpc_calc_protoc -unittest: prepare libhv +APPLE_TLS_UNITTESTS = +ifeq ($(shell uname -s),Darwin) +APPLE_TLS_UNITTESTS += appletls_pem_test appletls_test +endif + +unittest: prepare libhv $(APPLE_TLS_UNITTESTS) $(CC) -g -Wall -O0 -std=c99 -I. -Ibase -o bin/rbtree_test unittest/rbtree_test.c base/rbtree.c $(CC) -g -Wall -O0 -std=c99 -I. -Ibase -o bin/hbase_test unittest/hbase_test.c base/hbase.c $(CC) -g -Wall -O0 -std=c99 -I. -Ibase -o bin/mkdir_p unittest/mkdir_test.c base/hbase.c @@ -423,6 +428,18 @@ ifeq ($(WITH_REDIS), yes) $(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Issl -Ievent -Icpputil -Iredis -Ievpp -o bin/redis_subscriber_test unittest/redis_subscriber_test.cpp unittest/redis_test_server.cpp -Llib -lhv -pthread endif endif + +appletls_pem_test: prepare + $(CC) -g -Wall -Wextra -O0 -std=c99 -DAPPLETLS_PEM_TESTING -I. -Ibase -Issl \ + -o bin/appletls_pem_test unittest/appletls_pem_test.c ssl/appletls_pem.c + +ifeq ($(shell uname -s),Darwin) +appletls_test: prepare + $(CC) -g -Wall -Wextra -O0 -std=c99 -DAPPLETLS_TESTING \ + -I. -Ibase -Issl -Ievent -o bin/appletls_test \ + unittest/appletls_test.c ssl/appletls.c ssl/appletls_pem.c \ + -framework CoreFoundation -framework Security +endif ifeq ($(WITH_LUA), yes) $(CXX) -g -Wall -O0 -std=c++11 -DWITH_LUA $(LUA_CFLAGS) -I. -Ibase -Issl -Ievent -Icpputil -Ievpp -Ilua -o bin/lua_binding_test unittest/lua_binding_test.cpp -Llib -lhv -pthread $(LUA_LIBS) $(CXX) -g -Wall -O0 -std=c++11 -DWITH_LUA $(LUA_CFLAGS) -I. -Ibase -Issl -Ievent -Icpputil -Ievpp -Ilua -o bin/lua_io_test unittest/lua_io_test.cpp -Llib -lhv -pthread $(LUA_LIBS) diff --git a/docs/cn/TcpClient.md b/docs/cn/TcpClient.md index 18a2aa9b5..3a3f75ef4 100644 --- a/docs/cn/TcpClient.md +++ b/docs/cn/TcpClient.md @@ -61,3 +61,36 @@ class TcpClient { ``` 测试代码见 [evpp/TcpClient_test.cpp](../../evpp/TcpClient_test.cpp) + +## TLS 证书验证 + +`withTLS()` 默认只启用 TLS,不验证对端证书。需要验证服务端身份时, +将 `verify_peer` 设为 `1`: + +```cpp +hssl_ctx_opt_t ssl = {}; +ssl.verify_peer = 1; +client.withTLS(&ssl); +``` + +Apple TLS 后端在未设置 `ca_file` 和 `ca_path` 时使用系统信任根;设置了 +任一项时,仅信任指定的自定义 CA,不再使用系统信任根。`ca_file` 可以 +包含多个 PEM `CERTIFICATE` 块,`ca_path` 指向包含 PEM 或 DER CA 证书的 +普通文件目录。通过域名连接时,Apple TLS 后端还会校验证书中的 DNS +主机名。验证时不会在线下载缺失的中间证书,服务端应发送完整证书链。 + +客户端双向 TLS 示例: + +```cpp +hssl_ctx_opt_t ssl = {}; +ssl.crt_file = "cert/client-chain.pem"; +ssl.key_file = "cert/client.key"; +ssl.ca_file = "cert/server-ca.pem"; +ssl.verify_peer = 1; +client.withTLS(&ssl); +``` + +Apple TLS 后端的本地身份目前只支持未加密的 RSA 私钥: +`RSA PRIVATE KEY`(PKCS#1)或 RSA `PRIVATE KEY`(PKCS#8)。不支持 EC +私钥和加密私钥。加载本地身份要求 macOS 10.12 或 iOS 11.2 及以上; +不加载客户端身份时仍可在更早的系统版本上使用客户端 TLS。 diff --git a/docs/cn/TcpServer.md b/docs/cn/TcpServer.md index e0ebdb06a..cb5d1e059 100644 --- a/docs/cn/TcpServer.md +++ b/docs/cn/TcpServer.md @@ -60,3 +60,37 @@ class TcpServer { ``` 测试代码见 [evpp/TcpServer_test.cpp](../../evpp/TcpServer_test.cpp) + +## TLS 证书与双向认证 + +TLS 服务端必须提供证书链和对应私钥: + +```cpp +hssl_ctx_opt_t ssl = {}; +ssl.crt_file = "cert/server-chain.pem"; +ssl.key_file = "cert/server.key"; +server.withTLS(&ssl); +``` + +证书链文件使用 PEM 格式,第一张为服务端证书,后续依次为中间证书。 + +Apple TLS 后端将 `verify_peer` 设为 `1` 时会启用强制双向 TLS:客户端 +必须提供受信任的证书,否则握手失败。 + +```cpp +hssl_ctx_opt_t ssl = {}; +ssl.crt_file = "cert/server-chain.pem"; +ssl.key_file = "cert/server.key"; +ssl.ca_file = "cert/client-ca.pem"; +ssl.verify_peer = 1; +server.withTLS(&ssl); +``` + +Apple TLS 后端在未设置 `ca_file` 和 `ca_path` 时使用系统信任根;设置了 +任一项时,仅信任指定的自定义 CA。`ca_file` 可以包含多张 PEM 证书, +`ca_path` 指向包含 PEM 或 DER CA 证书的普通文件目录。其他 TLS 后端的 +证书验证和 CA 目录格式由相应后端实现决定。 + +Apple TLS 后端的本地身份目前只支持未加密的 RSA 私钥: +`RSA PRIVATE KEY`(PKCS#1)或 RSA `PRIVATE KEY`(PKCS#8)。不支持 EC +私钥和加密私钥,且服务端身份要求 macOS 10.12 或 iOS 11.2 及以上。 diff --git a/event/tls.c b/event/tls.c index 23fba18f8..606a7e1eb 100644 --- a/event/tls.c +++ b/event/tls.c @@ -61,7 +61,11 @@ int tls_client_handshake_start(hio_t* io) { sni = io->hostname; } if (sni) { - hssl_set_sni_hostname(io->ssl, sni); + if (hssl_set_sni_hostname(io->ssl, sni) != HSSL_OK) { + io->error = ERR_SSL_HANDSHAKE; + hio_close(io); + return -1; + } } io->phase = HIO_PHASE_TLS_CLIENT_HANDSHAKING; diff --git a/http/client/HttpClient.cpp b/http/client/HttpClient.cpp index 6bb90146a..d35d96bfd 100644 --- a/http/client/HttpClient.cpp +++ b/http/client/HttpClient.cpp @@ -266,7 +266,11 @@ static int http_client_ssl_handshake(http_client_t* cli, int connfd, const char* return NABS(ERR_NEW_SSL); } if (sni_host && !is_ipaddr(sni_host)) { - hssl_set_sni_hostname(cli->ssl, sni_host); + if (hssl_set_sni_hostname(cli->ssl, sni_host) != HSSL_OK) { + hssl_free(cli->ssl); + cli->ssl = NULL; + return NABS(ERR_SSL_HANDSHAKE); + } } #ifdef WITH_OPENSSL // Offer ALPN "h2" only when HTTP/2 is intended, so an h2-capable server diff --git a/scripts/unittest.sh b/scripts/unittest.sh index 5a4d05668..abcc21a6a 100755 --- a/scripts/unittest.sh +++ b/scripts/unittest.sh @@ -28,6 +28,12 @@ bin/hurl_test # bin/hthread_test # bin/hmutex_test bin/socketpair_test +if [ -x bin/appletls_pem_test ]; then + bin/appletls_pem_test || exit $? +fi +if [ "$(uname -s)" = "Darwin" ] && [ -x bin/appletls_test ]; then + bin/appletls_test || exit $? +fi # bin/threadpool_test # bin/objectpool_test bin/sizeof_test diff --git a/ssl/appletls.c b/ssl/appletls.c index a74fee1df..0f14e5d8f 100644 --- a/ssl/appletls.c +++ b/ssl/appletls.c @@ -3,6 +3,15 @@ #ifdef WITH_APPLETLS /* Disclaimer: excerpted from curl */ +/* + * Secure Transport is deprecated, but Network.framework cannot adopt the + * connected socket owned by libhv's event loop. Keep this compatibility + * warning local to the Apple TLS backend instead of weakening global flags. + */ +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif #include /* For some reason, when building for iOS, the omnibus header above does @@ -11,6 +20,7 @@ #include #include +#include "appletls_pem.h" #include "hsocket.h" /* The Security framework has changed greatly between iOS and different macOS @@ -85,18 +95,6 @@ static const SSLProtocol kTLSProtocol12 = (SSLProtocol)8; static const SSLProtocol kTLSProtocol13 = (SSLProtocol)10; #endif -static inline const char* SSLProtocolToString(SSLProtocol proto) { - switch(proto) { - case kSSLProtocol2: return "SSLv2"; - case kSSLProtocol3: return "SSLv3"; - case kTLSProtocol1: return "TLSv1"; - case kTLSProtocol11: return "TLSv1.1"; - case kTLSProtocol12: return "TLSv1.2"; - case kTLSProtocol13: return "TLSv1.3"; - default: return "Unknown"; - } -} - struct st_cipher { const char *name; /* Cipher suite IANA name. It starts with "TLS_" prefix */ const char *alias_name; /* Alias name is the same as OpenSSL cipher name */ @@ -725,25 +723,6 @@ const static struct st_cipher ciphertable[] = { #define NUM_OF_CIPHERS sizeof(ciphertable)/sizeof(ciphertable[0]) -static const char* SSLCipherSuiteToString(SSLCipherSuite cipher) -{ - /* The first ciphers in the ciphertable are continuos. Here we do small - optimization and instead of loop directly get SSL name by cipher number. - */ - if(cipher <= SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA) { - return ciphertable[cipher].name; - } - /* Iterate through the rest of the ciphers */ - for(size_t i = SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA + 1; - i < NUM_OF_CIPHERS; - ++i) { - if(ciphertable[i].num == cipher) { - return ciphertable[i].name; - } - } - return ciphertable[SSL_NULL_WITH_NULL_NULL].name; -} - static bool is_cipher_suite_strong(SSLCipherSuite suite_num) { for(size_t i = 0; i < NUM_OF_CIPHERS; ++i) { @@ -762,121 +741,338 @@ const char* hssl_backend() { } typedef struct appletls_ctx { - SecIdentityRef cert; - hssl_ctx_opt_t* param; + SecIdentityRef identity; + CFArrayRef certificate_chain; + CFArrayRef trust_anchors; + short verify_peer; } appletls_ctx_t; +static CFDataRef appletls_cfdata(const appletls_der_t* der) { + return CFDataCreate(NULL, der->data, (CFIndex)der->len); +} + +static int appletls_create_identity(appletls_ctx_t* ctx, const char* cert_file, const char* key_file) { + appletls_der_list_t certificates = {0}; + appletls_der_t key_data = {0}; + CFMutableArrayRef chain = NULL; + SecCertificateRef leaf = NULL; + SecKeyRef private_key = NULL; + SecIdentityRef identity = NULL; + CFDataRef data = NULL; + CFDictionaryRef attributes = NULL; + CFErrorRef error = NULL; + int ret; + int result = HSSL_ERROR; + size_t i; + + ret = appletls_pem_load_certificates(cert_file, 0, 64, &certificates); + if (ret != APPLETLS_PEM_OK) { + fprintf(stderr, "Apple TLS certificate file failed: %s\n", appletls_pem_error_string(ret)); + goto cleanup; + } + ret = appletls_pem_load_rsa_private_key(key_file, &key_data); + if (ret != APPLETLS_PEM_OK) { + fprintf(stderr, "Apple TLS RSA private key failed: %s\n", appletls_pem_error_string(ret)); + goto cleanup; + } + +#if (TARGET_OS_MAC && MAC_OS_X_VERSION_MAX_ALLOWED >= 101200) || \ + ((TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110200) + if (__builtin_available(macOS 10.12, iOS 11.2, *)) { + data = appletls_cfdata(&certificates.items[0]); + if (data == NULL) goto cleanup; + leaf = SecCertificateCreateWithData(NULL, data); + CFRelease(data); + data = NULL; + if (leaf == NULL) { + fprintf(stderr, "Apple TLS leaf certificate is invalid\n"); + goto cleanup; + } + + data = appletls_cfdata(&key_data); + if (data == NULL) goto cleanup; + { + const void* keys[] = {kSecAttrKeyType, kSecAttrKeyClass}; + const void* values[] = {kSecAttrKeyTypeRSA, kSecAttrKeyClassPrivate}; + attributes = CFDictionaryCreate(NULL, keys, values, 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + } + if (attributes == NULL) goto cleanup; + private_key = SecKeyCreateWithData(data, attributes, &error); + CFRelease(data); + data = NULL; + if (private_key == NULL) { + fprintf(stderr, "Apple TLS could not create the RSA private key\n"); + goto cleanup; + } + identity = SecIdentityCreate(NULL, leaf, private_key); + if (identity == NULL) { + fprintf(stderr, "Apple TLS certificate and private key do not match\n"); + goto cleanup; + } + } else { + fprintf(stderr, "Apple TLS local identities require macOS 10.12 or iOS 11.2\n"); + goto cleanup; + } +#else + fprintf(stderr, "Apple TLS local identities require an SDK with SecIdentityCreate\n"); + goto cleanup; +#endif + + chain = CFArrayCreateMutable(NULL, (CFIndex)certificates.count, &kCFTypeArrayCallBacks); + if (chain == NULL) goto cleanup; + CFArrayAppendValue(chain, identity); + for (i = 1; i < certificates.count; ++i) { + SecCertificateRef certificate; + data = appletls_cfdata(&certificates.items[i]); + if (data == NULL) goto cleanup; + certificate = SecCertificateCreateWithData(NULL, data); + CFRelease(data); + data = NULL; + if (certificate == NULL) { + fprintf(stderr, "Apple TLS certificate chain contains an invalid certificate\n"); + goto cleanup; + } + CFArrayAppendValue(chain, certificate); + CFRelease(certificate); + } + + ctx->identity = identity; + ctx->certificate_chain = chain; + identity = NULL; + chain = NULL; + result = HSSL_OK; + +cleanup: + if (error) CFRelease(error); + if (attributes) CFRelease(attributes); + if (data) CFRelease(data); + if (identity) CFRelease(identity); + if (private_key) CFRelease(private_key); + if (leaf) CFRelease(leaf); + if (chain) CFRelease(chain); + appletls_der_free(&key_data); + appletls_der_list_free(&certificates); + return result; +} + +static int appletls_append_anchor_file(CFMutableArrayRef anchors, const char* path, + size_t* anchor_count, int strict) { + appletls_der_list_t certificates = {0}; + int ret; + size_t i; + + if (*anchor_count >= APPLETLS_PEM_MAX_CERTIFICATES) return HSSL_ERROR; + ret = appletls_pem_load_certificates(path, 1, + APPLETLS_PEM_MAX_CERTIFICATES - *anchor_count, &certificates); + if (ret != APPLETLS_PEM_OK) { + if (!strict && (ret == APPLETLS_PEM_ERROR_LIMIT || ret == APPLETLS_PEM_ERROR_NOMEM)) { + return HSSL_ERROR; + } + if (strict) { + fprintf(stderr, "Apple TLS CA file failed: %s\n", appletls_pem_error_string(ret)); + } + return strict ? HSSL_ERROR : HSSL_OK; + } + for (i = 0; i < certificates.count; ++i) { + CFDataRef data = appletls_cfdata(&certificates.items[i]); + SecCertificateRef certificate = data ? SecCertificateCreateWithData(NULL, data) : NULL; + if (data) CFRelease(data); + if (certificate == NULL) { + if (strict) fprintf(stderr, "Apple TLS CA file contains an invalid certificate\n"); + appletls_der_list_free(&certificates); + return strict ? HSSL_ERROR : HSSL_OK; + } + CFArrayAppendValue(anchors, certificate); + CFRelease(certificate); + ++*anchor_count; + } + appletls_der_list_free(&certificates); + return HSSL_OK; +} + +static int appletls_load_anchors(appletls_ctx_t* ctx, const char* ca_file, const char* ca_path) { + CFMutableArrayRef anchors = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + size_t anchor_count = 0; + if (anchors == NULL) return HSSL_ERROR; + + if (ca_file && *ca_file && appletls_append_anchor_file(anchors, ca_file, &anchor_count, 1) != HSSL_OK) { + CFRelease(anchors); + return HSSL_ERROR; + } + if (ca_path && *ca_path) { + DIR* dir = opendir(ca_path); + struct dirent* entry; + if (dir == NULL) { + fprintf(stderr, "Apple TLS CA directory could not be opened\n"); + CFRelease(anchors); + return HSSL_ERROR; + } + while ((entry = readdir(dir)) != NULL) { + struct stat st; + char path[PATH_MAX]; + int length; + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; + length = snprintf(path, sizeof(path), "%s/%s", ca_path, entry->d_name); + if (length < 0 || (size_t)length >= sizeof(path)) continue; + if (lstat(path, &st) != 0 || !S_ISREG(st.st_mode)) continue; + if (appletls_append_anchor_file(anchors, path, &anchor_count, 0) != HSSL_OK) { + closedir(dir); + CFRelease(anchors); + return HSSL_ERROR; + } + } + closedir(dir); + } + if (anchor_count == 0) { + fprintf(stderr, "Apple TLS custom CA source contains no valid certificates\n"); + CFRelease(anchors); + return HSSL_ERROR; + } + ctx->trust_anchors = anchors; + return HSSL_OK; +} + hssl_ctx_t hssl_ctx_new(hssl_ctx_opt_t* param) { - appletls_ctx_t* ctx = (appletls_ctx_t*)malloc(sizeof(appletls_ctx_t)); + const char* crt_file = param && param->crt_file && *param->crt_file ? param->crt_file : NULL; + const char* key_file = param && param->key_file && *param->key_file ? param->key_file : NULL; + const char* ca_file = param && param->ca_file && *param->ca_file ? param->ca_file : NULL; + const char* ca_path = param && param->ca_path && *param->ca_path ? param->ca_path : NULL; + appletls_ctx_t* ctx = (appletls_ctx_t*)calloc(1, sizeof(appletls_ctx_t)); if (ctx == NULL) return NULL; - ctx->cert = NULL; - ctx->param = param; + ctx->verify_peer = param ? param->verify_peer : 0; + + if ((crt_file == NULL) != (key_file == NULL)) { + fprintf(stderr, "Apple TLS requires both crt_file and key_file\n"); + goto error; + } + if (crt_file && appletls_create_identity(ctx, crt_file, key_file) != HSSL_OK) goto error; + if (param && param->endpoint == HSSL_SERVER && ctx->identity == NULL) { + fprintf(stderr, "Apple TLS server requires an RSA certificate and private key\n"); + goto error; + } + if ((ca_file || ca_path) && appletls_load_anchors(ctx, ca_file, ca_path) != HSSL_OK) goto error; return ctx; + +error: + hssl_ctx_free(ctx); + return NULL; } void hssl_ctx_free(hssl_ctx_t ssl_ctx) { if (ssl_ctx == NULL) return; appletls_ctx_t* ctx = (appletls_ctx_t*)ssl_ctx; - if (ctx->cert) { - CFRelease(ctx->cert); - ctx->cert = NULL; - } + if (ctx->certificate_chain) CFRelease(ctx->certificate_chain); + if (ctx->trust_anchors) CFRelease(ctx->trust_anchors); + if (ctx->identity) CFRelease(ctx->identity); free(ctx); } typedef struct appletls_s { SSLContextRef session; appletls_ctx_t* ctx; + char* hostname; int fd; + int endpoint; + int blocked_on; + bool peer_auth_handled; } appletls_t; hssl_t hssl_new(hssl_ctx_t ssl_ctx, int fd) { if (ssl_ctx == NULL) return NULL; - appletls_t* appletls = (appletls_t*)malloc(sizeof(appletls_t)); + appletls_t* appletls = (appletls_t*)calloc(1, sizeof(appletls_t)); if (appletls == NULL) return NULL; - appletls->session = NULL; appletls->ctx = (appletls_ctx_t*)ssl_ctx; appletls->fd = fd; + appletls->endpoint = -1; return (hssl_t)appletls; } static OSStatus SocketRead(SSLConnectionRef conn, void* data, size_t* len) { - // printf("SocketRead(%d)\n", (int)*len); appletls_t* appletls = (appletls_t*)conn; uint8_t* buffer = (uint8_t*)data; size_t remain = *len; *len = 0; int fd = appletls->fd; - // int timeout = 1000; - // struct timeval tv = { timeout / 1000, (timeout % 1000) * 1000 }; - // fd_set readfds; while (remain) { - /* - FD_ZERO(&readfds); - FD_SET(fd, &readfds); - int nselect = select(fd + 1, &readfds, 0, 0, &tv); - printf("nselect=%d\n", nselect); - if (nselect < 0) { - return errSSLClosedAbort; - } - if (nselect == 0) { - return errSSLWouldBlock; - } - */ - // printf("read(%d)\n", (int)remain); - // NOTE: avoid blocking - if (remain < 16) { - so_rcvtimeo(fd, 1000); - } ssize_t nread = read(fd, buffer, remain); - // printf("nread=%d errno=%d\n", (int)nread, (int)errno); if (nread == 0) return errSSLClosedGraceful; if (nread < 0) { switch (errno) { case ENOENT: return errSSLClosedGraceful; case ECONNRESET:return errSSLClosedAbort; - case EAGAIN: return errSSLWouldBlock; + case EINTR: + continue; + case EAGAIN: +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif + appletls->blocked_on = HSSL_WANT_READ; + return errSSLWouldBlock; default: return errSSLClosedAbort; } } - *len += nread; - remain -= nread; + *len += (size_t)nread; + remain -= (size_t)nread; buffer += nread; } return noErr; } static OSStatus SocketWrite(SSLConnectionRef conn, const void* data, size_t* len) { - // printf("SocketWrite(%d)\n", (int)*len); appletls_t* appletls = (appletls_t*)conn; uint8_t* buffer = (uint8_t*)data; size_t remain = *len; *len = 0; int fd = appletls->fd; while (remain) { - if (remain < 16) { - so_sndtimeo(fd, 1000); - } - // printf("write(%d)\n", (int)remain); ssize_t nwrite = write(fd, buffer, remain); - // printf("nwrite=%d errno=%d\n", (int)nwrite, (int)errno); if (nwrite <= 0) { switch (errno) { - case EAGAIN: return errSSLWouldBlock; + case EINTR: + continue; + case EAGAIN: +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif + appletls->blocked_on = HSSL_WANT_WRITE; + return errSSLWouldBlock; default: return errSSLClosedAbort; } } - remain -= nwrite; + remain -= (size_t)nwrite; buffer += nwrite; - *len += nwrite; + *len += (size_t)nwrite; } return noErr; } +static void appletls_free_session(appletls_t* appletls) { + if (appletls->session == NULL) return; +#if defined(__MAC_10_8) + CFRelease(appletls->session); +#else + SSLDisposeContext(appletls->session); +#endif + appletls->session = NULL; + appletls->endpoint = -1; + appletls->peer_auth_handled = false; +} + static int hssl_init(hssl_t ssl, int endpoint) { if (ssl == NULL) return HSSL_ERROR; appletls_t* appletls = (appletls_t*)ssl; OSStatus ret = noErr; + size_t all_ciphers_count = 0; + size_t allowed_ciphers_count = 0; + SSLCipherSuite* all_ciphers = NULL; + SSLCipherSuite* allowed_ciphers = NULL; + if (appletls->endpoint != -1 && appletls->endpoint != endpoint) return HSSL_ERROR; + if (endpoint == HSSL_SERVER && appletls->ctx->identity == NULL) { + fprintf(stderr, "Apple TLS server requires a local identity\n"); + return HSSL_ERROR; + } if (appletls->session == NULL) { #if defined(__MAC_10_8) appletls->session = SSLCreateContext(NULL, endpoint == HSSL_SERVER ? kSSLServerSide : kSSLClientSide, kSSLStreamType); @@ -893,39 +1089,34 @@ static int hssl_init(hssl_t ssl, int endpoint) { ret = SSLSetProtocolVersionMin(appletls->session, kTLSProtocol12); if (ret != noErr) { fprintf(stderr, "SSLSetProtocolVersionMin failed!\n"); - return HSSL_ERROR; + goto error; } - bool verify_peer = false; - if (appletls->ctx->param && appletls->ctx->param->verify_peer) { - verify_peer = true; - } #if defined(__MAC_10_8) - ret = SSLSetSessionOption(appletls->session, kSSLSessionOptionBreakOnServerAuth, !verify_peer); + if (endpoint == HSSL_CLIENT) { + ret = SSLSetSessionOption(appletls->session, kSSLSessionOptionBreakOnServerAuth, true); + } else if (appletls->ctx->verify_peer) { + ret = SSLSetClientSideAuthenticate(appletls->session, kAlwaysAuthenticate); + if (ret == noErr) { + ret = SSLSetSessionOption(appletls->session, kSSLSessionOptionBreakOnClientAuth, true); + } + } #else - ret = SSLSetEnableCertVerify(appletls->session, verify_peer); + ret = SSLSetEnableCertVerify(appletls->session, appletls->ctx->verify_peer); #endif if (ret != noErr) { - fprintf(stderr, "SSLSetEnableCertVerify failed!\n"); - return HSSL_ERROR; + fprintf(stderr, "Apple TLS authentication setup failed\n"); + goto error; } - if (appletls->ctx->cert) { - CFArrayRef certs = CFArrayCreate(NULL, (const void**)&appletls->ctx->cert, 1, NULL); - if (!certs) { - fprintf(stderr, "CFArrayCreate failed!\n"); - return HSSL_ERROR; - } - ret = SSLSetCertificate(appletls->session, certs); - CFRelease(certs); + if (appletls->ctx->certificate_chain) { + ret = SSLSetCertificate(appletls->session, appletls->ctx->certificate_chain); if (ret != noErr) { fprintf(stderr, "SSLSetCertificate failed!\n"); - return HSSL_ERROR; + goto error; } } - size_t all_ciphers_count = 0, allowed_ciphers_count = 0; - SSLCipherSuite *all_ciphers = NULL, *allowed_ciphers = NULL; ret = SSLGetNumberSupportedCiphers(appletls->session, &all_ciphers_count); if (ret != noErr) { fprintf(stderr, "SSLGetNumberSupportedCiphers failed!\n"); @@ -964,12 +1155,12 @@ static int hssl_init(hssl_t ssl, int endpoint) { ret = SSLSetIOFuncs(appletls->session, SocketRead, SocketWrite); if (ret != noErr) { fprintf(stderr, "SSLSetIOFuncs failed!\n"); - return HSSL_ERROR; + goto error; } ret = SSLSetConnection(appletls->session, appletls); if (ret != noErr) { fprintf(stderr, "SSLSetConnection failed!\n"); - return HSSL_ERROR; + goto error; } /* @@ -982,6 +1173,7 @@ static int hssl_init(hssl_t ssl, int endpoint) { } */ + appletls->endpoint = endpoint; return HSSL_OK; error: if (all_ciphers) { @@ -990,57 +1182,121 @@ static int hssl_init(hssl_t ssl, int endpoint) { if (allowed_ciphers) { free(allowed_ciphers); } + appletls_free_session(appletls); return HSSL_ERROR; } void hssl_free(hssl_t ssl) { if (ssl == NULL) return; appletls_t* appletls = (appletls_t*)ssl; - if (appletls->session) { -#if defined(__MAC_10_8) - CFRelease(appletls->session); -#else - SSLDisposeContext(appletls->session); + appletls_free_session(appletls); + free(appletls->hostname); + free(appletls); +} + +static int appletls_evaluate_trust(SecTrustRef trust) { +#ifdef APPLETLS_TESTING + /* Keep checked-in TLS fixtures deterministic instead of making tests + * depend on their wall-clock validity at runtime. */ + CFDateRef verify_date = CFDateCreate(NULL, 812592000.0); /* 2026-10-02 UTC */ + if (verify_date == NULL || SecTrustSetVerifyDate(trust, verify_date) != errSecSuccess) { + if (verify_date) CFRelease(verify_date); + return HSSL_ERROR; + } + CFRelease(verify_date); #endif - appletls->session = NULL; +#if (TARGET_OS_MAC && MAC_OS_X_VERSION_MAX_ALLOWED >= 101400) || \ + ((TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000) + if (__builtin_available(macOS 10.14, iOS 12.0, *)) { + CFErrorRef error = NULL; + bool trusted = SecTrustEvaluateWithError(trust, &error); + if (error) CFRelease(error); + return trusted ? HSSL_OK : HSSL_ERROR; + } +#endif + { + SecTrustResultType result = kSecTrustResultInvalid; + OSStatus status = SecTrustEvaluate(trust, &result); + if (status != errSecSuccess) return HSSL_ERROR; + return result == kSecTrustResultProceed || result == kSecTrustResultUnspecified + ? HSSL_OK : HSSL_ERROR; } - free(appletls); +} + +static int appletls_verify_peer(appletls_t* appletls, int peer_is_server) { + SecTrustRef trust = NULL; + SecPolicyRef policy = NULL; + CFStringRef hostname = NULL; + int result = HSSL_ERROR; + + if (SSLCopyPeerTrust(appletls->session, &trust) != errSecSuccess || trust == NULL) { + fprintf(stderr, "Apple TLS peer did not provide a certificate\n"); + goto cleanup; + } + if (peer_is_server && appletls->hostname) { + hostname = CFStringCreateWithCString(NULL, appletls->hostname, kCFStringEncodingUTF8); + if (hostname == NULL) goto cleanup; + } + policy = SecPolicyCreateSSL((Boolean)peer_is_server, hostname); + if (policy == NULL || SecTrustSetPolicies(trust, policy) != errSecSuccess) goto cleanup; +#if (TARGET_OS_MAC && MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) || \ + ((TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) + if (__builtin_available(macOS 10.9, iOS 7.0, *)) { + if (SecTrustSetNetworkFetchAllowed(trust, false) != errSecSuccess) goto cleanup; + } +#endif + if (appletls->ctx->trust_anchors) { + if (SecTrustSetAnchorCertificates(trust, appletls->ctx->trust_anchors) != errSecSuccess || + SecTrustSetAnchorCertificatesOnly(trust, true) != errSecSuccess) { + goto cleanup; + } + } + result = appletls_evaluate_trust(trust); + if (result != HSSL_OK) fprintf(stderr, "Apple TLS peer certificate verification failed\n"); + +cleanup: + if (policy) CFRelease(policy); + if (hostname) CFRelease(hostname); + if (trust) CFRelease(trust); + return result; } static int hssl_handshake(hssl_t ssl) { if (ssl == NULL) return HSSL_ERROR; appletls_t* appletls = (appletls_t*)ssl; - OSStatus ret = SSLHandshake(appletls->session); - // printf("SSLHandshake retval=%d\n", (int)ret); - switch(ret) { - case noErr: - break; - case errSSLWouldBlock: - return HSSL_WANT_READ; - case errSSLPeerAuthCompleted: /* peer cert is valid, or was ignored if verification disabled */ - return hssl_handshake(ssl); - case errSSLBadConfiguration: - return HSSL_WANT_READ; - default: - return HSSL_ERROR; + int continuation; + for (continuation = 0; continuation < 3; ++continuation) { + OSStatus ret; + appletls->blocked_on = HSSL_WANT_READ; + ret = SSLHandshake(appletls->session); + switch(ret) { + case noErr: + return HSSL_OK; + case errSSLWouldBlock: + return appletls->blocked_on; + case errSSLPeerAuthCompleted: + if (appletls->peer_auth_handled) return HSSL_ERROR; + if (appletls->ctx->verify_peer && + appletls_verify_peer(appletls, appletls->endpoint == HSSL_CLIENT) != HSSL_OK) { + return HSSL_ERROR; + } + appletls->peer_auth_handled = true; + break; + case errSSLClientCertRequested: + if (appletls->ctx->identity == NULL) return HSSL_ERROR; + break; + default: + return HSSL_ERROR; + } } - - /* - SSLProtocol protocol = kSSLProtocolUnknown; - SSLGetNegotiatedProtocolVersion(appletls->session, &protocol); - SSLCipherSuite cipher = SSL_NO_SUCH_CIPHERSUITE; - SSLGetNegotiatedCipher(appletls->session, &cipher); - printf("* %s connection using %s\n", SSLProtocolToString(protocol), SSLCipherSuiteToString(cipher)); - */ - - return HSSL_OK; + return HSSL_ERROR; } int hssl_accept(hssl_t ssl) { if (ssl == NULL) return HSSL_ERROR; appletls_t* appletls = (appletls_t*)ssl; if (appletls->session == NULL) { - hssl_init(ssl, HSSL_SERVER); + if (hssl_init(ssl, HSSL_SERVER) != HSSL_OK) return HSSL_ERROR; } return hssl_handshake(ssl); } @@ -1049,47 +1305,55 @@ int hssl_connect(hssl_t ssl) { if (ssl == NULL) return HSSL_ERROR; appletls_t* appletls = (appletls_t*)ssl; if (appletls->session == NULL) { - hssl_init(ssl, HSSL_CLIENT); + if (hssl_init(ssl, HSSL_CLIENT) != HSSL_OK) return HSSL_ERROR; } return hssl_handshake(ssl); } int hssl_read(hssl_t ssl, void* buf, int len) { - if (ssl == NULL) return HSSL_ERROR; + if (ssl == NULL || buf == NULL || len <= 0) return HSSL_ERROR; appletls_t* appletls = (appletls_t*)ssl; + if (appletls->session == NULL) return HSSL_ERROR; size_t processed = 0; // printf("SSLRead(%d)\n", len); - OSStatus ret = SSLRead(appletls->session, buf, len, &processed); + OSStatus ret = SSLRead(appletls->session, buf, (size_t)len, &processed); // printf("SSLRead retval=%d processed=%d\n", (int)ret, (int)processed); switch (ret) { case noErr: - return processed; + return (int)processed; case errSSLWouldBlock: - return processed ? processed : HSSL_WOULD_BLOCK; + if (processed) return (int)processed; + errno = EAGAIN; + return HSSL_WOULD_BLOCK; case errSSLClosedGraceful: case errSSLClosedNoNotify: return 0; default: + errno = EIO; return HSSL_ERROR; } } int hssl_write(hssl_t ssl, const void* buf, int len) { - if (ssl == NULL) return HSSL_ERROR; + if (ssl == NULL || buf == NULL || len <= 0) return HSSL_ERROR; appletls_t* appletls = (appletls_t*)ssl; + if (appletls->session == NULL) return HSSL_ERROR; size_t processed = 0; // printf("SSLWrite(%d)\n", len); - OSStatus ret = SSLWrite(appletls->session, buf, len, &processed); + OSStatus ret = SSLWrite(appletls->session, buf, (size_t)len, &processed); // printf("SSLWrite retval=%d processed=%d\n", (int)ret, (int)processed); switch (ret) { case noErr: - return processed; + return (int)processed; case errSSLWouldBlock: - return processed ? processed : HSSL_WOULD_BLOCK; + if (processed) return (int)processed; + errno = EAGAIN; + return HSSL_WOULD_BLOCK; case errSSLClosedGraceful: case errSSLClosedNoNotify: return 0; default: + errno = EIO; return HSSL_ERROR; } } @@ -1097,18 +1361,35 @@ int hssl_write(hssl_t ssl, const void* buf, int len) { int hssl_close(hssl_t ssl) { if (ssl == NULL) return HSSL_ERROR; appletls_t* appletls = (appletls_t*)ssl; - SSLClose(appletls->session); + if (appletls->session) SSLClose(appletls->session); return 0; } int hssl_set_sni_hostname(hssl_t ssl, const char* hostname) { - if (ssl == NULL) return HSSL_ERROR; + size_t len; + char* copy; + OSStatus status; + if (ssl == NULL || hostname == NULL || *hostname == '\0') return HSSL_ERROR; appletls_t* appletls = (appletls_t*)ssl; if (appletls->session == NULL) { - hssl_init(ssl, HSSL_CLIENT); + if (hssl_init(ssl, HSSL_CLIENT) != HSSL_OK) return HSSL_ERROR; } - SSLSetPeerDomainName(appletls->session, hostname, strlen(hostname)); - return 0; + len = strlen(hostname); + copy = (char*)malloc(len + 1); + if (copy == NULL) return HSSL_ERROR; + memcpy(copy, hostname, len + 1); + status = SSLSetPeerDomainName(appletls->session, hostname, len); + if (status != noErr) { + free(copy); + return HSSL_ERROR; + } + free(appletls->hostname); + appletls->hostname = copy; + return HSSL_OK; } +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + #endif // WITH_APPLETLS diff --git a/ssl/appletls_pem.c b/ssl/appletls_pem.c new file mode 100644 index 000000000..e3210a63e --- /dev/null +++ b/ssl/appletls_pem.c @@ -0,0 +1,445 @@ +#include "appletls_pem.h" +#include "hssl.h" + +#if defined(WITH_APPLETLS) || defined(APPLETLS_PEM_TESTING) + +#include +#include +#include +#include +#include +#include + +typedef struct { + const unsigned char* ptr; + const unsigned char* end; +} der_cursor_t; + +static const unsigned char rsa_encryption_oid[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01}; + +static int read_file(const char* path, unsigned char** data, size_t* len) { + FILE* fp = NULL; + long file_len; + unsigned char* buffer = NULL; + + if (path == NULL || data == NULL || len == NULL) return APPLETLS_PEM_ERROR_FORMAT; + *data = NULL; + *len = 0; + + fp = fopen(path, "rb"); + if (fp == NULL) return APPLETLS_PEM_ERROR_IO; + if (fseek(fp, 0, SEEK_END) != 0) goto io_error; + file_len = ftell(fp); + if (file_len < 0) goto io_error; + if ((unsigned long)file_len > APPLETLS_PEM_MAX_FILE_SIZE) { + fclose(fp); + return APPLETLS_PEM_ERROR_LIMIT; + } + if (file_len == 0 || fseek(fp, 0, SEEK_SET) != 0) goto io_error; + + buffer = (unsigned char*)malloc((size_t)file_len + 1u); + if (buffer == NULL) { + fclose(fp); + return APPLETLS_PEM_ERROR_NOMEM; + } + if (fread(buffer, 1, (size_t)file_len, fp) != (size_t)file_len) { + free(buffer); + goto io_error; + } + buffer[file_len] = '\0'; + fclose(fp); + *data = buffer; + *len = (size_t)file_len; + return APPLETLS_PEM_OK; + +io_error: + fclose(fp); + return APPLETLS_PEM_ERROR_IO; +} + +static int is_space_only(const unsigned char* begin, const unsigned char* end) { + while (begin < end) { + if (!isspace(*begin)) return 0; + ++begin; + } + return 1; +} + +static int base64_value(unsigned char ch) { + if (ch >= 'A' && ch <= 'Z') return ch - 'A'; + if (ch >= 'a' && ch <= 'z') return ch - 'a' + 26; + if (ch >= '0' && ch <= '9') return ch - '0' + 52; + if (ch == '+') return 62; + if (ch == '/') return 63; + return -1; +} + +static int decode_base64(const unsigned char* begin, const unsigned char* end, appletls_der_t* der) { + unsigned char quartet[4]; + size_t quartet_len = 0; + size_t useful = 0; + size_t out_len = 0; + int padded = 0; + unsigned char* out; + const unsigned char* ptr; + + for (ptr = begin; ptr < end; ++ptr) { + if (isspace(*ptr)) continue; + if (*ptr != '=' && base64_value(*ptr) < 0) return APPLETLS_PEM_ERROR_FORMAT; + ++useful; + } + if (useful == 0 || useful % 4 != 0) return APPLETLS_PEM_ERROR_FORMAT; + if (useful / 4 > (APPLETLS_PEM_MAX_BLOCK_SIZE + 2u) / 3u) return APPLETLS_PEM_ERROR_LIMIT; + + out = (unsigned char*)malloc((useful / 4) * 3); + if (out == NULL) return APPLETLS_PEM_ERROR_NOMEM; + + for (ptr = begin; ptr < end; ++ptr) { + int a, b, c, d; + if (isspace(*ptr)) continue; + if (padded) { + free(out); + return APPLETLS_PEM_ERROR_FORMAT; + } + quartet[quartet_len++] = *ptr; + if (quartet_len != 4) continue; + + a = base64_value(quartet[0]); + b = base64_value(quartet[1]); + c = quartet[2] == '=' ? -2 : base64_value(quartet[2]); + d = quartet[3] == '=' ? -2 : base64_value(quartet[3]); + if (a < 0 || b < 0 || c == -1 || d == -1 || (c == -2 && d != -2)) { + free(out); + return APPLETLS_PEM_ERROR_FORMAT; + } + out[out_len++] = (unsigned char)((a << 2) | (b >> 4)); + if (c == -2) { + if ((b & 0x0f) != 0) goto invalid_padding; + padded = 1; + } + else { + out[out_len++] = (unsigned char)((b << 4) | (c >> 2)); + if (d == -2) { + if ((c & 0x03) != 0) goto invalid_padding; + padded = 1; + } + else { + out[out_len++] = (unsigned char)((c << 6) | d); + } + } + quartet_len = 0; + } + if (quartet_len != 0 || out_len == 0 || out_len > APPLETLS_PEM_MAX_BLOCK_SIZE) { + free(out); + return out_len > APPLETLS_PEM_MAX_BLOCK_SIZE ? APPLETLS_PEM_ERROR_LIMIT : APPLETLS_PEM_ERROR_FORMAT; + } + der->data = out; + der->len = out_len; + return APPLETLS_PEM_OK; + +invalid_padding: + free(out); + return APPLETLS_PEM_ERROR_FORMAT; +} + +static int der_read_length(der_cursor_t* cursor, size_t* len) { + unsigned char first; + size_t value = 0; + size_t bytes; + size_t i; + + if (cursor->ptr >= cursor->end) return APPLETLS_PEM_ERROR_FORMAT; + first = *cursor->ptr++; + if ((first & 0x80) == 0) { + *len = first; + return (size_t)(cursor->end - cursor->ptr) >= *len ? APPLETLS_PEM_OK : APPLETLS_PEM_ERROR_FORMAT; + } + bytes = first & 0x7f; + if (bytes == 0 || bytes > sizeof(size_t) || (size_t)(cursor->end - cursor->ptr) < bytes) { + return APPLETLS_PEM_ERROR_FORMAT; + } + if (*cursor->ptr == 0) return APPLETLS_PEM_ERROR_FORMAT; + for (i = 0; i < bytes; ++i) { + if (value > (SIZE_MAX >> 8)) return APPLETLS_PEM_ERROR_LIMIT; + value = (value << 8) | *cursor->ptr++; + } + if (value < 128 || (size_t)(cursor->end - cursor->ptr) < value) return APPLETLS_PEM_ERROR_FORMAT; + *len = value; + return APPLETLS_PEM_OK; +} + +static int der_read_tlv(der_cursor_t* cursor, unsigned char expected_tag, der_cursor_t* value) { + size_t len; + int ret; + if (cursor->ptr >= cursor->end || *cursor->ptr++ != expected_tag) return APPLETLS_PEM_ERROR_FORMAT; + ret = der_read_length(cursor, &len); + if (ret != APPLETLS_PEM_OK) return ret; + value->ptr = cursor->ptr; + value->end = cursor->ptr + len; + cursor->ptr += len; + return APPLETLS_PEM_OK; +} + +static int der_integer_is_minimal(const der_cursor_t* integer) { + size_t len = (size_t)(integer->end - integer->ptr); + if (len == 0 || (integer->ptr[0] & 0x80) != 0) return 0; + if (len > 1 && integer->ptr[0] == 0 && (integer->ptr[1] & 0x80) == 0) return 0; + return 1; +} + +static int validate_pkcs1_rsa(const unsigned char* data, size_t len) { + der_cursor_t outer = {data, data + len}; + der_cursor_t sequence; + der_cursor_t integer; + size_t i; + int ret = der_read_tlv(&outer, 0x30, &sequence); + if (ret != APPLETLS_PEM_OK || outer.ptr != outer.end) return APPLETLS_PEM_ERROR_FORMAT; + ret = der_read_tlv(&sequence, 0x02, &integer); + if (ret != APPLETLS_PEM_OK || !der_integer_is_minimal(&integer) || integer.end - integer.ptr != 1 || integer.ptr[0] != 0) { + return APPLETLS_PEM_ERROR_FORMAT; + } + for (i = 0; i < 8; ++i) { + ret = der_read_tlv(&sequence, 0x02, &integer); + if (ret != APPLETLS_PEM_OK || !der_integer_is_minimal(&integer)) return APPLETLS_PEM_ERROR_FORMAT; + } + return sequence.ptr == sequence.end ? APPLETLS_PEM_OK : APPLETLS_PEM_ERROR_FORMAT; +} + +static int copy_der(const unsigned char* data, size_t len, appletls_der_t* out) { + unsigned char* copy; + if (len == 0 || len > APPLETLS_PEM_MAX_BLOCK_SIZE) return APPLETLS_PEM_ERROR_LIMIT; + copy = (unsigned char*)malloc(len); + if (copy == NULL) return APPLETLS_PEM_ERROR_NOMEM; + memcpy(copy, data, len); + out->data = copy; + out->len = len; + return APPLETLS_PEM_OK; +} + +static int unwrap_pkcs8_rsa(const unsigned char* data, size_t len, appletls_der_t* pkcs1_key) { + der_cursor_t outer = {data, data + len}; + der_cursor_t sequence; + der_cursor_t version; + der_cursor_t algorithm; + der_cursor_t oid; + der_cursor_t null_value; + der_cursor_t private_key; + int ret = der_read_tlv(&outer, 0x30, &sequence); + if (ret != APPLETLS_PEM_OK || outer.ptr != outer.end) return APPLETLS_PEM_ERROR_FORMAT; + ret = der_read_tlv(&sequence, 0x02, &version); + if (ret != APPLETLS_PEM_OK || version.end - version.ptr != 1 || version.ptr[0] != 0) return APPLETLS_PEM_ERROR_FORMAT; + ret = der_read_tlv(&sequence, 0x30, &algorithm); + if (ret != APPLETLS_PEM_OK) return ret; + ret = der_read_tlv(&algorithm, 0x06, &oid); + if (ret != APPLETLS_PEM_OK) return ret; + if ((size_t)(oid.end - oid.ptr) != sizeof(rsa_encryption_oid) || memcmp(oid.ptr, rsa_encryption_oid, sizeof(rsa_encryption_oid)) != 0) { + return APPLETLS_PEM_ERROR_UNSUPPORTED; + } + if (algorithm.ptr != algorithm.end) { + ret = der_read_tlv(&algorithm, 0x05, &null_value); + if (ret != APPLETLS_PEM_OK || null_value.ptr != null_value.end || algorithm.ptr != algorithm.end) { + return APPLETLS_PEM_ERROR_FORMAT; + } + } + ret = der_read_tlv(&sequence, 0x04, &private_key); + if (ret != APPLETLS_PEM_OK || sequence.ptr != sequence.end) return APPLETLS_PEM_ERROR_FORMAT; + ret = validate_pkcs1_rsa(private_key.ptr, (size_t)(private_key.end - private_key.ptr)); + if (ret != APPLETLS_PEM_OK) return ret; + return copy_der(private_key.ptr, (size_t)(private_key.end - private_key.ptr), pkcs1_key); +} + +static const unsigned char* find_bytes(const unsigned char* begin, const unsigned char* end, const char* needle) { + size_t needle_len = strlen(needle); + const unsigned char* ptr; + if ((size_t)(end - begin) < needle_len) return NULL; + for (ptr = begin; (size_t)(end - ptr) >= needle_len; ++ptr) { + if (memcmp(ptr, needle, needle_len) == 0) return ptr; + } + return NULL; +} + +static int append_der(appletls_der_list_t* list, size_t max_count, appletls_der_t* der) { + appletls_der_t* items; + size_t new_count; + if (list->count >= max_count || list->count >= APPLETLS_PEM_MAX_CERTIFICATES) return APPLETLS_PEM_ERROR_LIMIT; + new_count = list->count + 1; + if (new_count > SIZE_MAX / sizeof(*items)) return APPLETLS_PEM_ERROR_LIMIT; + items = (appletls_der_t*)realloc(list->items, new_count * sizeof(*items)); + if (items == NULL) return APPLETLS_PEM_ERROR_NOMEM; + list->items = items; + list->items[list->count] = *der; + list->count = new_count; + der->data = NULL; + der->len = 0; + return APPLETLS_PEM_OK; +} + +static int validate_der_object(const unsigned char* data, size_t len) { + der_cursor_t outer = {data, data + len}; + der_cursor_t sequence; + int ret = der_read_tlv(&outer, 0x30, &sequence); + if (ret != APPLETLS_PEM_OK || outer.ptr != outer.end || sequence.ptr == sequence.end) return APPLETLS_PEM_ERROR_FORMAT; + return APPLETLS_PEM_OK; +} + +int appletls_pem_load_certificates(const char* path, int allow_der, size_t max_certificates, appletls_der_list_t* certificates) { + static const char begin_marker[] = "-----BEGIN CERTIFICATE-----"; + static const char end_marker[] = "-----END CERTIFICATE-----"; + unsigned char* file = NULL; + size_t file_len = 0; + const unsigned char* ptr; + const unsigned char* end; + int ret; + + if (certificates == NULL || max_certificates == 0) return APPLETLS_PEM_ERROR_FORMAT; + certificates->items = NULL; + certificates->count = 0; + ret = read_file(path, &file, &file_len); + if (ret != APPLETLS_PEM_OK) return ret; + ptr = file; + end = file + file_len; + + if (find_bytes(ptr, end, begin_marker) == NULL) { + appletls_der_t der = {0}; + if (!allow_der) { + ret = APPLETLS_PEM_ERROR_FORMAT; + goto done; + } + ret = validate_der_object(file, file_len); + if (ret == APPLETLS_PEM_OK) ret = copy_der(file, file_len, &der); + if (ret == APPLETLS_PEM_OK) ret = append_der(certificates, max_certificates, &der); + appletls_der_free(&der); + goto done; + } + + while (ptr < end) { + const unsigned char* block_begin; + const unsigned char* block_end; + appletls_der_t der = {0}; + block_begin = find_bytes(ptr, end, begin_marker); + if (block_begin == NULL) { + ret = is_space_only(ptr, end) ? APPLETLS_PEM_OK : APPLETLS_PEM_ERROR_FORMAT; + break; + } + if (!is_space_only(ptr, block_begin)) { + ret = APPLETLS_PEM_ERROR_FORMAT; + break; + } + block_begin += strlen(begin_marker); + block_end = find_bytes(block_begin, end, end_marker); + if (block_end == NULL) { + ret = APPLETLS_PEM_ERROR_FORMAT; + break; + } + ret = decode_base64(block_begin, block_end, &der); + if (ret == APPLETLS_PEM_OK) ret = validate_der_object(der.data, der.len); + if (ret == APPLETLS_PEM_OK) ret = append_der(certificates, max_certificates, &der); + appletls_der_free(&der); + if (ret != APPLETLS_PEM_OK) break; + ptr = block_end + strlen(end_marker); + } + if (ret == APPLETLS_PEM_OK && certificates->count == 0) ret = APPLETLS_PEM_ERROR_FORMAT; + +done: + free(file); + if (ret != APPLETLS_PEM_OK) appletls_der_list_free(certificates); + return ret; +} + +int appletls_pem_load_rsa_private_key(const char* path, appletls_der_t* pkcs1_key) { + static const char rsa_begin[] = "-----BEGIN RSA PRIVATE KEY-----"; + static const char rsa_end[] = "-----END RSA PRIVATE KEY-----"; + static const char pkcs8_begin[] = "-----BEGIN PRIVATE KEY-----"; + static const char pkcs8_end[] = "-----END PRIVATE KEY-----"; + static const char encrypted_begin[] = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; + static const char ec_begin[] = "-----BEGIN EC PRIVATE KEY-----"; + unsigned char* file = NULL; + size_t file_len = 0; + const unsigned char* begin; + const unsigned char* end; + const unsigned char* body_end; + const char* end_marker; + int pkcs8 = 0; + int ret; + appletls_der_t decoded = {0}; + + if (pkcs1_key == NULL) return APPLETLS_PEM_ERROR_FORMAT; + pkcs1_key->data = NULL; + pkcs1_key->len = 0; + ret = read_file(path, &file, &file_len); + if (ret != APPLETLS_PEM_OK) return ret; + end = file + file_len; + if (find_bytes(file, end, encrypted_begin) || find_bytes(file, end, ec_begin) || find_bytes(file, end, "Proc-Type: 4,ENCRYPTED")) { + ret = APPLETLS_PEM_ERROR_UNSUPPORTED; + goto done; + } + begin = find_bytes(file, end, rsa_begin); + end_marker = rsa_end; + if (begin == NULL) { + begin = find_bytes(file, end, pkcs8_begin); + end_marker = pkcs8_end; + pkcs8 = 1; + } + if (begin == NULL || !is_space_only(file, begin)) { + ret = APPLETLS_PEM_ERROR_FORMAT; + goto done; + } + begin += strlen(pkcs8 ? pkcs8_begin : rsa_begin); + body_end = find_bytes(begin, end, end_marker); + if (body_end == NULL) { + ret = APPLETLS_PEM_ERROR_FORMAT; + goto done; + } + if (!is_space_only(body_end + strlen(end_marker), end)) { + ret = APPLETLS_PEM_ERROR_FORMAT; + goto done; + } + ret = decode_base64(begin, body_end, &decoded); + if (ret != APPLETLS_PEM_OK) goto done; + if (pkcs8) { + ret = unwrap_pkcs8_rsa(decoded.data, decoded.len, pkcs1_key); + } + else { + ret = validate_pkcs1_rsa(decoded.data, decoded.len); + if (ret == APPLETLS_PEM_OK) { + *pkcs1_key = decoded; + decoded.data = NULL; + decoded.len = 0; + } + } + +done: + appletls_der_free(&decoded); + free(file); + return ret; +} + +void appletls_der_free(appletls_der_t* der) { + if (der == NULL) return; + free(der->data); + der->data = NULL; + der->len = 0; +} + +void appletls_der_list_free(appletls_der_list_t* list) { + size_t i; + if (list == NULL) return; + for (i = 0; i < list->count; ++i) appletls_der_free(&list->items[i]); + free(list->items); + list->items = NULL; + list->count = 0; +} + +const char* appletls_pem_error_string(int error) { + switch (error) { + case APPLETLS_PEM_OK: return "success"; + case APPLETLS_PEM_ERROR_IO: return "file I/O failed"; + case APPLETLS_PEM_ERROR_FORMAT: return "invalid PEM or DER format"; + case APPLETLS_PEM_ERROR_UNSUPPORTED: return "unsupported key format"; + case APPLETLS_PEM_ERROR_LIMIT: return "input limit exceeded"; + case APPLETLS_PEM_ERROR_NOMEM: return "out of memory"; + default: return "unknown parser error"; + } +} + +#endif diff --git a/ssl/appletls_pem.h b/ssl/appletls_pem.h new file mode 100644 index 000000000..f36bd70b4 --- /dev/null +++ b/ssl/appletls_pem.h @@ -0,0 +1,43 @@ +#ifndef HV_APPLETLS_PEM_H_ +#define HV_APPLETLS_PEM_H_ + +#include + +#if defined(__GNUC__) +#define APPLETLS_PRIVATE __attribute__((visibility("hidden"))) +#else +#define APPLETLS_PRIVATE +#endif + +#define APPLETLS_PEM_MAX_FILE_SIZE (16u * 1024u * 1024u) +#define APPLETLS_PEM_MAX_BLOCK_SIZE (4u * 1024u * 1024u) +#define APPLETLS_PEM_MAX_CERTIFICATES 1024u + +typedef struct { + unsigned char* data; + size_t len; +} appletls_der_t; + +typedef struct { + appletls_der_t* items; + size_t count; +} appletls_der_list_t; + +typedef enum { + APPLETLS_PEM_OK = 0, + APPLETLS_PEM_ERROR_IO = -1, + APPLETLS_PEM_ERROR_FORMAT = -2, + APPLETLS_PEM_ERROR_UNSUPPORTED = -3, + APPLETLS_PEM_ERROR_LIMIT = -4, + APPLETLS_PEM_ERROR_NOMEM = -5 +} appletls_pem_error_t; + +APPLETLS_PRIVATE int appletls_pem_load_certificates(const char* path, int allow_der, size_t max_certificates, appletls_der_list_t* certificates); +APPLETLS_PRIVATE int appletls_pem_load_rsa_private_key(const char* path, appletls_der_t* pkcs1_key); +APPLETLS_PRIVATE void appletls_der_free(appletls_der_t* der); +APPLETLS_PRIVATE void appletls_der_list_free(appletls_der_list_t* list); +APPLETLS_PRIVATE const char* appletls_pem_error_string(int error); + +#undef APPLETLS_PRIVATE + +#endif diff --git a/unittest/appletls_pem_test.c b/unittest/appletls_pem_test.c new file mode 100644 index 000000000..76f6c7a6e --- /dev/null +++ b/unittest/appletls_pem_test.c @@ -0,0 +1,164 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include + +#include "appletls_pem.h" + +#define FIXTURE(name) "unittest/fixtures/appletls/" name + +static void assert_file_equal(const appletls_der_t* lhs, const appletls_der_t* rhs) { + assert(lhs->len == rhs->len); + assert(memcmp(lhs->data, rhs->data, lhs->len) == 0); +} + +static char* write_temp(const void* data, size_t len) { + char* path = strdup("/tmp/libhv-appletls-pem-XXXXXX"); + assert(path != NULL); + int fd = mkstemp(path); + assert(fd >= 0); + const unsigned char* ptr = (const unsigned char*)data; + while (len != 0) { + ssize_t nwrite = write(fd, ptr, len); + assert(nwrite > 0); + ptr += nwrite; + len -= (size_t)nwrite; + } + assert(close(fd) == 0); + return path; +} + +static void remove_temp(char* path) { + assert(unlink(path) == 0); + free(path); +} + +static void test_load_pkcs1_rsa(void) { + appletls_der_t key = {0}; + assert(appletls_pem_load_rsa_private_key(FIXTURE("server-pkcs1.key"), &key) == APPLETLS_PEM_OK); + assert(key.data != NULL && key.len > 256); + assert(key.data[0] == 0x30); + appletls_der_free(&key); + assert(key.data == NULL && key.len == 0); +} + +static void test_unwrap_pkcs8_rsa(void) { + appletls_der_t pkcs1 = {0}; + appletls_der_t pkcs8 = {0}; + assert(appletls_pem_load_rsa_private_key(FIXTURE("server-pkcs1.key"), &pkcs1) == APPLETLS_PEM_OK); + assert(appletls_pem_load_rsa_private_key(FIXTURE("server-pkcs8.key"), &pkcs8) == APPLETLS_PEM_OK); + assert_file_equal(&pkcs1, &pkcs8); + appletls_der_free(&pkcs1); + appletls_der_free(&pkcs8); +} + +static void test_load_certificate_chain_in_order(void) { + appletls_der_list_t chain = {0}; + appletls_der_list_t leaf = {0}; + appletls_der_list_t intermediate = {0}; + assert(appletls_pem_load_certificates(FIXTURE("server-chain.pem"), 0, 64, &chain) == APPLETLS_PEM_OK); + assert(appletls_pem_load_certificates(FIXTURE("server.crt"), 0, 1, &leaf) == APPLETLS_PEM_OK); + assert(appletls_pem_load_certificates(FIXTURE("intermediate.crt"), 0, 1, &intermediate) == APPLETLS_PEM_OK); + assert(chain.count == 2 && leaf.count == 1 && intermediate.count == 1); + assert_file_equal(&chain.items[0], &leaf.items[0]); + assert_file_equal(&chain.items[1], &intermediate.items[0]); + appletls_der_list_free(&chain); + appletls_der_list_free(&leaf); + appletls_der_list_free(&intermediate); +} + +static void test_load_der_certificate(void) { + appletls_der_list_t certificates = {0}; + assert(appletls_pem_load_certificates(FIXTURE("root.der"), 1, 1, &certificates) == APPLETLS_PEM_OK); + assert(certificates.count == 1); + appletls_der_list_free(&certificates); +} + +static void test_reject_ec_and_encrypted_keys(void) { + appletls_der_t key = {0}; + assert(appletls_pem_load_rsa_private_key(FIXTURE("ec-sec1.key"), &key) == APPLETLS_PEM_ERROR_UNSUPPORTED); + assert(appletls_pem_load_rsa_private_key(FIXTURE("ec-pkcs8.key"), &key) == APPLETLS_PEM_ERROR_UNSUPPORTED); + assert(appletls_pem_load_rsa_private_key(FIXTURE("encrypted-key.pem"), &key) == APPLETLS_PEM_ERROR_UNSUPPORTED); +} + +static void test_reject_invalid_base64(void) { + appletls_der_list_t certs = {0}; + assert(appletls_pem_load_certificates(FIXTURE("malformed-base64.pem"), 0, 1, &certs) == APPLETLS_PEM_ERROR_FORMAT); +} + +static void test_reject_invalid_der_lengths(void) { + static const unsigned char indefinite[] = {0x30, 0x80, 0x00, 0x00}; + static const unsigned char nonminimal[] = {0x30, 0x81, 0x01, 0x00}; + static const unsigned char truncated[] = {0x30, 0x04, 0x02, 0x01}; + static const unsigned char trailing[] = {0x30, 0x00, 0x00}; + const unsigned char* cases[] = {indefinite, nonminimal, truncated, trailing}; + const size_t sizes[] = {sizeof(indefinite), sizeof(nonminimal), sizeof(truncated), sizeof(trailing)}; + size_t i; + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + char* path = write_temp(cases[i], sizes[i]); + appletls_der_list_t certs = {0}; + assert(appletls_pem_load_certificates(path, 1, 1, &certs) == APPLETLS_PEM_ERROR_FORMAT); + remove_temp(path); + } +} + +static void test_reject_non_rsa_pkcs8_oid(void) { + appletls_der_t key = {0}; + assert(appletls_pem_load_rsa_private_key(FIXTURE("ec-pkcs8.key"), &key) == APPLETLS_PEM_ERROR_UNSUPPORTED); + assert(appletls_pem_load_rsa_private_key(FIXTURE("malformed-pkcs8.pem"), &key) == APPLETLS_PEM_ERROR_FORMAT); +} + +static void test_reject_multiple_private_keys(void) { + FILE* source = fopen(FIXTURE("server-pkcs1.key"), "rb"); + assert(source != NULL); + assert(fseek(source, 0, SEEK_END) == 0); + long length = ftell(source); + assert(length > 0 && fseek(source, 0, SEEK_SET) == 0); + char* key = (char*)malloc((size_t)length); + char* twice = (char*)malloc((size_t)length * 2); + assert(key != NULL && twice != NULL); + assert(fread(key, 1, (size_t)length, source) == (size_t)length); + fclose(source); + memcpy(twice, key, (size_t)length); + memcpy(twice + length, key, (size_t)length); + char* path = write_temp(twice, (size_t)length * 2); + appletls_der_t parsed = {0}; + assert(appletls_pem_load_rsa_private_key(path, &parsed) == APPLETLS_PEM_ERROR_FORMAT); + remove_temp(path); + free(twice); + free(key); +} + +static void test_enforce_file_block_and_count_limits(void) { + appletls_der_list_t certs = {0}; + char* path; + int fd; + assert(appletls_pem_load_certificates(FIXTURE("server-chain.pem"), 0, 1, &certs) == APPLETLS_PEM_ERROR_LIMIT); + + path = strdup("/tmp/libhv-appletls-large-XXXXXX"); + assert(path != NULL); + fd = mkstemp(path); + assert(fd >= 0); + assert(ftruncate(fd, (off_t)APPLETLS_PEM_MAX_FILE_SIZE + 1) == 0); + assert(close(fd) == 0); + assert(appletls_pem_load_certificates(path, 1, 1, &certs) == APPLETLS_PEM_ERROR_LIMIT); + remove_temp(path); +} + +int main(void) { + test_load_pkcs1_rsa(); + test_unwrap_pkcs8_rsa(); + test_load_certificate_chain_in_order(); + test_load_der_certificate(); + test_reject_ec_and_encrypted_keys(); + test_reject_invalid_base64(); + test_reject_invalid_der_lengths(); + test_reject_non_rsa_pkcs8_oid(); + test_reject_multiple_private_keys(); + test_enforce_file_block_and_count_limits(); + puts("appletls_pem_test: PASS"); + return 0; +} diff --git a/unittest/appletls_test.c b/unittest/appletls_test.c new file mode 100644 index 000000000..2c4185a63 --- /dev/null +++ b/unittest/appletls_test.c @@ -0,0 +1,318 @@ +#include +#include +#include +#include +#include +#include + +#include "hsocket.h" +#include "hssl.h" + +#define FIXTURE(name) "unittest/fixtures/appletls/" name + +static hssl_ctx_opt_t server_options(const char* key_file) { + hssl_ctx_opt_t options; + memset(&options, 0, sizeof(options)); + options.endpoint = HSSL_SERVER; + options.crt_file = FIXTURE("server-chain.pem"); + options.key_file = key_file; + return options; +} + +static void test_server_context_accepts_matching_pkcs1_identity(void) { + hssl_ctx_opt_t options = server_options(FIXTURE("server-pkcs1.key")); + hssl_ctx_t ctx = hssl_ctx_new(&options); + assert(ctx != NULL); + hssl_ctx_free(ctx); +} + +static void test_server_context_accepts_matching_pkcs8_identity(void) { + hssl_ctx_opt_t options = server_options(FIXTURE("server-pkcs8.key")); + hssl_ctx_t ctx = hssl_ctx_new(&options); + assert(ctx != NULL); + hssl_ctx_free(ctx); +} + +static void test_server_context_rejects_missing_identity(void) { + hssl_ctx_opt_t options; + memset(&options, 0, sizeof(options)); + options.endpoint = HSSL_SERVER; + assert(hssl_ctx_new(&options) == NULL); +} + +static void test_context_rejects_partial_identity(void) { + hssl_ctx_opt_t options = server_options(NULL); + assert(hssl_ctx_new(&options) == NULL); + options = server_options(FIXTURE("server-pkcs1.key")); + options.crt_file = NULL; + assert(hssl_ctx_new(&options) == NULL); +} + +static void test_context_rejects_mismatched_identity(void) { + hssl_ctx_opt_t options = server_options(FIXTURE("wrong-server.key")); + assert(hssl_ctx_new(&options) == NULL); +} + +static void test_context_loads_ca_file(void) { + hssl_ctx_opt_t options; + memset(&options, 0, sizeof(options)); + options.endpoint = HSSL_CLIENT; + options.verify_peer = 1; + options.ca_file = FIXTURE("multi-ca.pem"); + hssl_ctx_t ctx = hssl_ctx_new(&options); + assert(ctx != NULL); + hssl_ctx_free(ctx); +} + +static void test_context_loads_ca_directory_and_skips_noise(void) { + hssl_ctx_opt_t options; + memset(&options, 0, sizeof(options)); + options.endpoint = HSSL_CLIENT; + options.verify_peer = 1; + options.ca_path = FIXTURE("ca-dir"); + hssl_ctx_t ctx = hssl_ctx_new(&options); + assert(ctx != NULL); + hssl_ctx_free(ctx); +} + +static void test_context_rejects_empty_ca_source(void) { + hssl_ctx_opt_t options; + memset(&options, 0, sizeof(options)); + options.endpoint = HSSL_CLIENT; + options.verify_peer = 1; + options.ca_file = FIXTURE("malformed-base64.pem"); + assert(hssl_ctx_new(&options) == NULL); + options.ca_file = NULL; + options.ca_path = FIXTURE("empty-ca-dir"); + assert(hssl_ctx_new(&options) == NULL); +} + +static void test_invalid_sni_and_identityless_server_fail_cleanly(void) { + hssl_ctx_t ctx = hssl_ctx_new(NULL); + hssl_t ssl; + assert(ctx != NULL); + ssl = hssl_new(ctx, -1); + assert(ssl != NULL); + assert(hssl_set_sni_hostname(ssl, NULL) == HSSL_ERROR); + assert(hssl_set_sni_hostname(ssl, "") == HSSL_ERROR); + assert(hssl_accept(ssl) == HSSL_ERROR); + hssl_free(ssl); + hssl_ctx_free(ctx); +} + +typedef struct { + hssl_ctx_t server_ctx; + hssl_ctx_t client_ctx; + hssl_t server; + hssl_t client; + int fds[2]; +} tls_pair_t; + +static void tls_pair_close(tls_pair_t* pair) { + if (pair->server) hssl_free(pair->server); + if (pair->client) hssl_free(pair->client); + if (pair->server_ctx) hssl_ctx_free(pair->server_ctx); + if (pair->client_ctx) hssl_ctx_free(pair->client_ctx); + if (pair->fds[0] >= 0) close(pair->fds[0]); + if (pair->fds[1] >= 0) close(pair->fds[1]); + memset(pair, 0, sizeof(*pair)); + pair->fds[0] = pair->fds[1] = -1; +} + +static int tls_pair_open(tls_pair_t* pair, const hssl_ctx_opt_t* server_options, const hssl_ctx_opt_t* client_options, const char* hostname) { + int i; + int server_status = HSSL_WANT_READ; + int client_status = HSSL_WANT_READ; + memset(pair, 0, sizeof(*pair)); + pair->fds[0] = pair->fds[1] = -1; + pair->server_ctx = hssl_ctx_new((hssl_ctx_opt_t*)server_options); + pair->client_ctx = hssl_ctx_new((hssl_ctx_opt_t*)client_options); + if (pair->server_ctx == NULL || pair->client_ctx == NULL) goto error; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair->fds) != 0) goto error; + if (nonblocking(pair->fds[0]) != 0 || nonblocking(pair->fds[1]) != 0) goto error; + pair->server = hssl_new(pair->server_ctx, pair->fds[0]); + pair->client = hssl_new(pair->client_ctx, pair->fds[1]); + if (pair->server == NULL || pair->client == NULL) goto error; + if (hostname && hssl_set_sni_hostname(pair->client, hostname) != HSSL_OK) goto error; + + for (i = 0; i < 10000; ++i) { + if (server_status != HSSL_OK) server_status = hssl_accept(pair->server); + if (client_status != HSSL_OK) client_status = hssl_connect(pair->client); + if (server_status == HSSL_ERROR || client_status == HSSL_ERROR) goto error; + if (server_status == HSSL_OK && client_status == HSSL_OK) return 0; + } + +error: + tls_pair_close(pair); + return -1; +} + +static void assert_bidirectional_data(tls_pair_t* pair) { + static const char request[] = "client-to-server"; + static const char response[] = "server-to-client"; + char buffer[64]; + int ret; + int i; + + assert(hssl_write(pair->client, request, (int)sizeof(request)) == (int)sizeof(request)); + ret = HSSL_WOULD_BLOCK; + for (i = 0; i < 1000 && ret == HSSL_WOULD_BLOCK; ++i) { + ret = hssl_read(pair->server, buffer, sizeof(buffer)); + } + assert(ret == (int)sizeof(request)); + assert(memcmp(buffer, request, sizeof(request)) == 0); + + assert(hssl_write(pair->server, response, (int)sizeof(response)) == (int)sizeof(response)); + ret = HSSL_WOULD_BLOCK; + for (i = 0; i < 1000 && ret == HSSL_WOULD_BLOCK; ++i) { + ret = hssl_read(pair->client, buffer, sizeof(buffer)); + } + assert(ret == (int)sizeof(response)); + assert(memcmp(buffer, response, sizeof(response)) == 0); +} + +static hssl_ctx_opt_t verified_client_options(const char* ca_file) { + hssl_ctx_opt_t options; + memset(&options, 0, sizeof(options)); + options.endpoint = HSSL_CLIENT; + options.verify_peer = 1; + options.ca_file = ca_file; + return options; +} + +static void test_server_handshake_and_bidirectional_data(void) { + hssl_ctx_opt_t server = server_options(FIXTURE("server-pkcs1.key")); + hssl_ctx_opt_t client; + tls_pair_t pair; + memset(&client, 0, sizeof(client)); + client.endpoint = HSSL_CLIENT; + assert(tls_pair_open(&pair, &server, &client, NULL) == 0); + assert_bidirectional_data(&pair); + tls_pair_close(&pair); +} + +static void test_custom_ca_and_matching_hostname_succeed(void) { + hssl_ctx_opt_t server = server_options(FIXTURE("server-pkcs1.key")); + hssl_ctx_opt_t client = verified_client_options(FIXTURE("root.crt")); + tls_pair_t pair; + assert(tls_pair_open(&pair, &server, &client, "localhost") == 0); + tls_pair_close(&pair); +} + +static void test_custom_ca_directory_and_matching_hostname_succeed(void) { + hssl_ctx_opt_t server = server_options(FIXTURE("server-pkcs1.key")); + hssl_ctx_opt_t client = verified_client_options(NULL); + tls_pair_t pair; + client.ca_path = FIXTURE("ca-dir"); + assert(tls_pair_open(&pair, &server, &client, "localhost") == 0); + tls_pair_close(&pair); +} + +static void test_empty_read_is_nonblocking(void) { + hssl_ctx_opt_t server = server_options(FIXTURE("server-pkcs1.key")); + hssl_ctx_opt_t client; + tls_pair_t pair; + char byte; + memset(&client, 0, sizeof(client)); + client.endpoint = HSSL_CLIENT; + assert(tls_pair_open(&pair, &server, &client, NULL) == 0); + errno = 0; + assert(hssl_read(pair.client, &byte, 1) == HSSL_WOULD_BLOCK); + assert(errno == EAGAIN || errno == EWOULDBLOCK); + tls_pair_close(&pair); +} + +static void test_handshake_reports_write_block(void) { + hssl_ctx_opt_t options; + hssl_ctx_t ctx; + hssl_t ssl; + int fds[2]; + char fill[4096] = {0}; + ssize_t nwrite; + + memset(&options, 0, sizeof(options)); + options.endpoint = HSSL_CLIENT; + ctx = hssl_ctx_new(&options); + assert(ctx != NULL); + assert(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); + assert(nonblocking(fds[0]) == 0 && nonblocking(fds[1]) == 0); + do { + nwrite = write(fds[0], fill, sizeof(fill)); + } while (nwrite > 0); + assert(errno == EAGAIN || errno == EWOULDBLOCK); + + ssl = hssl_new(ctx, fds[0]); + assert(ssl != NULL); + assert(hssl_connect(ssl) == HSSL_WANT_WRITE); + hssl_free(ssl); + hssl_ctx_free(ctx); + close(fds[0]); + close(fds[1]); +} + +static void test_wrong_hostname_fails(void) { + hssl_ctx_opt_t server = server_options(FIXTURE("server-pkcs1.key")); + hssl_ctx_opt_t client = verified_client_options(FIXTURE("root.crt")); + tls_pair_t pair; + assert(tls_pair_open(&pair, &server, &client, "wrong.example") != 0); +} + +static void test_wrong_custom_ca_fails(void) { + hssl_ctx_opt_t server = server_options(FIXTURE("server-pkcs1.key")); + hssl_ctx_opt_t client = verified_client_options(FIXTURE("wrong-root.crt")); + tls_pair_t pair; + assert(tls_pair_open(&pair, &server, &client, "localhost") != 0); +} + +static void test_system_roots_reject_fixture_server(void) { + hssl_ctx_opt_t server = server_options(FIXTURE("server-pkcs1.key")); + hssl_ctx_opt_t client = verified_client_options(NULL); + tls_pair_t pair; + assert(tls_pair_open(&pair, &server, &client, "localhost") != 0); +} + +static void test_server_mtls_rejects_missing_client_certificate(void) { + hssl_ctx_opt_t server = server_options(FIXTURE("server-pkcs1.key")); + hssl_ctx_opt_t client = verified_client_options(FIXTURE("root.crt")); + tls_pair_t pair; + server.verify_peer = 1; + server.ca_file = FIXTURE("root.crt"); + assert(tls_pair_open(&pair, &server, &client, "localhost") != 0); +} + +static void test_server_mtls_accepts_trusted_client_certificate(void) { + hssl_ctx_opt_t server = server_options(FIXTURE("server-pkcs1.key")); + hssl_ctx_opt_t client = verified_client_options(FIXTURE("root.crt")); + tls_pair_t pair; + server.verify_peer = 1; + server.ca_file = FIXTURE("root.crt"); + client.crt_file = FIXTURE("client-chain.pem"); + client.key_file = FIXTURE("client.key"); + assert(tls_pair_open(&pair, &server, &client, "localhost") == 0); + assert_bidirectional_data(&pair); + tls_pair_close(&pair); +} + +int main(void) { + test_server_context_accepts_matching_pkcs1_identity(); + test_server_context_accepts_matching_pkcs8_identity(); + test_server_context_rejects_missing_identity(); + test_context_rejects_partial_identity(); + test_context_rejects_mismatched_identity(); + test_context_loads_ca_file(); + test_context_loads_ca_directory_and_skips_noise(); + test_context_rejects_empty_ca_source(); + test_invalid_sni_and_identityless_server_fail_cleanly(); + test_server_handshake_and_bidirectional_data(); + test_custom_ca_and_matching_hostname_succeed(); + test_custom_ca_directory_and_matching_hostname_succeed(); + test_empty_read_is_nonblocking(); + test_handshake_reports_write_block(); + test_wrong_hostname_fails(); + test_wrong_custom_ca_fails(); + test_system_roots_reject_fixture_server(); + test_server_mtls_rejects_missing_client_certificate(); + test_server_mtls_accepts_trusted_client_certificate(); + puts("appletls_test: PASS"); + return 0; +} diff --git a/unittest/fixtures/appletls/README.md b/unittest/fixtures/appletls/README.md new file mode 100644 index 000000000..4f1cf55a5 --- /dev/null +++ b/unittest/fixtures/appletls/README.md @@ -0,0 +1,6 @@ +# Apple TLS test certificates + +All private keys in this directory are disposable test fixtures and must never +be used outside the unit tests. Run `generate.sh` from any directory to replace +the fixture set. The checked-in files let tests run without an OpenSSL runtime +dependency. diff --git a/unittest/fixtures/appletls/ca-dir/README.txt b/unittest/fixtures/appletls/ca-dir/README.txt new file mode 100644 index 000000000..2716bf650 --- /dev/null +++ b/unittest/fixtures/appletls/ca-dir/README.txt @@ -0,0 +1 @@ +not a certificate diff --git a/unittest/fixtures/appletls/ca-dir/root.crt b/unittest/fixtures/appletls/ca-dir/root.crt new file mode 100644 index 000000000..ca46c5887 --- /dev/null +++ b/unittest/fixtures/appletls/ca-dir/root.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBDCCAeygAwIBAgICAQAwDQYJKoZIhvcNAQELBQAwIzEhMB8GA1UEAwwYbGli +aHYtYXBwbGV0bHMtdGVzdC1yb290MB4XDTI2MTAwMTAwMDAwMFoXDTQ2MTAwMTAw +MDAwMFowIzEhMB8GA1UEAwwYbGliaHYtYXBwbGV0bHMtdGVzdC1yb290MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvqo6cJBRm1lKsQX5Fsz+jnnzfDqW +jl0M7fSs0zDVW8WbPk+6OkysFvgIzPclV7jkFrEYcB4UuHUifSmsFROf+CCqL4m2 +mFaSrmStqasjiNN/r3DW8XgjMn4KWgUae1X6xvjhfOI5qOEui0Zn7/x3HbwD/Ha0 +1WyjtY5+ZLvkn11dSlioWVBnk6vEnxtEphTbhGWi68cgF1RLElU0VeyXzjqkewTW +qtbqToAfY7wkRDBvcLg2p2tsLyyeAPWJCxmBKDZO+J6AYz5idi/0RdlFSXqBmEPU +O1qzTDEO/K78GS614e1DFNhYhNgk6b+wqWGvfr7qxqE+5D5IJ9fkU7zewwIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU +9e3jmUuilGBydDW7H3Zl7N9ldhwwDQYJKoZIhvcNAQELBQADggEBAHbaSvL/Ai19 +hA/h/0/nHXwRjspzcD8BsRkgwtE1uA6TOly5qzecRD+1yuNxSM3IvdVxhTdOrCyu +HsPgQCDczz09JqkSgX1F/pSgsIdC+ygl+GFJW+Om+84lXrNfn0EFiWmj9e8qsOR/ +3nAmn9Wn/RC4WMypHBCQyxNMJ74X1Zzq8VToki8iThA62nmYb+7jbwkHFj7jMF6f +y/75EwjZ7zZyK4ObpEgcOdp32LOl6/H2ERduN48WI2WRCLKmZLvsVEsp7WxPzWJc +2WZIFHptfRO8byuE/911PgDskTJedCuIActXE8zt4LBEIRVXIMsUUS6fO7ZJSBgx +JgqB1zRUvZE= +-----END CERTIFICATE----- diff --git a/unittest/fixtures/appletls/client-chain.pem b/unittest/fixtures/appletls/client-chain.pem new file mode 100644 index 000000000..115959f12 --- /dev/null +++ b/unittest/fixtures/appletls/client-chain.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDOTCCAiGgAwIBAgICMAAwDQYJKoZIhvcNAQELBQAwIzEhMB8GA1UEAwwYbGli +aHYtYXBwbGV0bHMtdGVzdC1yb290MB4XDTI2MTAwMTAwMDAwMFoXDTI3MTAwMTAw +MDAwMFowJTEjMCEGA1UEAwwabGliaHYtYXBwbGV0bHMtdGVzdC1jbGllbnQwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHBf/7eOrciHGTNSRFfryJhpPN +VfOnPt4Oja5ZHHmLx6L12vm95f3dYOu3otQof2kC/3vYQ7Np3s3ebf2k7YX2agyB +DpaqwSOd6RBclF7qse9oyy+Z+5zySP7xzfU3SAscudstw6y/2uANDHeA7FbQ7INk +WOen4VizU5vytAw0b/5Dq0w60cG+MfXyZp+qiP6mZOV3yUzY9++bduCiHIm84LLq +bbVx3uz/F2ElwZnsAbFQvwz+S5KnXuWIrdVNu3jEzw7fLeJPWs6psvDPpgSgh9qn +BLPyvYeXwRdazahsE7YeaYuc8K7QRbi+ZACTUHktlxo6EGfmMEoDDwf9pMaLAgMB +AAGjdTBzMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoG +CCsGAQUFBwMCMB0GA1UdDgQWBBTbunsLFLbkCb8wVTzyd7dANQS/YjAfBgNVHSME +GDAWgBT17eOZS6KUYHJ0NbsfdmXs32V2HDANBgkqhkiG9w0BAQsFAAOCAQEAjnE5 +QQRSZfkaB3xFKoa5TZIA4qcoKe755XIf3ZDzeorRt194P2+tL2zlQgxlzT5cbla8 +KWJj7gKNyfN/OAAjUGhHs7L9WhdsWrLPOaqA87li3hg/IpjvTVHGexIUG1SSBNbd +2Wc2oVcMIl+Qm+kyUYzl+g4PbuPk99jjCYtRtlkb05J0yR+13OU+HeYyGuGyreyI +yUzYS+S0xC9HYrG8FXcixjPxbY99aXft7ILd/jfw1DaAUuCXu04TMT/x+dGuNVpg +HxRinOAHierP8qqQ2FyfiooipmW2uTPeZamoL2zTYByfwv5EAxrWdNYonSYZ1DLk +7LMrgqOHnnTeuCi2gQ== +-----END CERTIFICATE----- diff --git a/unittest/fixtures/appletls/client.crt b/unittest/fixtures/appletls/client.crt new file mode 100644 index 000000000..115959f12 --- /dev/null +++ b/unittest/fixtures/appletls/client.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDOTCCAiGgAwIBAgICMAAwDQYJKoZIhvcNAQELBQAwIzEhMB8GA1UEAwwYbGli +aHYtYXBwbGV0bHMtdGVzdC1yb290MB4XDTI2MTAwMTAwMDAwMFoXDTI3MTAwMTAw +MDAwMFowJTEjMCEGA1UEAwwabGliaHYtYXBwbGV0bHMtdGVzdC1jbGllbnQwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHBf/7eOrciHGTNSRFfryJhpPN +VfOnPt4Oja5ZHHmLx6L12vm95f3dYOu3otQof2kC/3vYQ7Np3s3ebf2k7YX2agyB +DpaqwSOd6RBclF7qse9oyy+Z+5zySP7xzfU3SAscudstw6y/2uANDHeA7FbQ7INk +WOen4VizU5vytAw0b/5Dq0w60cG+MfXyZp+qiP6mZOV3yUzY9++bduCiHIm84LLq +bbVx3uz/F2ElwZnsAbFQvwz+S5KnXuWIrdVNu3jEzw7fLeJPWs6psvDPpgSgh9qn +BLPyvYeXwRdazahsE7YeaYuc8K7QRbi+ZACTUHktlxo6EGfmMEoDDwf9pMaLAgMB +AAGjdTBzMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoG +CCsGAQUFBwMCMB0GA1UdDgQWBBTbunsLFLbkCb8wVTzyd7dANQS/YjAfBgNVHSME +GDAWgBT17eOZS6KUYHJ0NbsfdmXs32V2HDANBgkqhkiG9w0BAQsFAAOCAQEAjnE5 +QQRSZfkaB3xFKoa5TZIA4qcoKe755XIf3ZDzeorRt194P2+tL2zlQgxlzT5cbla8 +KWJj7gKNyfN/OAAjUGhHs7L9WhdsWrLPOaqA87li3hg/IpjvTVHGexIUG1SSBNbd +2Wc2oVcMIl+Qm+kyUYzl+g4PbuPk99jjCYtRtlkb05J0yR+13OU+HeYyGuGyreyI +yUzYS+S0xC9HYrG8FXcixjPxbY99aXft7ILd/jfw1DaAUuCXu04TMT/x+dGuNVpg +HxRinOAHierP8qqQ2FyfiooipmW2uTPeZamoL2zTYByfwv5EAxrWdNYonSYZ1DLk +7LMrgqOHnnTeuCi2gQ== +-----END CERTIFICATE----- diff --git a/unittest/fixtures/appletls/client.key b/unittest/fixtures/appletls/client.key new file mode 100644 index 000000000..5b666cb5f --- /dev/null +++ b/unittest/fixtures/appletls/client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEoQIBAAKCAQEAxwX/+3jq3IhxkzUkRX68iYaTzVXzpz7eDo2uWRx5i8ei9dr5 +veX93WDrt6LUKH9pAv972EOzad7N3m39pO2F9moMgQ6WqsEjnekQXJRe6rHvaMsv +mfuc8kj+8c31N0gLHLnbLcOsv9rgDQx3gOxW0OyDZFjnp+FYs1Ob8rQMNG/+Q6tM +OtHBvjH18mafqoj+pmTld8lM2Pfvm3bgohyJvOCy6m21cd7s/xdhJcGZ7AGxUL8M +/kuSp17liK3VTbt4xM8O3y3iT1rOqbLwz6YEoIfapwSz8r2Hl8EXWs2obBO2HmmL +nPCu0EW4vmQAk1B5LZcaOhBn5jBKAw8H/aTGiwIDAQABAoH/WJ0QIfhgsyiTeeb9 +BJwmH3HhJpQm6FSLCDL4IlBdLmnrcG6qeENcIWOQe1z8DPs8YagHLIJhMakbiw9d +TQlfvbwqW2U0Pw23P1TNenN4Adk32LQQeYdw2/oqprWoY/TI/dMjQYzeK1/9RMV6 +mfC6Bp/OTdc/d65LPTkebPX4RtFBolwJRQ/uCkieiqht6mAAI3I9QlBV4aCTGomf +UylfDNtjc864ifcwm33KNd6J+fATTyN9/Sb9B2oj4L7fOdxuEgftSVvbCaoJycYm +eA2x9YLIocJJoly3Uph2AxwMcvuEGQEHRjh8Tw+Zo/lQSgG1tW1sy0io//6MUVL9 +5AH5AoGBAP5x+0vE/qa6QO8dbDdxsiaybSavP57J3upwdxyr7mZrnlI2ZHzGK7M9 +qXgv5z0FfwmM8FpX7+mq58OqrSk4dOgQhwf+qOD59XPtVXWNlOrrLa0M/G4eJIRr +wfVvPfc/XS701gDPBzftLOK63n6tPgiFp/WxUVeNjm5NIr5v7dQfAoGBAMg9UwBy +Le48IDhESL+qwPDeDSKiehiKvqyPjnbEcd39pZzeowddnMlGxBgFh63FAJsrq7AU +p73cOkJrrfyrUnUF8Nin80oBrleobRVjMBCDLzMPxkpcGobX+d/wSARYT13kjTiD ++RluL3aDk32zKzi0ecJbVxlIGn84lahbkaAVAoGAb4cadQ216PK+v5CP/IVzZw7S +VSB/rGQjt3LwYrrh1jbZwINi7GYYhYpDnBCzNMYR9ue1+oOM/0kQKW3IVMcQRx/n +cBOphmNJuTSQPuR+H7JR5QZKJyJDM1/gNmo6KQO3HnvmyZf1PAhTolXybGgXRrj+ +8+xzEQNVKPfagMqi3lUCgYAHNrZbyMFA2OesP0GDriofQ1Cj08ikQ7NU0iMmwjL7 +9VxXB2pGJKg2VBdSajiWGQfJj0wzHrtgSqPKqshs1ts0ng8sLvjDzMW0ILw1Tgp4 +PdGgfEv/d8v42aUF3Ukq5ueNEWPJCnAZ629ITLsuhxrigxXz24iKv588wBQcKZhY +dQKBgQCyqe9ZFiOmPKTaSF//AbLPLub1kdAfB1aAVq4hICH+m1G83Fz0hRX2cns0 +S3Sz5TYGnrIeZX00FKRWVYy+NHdLUxQkkJ6/egtcLLyIwYsmtZSmfJOyTDWlAKTu +7Z0sBlP/608xdjIQtoGhVxUNXIGNA6u3teonBVQeo/nxDQmxMw== +-----END RSA PRIVATE KEY----- diff --git a/unittest/fixtures/appletls/ec-pkcs8.key b/unittest/fixtures/appletls/ec-pkcs8.key new file mode 100644 index 000000000..0234db2bb --- /dev/null +++ b/unittest/fixtures/appletls/ec-pkcs8.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgGK7POP0d0lemDmWY +Nc9wJwJ1kKZpPilEKl+kjo8YWhGhRANCAAT5quos7H62iqN9U4SrhuVYCtaC6KXk +K3yhHvaKhIDdsEvSfexojCF8HZk/ojRqvSVPoPisesqRxKsvmuUumvJJ +-----END PRIVATE KEY----- diff --git a/unittest/fixtures/appletls/ec-sec1.key b/unittest/fixtures/appletls/ec-sec1.key new file mode 100644 index 000000000..ba1f6b243 --- /dev/null +++ b/unittest/fixtures/appletls/ec-sec1.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIBiuzzj9HdJXpg5lmDXPcCcCdZCmaT4pRCpfpI6PGFoRoAoGCCqGSM49 +AwEHoUQDQgAE+arqLOx+toqjfVOEq4blWArWguil5Ct8oR72ioSA3bBL0n3saIwh +fB2ZP6I0ar0lT6D4rHrKkcSrL5rlLprySQ== +-----END EC PRIVATE KEY----- diff --git a/unittest/fixtures/appletls/empty-ca-dir/README.txt b/unittest/fixtures/appletls/empty-ca-dir/README.txt new file mode 100644 index 000000000..2716bf650 --- /dev/null +++ b/unittest/fixtures/appletls/empty-ca-dir/README.txt @@ -0,0 +1 @@ +not a certificate diff --git a/unittest/fixtures/appletls/encrypted-key.pem b/unittest/fixtures/appletls/encrypted-key.pem new file mode 100644 index 000000000..12f939231 --- /dev/null +++ b/unittest/fixtures/appletls/encrypted-key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQxmdbcASuFeL+WbjT +t4DdrgICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEKj2uV4GkALqChfb +bHpinTEEggTQ8dLs/RBfZG8M1k8YnERnZCuBXopVpTzHJ+D4taVog9mTEnosgzI9 +U+pHo7/Vt73eyFsQZ3Jbv0m5fxkvAfxbVs8LWqUpD5t1zPausPKTFWgwUwzm+Lj1 +hcNKFpj8RT4PnYukcH2uSXCXFvlQBIZBvcIIiWyZxhjTr32v78BKTm2Gi3KEZwTH +xZeKV/k1fK88R6L4RJQPea8Ski9xXYgLiyPU22zAClWGu/6cjJ2/zQhlGwrPhgoe +OXSCOc2NpSHvl4eWh4gW+Ne/eHA/sLBQ/gSEEnVvpFgF5Dqj8nmsskAqUWQa5qyP +E3OmY6oGkJjd9CRkcDTzxKnT3ccseKX02LNEZsF1TVbnYfAmPo2HdF93MwOYRo9s +aXGiMlOOF1zG33mG5AexcV7KuFvtSRPPAe+X89+deciloZfcCZHKWcYSCrvKG8v9 +GDBCjebiHLNPYntBbWlT+YPDG3Tik8ew8LH7WEMrDPxBq6snOHmTbamYGVcMP6tj +6O3u79vQt1zDBdErAp0QOU02HO3LWF6GsphUhxxGnZgKr6iq3ZW1mUmeAYHJk1sL +/c1/rRuyNI+/KOyhmJcGN7uxQr1VNTact/aZRm2epfVwOMwJUoWOwWkNkk35C9Hw +1BJqIQaDSusBkMURs4sITu78nHdNesow0vD52qgdWOVqrgm+NYzrlEhf0LLkoOt7 +lWht/4Dg5a76Mhe+QS4/g99W9Ox1mEzT/Wjdm5WxlkVeLOp2JhoVnfe6EwBQWA3N +6/np1Q89WXfpjriGRO0u2Xys971UrBcTVyhjqGtEUJ8OG2k29fx0FyWR44lFHNc2 +3Loblc63kO3OKpcff8PGiKC26hR/OVuI822MQiRAZF5GDFyzdC2M+MRrL2PMlxM6 +frXp+fcGskaxnEi9N+ZFFrdiFEOzwZD/IHYT1Lg/GiF53pOGSA/SfmN4GBDEokX3 +92Z5D/vwJ55Rg63nidQvP6pyxSPy/xfTa2yCmSsImdOHLKIRlOGl8BDrWSSPt7x9 +4tVtS3zWdVqvcCe91o8j2H3TYgFPjJG/GQHMS5npyFoeuCNj/YjV973GvNW7xBCi +Sllf/zarF6FmwZq/DGLh8CGodU+xEJmydHuws7Q/nVhiY2y4ieT7x5k+Ro0gOSL0 +5oeNgMsrdyfUWicWFj1HDZa9R3pFSPXKVU+YF0TIl2V5wH8vls921zLM61IieioC +/1Cd1IwFjrNUm6uC7nbSHqY41DdHcVDAflkCBA832gGR8bYfty54DJxtd+Wmnj4Z +WYG7qsgkGIeJsakoeTs6v7XstW3D+8fJEqcJUAhWXlz4/bNs9xYR1ZFLQy+JZEpc +0E6NgTbsuKhVNf969tolhMVnObWyCcqa8Dt3RqoWUQysOv7opq3ULGfsTdvlDOCF +7LNtS4a9dbu5DNSkssYWVOma1yxQZ4UwMDBICPaJVvrbTgYFCOhkEPpfiLeJolvK +7pm4QI5R5yaaGXE2e6MVQ/oWq6UZWv2rWBg/+G/3mW8g7Uz88Nfk7se9URd1tnzi +UamYgZpbBmGYh/jhj6qzo8ntxS/pqIavSOcjEhNpEWU0uVt+AlltGtw6TOrY5J8D +z/OTOmYISVqRFZlImqCQaTAKrwF3aUA4o1pulGto9a5KCzy/P5VBR8s= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/unittest/fixtures/appletls/generate.sh b/unittest/fixtures/appletls/generate.sh new file mode 100755 index 000000000..c0894da82 --- /dev/null +++ b/unittest/fixtures/appletls/generate.sh @@ -0,0 +1,139 @@ +#!/bin/sh +set -eu + +fixture_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +tmp_dir=$(mktemp -d) +trap 'rm -rf "$tmp_dir"' EXIT INT TERM +start_date=20261001000000Z +ca_end_date=20461001000000Z +leaf_end_date=20271001000000Z + +cat > "$tmp_dir/ca.cnf" < "$tmp_dir/serial" + +make_key() { + openssl genrsa -traditional -out "$1" 2048 +} + +make_key "$tmp_dir/root.key" +openssl req -new -sha256 -key "$tmp_dir/root.key" -out "$tmp_dir/root.csr" \ + -subj /CN=libhv-appletls-test-root +printf '%s\n' \ + '[x509_ext]' \ + 'basicConstraints=critical,CA:true' \ + 'keyUsage=critical,keyCertSign,cRLSign' \ + 'subjectKeyIdentifier=hash' > "$tmp_dir/root.ext" +openssl ca -batch -selfsign -config "$tmp_dir/ca.cnf" \ + -keyfile "$tmp_dir/root.key" -in "$tmp_dir/root.csr" \ + -startdate "$start_date" -enddate "$ca_end_date" \ + -extensions x509_ext -extfile "$tmp_dir/root.ext" \ + -out "$fixture_dir/root.crt" -notext + +make_key "$tmp_dir/intermediate.key" +openssl req -new -sha256 -key "$tmp_dir/intermediate.key" \ + -out "$tmp_dir/intermediate.csr" -subj /CN=libhv-appletls-test-intermediate +printf '%s\n' \ + '[x509_ext]' \ + 'basicConstraints=critical,CA:true,pathlen:0' \ + 'keyUsage=critical,keyCertSign,cRLSign' \ + 'subjectKeyIdentifier=hash' \ + 'authorityKeyIdentifier=keyid,issuer' > "$tmp_dir/intermediate.ext" +cp "$tmp_dir/root.key" "$tmp_dir/signer.key" +cp "$fixture_dir/root.crt" "$tmp_dir/signer.crt" +printf '1000\n' > "$tmp_dir/serial" +openssl ca -batch -config "$tmp_dir/ca.cnf" -in "$tmp_dir/intermediate.csr" \ + -startdate "$start_date" -enddate "$ca_end_date" \ + -extensions x509_ext -extfile "$tmp_dir/intermediate.ext" \ + -out "$fixture_dir/intermediate.crt" -notext + +make_key "$fixture_dir/server-pkcs1.key" +openssl pkcs8 -topk8 -nocrypt -in "$fixture_dir/server-pkcs1.key" \ + -out "$fixture_dir/server-pkcs8.key" +openssl req -new -sha256 -key "$fixture_dir/server-pkcs1.key" \ + -out "$tmp_dir/server.csr" -subj /CN=localhost +printf '%s\n' \ + '[x509_ext]' \ + 'basicConstraints=critical,CA:false' \ + 'keyUsage=critical,digitalSignature,keyEncipherment' \ + 'extendedKeyUsage=serverAuth' \ + 'subjectAltName=DNS:localhost' > "$tmp_dir/server.ext" +cp "$tmp_dir/intermediate.key" "$tmp_dir/signer.key" +cp "$fixture_dir/intermediate.crt" "$tmp_dir/signer.crt" +printf '2000\n' > "$tmp_dir/serial" +openssl ca -batch -config "$tmp_dir/ca.cnf" -in "$tmp_dir/server.csr" \ + -startdate "$start_date" -enddate "$leaf_end_date" \ + -extensions x509_ext -extfile "$tmp_dir/server.ext" \ + -out "$fixture_dir/server.crt" -notext + +make_key "$fixture_dir/client.key" +openssl req -new -sha256 -key "$fixture_dir/client.key" \ + -out "$tmp_dir/client.csr" -subj /CN=libhv-appletls-test-client +printf '%s\n' \ + '[x509_ext]' \ + 'basicConstraints=critical,CA:false' \ + 'keyUsage=critical,digitalSignature,keyEncipherment' \ + 'extendedKeyUsage=clientAuth' > "$tmp_dir/client.ext" +cp "$tmp_dir/root.key" "$tmp_dir/signer.key" +cp "$fixture_dir/root.crt" "$tmp_dir/signer.crt" +printf '3000\n' > "$tmp_dir/serial" +openssl ca -batch -config "$tmp_dir/ca.cnf" -in "$tmp_dir/client.csr" \ + -startdate "$start_date" -enddate "$leaf_end_date" \ + -extensions x509_ext -extfile "$tmp_dir/client.ext" \ + -out "$fixture_dir/client.crt" -notext + +make_key "$tmp_dir/wrong-root.key" +openssl req -new -sha256 -key "$tmp_dir/wrong-root.key" \ + -out "$tmp_dir/wrong-root.csr" -subj /CN=libhv-appletls-wrong-root +cp "$tmp_dir/wrong-root.key" "$tmp_dir/signer.key" +printf '4000\n' > "$tmp_dir/serial" +openssl ca -batch -selfsign -config "$tmp_dir/ca.cnf" \ + -keyfile "$tmp_dir/wrong-root.key" -in "$tmp_dir/wrong-root.csr" \ + -startdate "$start_date" -enddate "$ca_end_date" \ + -extensions x509_ext -extfile "$tmp_dir/root.ext" \ + -out "$fixture_dir/wrong-root.crt" -notext +make_key "$fixture_dir/wrong-server.key" + +openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 \ + -out "$fixture_dir/ec-pkcs8.key" +openssl pkey -in "$fixture_dir/ec-pkcs8.key" -traditional \ + -out "$fixture_dir/ec-sec1.key" +openssl pkcs8 -topk8 -v2 aes-256-cbc -passout pass:test-only \ + -in "$fixture_dir/server-pkcs1.key" -out "$fixture_dir/encrypted-key.pem" + +cat "$fixture_dir/server.crt" "$fixture_dir/intermediate.crt" \ + > "$fixture_dir/server-chain.pem" +cat "$fixture_dir/client.crt" > "$fixture_dir/client-chain.pem" +cat "$fixture_dir/root.crt" "$fixture_dir/wrong-root.crt" \ + > "$fixture_dir/multi-ca.pem" + +mkdir -p "$fixture_dir/ca-dir" +cp "$fixture_dir/root.crt" "$fixture_dir/ca-dir/root.crt" +openssl x509 -in "$fixture_dir/root.crt" -outform DER -out "$fixture_dir/root.der" +printf '%s\n' 'not a certificate' > "$fixture_dir/ca-dir/README.txt" +mkdir -p "$fixture_dir/empty-ca-dir" +printf '%s\n' 'not a certificate' > "$fixture_dir/empty-ca-dir/README.txt" + +printf '%s\n' \ + '-----BEGIN CERTIFICATE-----' \ + 'not-base64!' \ + '-----END CERTIFICATE-----' > "$fixture_dir/malformed-base64.pem" +printf '%s\n' \ + '-----BEGIN PRIVATE KEY-----' \ + 'MAMCAQE=' \ + '-----END PRIVATE KEY-----' > "$fixture_dir/malformed-pkcs8.pem" + +echo "Apple TLS fixtures regenerated in $fixture_dir" diff --git a/unittest/fixtures/appletls/intermediate.crt b/unittest/fixtures/appletls/intermediate.crt new file mode 100644 index 000000000..b04a73dd4 --- /dev/null +++ b/unittest/fixtures/appletls/intermediate.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwIzEhMB8GA1UEAwwYbGli +aHYtYXBwbGV0bHMtdGVzdC1yb290MB4XDTI2MTAwMTAwMDAwMFoXDTQ2MTAwMTAw +MDAwMFowKzEpMCcGA1UEAwwgbGliaHYtYXBwbGV0bHMtdGVzdC1pbnRlcm1lZGlh +dGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD2Sl9gWEHIQhObIrRG +JxqX9f1SUu90pSzkeSZqLIazFa+yd09rsoDvTHKtbyZ86ybFpB5NG0LlD8RUFTEi +o5BjW2E5Gl3IkcdAckydO3DbYEvPzR+Of3uC2up/oHZGOx7LvKJA/2jgRUPPGV0X +lGZYchwUg8lqczzME4UT7wWX4zdQIdxOlt8ANjLs6/Kqrkg/NT7RTH7+yxG4aWyZ +7ch6aR8X3r4PTj0enKGrMgcqLnoDGHefwNzA6Fn4OwSbquQGCTRulm7JIPPMLbNo +OBPIw+TeJRIxNvc1051NujbHlDMwISPsuuJe0Mus4pzad6NFtUFdWUG5CoAHkbnL +TGmDAgMBAAGjZjBkMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEG +MB0GA1UdDgQWBBSIuIWrJdxJfk0S9ZFxv4YDZ2NN5zAfBgNVHSMEGDAWgBT17eOZ +S6KUYHJ0NbsfdmXs32V2HDANBgkqhkiG9w0BAQsFAAOCAQEAM6T7H8KLo9fzkcDQ +/bLaSO8McclVrgFgaI5L8wBumX5aGAa8TmfpVVPseM54J4a5qx6zuI83QBWRfqv2 +tcX8da04cOoKHE935VsbSgh6V/h47EPheWR9v2AwNGF7EWFYZCZT+KDBSwdCn7ms +NuWiocBAzZO5sitvy/1vmPbU+4tZ8PODlRO6DF2XKthb0upkrXkMMerbYqH4L/hF +Ujw4L53/3gmEvw/kB+//53IZOvrEC5QJpyy8ED9jLnzquNVLJEWf2RlLLfYIDMfD +0bq1On8LiToqK4wS0Ykc7P97L292jERDkMYbIaDROaPKC+SzqgluFJI/O2LCPGRS +y35Nrg== +-----END CERTIFICATE----- diff --git a/unittest/fixtures/appletls/malformed-base64.pem b/unittest/fixtures/appletls/malformed-base64.pem new file mode 100644 index 000000000..3a7cb2100 --- /dev/null +++ b/unittest/fixtures/appletls/malformed-base64.pem @@ -0,0 +1,3 @@ +-----BEGIN CERTIFICATE----- +not-base64! +-----END CERTIFICATE----- diff --git a/unittest/fixtures/appletls/malformed-pkcs8.pem b/unittest/fixtures/appletls/malformed-pkcs8.pem new file mode 100644 index 000000000..bb29ed3cb --- /dev/null +++ b/unittest/fixtures/appletls/malformed-pkcs8.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MAMCAQE= +-----END PRIVATE KEY----- diff --git a/unittest/fixtures/appletls/multi-ca.pem b/unittest/fixtures/appletls/multi-ca.pem new file mode 100644 index 000000000..5143925e7 --- /dev/null +++ b/unittest/fixtures/appletls/multi-ca.pem @@ -0,0 +1,38 @@ +-----BEGIN CERTIFICATE----- +MIIDBDCCAeygAwIBAgICAQAwDQYJKoZIhvcNAQELBQAwIzEhMB8GA1UEAwwYbGli +aHYtYXBwbGV0bHMtdGVzdC1yb290MB4XDTI2MTAwMTAwMDAwMFoXDTQ2MTAwMTAw +MDAwMFowIzEhMB8GA1UEAwwYbGliaHYtYXBwbGV0bHMtdGVzdC1yb290MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvqo6cJBRm1lKsQX5Fsz+jnnzfDqW +jl0M7fSs0zDVW8WbPk+6OkysFvgIzPclV7jkFrEYcB4UuHUifSmsFROf+CCqL4m2 +mFaSrmStqasjiNN/r3DW8XgjMn4KWgUae1X6xvjhfOI5qOEui0Zn7/x3HbwD/Ha0 +1WyjtY5+ZLvkn11dSlioWVBnk6vEnxtEphTbhGWi68cgF1RLElU0VeyXzjqkewTW +qtbqToAfY7wkRDBvcLg2p2tsLyyeAPWJCxmBKDZO+J6AYz5idi/0RdlFSXqBmEPU +O1qzTDEO/K78GS614e1DFNhYhNgk6b+wqWGvfr7qxqE+5D5IJ9fkU7zewwIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU +9e3jmUuilGBydDW7H3Zl7N9ldhwwDQYJKoZIhvcNAQELBQADggEBAHbaSvL/Ai19 +hA/h/0/nHXwRjspzcD8BsRkgwtE1uA6TOly5qzecRD+1yuNxSM3IvdVxhTdOrCyu +HsPgQCDczz09JqkSgX1F/pSgsIdC+ygl+GFJW+Om+84lXrNfn0EFiWmj9e8qsOR/ +3nAmn9Wn/RC4WMypHBCQyxNMJ74X1Zzq8VToki8iThA62nmYb+7jbwkHFj7jMF6f +y/75EwjZ7zZyK4ObpEgcOdp32LOl6/H2ERduN48WI2WRCLKmZLvsVEsp7WxPzWJc +2WZIFHptfRO8byuE/911PgDskTJedCuIActXE8zt4LBEIRVXIMsUUS6fO7ZJSBgx +JgqB1zRUvZE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDBjCCAe6gAwIBAgICQAAwDQYJKoZIhvcNAQELBQAwJDEiMCAGA1UEAwwZbGli +aHYtYXBwbGV0bHMtd3Jvbmctcm9vdDAeFw0yNjEwMDEwMDAwMDBaFw00NjEwMDEw +MDAwMDBaMCQxIjAgBgNVBAMMGWxpYmh2LWFwcGxldGxzLXdyb25nLXJvb3QwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCadHYZpthCrE4ajMa+5sSAY+dc +cKhrbj804NNizFnBn13K+mdEtwhW6TssACxPe0Hp9M1D4+rYbSE9bRv+mCTdtO6J +oK/5q3aT+WxDCuPBMhY4aubPk/OA+mMGvciqyET9raIf3jrO0XHc1Iw7Ilknqr0N +IQ7gL9JN7uJtsW7hB3t4KgsdzXMpqr/oGbowjC2XyMGh0AWQcQszHoN6pTQQBqEM +KJVXrRIA6pVaxAgrFXfQg4hvk+qjQObi81TMbnl5Itfv4cgq4cKSS8EMwYVI/YP5 +R6M4+FO4xJzKSPQbX/NefUx7oj6lD5kx3qCJL7FciqBG9k7CkWhFMXkZ4izxAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQW +BBTOFmrv/JS9Ia81lBQKjaFQZBaqnDANBgkqhkiG9w0BAQsFAAOCAQEALslwHXii +hmqp7A8L/cDMvTCZSJu12JD27kmUTocnLQ+r8y/m31aCERd0QcXint/qEudb45RW +sXXTFv9P5Gg+YA0yc6aN/1Rw/43XKfjl/68P0GeNi6xlsmctYqtznBziuEVhes8Q +qukGOpxZ3ptLRHLURlt1U7MHleWA3Blr8SVAkSdaDpB1dqA9OGxA7i3FgVMvpXzh +ltyyF1kAtzDZpN8WNTlZ/J/qNpGc8sN8yaQf4rsIHeg1yrRwPRDhqYL0y6usBofr +GZszxv6YSYhr3svuEbzxfcQBn2SBhdLsR24vuzozCShmENsNzUtmzLSsPpzfcqJb +dswNO4O4e3pIAg== +-----END CERTIFICATE----- diff --git a/unittest/fixtures/appletls/root.crt b/unittest/fixtures/appletls/root.crt new file mode 100644 index 000000000..ca46c5887 --- /dev/null +++ b/unittest/fixtures/appletls/root.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBDCCAeygAwIBAgICAQAwDQYJKoZIhvcNAQELBQAwIzEhMB8GA1UEAwwYbGli +aHYtYXBwbGV0bHMtdGVzdC1yb290MB4XDTI2MTAwMTAwMDAwMFoXDTQ2MTAwMTAw +MDAwMFowIzEhMB8GA1UEAwwYbGliaHYtYXBwbGV0bHMtdGVzdC1yb290MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvqo6cJBRm1lKsQX5Fsz+jnnzfDqW +jl0M7fSs0zDVW8WbPk+6OkysFvgIzPclV7jkFrEYcB4UuHUifSmsFROf+CCqL4m2 +mFaSrmStqasjiNN/r3DW8XgjMn4KWgUae1X6xvjhfOI5qOEui0Zn7/x3HbwD/Ha0 +1WyjtY5+ZLvkn11dSlioWVBnk6vEnxtEphTbhGWi68cgF1RLElU0VeyXzjqkewTW +qtbqToAfY7wkRDBvcLg2p2tsLyyeAPWJCxmBKDZO+J6AYz5idi/0RdlFSXqBmEPU +O1qzTDEO/K78GS614e1DFNhYhNgk6b+wqWGvfr7qxqE+5D5IJ9fkU7zewwIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU +9e3jmUuilGBydDW7H3Zl7N9ldhwwDQYJKoZIhvcNAQELBQADggEBAHbaSvL/Ai19 +hA/h/0/nHXwRjspzcD8BsRkgwtE1uA6TOly5qzecRD+1yuNxSM3IvdVxhTdOrCyu +HsPgQCDczz09JqkSgX1F/pSgsIdC+ygl+GFJW+Om+84lXrNfn0EFiWmj9e8qsOR/ +3nAmn9Wn/RC4WMypHBCQyxNMJ74X1Zzq8VToki8iThA62nmYb+7jbwkHFj7jMF6f +y/75EwjZ7zZyK4ObpEgcOdp32LOl6/H2ERduN48WI2WRCLKmZLvsVEsp7WxPzWJc +2WZIFHptfRO8byuE/911PgDskTJedCuIActXE8zt4LBEIRVXIMsUUS6fO7ZJSBgx +JgqB1zRUvZE= +-----END CERTIFICATE----- diff --git a/unittest/fixtures/appletls/root.der b/unittest/fixtures/appletls/root.der new file mode 100644 index 000000000..15aa0fb50 Binary files /dev/null and b/unittest/fixtures/appletls/root.der differ diff --git a/unittest/fixtures/appletls/server-chain.pem b/unittest/fixtures/appletls/server-chain.pem new file mode 100644 index 000000000..c30cb4e62 --- /dev/null +++ b/unittest/fixtures/appletls/server-chain.pem @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIDSDCCAjCgAwIBAgICIAAwDQYJKoZIhvcNAQELBQAwKzEpMCcGA1UEAwwgbGli +aHYtYXBwbGV0bHMtdGVzdC1pbnRlcm1lZGlhdGUwHhcNMjYxMDAxMDAwMDAwWhcN +MjcxMDAxMDAwMDAwWjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCeokqQLxGsQrZkqkghZM1vrdh2NBp3b1h5N/ea +s6P+Yg6XbGNm2gg2g+vNNfJgUE772L/huvTcJr04qqI6uIP0gN4uAtog9604rgaC +TdyB3oTKuU/Uf3S5SPbpGuMIL3I4hoLp8ND+S+074u2B/oEg2ZtlMqDlriJbpFFD +tFl4atlyxur5j3GB8iZ9nfsP1Pc7NPEL565fQPILx3eKG8PpRCQTX4pQZD2fNqBZ +Pl+dGbvtPNe/TS/Z2pB1o1G4MEfMGO0HZbOIU9e08da0IZ3r6kl97Hi+aemNnACH +Zu95Pe+J+RfW4R8IBL23hYHijlc/qaeRXGZYU4PDjepVppgTAgMBAAGjgYwwgYkw +DAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH +AwEwFAYDVR0RBA0wC4IJbG9jYWxob3N0MB0GA1UdDgQWBBQDVKB33y0wpzacDhGt +h/ThhnSkZDAfBgNVHSMEGDAWgBSIuIWrJdxJfk0S9ZFxv4YDZ2NN5zANBgkqhkiG +9w0BAQsFAAOCAQEAni/iXfN+7uJATUqtIyZM4nOIKulHczuXYrqQP4MXy5FK1yYZ +lhh/q5P0EPn05D5on8HbNmhTebukOziUi7CxusxD46RP/GBkEGGCjauIQPupENOg +fdNyRDjjOpn5A7CqgUcuZFpPCaSSGAX22VcEmXQBbAmGLtdszZo/9s6wty4sqtXn +xMmRwgrYNvad/JNxnbS1xVxCCJFMLMp+JJU4w62TpibmIsINUOmip/wkA27rcb5+ +sfsul/LEVZWx4+Zkom4qEmYWlgHZf75gNHID/LbLfbNlX07AIuWkIiozTvBzrXxt +wnWXghfCUmDhPJtalBKTVjKifGnoxOuM+p51aA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwIzEhMB8GA1UEAwwYbGli +aHYtYXBwbGV0bHMtdGVzdC1yb290MB4XDTI2MTAwMTAwMDAwMFoXDTQ2MTAwMTAw +MDAwMFowKzEpMCcGA1UEAwwgbGliaHYtYXBwbGV0bHMtdGVzdC1pbnRlcm1lZGlh +dGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD2Sl9gWEHIQhObIrRG +JxqX9f1SUu90pSzkeSZqLIazFa+yd09rsoDvTHKtbyZ86ybFpB5NG0LlD8RUFTEi +o5BjW2E5Gl3IkcdAckydO3DbYEvPzR+Of3uC2up/oHZGOx7LvKJA/2jgRUPPGV0X +lGZYchwUg8lqczzME4UT7wWX4zdQIdxOlt8ANjLs6/Kqrkg/NT7RTH7+yxG4aWyZ +7ch6aR8X3r4PTj0enKGrMgcqLnoDGHefwNzA6Fn4OwSbquQGCTRulm7JIPPMLbNo +OBPIw+TeJRIxNvc1051NujbHlDMwISPsuuJe0Mus4pzad6NFtUFdWUG5CoAHkbnL +TGmDAgMBAAGjZjBkMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEG +MB0GA1UdDgQWBBSIuIWrJdxJfk0S9ZFxv4YDZ2NN5zAfBgNVHSMEGDAWgBT17eOZ +S6KUYHJ0NbsfdmXs32V2HDANBgkqhkiG9w0BAQsFAAOCAQEAM6T7H8KLo9fzkcDQ +/bLaSO8McclVrgFgaI5L8wBumX5aGAa8TmfpVVPseM54J4a5qx6zuI83QBWRfqv2 +tcX8da04cOoKHE935VsbSgh6V/h47EPheWR9v2AwNGF7EWFYZCZT+KDBSwdCn7ms +NuWiocBAzZO5sitvy/1vmPbU+4tZ8PODlRO6DF2XKthb0upkrXkMMerbYqH4L/hF +Ujw4L53/3gmEvw/kB+//53IZOvrEC5QJpyy8ED9jLnzquNVLJEWf2RlLLfYIDMfD +0bq1On8LiToqK4wS0Ykc7P97L292jERDkMYbIaDROaPKC+SzqgluFJI/O2LCPGRS +y35Nrg== +-----END CERTIFICATE----- diff --git a/unittest/fixtures/appletls/server-pkcs1.key b/unittest/fixtures/appletls/server-pkcs1.key new file mode 100644 index 000000000..0204e9496 --- /dev/null +++ b/unittest/fixtures/appletls/server-pkcs1.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAnqJKkC8RrEK2ZKpIIWTNb63YdjQad29YeTf3mrOj/mIOl2xj +ZtoINoPrzTXyYFBO+9i/4br03Ca9OKqiOriD9IDeLgLaIPetOK4Ggk3cgd6EyrlP +1H90uUj26RrjCC9yOIaC6fDQ/kvtO+Ltgf6BINmbZTKg5a4iW6RRQ7RZeGrZcsbq ++Y9xgfImfZ37D9T3OzTxC+euX0DyC8d3ihvD6UQkE1+KUGQ9nzagWT5fnRm77TzX +v00v2dqQdaNRuDBHzBjtB2WziFPXtPHWtCGd6+pJfex4vmnpjZwAh2bveT3vifkX +1uEfCAS9t4WB4o5XP6mnkVxmWFODw43qVaaYEwIDAQABAoIBAAbmiA157AVic4ex +JpPD9vHJjgB6UhUUSV912/X6pC1v0N2tD9fiBSMbSE8ew80vwSJ7HRcHKG6ehXL+ +bTHWY/BnXgLXX0C5isChRVQ2vzBWYHq8ZpNs+0QHagIy3WG2RVGIfGULyN4+/vIG +HuRy5eVqwd6Rk2aFACactmeWY/afxwD6YlJYLXSLYWjZ713nSBpqd+dibaFvx6Mh +iIPwrXXUQrMjLerEmrjG+fSbweRE4BLKvVIOKqH/mHHRtnPPGJvh5FQcjouHv9+V +1VhQmfEaxQXRdrLOfADRykxkTxIDEm07XMaihRrnOTDnoc8zeBEcVyIY5QnU0YJz +qJaGH3UCgYEA2iuiSMPEZn02rwR4T+AjiRF51ZfrgkL1f5ruhTkqd0AnpzICBV8p +KvM3MgTfNzgN40TQ7zCJDsizd+3bYZqOBh3NgNE6umuOXDCIaEhhmVuZt/ySP7uy +IV3KPI6nxgjOjqe2g4EqV5kFRVN7dQZ4AB4qvdU6fC6uz75fhKaklPUCgYEAuiPk +ciHuzP4pbci8w8K5d3gl9+vOWAi2NejoTcsJNa/ENqb6Mo6dhdGiqdQLuF5zuigG +2GW+kyA/jlalr3lX7QhsD3bwDSUBpBsepovnFkPzw+8h0BoYDEGtiUl+5k0WLy7g +gQdHTYY559GnauDpT/hBuTgvs5cD/lWBdQH+E+cCgYEAnI37IiP452KqvuSCQS8h +0NYNi0w833iVGkBfMJJY+sG6squb76bdr8hfrzD3zHQY4yEanMlDUKyMvF6esmdp +ytHQCcQaVHOh8F2TROHLx3SH1hMUD0/s/yyCVvo3QlBHiO6oda1WaQ2RTrIdQVim ++86p9RbxpPMK1mBRwqqiJ5ECgYBr5cRVWFAZabbQGY06GJTzqKSxqnGo3Bvq8gA+ +n9PN1/3wBCiBx+ONn3Af63eDvtRscylMpOWsWQQeWSGfrsIF91zUNgh+RhZGbyX8 +OpqQcIFL8UDzZTzNVgDm1MCHdOXIIe8nO/Xb/VNxijj2njsADRLj76rO+lnpQt1Q +2v/RvwKBgBon5F7DSI/ZVoXRda3JWS8k5eFt3L09brQl/wm/edkI+FZIfuD0wNb/ +CY4lecMpH9esWle1T1JsTL8SryIbI5PFRNkvzrsOmi/YsALnvdospeI7gCQ4eLo9 +1Q0dJKXa73tuKq5bP+5Atc3rdfCRtNnKUGqG8ZJWs45bUiykq7k2 +-----END RSA PRIVATE KEY----- diff --git a/unittest/fixtures/appletls/server-pkcs8.key b/unittest/fixtures/appletls/server-pkcs8.key new file mode 100644 index 000000000..45f4d42af --- /dev/null +++ b/unittest/fixtures/appletls/server-pkcs8.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCeokqQLxGsQrZk +qkghZM1vrdh2NBp3b1h5N/eas6P+Yg6XbGNm2gg2g+vNNfJgUE772L/huvTcJr04 +qqI6uIP0gN4uAtog9604rgaCTdyB3oTKuU/Uf3S5SPbpGuMIL3I4hoLp8ND+S+07 +4u2B/oEg2ZtlMqDlriJbpFFDtFl4atlyxur5j3GB8iZ9nfsP1Pc7NPEL565fQPIL +x3eKG8PpRCQTX4pQZD2fNqBZPl+dGbvtPNe/TS/Z2pB1o1G4MEfMGO0HZbOIU9e0 +8da0IZ3r6kl97Hi+aemNnACHZu95Pe+J+RfW4R8IBL23hYHijlc/qaeRXGZYU4PD +jepVppgTAgMBAAECggEABuaIDXnsBWJzh7Emk8P28cmOAHpSFRRJX3Xb9fqkLW/Q +3a0P1+IFIxtITx7DzS/BInsdFwcobp6Fcv5tMdZj8GdeAtdfQLmKwKFFVDa/MFZg +erxmk2z7RAdqAjLdYbZFUYh8ZQvI3j7+8gYe5HLl5WrB3pGTZoUAJpy2Z5Zj9p/H +APpiUlgtdIthaNnvXedIGmp352JtoW/HoyGIg/CtddRCsyMt6sSauMb59JvB5ETg +Esq9Ug4qof+YcdG2c88Ym+HkVByOi4e/35XVWFCZ8RrFBdF2ss58ANHKTGRPEgMS +bTtcxqKFGuc5MOehzzN4ERxXIhjlCdTRgnOoloYfdQKBgQDaK6JIw8RmfTavBHhP +4COJEXnVl+uCQvV/mu6FOSp3QCenMgIFXykq8zcyBN83OA3jRNDvMIkOyLN37dth +mo4GHc2A0Tq6a45cMIhoSGGZW5m3/JI/u7IhXco8jqfGCM6Op7aDgSpXmQVFU3t1 +BngAHiq91Tp8Lq7Pvl+EpqSU9QKBgQC6I+RyIe7M/iltyLzDwrl3eCX3685YCLY1 +6OhNywk1r8Q2pvoyjp2F0aKp1Au4XnO6KAbYZb6TID+OVqWveVftCGwPdvANJQGk +Gx6mi+cWQ/PD7yHQGhgMQa2JSX7mTRYvLuCBB0dNhjnn0adq4OlP+EG5OC+zlwP+ +VYF1Af4T5wKBgQCcjfsiI/jnYqq+5IJBLyHQ1g2LTDzfeJUaQF8wklj6wbqyq5vv +pt2vyF+vMPfMdBjjIRqcyUNQrIy8Xp6yZ2nK0dAJxBpUc6HwXZNE4cvHdIfWExQP +T+z/LIJW+jdCUEeI7qh1rVZpDZFOsh1BWKb7zqn1FvGk8wrWYFHCqqInkQKBgGvl +xFVYUBlpttAZjToYlPOopLGqcajcG+ryAD6f083X/fAEKIHH442fcB/rd4O+1Gxz +KUyk5axZBB5ZIZ+uwgX3XNQ2CH5GFkZvJfw6mpBwgUvxQPNlPM1WAObUwId05cgh +7yc79dv9U3GKOPaeOwANEuPvqs76WelC3VDa/9G/AoGAGifkXsNIj9lWhdF1rclZ +LyTl4W3cvT1utCX/Cb952Qj4Vkh+4PTA1v8JjiV5wykf16xaV7VPUmxMvxKvIhsj +k8VE2S/Ouw6aL9iwAue92iyl4juAJDh4uj3VDR0kpdrve24qrls/7kC1zet18JG0 +2cpQaobxklazjltSLKSruTY= +-----END PRIVATE KEY----- diff --git a/unittest/fixtures/appletls/server.crt b/unittest/fixtures/appletls/server.crt new file mode 100644 index 000000000..171f9d3ed --- /dev/null +++ b/unittest/fixtures/appletls/server.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSDCCAjCgAwIBAgICIAAwDQYJKoZIhvcNAQELBQAwKzEpMCcGA1UEAwwgbGli +aHYtYXBwbGV0bHMtdGVzdC1pbnRlcm1lZGlhdGUwHhcNMjYxMDAxMDAwMDAwWhcN +MjcxMDAxMDAwMDAwWjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCeokqQLxGsQrZkqkghZM1vrdh2NBp3b1h5N/ea +s6P+Yg6XbGNm2gg2g+vNNfJgUE772L/huvTcJr04qqI6uIP0gN4uAtog9604rgaC +TdyB3oTKuU/Uf3S5SPbpGuMIL3I4hoLp8ND+S+074u2B/oEg2ZtlMqDlriJbpFFD +tFl4atlyxur5j3GB8iZ9nfsP1Pc7NPEL565fQPILx3eKG8PpRCQTX4pQZD2fNqBZ +Pl+dGbvtPNe/TS/Z2pB1o1G4MEfMGO0HZbOIU9e08da0IZ3r6kl97Hi+aemNnACH +Zu95Pe+J+RfW4R8IBL23hYHijlc/qaeRXGZYU4PDjepVppgTAgMBAAGjgYwwgYkw +DAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH +AwEwFAYDVR0RBA0wC4IJbG9jYWxob3N0MB0GA1UdDgQWBBQDVKB33y0wpzacDhGt +h/ThhnSkZDAfBgNVHSMEGDAWgBSIuIWrJdxJfk0S9ZFxv4YDZ2NN5zANBgkqhkiG +9w0BAQsFAAOCAQEAni/iXfN+7uJATUqtIyZM4nOIKulHczuXYrqQP4MXy5FK1yYZ +lhh/q5P0EPn05D5on8HbNmhTebukOziUi7CxusxD46RP/GBkEGGCjauIQPupENOg +fdNyRDjjOpn5A7CqgUcuZFpPCaSSGAX22VcEmXQBbAmGLtdszZo/9s6wty4sqtXn +xMmRwgrYNvad/JNxnbS1xVxCCJFMLMp+JJU4w62TpibmIsINUOmip/wkA27rcb5+ +sfsul/LEVZWx4+Zkom4qEmYWlgHZf75gNHID/LbLfbNlX07AIuWkIiozTvBzrXxt +wnWXghfCUmDhPJtalBKTVjKifGnoxOuM+p51aA== +-----END CERTIFICATE----- diff --git a/unittest/fixtures/appletls/wrong-root.crt b/unittest/fixtures/appletls/wrong-root.crt new file mode 100644 index 000000000..cd989adea --- /dev/null +++ b/unittest/fixtures/appletls/wrong-root.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBjCCAe6gAwIBAgICQAAwDQYJKoZIhvcNAQELBQAwJDEiMCAGA1UEAwwZbGli +aHYtYXBwbGV0bHMtd3Jvbmctcm9vdDAeFw0yNjEwMDEwMDAwMDBaFw00NjEwMDEw +MDAwMDBaMCQxIjAgBgNVBAMMGWxpYmh2LWFwcGxldGxzLXdyb25nLXJvb3QwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCadHYZpthCrE4ajMa+5sSAY+dc +cKhrbj804NNizFnBn13K+mdEtwhW6TssACxPe0Hp9M1D4+rYbSE9bRv+mCTdtO6J +oK/5q3aT+WxDCuPBMhY4aubPk/OA+mMGvciqyET9raIf3jrO0XHc1Iw7Ilknqr0N +IQ7gL9JN7uJtsW7hB3t4KgsdzXMpqr/oGbowjC2XyMGh0AWQcQszHoN6pTQQBqEM +KJVXrRIA6pVaxAgrFXfQg4hvk+qjQObi81TMbnl5Itfv4cgq4cKSS8EMwYVI/YP5 +R6M4+FO4xJzKSPQbX/NefUx7oj6lD5kx3qCJL7FciqBG9k7CkWhFMXkZ4izxAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQW +BBTOFmrv/JS9Ia81lBQKjaFQZBaqnDANBgkqhkiG9w0BAQsFAAOCAQEALslwHXii +hmqp7A8L/cDMvTCZSJu12JD27kmUTocnLQ+r8y/m31aCERd0QcXint/qEudb45RW +sXXTFv9P5Gg+YA0yc6aN/1Rw/43XKfjl/68P0GeNi6xlsmctYqtznBziuEVhes8Q +qukGOpxZ3ptLRHLURlt1U7MHleWA3Blr8SVAkSdaDpB1dqA9OGxA7i3FgVMvpXzh +ltyyF1kAtzDZpN8WNTlZ/J/qNpGc8sN8yaQf4rsIHeg1yrRwPRDhqYL0y6usBofr +GZszxv6YSYhr3svuEbzxfcQBn2SBhdLsR24vuzozCShmENsNzUtmzLSsPpzfcqJb +dswNO4O4e3pIAg== +-----END CERTIFICATE----- diff --git a/unittest/fixtures/appletls/wrong-server.key b/unittest/fixtures/appletls/wrong-server.key new file mode 100644 index 000000000..033373b9e --- /dev/null +++ b/unittest/fixtures/appletls/wrong-server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuz3le6CgeKyCzN5/1l9hawkG7RwYDiePzxh5gRKhVGGjzghR +o1DT7u8/YpluTSSpy32oud9BSGI1QYPgr7IREpjPECqgmlfU3nE5X6Gh1ha1M19w +eMZ+z+V+aV7qDQ8PWBn7wYWoKvxlSGfHt10mk+gv3RXK9EM+7yN72qqWEex9GHtb +7xd/BvBFGR170yqUYWefgF9YoqVPPTJ8qvM9yUqvEV4nmQeKELrQvXOD2vebtKDk +3BT3W6/JBqTigyOJOK0ybIo5/0cDLKl5QD9AzGVNrTQWETtVR6zpyYAbbMr2GCcU +pq9kWYhJFzdW+SQy4bLSAtAujXKo8CwxOnINgwIDAQABAoIBADehfr6Z7UOdQal7 +y/CV2xxRjMjC6ZS/BVeq9mK2dc9byS7MQvEqcSVS2PrDCkYKSBZpCcnbvooyf48t +DwM0fSUQ2Kh9ecURpnHGoxy6KZqxmXmfeAuyMfgzv7iv1gs2N8FdyvJm8x1+ats1 +1l0w5Gbae+oMcQXcuuSTHtfLihXdsuCW4kBaGTgVqFzN4KYjJUPWShctxhRf6AKc +jCOonmJX02o6ZGkZpeauB/rjIEXBuHY77fKd55KzqqW5Ub3jfmL5kK+yMwarfYSZ +YmW1yrdiJHjy/TBb3J8V2JZ38SaPcl6Vgs1KmgQ6Rga3TidzQwjhWs4fUIjZ3Lm1 +QhcnmhUCgYEA5Fc5qy/2MeXsDisoJYSqnkQ6DsQ9DJVB2fmB04dSkkdrWlekj1kM +JTl6OP3nHp0Mf9lrRsQcDfwt6wqq8rr+yQ/XjEpZOLgqlg4odcynKHcMFMJ4WZbV +tTv+Zpi1Ll/48QG8WNW9LJj2kOECtrvcPpFYdT/HH46P6vEBgo7lniUCgYEA0ew0 +ruIVoBzy7te8bdwsLkRPgaKwXV2ReA7ZjxxzymksFQuVVsGTSujpAlXf5+H8MpUL +B9nTjqrpQqst5Uzy+jm6yWKLALN1botitx3KpCrEERXqo0om32/F/4B8Qb7oUZ5I +sJ4ATRsa6NnLaKlwfp90tRR4DAq4QVoJDBp4iIcCgYB2HqEYazEiUtwv4CdecSiJ +wqV96gnirntoNOfx8rjGLufbzia0eM4Nm5QcU0vxzDAhGumhq+UPXvqp8dTIx+7x +N567VeKdsrdk9GH9viZiimLXglobjilzHVvW2vwuxzrSAaHbGoZHwcEa5SswODIQ +s8Ncs+df7bGJSa8MSLuDeQKBgQDJiKGTfA4S53f7ATcvqX7o9D/4Tv5OxHPR9T+8 ++y3XF11w3uwCnFkSQ8Tfrx4E+poq3Zf3c5J9l2vVy42GZHeo7x96E5crlx6YgEzH +TFytezk8xfO7zIbBYAeQogOpAuT65qEWnrnfTLRq9B0qX/FWPzPGXN5bgTcf1pZ0 +/jzVpQKBgQDZHgo80xUGlPdUDG2G32E/IcWyQ+vNNYHIOwuomVePJ4MIryTQp/Mr +DJW4FXd8swxW5NXJIDdJFVeR2JjpOO5qLR1SzDfdeHBjhJyIN+fB7tCDLxA8JM1t +N7nKhtmfUiLc+Wgl0Mp57jC6rbXE7t7C4AlPXjFXWsn2iYGjdYzBtw== +-----END RSA PRIVATE KEY-----