#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "ne_request.h"
#include "ne_socket.h"
#include "ne_ssl.h"
#include "ne_auth.h"
#include "tests.h"
#include "child.h"
#include "utils.h"
#ifndef NE_HAVE_SSL
#error SSL not supported
#endif
#include "ne_pkcs11.h"
#define SERVER_CERT "server.cert"
#define CA_CERT "ca/cert.pem"
#define P12_PASSPHRASE "foobar"
#define SERVER_DNAME "Neon QA Dept, Neon Hackers Ltd, " \
"Cambridge, Cambridgeshire, GB"
#define CACERT_DNAME "Random Dept, Neosign, Oakland, California, US"
static char *srcdir = ".";
static char *server_key = NULL;
static ne_ssl_certificate *def_ca_cert = NULL, *def_server_cert;
static ne_ssl_client_cert *def_cli_cert;
static int check_dname(const ne_ssl_dname *dn, const char *expected,
const char *which);
struct ssl_server_args {
char *cert;
const char *response;
int numreqs;
int require_cc;
const char *ca_list;
int fail_silently;
int cache;
struct ssl_session {
unsigned char id[128];
size_t len;
} session;
int count;
int use_ssl2;
const char *key;
};
#define DEF_RESP "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n"
static int ssl_server(ne_socket *sock, void *userdata)
{
struct ssl_server_args *args = userdata;
int ret;
char buf[BUFSIZ];
const char *key;
static ne_ssl_context *ctx = NULL;
if (ctx == NULL) {
ctx = ne_ssl_context_create(args->use_ssl2 ? NE_SSL_CTX_SERVERv2
: NE_SSL_CTX_SERVER);
}
ONV(ctx == NULL, ("could not create SSL context"));
key = args->key ? args->key : server_key;
NE_DEBUG(NE_DBG_HTTP, "SSL server init with keypair (%s, %s).\n",
args->cert, key);
ONV(ne_ssl_context_keypair(ctx, args->cert, key),
("failed to load server keypair: ..."));
if (args->require_cc && !args->ca_list) {
args->ca_list = CA_CERT;
}
ne_ssl_context_set_verify(ctx, args->require_cc,
args->ca_list, args->ca_list);
ret = ne_sock_accept_ssl(sock, ctx);
if (ret && args->fail_silently) {
return 0;
}
ONV(ret, ("SSL accept failed: %s", ne_sock_error(sock)));
args->count++;
do {
const char *response = args->response ? args->response : DEF_RESP;
ret = ne_sock_read(sock, buf, BUFSIZ - 1);
if (ret == NE_SOCK_CLOSED)
return 0;
ONV(ret < 0, ("SSL read failed (%d): %s", ret,
ne_sock_error(sock)));
buf[ret] = '\0';
NE_DEBUG(NE_DBG_HTTP, "Request over SSL was: [%s]\n", buf);
if (strstr(buf, "Proxy-Authorization:") != NULL) {
NE_DEBUG(NE_DBG_HTTP, "Got Proxy-Auth header over SSL!\n");
response = "HTTP/1.1 500 Client Leaks Credentials\r\n"
"Content-Length: 0\r\n" "\r\n";
}
ONV(ne_sock_fullwrite(sock, response, strlen(response)),
("SSL write failed: %s", ne_sock_error(sock)));
} while (--args->numreqs > 0);
if (args->cache) {
unsigned char sessid[128];
size_t len = sizeof sessid;
ONN("could not retrieve session ID",
ne_sock_sessid(sock, sessid, &len));
#ifdef NE_DEBUGGING
{
char *b64 = ne_base64(sessid, len);
NE_DEBUG(NE_DBG_SSL, "Session id retrieved (%d): [%s]\n",
args->count, b64);
ne_free(b64);
}
#endif
if (args->count == 1) {
memcpy(args->session.id, sessid, len);
args->session.len = len;
} else {
ONN("cached session not used",
args->session.len != len
|| memcmp(args->session.id, sessid, len));
}
}
return 0;
}
static int fail_serve(ne_socket *sock, void *ud)
{
struct ssl_server_args args = {0};
args.cert = ud;
ssl_server(sock, &args);
return OK;
}
#define DEFSESS (ne_session_create("https", "localhost", 7777))
static int any_ssl_request(ne_session *sess, server_fn fn, void *server_ud,
char *ca_cert,
ne_ssl_verify_fn verify_fn, void *verify_ud)
{
int ret;
if (ca_cert) {
ne_ssl_certificate *ca = ne_ssl_cert_read(ca_cert);
ONV(ca == NULL, ("could not load CA cert `%s'", ca_cert));
ne_ssl_trust_cert(sess, ca);
ne_ssl_cert_free(ca);
}
CALL(spawn_server(7777, fn, server_ud));
if (verify_fn)
ne_ssl_set_verify(sess, verify_fn, verify_ud);
ret = any_request(sess, "/foo");
CALL(await_server());
ONREQ(ret);
return OK;
}
static int init(void)
{
if (test_argc > 1) {
srcdir = test_argv[1];
server_key = ne_concat(srcdir, "/server.key", NULL);
} else {
server_key = "server.key";
}
if (ne_sock_init()) {
t_context("could not initialize socket/SSL library.");
return FAILHARD;
}
def_ca_cert = ne_ssl_cert_read(CA_CERT);
if (def_ca_cert == NULL) {
t_context("couldn't load CA cert %s", CA_CERT);
return FAILHARD;
}
def_server_cert = ne_ssl_cert_read(SERVER_CERT);
if (def_server_cert == NULL) {
t_context("couldn't load server cert %s", SERVER_CERT);
return FAILHARD;
}
def_cli_cert = ne_ssl_clicert_read("client.p12");
if (def_cli_cert == NULL) {
t_context("could not load client.p12");
return FAILHARD;
}
if (!ne_ssl_clicert_encrypted(def_cli_cert)) {
ne_ssl_clicert_free(def_cli_cert);
def_cli_cert = NULL;
t_context("client.p12 is not encrypted!?");
return FAIL;
}
if (ne_ssl_clicert_decrypt(def_cli_cert, P12_PASSPHRASE)) {
ne_ssl_clicert_free(def_cli_cert);
def_cli_cert = NULL;
t_context("failed to decrypt client.p12");
return FAIL;
}
return OK;
}
static int load_server_certs(void)
{
ne_ssl_certificate *cert;
cert = ne_ssl_cert_read("Makefile");
ONN("invalid CA cert file loaded successfully", cert != NULL);
cert = ne_ssl_cert_read("nonesuch.pem");
ONN("non-existent 'nonesuch.pem' loaded successfully", cert != NULL);
cert = ne_ssl_cert_read("ssigned.pem");
ONN("could not load ssigned.pem", cert == NULL);
ne_ssl_cert_free(cert);
return OK;
}
static int trust_default_ca(void)
{
ne_session *sess = DEFSESS;
ne_ssl_trust_default_ca(sess);
ne_session_destroy(sess);
return OK;
}
#define CC_NAME "Just A Neon Client Cert"
static int load_client_cert(void)
{
ne_ssl_client_cert *cc;
const ne_ssl_certificate *cert;
const char *name;
cc = ne_ssl_clicert_read("client.p12");
ONN("could not load client.p12", cc == NULL);
ONN("client.p12 not encrypted!?", !ne_ssl_clicert_encrypted(cc));
name = ne_ssl_clicert_name(cc);
if (name == NULL) {
t_warning("no friendly name given");
} else {
ONV(strcmp(name, CC_NAME), ("friendly name was %s not %s", name, CC_NAME));
}
ONN("failed to decrypt", ne_ssl_clicert_decrypt(cc, P12_PASSPHRASE));
ne_ssl_clicert_free(cc);
cc = ne_ssl_clicert_read("client.p12");
ONN("decrypted client.p12 with incorrect password!?",
ne_ssl_clicert_decrypt(cc, "barfoo") == 0);
ne_ssl_clicert_free(cc);
cc = ne_ssl_clicert_read("unclient.p12");
ONN("could not load unencrypted cert unclient.p12", cc == NULL);
ONN("unencrypted cert marked encrypted?", ne_ssl_clicert_encrypted(cc));
cert = ne_ssl_clicert_owner(cc);
ONN("client cert had no certificate", cert == NULL);
CALL(check_dname(ne_ssl_cert_subject(cert),
"Neon Client Cert, Neon Hackers Ltd, "
"Cambridge, Cambridgeshire, GB",
"client cert subject"));
CALL(check_dname(ne_ssl_cert_issuer(cert), CACERT_DNAME,
"client cert issuer"));
ne_ssl_clicert_free(cc);
cc = ne_ssl_clicert_read("noclient.p12");
ONN("could not load noclient.p12", cc == NULL);
name = ne_ssl_clicert_name(cc);
ONV(name != NULL, ("noclient.p12 had friendly name `%s'", name));
ne_ssl_clicert_free(cc);
cc = ne_ssl_clicert_read("clientca.p12");
ONN("could not load clientca.p12", cc == NULL);
ONN("encrypted cert marked unencrypted?", !ne_ssl_clicert_encrypted(cc));
ONN("could not decrypt clientca.p12",
ne_ssl_clicert_decrypt(cc, P12_PASSPHRASE));
ne_ssl_clicert_free(cc);
cc = ne_ssl_clicert_read("nkclient.p12");
ONN("did not fail to load clicert without pkey", cc != NULL);
cc = ne_ssl_clicert_read("ncclient.p12");
ONN("did not fail to load clicert without cert", cc != NULL);
cc = ne_ssl_clicert_read("Makefile");
ONN("loaded Makefile as client cert!?", cc != NULL);
cc = ne_ssl_clicert_read("nosuch.pem");
ONN("loaded nonexistent file as client cert!?", cc != NULL);
return OK;
}
static int accept_signed_cert_for_hostname(char *cert, const char *hostname)
{
ne_session *sess = ne_session_create("https", hostname, 7777);
struct ssl_server_args args = {cert, 0};
CALL(any_ssl_request(sess, ssl_server, &args, CA_CERT, NULL, NULL));
ne_session_destroy(sess);
return OK;
}
static int accept_signed_cert(char *cert)
{
return accept_signed_cert_for_hostname(cert, "localhost");
}
static int simple(void)
{
return accept_signed_cert(SERVER_CERT);
}
static int simple_sslv2(void)
{
ne_session *sess = ne_session_create("https", "localhost", 7777);
struct ssl_server_args args = {SERVER_CERT, 0};
args.use_ssl2 = 1;
ne_set_session_flag(sess, NE_SESSFLAG_SSLv2, 1);
CALL(any_ssl_request(sess, ssl_server, &args, CA_CERT, NULL, NULL));
ne_session_destroy(sess);
return OK;
}
static int serve_eof(ne_socket *sock, void *ud)
{
struct ssl_server_args args = {0};
args.cert = ud;
args.response = "HTTP/1.0 200 OK\r\n"
"Connection: close\r\n"
"\r\n"
"This is a response body, like it or not.";
return ssl_server(sock, &args);
}
static int simple_eof(void)
{
ne_session *sess = DEFSESS;
CALL(any_ssl_request(sess, serve_eof, SERVER_CERT, CA_CERT, NULL, NULL));
ne_session_destroy(sess);
return OK;
}
static int empty_truncated_eof(void)
{
ne_session *sess = DEFSESS;
struct ssl_server_args args = {0};
args.cert = SERVER_CERT;
args.response = "HTTP/1.0 200 OK\r\n" "\r\n";
CALL(any_ssl_request(sess, ssl_server, &args, CA_CERT, NULL, NULL));
ne_session_destroy(sess);
return OK;
}
static int just_serve_string(ne_socket *sock, void *userdata)
{
const char *str = userdata;
server_send(sock, str, strlen(str));
return 0;
}
static int fail_not_ssl(void)
{
ne_session *sess = DEFSESS;
int ret;
CALL(spawn_server(7777, just_serve_string, "Hello, world.\n"));
ret = any_request(sess, "/bar");
CALL(await_server());
ONN("request did not fail", ret != NE_ERROR);
ne_session_destroy(sess);
return OK;
}
static int tunnel_server(ne_socket *sock, void *ud)
{
struct ssl_server_args *args = ud;
CALL(discard_request(sock));
SEND_STRING(sock, "HTTP/1.1 200 OK\r\nServer: serve_tunnel\r\n\r\n");
return ssl_server(sock, args);
}
static int wildcard_match(void)
{
ne_session *sess;
struct ssl_server_args args = {"wildcard.cert", 0};
sess = ne_session_create("https", "anything.example.com", 443);
ne_session_proxy(sess, "localhost", 7777);
CALL(any_ssl_request(sess, tunnel_server, &args, CA_CERT, NULL, NULL));
ne_session_destroy(sess);
return OK;
}
static int caseless_match(void)
{
return accept_signed_cert("caseless.cert");
}
static int subject_altname(void)
{
return accept_signed_cert("altname1.cert");
}
static int two_subject_altname(void)
{
return accept_signed_cert("altname2.cert");
}
static int two_subject_altname2(void)
{
return accept_signed_cert("altname3.cert");
}
static int notdns_altname(void)
{
return accept_signed_cert("altname4.cert");
}
static int ipaddr_altname(void)
{
return accept_signed_cert_for_hostname("altname5.cert", "127.0.0.1");
}
static int uri_altname(void)
{
return accept_signed_cert_for_hostname("altname7.cert", "localhost");
}
static int multi_commonName(void)
{
return accept_signed_cert("twocn.cert");
}
static int commonName_first(void)
{
return accept_signed_cert("cnfirst.cert");
}
static int check_dname(const ne_ssl_dname *dn, const char *expected,
const char *which)
{
char *dname;
ONV(dn == NULL, ("certificate %s dname was NULL", which));
dname = ne_ssl_readable_dname(dn);
NE_DEBUG(NE_DBG_SSL, "Got dname `%s', expecting `%s'\n", dname, expected);
ONV(strcmp(dname, expected),
("certificate %s dname was `%s' not `%s'", which, dname, expected));
ne_free(dname);
return 0;
}
static int check_cert_dnames(const ne_ssl_certificate *cert,
const char *subject, const char *issuer)
{
ONN("no server certificate presented", cert == NULL);
CALL(check_dname(ne_ssl_cert_subject(cert), subject, "subject"));
return issuer ? check_dname(ne_ssl_cert_issuer(cert), issuer, "issuer") : OK;
}
static int check_cert(void *userdata, int fs, const ne_ssl_certificate *cert)
{
int *ret = userdata;
if (check_cert_dnames(cert, SERVER_DNAME, CACERT_DNAME) == FAIL)
*ret = -1;
else
*ret = 1;
return 0;
}
static int parse_cert(void)
{
ne_session *sess = DEFSESS;
int ret = 0;
struct ssl_server_args args = {SERVER_CERT, 0};
CALL(any_ssl_request(sess, ssl_server, &args, NULL,
check_cert, &ret));
ne_session_destroy(sess);
ONN("cert verification never called", ret == 0);
if (ret == -1)
return FAIL;
return OK;
}
#define WRONGCN_DNAME "Bad Hostname Department, Neon Hackers Ltd, " \
"Cambridge, Cambridgeshire, GB"
static int check_chain(void *userdata, int fs, const ne_ssl_certificate *cert)
{
int *ret = userdata;
if (check_cert_dnames(cert, WRONGCN_DNAME, CACERT_DNAME) == FAIL) {
*ret = -1;
return 0;
}
cert = ne_ssl_cert_signedby(cert);
if (cert == NULL) {
t_context("no CA cert in chain");
*ret = -1;
return 0;
}
if (check_cert_dnames(cert, CACERT_DNAME, CACERT_DNAME) == FAIL) {
*ret = -1;
return 0;
}
*ret = 1;
return 0;
}
static int parse_chain(void)
{
ne_session *sess = DEFSESS;
int ret = 0;
struct ssl_server_args args = {"wrongcn.cert", 0};
args.ca_list = "ca/cert.pem";
CALL(any_ssl_request(sess, ssl_server, &args, "ca/cert.pem",
check_chain, &ret));
ne_session_destroy(sess);
ONN("cert verification never called", ret == 0);
if (ret == -1)
return FAIL;
return OK;
}
static int count_vfy(void *userdata, int fs, const ne_ssl_certificate *c)
{
int *count = userdata;
(*count)++;
return 0;
}
static int no_verify(void)
{
ne_session *sess = DEFSESS;
int count = 0;
struct ssl_server_args args = {SERVER_CERT, 0};
CALL(any_ssl_request(sess, ssl_server, &args, CA_CERT, count_vfy,
&count));
ONN("verify callback called unnecessarily", count != 0);
ne_session_destroy(sess);
return OK;
}
static int cache_verify(void)
{
ne_session *sess = DEFSESS;
int ret, count = 0;
struct ssl_server_args args = {SERVER_CERT, 0};
ret = any_ssl_request(sess, ssl_server, &args, NULL, count_vfy,
&count);
CALL(spawn_server(7777, ssl_server, &args));
ret = any_request(sess, "/foo2");
CALL(await_server());
ONV(count != 1,
("verify callback result not cached: called %d times", count));
ne_session_destroy(sess);
return OK;
}
static int get_failures(void *userdata, int fs, const ne_ssl_certificate *c)
{
int *out = userdata;
*out = fs;
return -1;
}
static int fail_ssl_request_with_error2(char *cert, char *key, char *cacert,
const char *host, const char *fakehost,
const char *msg, int failures,
const char *errstr)
{
ne_session *sess = ne_session_create("https", host, 7777);
int gotf = 0, ret;
struct ssl_server_args args = {0};
ne_sock_addr *addr;
const ne_inet_addr *list[1];
if (fakehost) {
addr = ne_addr_resolve(fakehost, 0);
ONV(ne_addr_result(addr),
("fake hostname lookup failed for %s", fakehost));
list[0] = ne_addr_first(addr);
ne_set_addrlist(sess, list, 1);
}
args.cert = cert;
args.key = key;
args.fail_silently = 1;
ret = any_ssl_request(sess, ssl_server, &args, cacert,
get_failures, &gotf);
ONV(gotf == 0,
("no error in verification callback; error string: %s",
ne_get_error(sess)));
ONV(gotf & ~NE_SSL_FAILMASK,
("verification flags %x outside mask %x", gotf, NE_SSL_FAILMASK));
ONV(failures != gotf,
("verification flags were %d not %d", gotf, failures));
ONV(ret == NE_OK, ("%s", msg));
ne_session_destroy(sess);
return OK;
}
static int fail_ssl_request(char *cert, char *cacert, const char *host,
const char *msg, int failures)
{
return fail_ssl_request_with_error2(cert, NULL, cacert, host, NULL,
msg, failures, NULL);
}
static int fail_wrongCN(void)
{
return fail_ssl_request("wrongcn.cert", "ca/cert.pem", "localhost",
"certificate with incorrect CN was accepted",
NE_SSL_IDMISMATCH);
}
static int fail_nul_cn(void)
{
return fail_ssl_request_with_error2("nulcn.pem", "nulsrv.key", "nulca.pem",
"www.bank.com", "localhost",
"certificate with incorrect CN was accepted",
NE_SSL_IDMISMATCH,
"certificate issued for a different hostname");
}
static int fail_nul_san(void)
{
return fail_ssl_request_with_error2("nulsan.pem", "nulsrv.key", "nulca.pem",
"www.bank.com", "localhost",
"certificate with incorrect CN was accepted",
NE_SSL_IDMISMATCH,
"certificate issued for a different hostname");
}
static int fail_expired(void)
{
char *c = ne_concat(srcdir, "/expired.pem", NULL);
CALL(fail_ssl_request(c, c, "localhost",
"expired certificate was accepted", NE_SSL_EXPIRED));
ne_free(c);
return OK;
}
static int fail_notvalid(void)
{
char *c = ne_concat(srcdir, "/notvalid.pem", NULL);
CALL(fail_ssl_request(c, c, "localhost",
"not yet valid certificate was accepted",
NE_SSL_NOTYETVALID));
ne_free(c);
return OK;
}
static int fail_untrusted_ca(void)
{
return fail_ssl_request("server.cert", NULL, "localhost",
"untrusted CA.", NE_SSL_UNTRUSTED);
}
static int fail_self_signed(void)
{
return fail_ssl_request("ssigned.pem", NULL, "localhost",
"self-signed cert", NE_SSL_UNTRUSTED);
}
static int fail_missing_CN(void)
{
ne_session *sess = DEFSESS;
ONN("accepted server cert with missing commonName",
any_ssl_request(sess, fail_serve, "missingcn.cert", SERVER_CERT,
NULL, NULL) == NE_OK);
ONV(strstr(ne_get_error(sess), "missing commonName") == NULL,
("unexpected session error `%s'", ne_get_error(sess)));
ne_session_destroy(sess);
return OK;
}
static int fail_bad_ipaltname(void)
{
return fail_ssl_request("altname6.cert", CA_CERT, "127.0.0.1",
"bad IP altname cert", NE_SSL_IDMISMATCH);
}
static int fail_host_ipaltname(void)
{
return fail_ssl_request("altname5.cert", CA_CERT, "localhost",
"bad IP altname cert", NE_SSL_IDMISMATCH);
}
static int fail_bad_urialtname(void)
{
return fail_ssl_request("altname8.cert", CA_CERT, "localhost",
"bad URI altname cert", NE_SSL_IDMISMATCH);
}
static int session_cache(void)
{
struct ssl_server_args args = {0};
ne_session *sess = ne_session_create("https", "localhost", 7777);
args.cert = SERVER_CERT;
args.cache = 1;
ne_ssl_trust_cert(sess, def_ca_cert);
CALL(spawn_server_repeat(7777, ssl_server, &args, 4));
ONREQ(any_request(sess, "/req1"));
ONREQ(any_request(sess, "/req2"));
ne_session_destroy(sess);
ONN("error from child", dead_server());
reap_server();
return OK;
}
static void ccert_provider(void *userdata, ne_session *sess,
const ne_ssl_dname *const *dns, int dncount)
{
const ne_ssl_client_cert *cc = userdata;
ne_ssl_set_clicert(sess, cc);
}
static int client_cert_provided(void)
{
ne_session *sess = DEFSESS;
ne_ssl_client_cert *cc;
struct ssl_server_args args = {SERVER_CERT, NULL};
args.require_cc = 1;
cc = ne_ssl_clicert_read("client.p12");
ONN("could not load client.p12", cc == NULL);
ONN("could not decrypt client.p12",
ne_ssl_clicert_decrypt(cc, P12_PASSPHRASE));
ne_ssl_provide_clicert(sess, ccert_provider, cc);
CALL(any_ssl_request(sess, ssl_server, &args, CA_CERT,
NULL, NULL));
ne_session_destroy(sess);
ne_ssl_clicert_free(cc);
return OK;
}
#define DN_COUNT 5
static void cc_check_dnames(void *userdata, ne_session *sess,
const ne_ssl_dname *const *dns, int dncount)
{
int n, *ret = userdata;
static const char *expected[DN_COUNT] = {
CACERT_DNAME,
"First Random CA, CAs Ltd., Lincoln, Lincolnshire, GB",
"Second Random CA, CAs Ltd., Falmouth, Cornwall, GB",
"Third Random CA, CAs Ltd., Ipswich, Suffolk, GB",
"Fourth Random CA, CAs Ltd., Norwich, Norfolk, GB"
};
ne_ssl_set_clicert(sess, def_cli_cert);
if (dncount != DN_COUNT) {
t_context("dname count was %d not %d", dncount,
DN_COUNT);
*ret = -1;
return;
}
for (n = 0; n < DN_COUNT; n++) {
char which[5];
sprintf(which, "%d", n);
if (check_dname(dns[n], expected[n], which) == FAIL) {
*ret = -1;
return;
}
}
*ret = 1;
}
static int cc_provided_dnames(void)
{
int check = 0;
ne_session *sess = DEFSESS;
struct ssl_server_args args = {SERVER_CERT, NULL};
args.require_cc = 1;
args.ca_list = "calist.pem";
PRECOND(def_cli_cert);
ne_ssl_provide_clicert(sess, cc_check_dnames, &check);
CALL(any_ssl_request(sess, ssl_server, &args, CA_CERT, NULL, NULL));
ne_session_destroy(sess);
ONN("provider function not called", check == 0);
return (check == -1) ? FAIL : OK;
}
static int client_cert_pkcs12(void)
{
ne_session *sess = DEFSESS;
struct ssl_server_args args = {SERVER_CERT, NULL};
args.require_cc = 1;
PRECOND(def_cli_cert);
ne_ssl_set_clicert(sess, def_cli_cert);
CALL(any_ssl_request(sess, ssl_server, &args, CA_CERT, NULL, NULL));
ne_session_destroy(sess);
return OK;
}
static int client_cert_ca(void)
{
ne_session *sess = DEFSESS;
struct ssl_server_args args = {SERVER_CERT, NULL};
ne_ssl_client_cert *cc;
args.require_cc = 1;
cc = ne_ssl_clicert_read("clientca.p12");
ONN("could not load clientca.p12", cc == NULL);
ONN("encrypted cert marked unencrypted?", !ne_ssl_clicert_encrypted(cc));
ONN("could not decrypt clientca.p12",
ne_ssl_clicert_decrypt(cc, P12_PASSPHRASE));
ne_ssl_set_clicert(sess, cc);
CALL(any_ssl_request(sess, ssl_server, &args, CA_CERT, NULL, NULL));
ne_ssl_clicert_free(cc);
ne_session_destroy(sess);
return OK;
}
static int ccert_unencrypted(void)
{
ne_session *sess = DEFSESS;
ne_ssl_client_cert *ccert;
struct ssl_server_args args = {SERVER_CERT, NULL};
args.require_cc = 1;
ccert = ne_ssl_clicert_read("unclient.p12");
ONN("could not load unclient.p12", ccert == NULL);
ONN("unclient.p12 was encrypted", ne_ssl_clicert_encrypted(ccert));
ne_ssl_set_clicert(sess, ccert);
CALL(any_ssl_request(sess, ssl_server, &args, CA_CERT, NULL, NULL));
ne_ssl_clicert_free(ccert);
ne_session_destroy(sess);
return OK;
}
#define NOCERT_MESSAGE "client certificate was requested"
static int no_client_cert(void)
{
ne_session *sess = DEFSESS;
struct ssl_server_args args = {SERVER_CERT, NULL};
int ret;
args.require_cc = 1;
args.fail_silently = 1;
ne_ssl_trust_cert(sess, def_ca_cert);
CALL(spawn_server(7777, ssl_server, &args));
ret = any_request(sess, "/failme");
ONV(ret != NE_ERROR,
("unexpected result %d: %s", ret, ne_get_error(sess)));
ONV(strstr(ne_get_error(sess), NOCERT_MESSAGE) == NULL,
("error message was '%s', missing '%s'",
ne_get_error(sess), NOCERT_MESSAGE));
reap_server();
ne_session_destroy(sess);
return OK;
}
static int got_server_auth;
static void tunnel_header(char *value)
{
got_server_auth = 1;
}
static int serve_tunnel(ne_socket *sock, void *ud)
{
struct ssl_server_args *args = ud;
want_header = "Authorization";
got_header = tunnel_header;
got_server_auth = 0;
CALL(discard_request(sock));
if (got_server_auth) {
SEND_STRING(sock, "HTTP/1.1 500 Leaked Server Auth Creds\r\n"
"Content-Length: 0\r\n" "Server: serve_tunnel\r\n\r\n");
return 0;
} else {
SEND_STRING(sock, "HTTP/1.1 200 OK\r\nServer: serve_tunnel\r\n\r\n");
return ssl_server(sock, args);
}
}
static int fail_tunnel(void)
{
ne_session *sess = ne_session_create("https", "example.com", 443);
struct ssl_server_args args = {SERVER_CERT, NULL};
ne_session_proxy(sess, "localhost", 7777);
ONN("server cert verification didn't fail",
any_ssl_request(sess, serve_tunnel, &args, CA_CERT,
NULL, NULL) != NE_ERROR);
ne_session_destroy(sess);
return OK;
}
static int proxy_tunnel(void)
{
ne_session *sess = ne_session_create("https", "localhost", 443);
struct ssl_server_args args = {SERVER_CERT, NULL};
ne_session_proxy(sess, "localhost", 7777);
CALL(any_ssl_request(sess, serve_tunnel, &args, CA_CERT,
NULL, NULL));
ne_session_destroy(sess);
return OK;
}
#define RESP_0LENGTH "HTTP/1.1 200 OK\r\n" "Content-Length: 0\r\n" "\r\n"
static int apt_post_send(ne_request *req, void *ud, const ne_status *st)
{
int *code = ud;
if (st->code == *code) {
struct ssl_server_args args = {SERVER_CERT, NULL};
if (*code == 407) args.numreqs = 2;
args.response = RESP_0LENGTH;
NE_DEBUG(NE_DBG_HTTP, "Got challenge, awaiting server...\n");
CALL(await_server());
NE_DEBUG(NE_DBG_HTTP, "Spawning proper tunnel server...\n");
CALL(spawn_server(7777, serve_tunnel, &args));
NE_DEBUG(NE_DBG_HTTP, "Spawned.\n");
}
return OK;
}
static int apt_creds(void *userdata, const char *realm, int attempt,
char *username, char *password)
{
strcpy(username, "foo");
strcpy(password, "bar");
return attempt;
}
static int auth_proxy_tunnel(void)
{
ne_session *sess = ne_session_create("https", "localhost", 443);
int ret, code = 407;
ne_session_proxy(sess, "localhost", 7777);
ne_hook_post_send(sess, apt_post_send, &code);
ne_set_proxy_auth(sess, apt_creds, NULL);
ne_ssl_trust_cert(sess, def_ca_cert);
CALL(spawn_server(7777, single_serve_string,
"HTTP/1.0 407 I WANT MORE BISCUITS\r\n"
"Proxy-Authenticate: Basic realm=\"bigbluesea\"\r\n"
"Connection: close\r\n" "\r\n"));
ret = any_2xx_request(sess, "/foobar");
if (!ret) ret = any_2xx_request(sess, "/foobar2");
CALL(await_server());
CALL(ret);
ne_session_destroy(sess);
return 0;
}
static int auth_tunnel_creds(void)
{
ne_session *sess = ne_session_create("https", "localhost", 443);
int ret, code = 401;
struct ssl_server_args args = {SERVER_CERT, 0};
ne_session_proxy(sess, "localhost", 7777);
ne_hook_post_send(sess, apt_post_send, &code);
ne_set_server_auth(sess, apt_creds, NULL);
ne_ssl_trust_cert(sess, def_ca_cert);
args.response = "HTTP/1.1 401 I want a Shrubbery\r\n"
"WWW-Authenticate: Basic realm=\"bigredocean\"\r\n"
"Server: Python\r\n" "Content-Length: 0\r\n" "\r\n";
CALL(spawn_server(7777, serve_tunnel, &args));
ret = any_2xx_request(sess, "/foobar");
CALL(await_server());
CALL(ret);
ne_session_destroy(sess);
return OK;
}
static int auth_tunnel_fail(void)
{
ne_session *sess = ne_session_create("https", "localhost", 443);
int ret;
CALL(spawn_server(7777, single_serve_string,
"HTTP/1.1 407 Nyaaaaah\r\n"
"Proxy-Authenticate: GaBoogle\r\n"
"Connection: close\r\n"
"\r\n"));
ne_session_proxy(sess, "localhost", 7777);
ne_set_proxy_auth(sess, apt_creds, NULL);
ret = any_request(sess, "/bar");
ONV(ret != NE_PROXYAUTH, ("bad error code for tunnel failure: %d", ret));
ONV(strstr(ne_get_error(sess), "GaBoogle") == NULL,
("bad error string for tunnel failure: %s", ne_get_error(sess)));
ne_session_destroy(sess);
return await_server();
}
#define THE_DIGEST "cf:5c:95:93:76:c6:3c:01:8b:62:" \
"b1:6f:f7:7f:42:32:ac:e6:69:1b"
static int cert_fingerprint(void)
{
char *fn = ne_concat(srcdir, "/notvalid.pem", NULL);
ne_ssl_certificate *cert = ne_ssl_cert_read(fn);
char digest[60];
ne_free(fn);
ONN("could not load notvalid.pem", cert == NULL);
ONN("failed to digest", ne_ssl_cert_digest(cert, digest));
ne_ssl_cert_free(cert);
ONV(strcmp(digest, THE_DIGEST),
("digest was %s not %s", digest, THE_DIGEST));
return OK;
}
static int check_identity(const char *fname, const char *identity)
{
ne_ssl_certificate *cert = ne_ssl_cert_read(fname);
const char *id;
ONV(cert == NULL, ("could not read cert `%s'", fname));
id = ne_ssl_cert_identity(cert);
if (identity) {
ONV(id == NULL, ("certificate `%s' had no identity", fname));
ONV(strcmp(id, identity),
("certificate `%s' had identity `%s' not `%s'", fname,
id, identity));
} else {
ONV(id != NULL, ("certificate `%s' had identity `%s' (expected none)",
fname, id));
}
ne_ssl_cert_free(cert);
return OK;
}
static int cert_identities(void)
{
static const struct {
const char *fname, *identity;
} certs[] = {
{ "ssigned.pem", "localhost" },
{ "twocn.cert", "localhost" },
{ "altname1.cert", "localhost" },
{ "altname2.cert", "nohost.example.com" },
{ "altname4.cert", "localhost" },
{ "ca4.pem", "fourth.example.com" },
{ "altname8.cert", "http://nohost.example.com/" },
{ NULL, NULL }
};
int n;
for (n = 0; certs[n].fname != NULL; n++)
CALL(check_identity(certs[n].fname, certs[n].identity));
return OK;
}
static int nulcn_identity(void)
{
ne_ssl_certificate *cert = ne_ssl_cert_read("nulcn.pem");
const char *id, *expected = "www.bank.com\\x00.badguy.com";
ONN("could not read nulcn.pem", cert == NULL);
id = ne_ssl_cert_identity(cert);
ONV(id != NULL
&& strcmp(id, expected) != 0,
("certificate `nulcn.pem' had identity `%s' not `%s'",
id, expected));
ne_ssl_cert_free(cert);
return OK;
}
static int check_validity(const char *fname,
const char *from, const char *until)
{
char actfrom[NE_SSL_VDATELEN], actuntil[NE_SSL_VDATELEN];
ne_ssl_certificate *cert;
cert = ne_ssl_cert_read(fname);
ONV(cert == NULL, ("could not load cert `%s'", fname));
ne_ssl_cert_validity(cert, NULL, NULL);
ne_ssl_cert_validity(cert, actfrom, NULL);
ne_ssl_cert_validity(cert, NULL, actuntil);
ne_ssl_cert_validity(cert, actfrom, actuntil);
ONV(strcmp(actfrom, from),
("%s: start time was `%s' not `%s'", fname, actfrom, from));
ONV(strcmp(actuntil, until),
("%s: end time was `%s' not `%s'", fname, actuntil, until));
ne_ssl_cert_free(cert);
return OK;
}
static int cert_validity(void)
{
char *cert = ne_concat(srcdir, "/expired.pem", NULL);
CALL(check_validity(cert,
"Mon, 21 Jan 2002 20:39:04 GMT", "Thu, 31 Jan 2002 20:39:04 GMT"));
ne_free(cert);
cert = ne_concat(srcdir, "/notvalid.pem", NULL);
CALL(check_validity(cert,
"Wed, 27 Dec 2023 20:40:29 GMT", "Thu, 28 Dec 2023 20:40:29 GMT"));
ne_free(cert);
return OK;
}
static int dname_compare(void)
{
ne_ssl_certificate *ssigned;
const ne_ssl_dname *dn1, *dn2;
dn1 = ne_ssl_cert_subject(def_server_cert);
dn2 = ne_ssl_cert_subject(def_server_cert);
ONN("identical subject names not equal", ne_ssl_dname_cmp(dn1, dn2) != 0);
dn2 = ne_ssl_cert_issuer(def_server_cert);
ONN("issuer and subject names equal for signed cert",
ne_ssl_dname_cmp(dn1, dn2) == 0);
dn1 = ne_ssl_cert_subject(def_ca_cert);
ONN("issuer of signed cert not equal to subject of CA cert",
ne_ssl_dname_cmp(dn1, dn2) != 0);
ssigned = ne_ssl_cert_read("ssigned.pem");
ONN("could not load ssigned.pem", ssigned == NULL);
dn1 = ne_ssl_cert_subject(ssigned);
dn2 = ne_ssl_cert_issuer(ssigned);
ONN("issuer and subject names not equal for self-signed cert",
ne_ssl_dname_cmp(dn1, dn2));
ne_ssl_cert_free(ssigned);
return OK;
}
#define I18N_DNAME "H\xc3\xa8llo World, Neon Hackers Ltd, Cambridge, Cambridgeshire, GB"
static int dname_readable(void)
{
struct {
const char *cert;
const char *subjdn, *issuerdn;
} ts[] = {
{ "justmail.cert", "blah@example.com", NULL },
{ "t61subj.cert", I18N_DNAME, NULL },
{ "bmpsubj.cert", I18N_DNAME, NULL },
{ "utf8subj.cert", I18N_DNAME, NULL },
{ "twoou.cert", "First OU Dept, Second OU Dept, Neon Hackers Ltd, "
"Cambridge, Cambridgeshire, GB" }
};
size_t n;
for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) {
ne_ssl_certificate *cert = ne_ssl_cert_read(ts[n].cert);
ONV(cert == NULL, ("could not load cert %s", ts[n].cert));
CALL(check_cert_dnames(cert, ts[n].subjdn, ts[n].issuerdn));
ne_ssl_cert_free(cert);
}
return OK;
}
static int cert_compare(void)
{
ne_ssl_certificate *c1, *c2;
c1 = ne_ssl_cert_read("server.cert");
c2 = ne_ssl_cert_read("server.cert");
ONN("identical certs don't compare equal", ne_ssl_cert_cmp(c1, c2) != 0);
ONN("identical certs don't compare equal", ne_ssl_cert_cmp(c2, c1) != 0);
ne_ssl_cert_free(c2);
c2 = ne_ssl_cert_read("ssigned.pem");
ONN("different certs don't compare different",
ne_ssl_cert_cmp(c1, c2) == 0);
ONN("different certs don't compare different",
ne_ssl_cert_cmp(c2, c1) == 0);
ne_ssl_cert_free(c2);
ne_ssl_cert_free(c1);
return OK;
}
static int flatten_pem(const char *fname, char **out)
{
FILE *fp = fopen(fname, "r");
char buf[80];
size_t outlen = 0;
int ignore = 1;
ONV(fp == NULL, ("could not open %s", fname));
*out = NULL;
while (fgets(buf, sizeof buf, fp) != NULL) {
size_t len = strlen(buf) - 1;
if (len < 1) continue;
if (strncmp(buf, "-----", 5) == 0) {
ignore = !ignore;
continue;
}
if (ignore) continue;
*out = realloc(*out, outlen + len + 1);
memcpy(*out + outlen, buf, len);
outlen += len;
}
(*out)[outlen] = '\0';
fclose(fp);
return OK;
}
static int check_exported_data(const char *actual, const char *expected)
{
ONN("could not export cert", actual == NULL);
ONN("export data contained newline",
strchr(actual, '\r') || strchr(actual, '\n'));
ONV(strcmp(actual, expected), ("exported cert differed from expected:\n"
"actual: %s\nexpected: %s",
actual, expected));
return OK;
}
static int import_export(void)
{
char *expected, *actual;
ne_ssl_certificate *cert, *imp;
CALL(flatten_pem("server.cert", &expected));
cert = ne_ssl_cert_read("server.cert");
ONN("could not load server.cert", cert == NULL);
actual = ne_ssl_cert_export(cert);
CALL(check_exported_data(actual, expected));
imp = ne_ssl_cert_import(actual);
ONN("failed to import exported cert", imp == NULL);
ONN("imported cert was different to original",
ne_ssl_cert_cmp(imp, cert));
ne_free(actual);
actual = ne_ssl_cert_export(imp);
CALL(check_exported_data(actual, expected));
ne_ssl_cert_free(imp);
imp = ne_ssl_cert_import("!!");
ONN("imported bogus cert from bogus base64", imp != NULL);
imp = ne_ssl_cert_import("aaaa");
ONN("imported bogus cert from valid base64", imp != NULL);
ne_ssl_cert_free(cert);
ne_free(actual);
ne_free(expected);
return OK;
}
static int read_write(void)
{
ne_ssl_certificate *c1, *c2;
c1 = ne_ssl_cert_read("server.cert");
ONN("could not load server.cert", c1 == NULL);
ONN("could not write output.pem", ne_ssl_cert_write(c1, "output.pem"));
ONN("wrote to nonexistent directory",
ne_ssl_cert_write(c1, "nonesuch/output.pem") == 0);
c2 = ne_ssl_cert_read("output.pem");
ONN("could not read output.pem", c2 == NULL);
ONN("read of output.pem differs from original",
ne_ssl_cert_cmp(c2, c1));
ne_ssl_cert_free(c1);
ne_ssl_cert_free(c2);
return OK;
}
static int verify_cache(void *userdata, int fs,
const ne_ssl_certificate *cert)
{
char **cache = userdata;
if (*cache == NULL) {
*cache = ne_ssl_cert_export(cert);
return 0;
} else {
return -1;
}
}
static int cache_cert(void)
{
ne_session *sess = DEFSESS;
char *cache = NULL;
ne_ssl_certificate *cert;
struct ssl_server_args args = {0};
args.cert = "ssigned.pem";
args.cache = 1;
ONREQ(any_ssl_request(sess, ssl_server, &args, CA_CERT,
verify_cache, &cache));
ne_session_destroy(sess);
ONN("no cert was cached", cache == NULL);
cert = ne_ssl_cert_import(cache);
ONN("could not import cached cert", cert == NULL);
ne_free(cache);
sess = DEFSESS;
ne_ssl_trust_cert(sess, cert);
ne_ssl_cert_free(cert);
ONREQ(any_ssl_request(sess, ssl_server, &args, CA_CERT,
NULL, NULL));
ne_session_destroy(sess);
return OK;
}
static int nonssl_trust(void)
{
ne_session *sess = ne_session_create("http", "www.example.com", 80);
ne_ssl_trust_cert(sess, def_ca_cert);
ne_session_destroy(sess);
return OK;
}
static int pkcs11_pin(void *userdata, int attempt,
const char *slot_descr, const char *token_label,
unsigned int flags, char *pin)
{
char *sekrit = userdata;
NE_DEBUG(NE_DBG_SSL, "pkcs11: slot = [%s], token = [%s]\n",
slot_descr, token_label);
if (attempt == 0) {
strcpy(pin, sekrit);
return 0;
}
else {
return -1;
}
}
static int pkcs11(void)
{
ne_session *sess = DEFSESS;
struct ssl_server_args args = {SERVER_CERT, NULL};
ne_ssl_pkcs11_provider *prov;
int ret;
args.require_cc = 1;
if (access("nssdb", R_OK|X_OK)) {
t_warning("NSS required for PKCS#11 testing");
return SKIP;
}
ret = ne_ssl_pkcs11_nss_provider_init(&prov, "softokn3", "nssdb/", NULL,
NULL, NULL);
if (ret) {
if (ret == NE_PK11_NOTIMPL)
t_context("pakchois library required for PKCS#11 support");
else
t_context("could not load NSS softokn3 PKCS#11 provider");
return SKIP;
}
ne_ssl_pkcs11_provider_pin(prov, pkcs11_pin, "foobar");
ne_ssl_set_pkcs11_provider(sess, prov);
CALL(any_ssl_request(sess, ssl_server, &args, CA_CERT,
NULL, NULL));
ne_session_destroy(sess);
ne_ssl_pkcs11_provider_destroy(prov);
return OK;
}
ne_test tests[] = {
T_LEAKY(init),
T(load_server_certs),
T(trust_default_ca),
T(cert_fingerprint),
T(cert_identities),
T(cert_validity),
T(cert_compare),
T(dname_compare),
T(dname_readable),
T(import_export),
T(read_write),
T(load_client_cert),
T(simple),
T(simple_sslv2),
T(simple_eof),
T(empty_truncated_eof),
T(fail_not_ssl),
T(cache_cert),
T(client_cert_pkcs12),
T(ccert_unencrypted),
T(client_cert_provided),
T(cc_provided_dnames),
T(no_client_cert),
T(client_cert_ca),
T(parse_cert),
T(parse_chain),
T(no_verify),
T(cache_verify),
T(wildcard_match),
T(caseless_match),
T(subject_altname),
T(two_subject_altname),
T(two_subject_altname2),
T(notdns_altname),
T(ipaddr_altname),
T(uri_altname),
T(multi_commonName),
T(commonName_first),
T(fail_wrongCN),
T(fail_expired),
T(fail_notvalid),
T(fail_untrusted_ca),
T(fail_self_signed),
T(fail_missing_CN),
T(fail_host_ipaltname),
T(fail_bad_ipaltname),
T(fail_bad_urialtname),
T(nulcn_identity),
T(fail_nul_cn),
T(fail_nul_san),
T(session_cache),
T(fail_tunnel),
T(proxy_tunnel),
T(auth_proxy_tunnel),
T(auth_tunnel_creds),
T(auth_tunnel_fail),
T(nonssl_trust),
T(pkcs11),
T(NULL)
};