diff --git a/pjlib/build/clicert.der b/pjlib/build/clicert.der new file mode 100644 index 0000000000..55fa38aa0d Binary files /dev/null and b/pjlib/build/clicert.der differ diff --git a/pjlib/build/clicert.pem b/pjlib/build/clicert.pem new file mode 100644 index 0000000000..110b758f43 --- /dev/null +++ b/pjlib/build/clicert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEPjCCAiagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlwanNp +cC5sYWIwHhcNMjYxMDA4MTAwNTEzWhcNMzAxMjA2MTAwNTEzWjAbMRkwFwYDVQQD +DBBjbGllbnQucGpzaXAubGFiMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAp/pwuY8gnNAkWTTTpEazOnmSM5ywqfCFW4h0sWlbyzpWVkPnyrYcn+SanQZk +wZYolpazWx9+q5GA7ilBkCogcG44Qu5HWytW25iKMLgcPf/JhgGpvwW1G1UQseNK +l3PtVA68jyvpTSFiIi1FtgC5Q9gkxq7IzfdvXd/1yiTdfKd1w3MOjF57RJchIZL+ +x3mK2itYQ3RyZ80LN5DT4cmK3hS5j0rktJHJuRKZt9LKvef6O1upM9iEnSSaJA0i +30um8OO3mc5HCvX3VNYxw0dPUBHb9gQm4574N7/t0OtrIEMQkHpO+5BHHnB5dx2A +kX73kOxoudiRGsAfH2btpaDToQIDAQABo4GTMIGQMAwGA1UdEwEB/wQCMAAwDgYD +VR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMBsGA1UdEQQUMBKCEGNs +aWVudC5wanNpcC5sYWIwHQYDVR0OBBYEFGmTl3GUTyJdbS//27kRPoc9LmqgMB8G +A1UdIwQYMBaAFF6rnJ5vs8hTda7vbDQYWmekykW3MA0GCSqGSIb3DQEBCwUAA4IC +AQAgLvWuAIPpiinnZI2ROBcNd/u1S2W1kQ2qqhbwCpo/w5gaBjQyC+lhqJcPIrop +/8VLc3F2aa0PwLKEyx5PHSFXfjbZxI2RqPKE5+9nh5ROO7EkLZKwJSmQb9jQ6EmF +dFYMTbX42T+mvDOPLh/QembVfnyUDgzrteCTwO9pK6y4KHUh8DZdHYOOobrgJ2Ly +3y6G0kU4VDKtOOvAA2Lpjn/Dg0PtjMbRpYUdPGVs0Y93p3IgrRTSKD3NAECxtEZN +rnvTOjmY32J1C9jLA1hJa9nvNbON7sQglofJbhgygC4RU1ZsacArkG91dT7wTbOT +P8yGkuVfJxTtgvuUuI8hz8Y/6fF1MV3ayxNV/X6YSJdgukljQtzYtqsy26kFBLVq +m4EWdjtpTeW4YPYWYxhRhr4m8njBG68sfJTaOssoF0k67rehPBOGzLtF95qcFNls +xxQNrqbVWRdr+8a7QY3GT7tX0lfIk3Epo0VsjrF5OEM8/1dV2pSFGWbCVvMaKUPx ++zqzQKNCaUwqrQZCuMSGDOAmOb2VWhZZOwmKt+J0cMi5qI2uW5XpaVUBKM9bzPjs +Liqn1Q7t5XYZkC7j2JkHbn4k2b+s99Qz1AtGxnGnvarXyRbIE1bawC8br4jtucMs +WR0z5tKFtHBKCkiPPadxNiD7hrvlc8c0y8geRCZ807lAmQ== +-----END CERTIFICATE----- diff --git a/pjlib/build/cliprivkey.pem b/pjlib/build/cliprivkey.pem new file mode 100644 index 0000000000..6b46e4dfbf --- /dev/null +++ b/pjlib/build/cliprivkey.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ9rtm/f1rrMTBckut +zVwvhAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEM+p44rDxIWJMaBG +UqePmlgEggTQQblz8oxV7H1FBxNQVPA8sn6Yae7/gV52JlvMmtLPFmMx9p5fXtkW +B4mQzHM5b2Va262XKJZJHd/WnfleKfNH3kfzALyTov+JyOiMmxu3PlNaDbnZoNNH +b6CCIzjxuzDqRfOsGtTZp1zYJu/EhDe2o2ChqBoDAy9Kp9FYr5zX+IQmqV0CKl7H +x5a0IfK6IUzY+z3pKHbrjRiA0eGHtA72qypYZbZ+JEdTk+IDcjXK62AcSMXf3BKm +8uXRhVYVgewmm7mnb51qhyfCSOg/h/f9kyBqHZ3L30WfBjZfhFFDXgOK4GpmWxhz +n2d03AWTrt1XMLMfeY1M/Fm7Os1oAR5uoq7xuO/7L14rkO9e+3SOtTD4mBuqd7gh +VHhitLc3odpCFY2Wm+hBHESkyOOzGFd9LLO5hPx4kSNXduT2xfEjSCF6tnbIMqGO +3Use2NtxZYjQ1axvDzaj1ANR/o/HiT9SsXSLbtZ3o9uJBLG+uhB88d/6+AnHzTdC +OYVykXxQ+xhagKlC5QCgyP+2MB66rLHV2aq0U/oHLv8zcCQ2Ywmy6tedCgQ+qy/p +IJFFTeBHtRN+8zfrVY5iXrZC0zrrV8paxjAE5ldUyQtl2CLvNBpibUshK16GsUJX +MNJCloQNdafECZwLZLh1vfT0cRX+74lJiaXzenc+vudXYoHJIqJfDxp4fpm+eVuj +dUBLKHuv9IWIdEcyO1pvBJwtPZG6hwMp42Rn1cEngpnr37Q2mSE5N+FWptwmA7t3 +6tbtoglCv5Il2NFINv4l1YtIJPiiFw9Za2mF9ArX7xm0PvE6WA7iKirqQnoUlO6L +MQCuUZvNFn2CIefuj76MrSLvTIORYbOBrE2Rpys7ZlWbrDtPNsmW9WRcGYa3GBGp +/7TqYS6Ob1h9egiiht+OEKtUjLSTG6Ta1OH2e4fNOa920qpBUVQvVBZZ54Kug/WV +uriQtZ4QK4TBBC4GzceoT++Umc/SG3lzkYQO5q8Ertk0BbJ7LHPN/DDjB8xDJ70V +6VRUYoN4gSqhXAvkOqsV8SwcwDI3vKm0EusemjZBkTykl/eJD95sfbTNDwIJWN3z +3ib2djhXZ/++kvNOz3U/kAyb5uVjIPLEZtms3l5YCWtm+alaD1NxiEga7z0zD/+M +lGux/rlw6eO/oboZXlWNhpleZIlXSFERZxtuc55xY/rzvpWBpTc3Nw8OJeJXyXTx +yVWX0xKepKr0DVwDbljKUpFt+x8/noU9CHvxq6Z8L7WZsitw9cvCvEnvxu8PEkI/ +isD5TtdSJWwiTrAAWYo/pJHmsXTWb/1K18VPaJ/53p5aHABgTUdxygmXmnLr1EHA +0l0SVjKqbB7d7kcyN/dQ+0bUcsi9mvBTHodzhQBRwL+I5jo+cfx2scSYFq8O0sYL +eQqdD4WRQ6Fcl0BOxlPil7wVjkM0At9hMWsCyhsEx2P6AmtzM6NBg3lpabOm0DU4 +m3uLmNRWA1cRCeSWPEWDvlglWopCv04DMY8GQkt/G4TXhTk/C8ogouNbYRFbAJAB +rDwxSOTRNGTInH3fU1mTV1LfyY3TN6WGQWXS40dSiXm4kcVLHsad9wY+1b0k62pg +tzKAKMUXzWhJ0i9+w3G3xvf7GBtLkWJ2clHfdspTYAWsKilcoBsfx5c= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/pjlib/src/pj/ssl_sock_gtls.c b/pjlib/src/pj/ssl_sock_gtls.c index 02a1e53576..5f319f2020 100644 --- a/pjlib/src/pj/ssl_sock_gtls.c +++ b/pjlib/src/pj/ssl_sock_gtls.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2018 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2018-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2014-2017 Savoir-faire Linux. * (https://www.savoirfairelinux.com) * @@ -1141,9 +1141,13 @@ static void ssl_update_certs_info(pj_ssl_sock_t *ssock) const gnutls_datum_t *certs; unsigned int certslen = 0; int ret = GNUTLS_CERT_INVALID; + pj_bool_t updated = PJ_FALSE; pj_assert(ssock->ssl_state == SSL_STATE_ESTABLISHED); + /* Both raw chains live in info_pool, so release them together */ + pj_pool_reset(ssock->info_pool); + /* Get active local certificate */ us = gnutls_certificate_get_ours(gssock->session); if (!us) @@ -1159,17 +1163,18 @@ static void ssl_update_certs_info(pj_ssl_sock_t *ssock) goto us_out; tls_cert_get_info(ssock->pool, &ssock->local_cert_info, cert); - pj_pool_reset(ssock->info_pool); tls_cert_get_chain_raw(ssock->info_pool, &ssock->local_cert_info, us, 1); + updated = PJ_TRUE; us_out: tls_last_error = ret; if (cert) gnutls_x509_crt_deinit(cert); - else + if (!updated) pj_bzero(&ssock->local_cert_info, sizeof(pj_ssl_cert_info)); cert = NULL; + updated = PJ_FALSE; /* Get active remote certificate */ certs = gnutls_certificate_get_peers(gssock->session, &certslen); @@ -1187,15 +1192,15 @@ static void ssl_update_certs_info(pj_ssl_sock_t *ssock) goto peer_out; tls_cert_get_info(ssock->pool, &ssock->remote_cert_info, cert); - pj_pool_reset(ssock->info_pool); tls_cert_get_chain_raw(ssock->info_pool, &ssock->remote_cert_info, certs, certslen); + updated = PJ_TRUE; peer_out: tls_last_error = ret; if (cert) gnutls_x509_crt_deinit(cert); - else + if (!updated) pj_bzero(&ssock->remote_cert_info, sizeof(pj_ssl_cert_info)); } diff --git a/pjlib/src/pjlib-test/ssl_sock.c b/pjlib/src/pjlib-test/ssl_sock.c index cab1145941..420bc40f44 100644 --- a/pjlib/src/pjlib-test/ssl_sock.c +++ b/pjlib/src/pjlib-test/ssl_sock.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -66,7 +66,9 @@ struct test_state pj_pool_t *pool; /* pool */ pj_ioqueue_t *ioqueue; /* ioqueue */ pj_ssl_sock_t *accepted_ssock; /* accepted server-side socket */ + struct test_state *accepted_st; /* state of the accepted socket */ pj_bool_t is_server; /* server role flag */ + const char *cert_der; /* DER of this end's own certificate */ pj_bool_t is_verbose; /* verbose flag, e.g: cert info */ pj_bool_t echo; /* echo received data */ pj_status_t err; /* error flag */ @@ -117,6 +119,61 @@ static void dump_ssl_info(const pj_ssl_sock_info *si) } } +/* The local raw chain must hold the local certificate, in storage of its + * own rather than the peer chain's. der_name is the DER of the certificate + * this end presents, or NULL to compare the storage only. Backends that + * don't report the local raw chain, e.g. OpenSSL, leave cnt zero and are + * skipped. + */ +static pj_status_t check_raw_chain(const pj_ssl_sock_info *si, + pj_pool_t *pool, + const char *der_name) +{ + const pj_ssl_cert_info *lci = si->local_cert_info; + const pj_ssl_cert_info *rci = si->remote_cert_info; + pj_str_t der_file; + pj_oshandle_t fd = NULL; + pj_ssize_t size; + pj_str_t der; + pj_status_t status; + + if (!lci || lci->raw_chain.cnt == 0) + return PJ_SUCCESS; + + if (rci && rci->raw_chain.cnt && + lci->raw_chain.cert_raw == rci->raw_chain.cert_raw) + { + return PJ_EBUG; + } + + if (!der_name) + return PJ_SUCCESS; + + der_file = pj_str((char*)der_name); + size = (pj_ssize_t)pj_file_size(der_file.ptr); + if (size <= 0) + return PJ_ENOTFOUND; + + status = pj_file_open(pool, der_file.ptr, PJ_O_RDONLY, &fd); + if (status != PJ_SUCCESS) + return status; + + der.ptr = (char*)pj_pool_alloc(pool, size); + status = pj_file_read(fd, der.ptr, &size); + der.slen = size; + pj_file_close(fd); + if (status != PJ_SUCCESS) + return status; + + if (lci->raw_chain.cert_raw[0].slen != der.slen || + pj_memcmp(lci->raw_chain.cert_raw[0].ptr, der.ptr, der.slen)) + { + return PJ_EBUG; + } + + return PJ_SUCCESS; +} + static pj_bool_t ssl_on_connect_complete(pj_ssl_sock_t *ssock, pj_status_t status) @@ -138,6 +195,12 @@ static pj_bool_t ssl_on_connect_complete(pj_ssl_sock_t *ssock, goto on_return; } + status = check_raw_chain(&info, st->pool, st->cert_der); + if (status != PJ_SUCCESS) { + app_perror("...ERROR invalid local raw chain", status); + goto on_return; + } + pj_sockaddr_print((pj_sockaddr_t*)&info.local_addr, buf1, sizeof(buf1), 1); pj_sockaddr_print((pj_sockaddr_t*)&info.remote_addr, buf2, sizeof(buf2), 1); PJ_LOG(3, ("", "...Connected %s -> %s!", buf1, buf2)); @@ -218,6 +281,7 @@ static pj_bool_t ssl_on_accept_complete(pj_ssl_sock_t *ssock, /* Track accepted socket for cleanup */ parent_st->accepted_ssock = newsock; + parent_st->accepted_st = st; status = pj_ssl_sock_get_info(newsock, &info); if (status != PJ_SUCCESS) { @@ -225,6 +289,17 @@ static pj_bool_t ssl_on_accept_complete(pj_ssl_sock_t *ssock, goto on_return; } + status = check_raw_chain(&info, st->pool, st->cert_der); + if (status != PJ_SUCCESS) { + app_perror("...ERROR invalid local raw chain", status); + /* Report through the parent and keep echoing: closing from this + * callback leaves on_handshake_complete() using a released + * ssock->parent, and perf_test() waits for every client to finish. + */ + parent_st->err = status; + status = PJ_SUCCESS; + } + pj_sockaddr_print(src_addr, buf, sizeof(buf), 1); PJ_LOG(3, ("", "...Accepted connection from %s", buf)); @@ -691,6 +766,7 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, pj_sockaddr addr, listen_addr; pj_ssl_cipher ciphers[1]; pj_ssl_cert_t *cert = NULL; + pj_ssl_cert_t *cli_cert = NULL; pj_status_t status; pool = pj_pool_create(mem, "ssl_echo", 256, 256, NULL); @@ -731,6 +807,7 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, state_serv.echo = PJ_TRUE; state_serv.is_server = PJ_TRUE; state_serv.is_verbose = PJ_TRUE; + state_serv.cert_der = CERT_DER_FILE; status = pj_ssl_sock_create(pool, ¶m, &ssock_serv); if (status != PJ_SUCCESS) { @@ -834,6 +911,7 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, state_cli.pool = pool; state_cli.check_echo = PJ_TRUE; state_cli.is_verbose = PJ_TRUE; + state_cli.cert_der = CERT_DER_FILE; { /* srand() should be done centrally (blp) @@ -875,6 +953,8 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, } #else { + cli_cert = cert; + if (!client_provide_cert) { pj_str_t ca_file = pj_str(CERT_CA_FILE); pj_str_t null_str = pj_str(""); @@ -883,11 +963,12 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, #if (defined(TEST_LOAD_FROM_FILES) && TEST_LOAD_FROM_FILES==1) /* Reset any certificate & private keys previously set */ status = pj_ssl_cert_load_from_files(pool, &ca_file, &null_str, - &null_str, &null_str, &cert); + &null_str, &null_str, + &cli_cert); /* Also reset any direct backend objects */ pj_bzero(&cert_direct, sizeof(cert_direct)); - pj_ssl_cert_load_direct(pool, &cert_direct, &cert); + pj_ssl_cert_load_direct(pool, &cert_direct, &cli_cert); #else pj_ssl_cert_buffer null_buf, ca_buf; @@ -899,15 +980,53 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, } status = pj_ssl_cert_load_from_buffer(pool, &ca_buf, &null_buf, - &null_buf, &null_str, &cert); + &null_buf, &null_str, + &cli_cert); #endif if (status != PJ_SUCCESS) { goto on_return; } } +#if TEST_CLI_OWN_CERT + else { + /* A credential of its own, so each end's local certificate + * chain differs from the peer's in content as well as storage. + */ + pj_str_t ca_file = pj_str(CERT_CA_FILE); + pj_str_t cert_file = pj_str(CERT_CLI_FILE); + pj_str_t privkey_file = pj_str(CERT_CLI_PRIVKEY_FILE); + pj_str_t privkey_pass = pj_str(CERT_PRIVKEY_PASS); + pj_ssl_cert_t *own_cert = NULL; + +#if (defined(TEST_LOAD_FROM_FILES) && TEST_LOAD_FROM_FILES==1) + status = pj_ssl_cert_load_from_files(pool, &ca_file, &cert_file, + &privkey_file, &privkey_pass, + &own_cert); +#else + pj_ssl_cert_buffer ca_buf, cert_buf, privkey_buf; - status = pj_ssl_sock_set_certificate(ssock_cli, pool, cert); + status = load_cert_to_buf(pool, &ca_file, &ca_buf); + if (status == PJ_SUCCESS) + status = load_cert_to_buf(pool, &cert_file, &cert_buf); + if (status == PJ_SUCCESS) + status = load_cert_to_buf(pool, &privkey_file, &privkey_buf); + if (status == PJ_SUCCESS) { + status = pj_ssl_cert_load_from_buffer(pool, &ca_buf, &cert_buf, + &privkey_buf, + &privkey_pass, &own_cert); + } +#endif + if (status != PJ_SUCCESS) { + goto on_return; + } + + cli_cert = own_cert; + state_cli.cert_der = CERT_CLI_DER_FILE; + } +#endif + + status = pj_ssl_sock_set_certificate(ssock_cli, pool, cli_cert); if (status != PJ_SUCCESS) { goto on_return; } @@ -978,6 +1097,11 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, if (ssock_serv) pj_ssl_sock_close(ssock_serv); + if (state_serv.accepted_st && !state_serv.accepted_st->err && + !state_serv.accepted_st->done) + { + pj_ssl_sock_close(state_serv.accepted_ssock); + } if (ssock_cli && !state_cli.err && !state_cli.done) pj_ssl_sock_close(ssock_cli); if (ioqueue) diff --git a/pjlib/src/pjlib-test/ssl_sock_test.h b/pjlib/src/pjlib-test/ssl_sock_test.h index cd2a41acbd..a43e30de98 100644 --- a/pjlib/src/pjlib-test/ssl_sock_test.h +++ b/pjlib/src/pjlib-test/ssl_sock_test.h @@ -15,9 +15,29 @@ # define CERT_CA_FILE CERT_DIR "cacert.pem" #endif #define CERT_FILE CERT_DIR "cacert.pem" +#define CERT_DER_FILE CERT_DIR "cacert.der" #define CERT_PRIVKEY_FILE CERT_DIR "privkey.pem" #define CERT_PRIVKEY_PASS "privkeypass" +/* A second certificate, issued by the CA above, for the client to present + * when the server requires one. Having each end on its own certificate + * lets a test tell the local certificate chain from the peer's by content. + */ +#define CERT_CLI_FILE CERT_DIR "clicert.pem" +#define CERT_CLI_DER_FILE CERT_DIR "clicert.der" +#define CERT_CLI_PRIVKEY_FILE CERT_DIR "cliprivkey.pem" + +/* The Apple backends take the private key from the Keychain, where only the + * server key is stored, so there the client keeps reusing the server + * certificate. + */ +#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_DARWIN) || \ + (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_APPLE) +# define TEST_CLI_OWN_CERT 0 +#else +# define TEST_CLI_OWN_CERT 1 +#endif + #define TEST_LOAD_FROM_FILES 1