static struct ustream_fd stream, s_input;
static struct ustream_ssl ssl;
+static const char *host, *port;
static void *ctx;
static void client_notify_connected(struct ustream_ssl *ssl)
{
- fprintf(stderr, "SSL connection established\n");
+ fprintf(stderr, "SSL connection established (CN verified: %d)\n", ssl->valid_cn);
s_input.stream.notify_read = client_input_notify_read;
ustream_fd_init(&s_input, 0);
}
client_teardown();
}
+static void client_notify_verify_error(struct ustream_ssl *ssl, int error, const char *str)
+{
+ fprintf(stderr, "WARNING: SSL certificate error(%d): %s\n", error, str);
+}
+
static void client_notify_state(struct ustream *us)
{
if (!us->write_error && !us->eof)
fprintf(stderr, "Starting SSL negnotiation\n");
ssl.notify_error = client_notify_error;
+ ssl.notify_verify_error = client_notify_verify_error;
ssl.notify_connected = client_notify_connected;
ssl.stream.notify_read = client_ssl_notify_read;
ssl.stream.notify_state = client_notify_state;
ustream_fd_init(&stream, fd);
ustream_ssl_init(&ssl, &stream.stream, ctx, false);
+ ustream_ssl_set_peer_cn(&ssl, host);
}
static void example_connect_cb(struct uloop_fd *f, unsigned int events)
example_connect_ssl(fd.fd);
}
-static void connect_client(const char *host, const char *port)
+static void connect_client(void)
{
fd.fd = usock(USOCK_TCP | USOCK_NONBLOCK, host, port);
fd.cb = example_connect_cb;
uloop_fd_add(&fd, ULOOP_WRITE | ULOOP_EDGE_TRIGGER);
}
+static int usage(const char *progname)
+{
+ fprintf(stderr, "Usage: %s [options] <hostname> <port>\n", progname);
+ return 1;
+}
+
int main(int argc, char **argv)
{
- if (argc != 3) {
- fprintf(stderr, "Usage: %s <hostname> <port>\n", argv[0]);
- return 1;
- }
+ int ch;
ctx = ustream_ssl_context_new(false);
+
+ while ((ch = getopt(argc, argv, "c:")) != -1) {
+ switch(ch) {
+ case 'c':
+ ustream_ssl_context_add_ca_crt_file(ctx, optarg);
+ break;
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ if (argc != 2)
+ return usage(argv[0]);
+
uloop_init();
- connect_client(argv[1], argv[2]);
+ host = argv[0];
+ port = argv[1];
+ connect_client();
uloop_run();
close(fd.fd);
void ustream_set_io(struct ustream_ssl_ctx *ctx, void *ssl, struct ustream *s);
struct ustream_ssl_ctx *__ustream_ssl_context_new(bool server);
+int __ustream_ssl_add_ca_crt_file(struct ustream_ssl_ctx *ctx, const char *file);
int __ustream_ssl_set_crt_file(struct ustream_ssl_ctx *ctx, const char *file);
int __ustream_ssl_set_key_file(struct ustream_ssl_ctx *ctx, const char *file);
void __ustream_ssl_context_free(struct ustream_ssl_ctx *ctx);
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <ctype.h>
+#include <openssl/x509v3.h>
#include "ustream-ssl.h"
#include "ustream-internal.h"
if (!c)
return NULL;
- if (server)
- SSL_CTX_set_verify(c, SSL_VERIFY_NONE, NULL);
+ SSL_CTX_set_verify(c, SSL_VERIFY_NONE, NULL);
return (void *) c;
}
+__hidden int __ustream_ssl_add_ca_crt_file(struct ustream_ssl_ctx *ctx, const char *file)
+{
+ int ret;
+
+ ret = SSL_CTX_load_verify_locations((void *) ctx, file, NULL);
+ if (ret < 1)
+ return -1;
+
+ return 0;
+}
+
__hidden int __ustream_ssl_set_crt_file(struct ustream_ssl_ctx *ctx, const char *file)
{
int ret;
- ret = SSL_CTX_use_certificate_file((void *) ctx, file, SSL_FILETYPE_PEM);
+ ret = SSL_CTX_use_certificate_chain_file((void *) ctx, file);
if (ret < 1)
ret = SSL_CTX_use_certificate_file((void *) ctx, file, SSL_FILETYPE_ASN1);
uloop_timeout_set(&us->error_timer, 0);
}
+static bool host_pattern_match(const unsigned char *pattern, const char *cn)
+{
+ char c;
+
+ for (; (c = tolower(*pattern++)) != 0; cn++) {
+ if (c != '*') {
+ if (c != *cn)
+ return false;
+ continue;
+ }
+
+ do {
+ c = tolower(*pattern++);
+ } while (c == '*');
+
+ while (*cn) {
+ if (c == tolower(*cn) &&
+ host_pattern_match(pattern, cn))
+ return true;
+ if (*cn == '.')
+ return false;
+ cn++;
+ }
+
+ return !c;
+ }
+ return !*cn;
+}
+
+static bool host_pattern_match_asn1(ASN1_STRING *asn1, const char *cn)
+{
+ unsigned char *pattern;
+ bool ret = false;
+
+ if (ASN1_STRING_to_UTF8(&pattern, asn1) < 0)
+ return false;
+
+ if (!pattern)
+ return false;
+
+ if (strlen((char *) pattern) == ASN1_STRING_length(asn1))
+ ret = host_pattern_match(pattern, cn);
+
+ OPENSSL_free(pattern);
+
+ return ret;
+}
+
+static bool ustream_ssl_verify_cn_alt(struct ustream_ssl *us, X509 *cert)
+{
+ GENERAL_NAMES *alt_names;
+ int i, n_alt;
+
+ alt_names = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL);
+ if (!alt_names)
+ return false;
+
+ n_alt = sk_GENERAL_NAME_num(alt_names);
+ for (i = 0; i < n_alt; i++) {
+ const GENERAL_NAME *name = sk_GENERAL_NAME_value(alt_names, i);
+
+ if (!name)
+ continue;
+
+ if (name->type != GEN_DNS)
+ continue;
+
+ if (host_pattern_match_asn1(name->d.dNSName, us->peer_cn))
+ return true;
+ }
+
+ return false;
+}
+
+static bool ustream_ssl_verify_cn(struct ustream_ssl *us, X509 *cert)
+{
+ ASN1_STRING *astr;
+ X509_NAME *xname;
+ int i, last;
+
+ if (!us->peer_cn)
+ return false;
+
+ if (ustream_ssl_verify_cn_alt(us, cert))
+ return true;
+
+ xname = X509_get_subject_name(cert);
+
+ last = -1;
+ while (1) {
+ i = X509_NAME_get_index_by_NID(xname, NID_commonName, last);
+ if (i < 0)
+ break;
+
+ last = i;
+ }
+
+ if (last < 0)
+ return false;
+
+ astr = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(xname, last));
+
+ return host_pattern_match_asn1(astr, us->peer_cn);
+}
+
+
+static void ustream_ssl_verify_cert(struct ustream_ssl *us)
+{
+ void *ssl = us->ssl;
+ X509 *cert;
+ int res;
+
+ cert = SSL_get_peer_certificate(ssl);
+ if (!cert)
+ return;
+
+ res = SSL_get_verify_result(ssl);
+ if (res != X509_V_OK) {
+ if (us->notify_verify_error)
+ us->notify_verify_error(us, res, X509_verify_cert_error_string(res));
+ return;
+ }
+
+ us->valid_cert = true;
+ us->valid_cn = ustream_ssl_verify_cn(us, cert);
+}
+
__hidden enum ssl_conn_status __ustream_ssl_connect(struct ustream_ssl *us)
{
void *ssl = us->ssl;
else
r = SSL_connect(ssl);
- if (r == 1)
+ if (r == 1) {
+ ustream_ssl_verify_cert(us);
return U_SSL_OK;
+ }
r = SSL_get_error(ssl, r);
if (r == SSL_ERROR_WANT_READ || r == SSL_ERROR_WANT_WRITE)
return ERR_error_string(error, buffer);
}
+static inline void __ustream_ssl_update_peer_cn(struct ustream_ssl *us)
+{
+}
+
#endif
if (!ctx)
return NULL;
- ctx->auth = SSL_VERIFY_NONE;
ctx->server = server;
#ifdef USE_VERSION_1_3
pk_init(&ctx->key);
return ctx;
}
+__hidden int __ustream_ssl_add_ca_crt_file(struct ustream_ssl_ctx *ctx, const char *file)
+{
+ int ret;
+
+#ifdef USE_VERSION_1_3
+ ret = x509_crt_parse_file(&ctx->ca_cert, file);
+#else
+ ret = x509parse_crtfile(&ctx->ca_cert, file);
+#endif
+ if (ret)
+ return -1;
+
+ return 0;
+}
+
__hidden int __ustream_ssl_set_crt_file(struct ustream_ssl_ctx *ctx, const char *file)
{
int ret;
if (ret)
return -1;
- if (!ctx->server)
- ctx->auth = SSL_VERIFY_OPTIONAL;
-
return 0;
}
}
}
+static void ustream_ssl_verify_cert(struct ustream_ssl *us)
+{
+ void *ssl = us->ssl;
+ const char *msg = NULL;
+ bool cn_mismatch;
+ int r;
+
+ r = ssl_get_verify_result(ssl);
+ cn_mismatch = r & BADCERT_CN_MISMATCH;
+ r &= ~BADCERT_CN_MISMATCH;
+
+ if (r & BADCERT_EXPIRED)
+ msg = "certificate has expired";
+ else if (r & BADCERT_REVOKED)
+ msg = "certificate has been revoked";
+ else if (r & BADCERT_NOT_TRUSTED)
+ msg = "certificate is self-signed or not signed by a trusted CA";
+ else
+ msg = "unknown error";
+
+ if (r) {
+ us->notify_verify_error(us, r, msg);
+ return;
+ }
+
+ if (!cn_mismatch)
+ us->valid_cn = true;
+}
+
__hidden enum ssl_conn_status __ustream_ssl_connect(struct ustream_ssl *us)
{
void *ssl = us->ssl;
int r;
r = ssl_handshake(ssl);
- if (r == 0)
+ if (r == 0) {
+ ustream_ssl_verify_cert(us);
return U_SSL_OK;
+ }
if (ssl_do_wait(r))
return U_SSL_PENDING;
__hidden void *__ustream_ssl_session_new(struct ustream_ssl_ctx *ctx)
{
ssl_context *ssl;
+ int auth;
int ep;
ssl = calloc(1, sizeof(ssl_context));
return NULL;
}
- if (ctx->server)
+ if (ctx->server) {
ep = SSL_IS_SERVER;
- else
+ auth = SSL_VERIFY_NONE;
+ } else {
ep = SSL_IS_CLIENT;
+ auth = SSL_VERIFY_OPTIONAL;
+ }
ssl_set_ciphersuites(ssl, default_ciphersuites);
ssl_set_endpoint(ssl, ep);
- ssl_set_authmode(ssl, ctx->auth);
+ ssl_set_authmode(ssl, auth);
ssl_set_rng(ssl, _urandom, NULL);
if (ctx->server) {
if (ctx->cert.next)
ssl_set_ca_chain(ssl, ctx->cert.next, NULL, NULL);
ssl_set_own_cert(ssl, &ctx->cert, &ctx->key);
+ } else {
+ ssl_set_ca_chain(ssl, &ctx->cert, NULL, NULL);
}
ssl_session_reset(ssl);
ssl_free(ssl);
free(ssl);
}
+
+__hidden void __ustream_ssl_update_peer_cn(struct ustream_ssl *us)
+{
+ struct ustream_ssl_ctx *ctx = us->ctx;
+
+ ssl_set_ca_chain(us->ssl, &ctx->ca_cert, NULL, us->peer_cn);
+}
#else
rsa_context key;
#endif
+ x509_crt ca_cert;
x509_crt cert;
- int auth;
bool server;
};
return buffer;
}
+void __ustream_ssl_update_peer_cn(struct ustream_ssl *us);
void __ustream_ssl_session_free(void *ssl);
void *__ustream_ssl_session_new(struct ustream_ssl_ctx *ctx);
*/
#include <errno.h>
+#include <stdlib.h>
#include <libubox/ustream.h>
#include "ustream-ssl.h"
uloop_timeout_cancel(&us->error_timer);
__ustream_ssl_session_free(us->ssl);
+ free(us->peer_cn);
+
us->ctx = NULL;
us->ssl = NULL;
us->conn = NULL;
+ us->peer_cn = NULL;
us->connected = false;
us->error = false;
+ us->valid_cert = false;
+ us->valid_cn = false;
}
static bool ustream_ssl_poll(struct ustream *s)
return 0;
}
+static int _ustream_ssl_set_peer_cn(struct ustream_ssl *us, const char *name)
+{
+ us->peer_cn = strdup(name);
+ __ustream_ssl_update_peer_cn(us);
+
+ return 0;
+}
+
const struct ustream_ssl_ops ustream_ssl_ops = {
.context_new = __ustream_ssl_context_new,
.context_set_crt_file = __ustream_ssl_set_crt_file,
.context_set_key_file = __ustream_ssl_set_key_file,
+ .context_add_ca_crt_file = __ustream_ssl_add_ca_crt_file,
.context_free = __ustream_ssl_context_free,
.init = _ustream_ssl_init,
+ .set_peer_cn = _ustream_ssl_set_peer_cn,
};
void (*notify_connected)(struct ustream_ssl *us);
void (*notify_error)(struct ustream_ssl *us, int error, const char *str);
+ void (*notify_verify_error)(struct ustream_ssl *us, int error, const char *str);
struct ustream_ssl_ctx *ctx;
void *ssl;
+ char *peer_cn;
+
int error;
bool connected;
bool server;
+
+ bool valid_cert;
+ bool valid_cn;
};
struct ustream_ssl_ctx;
struct ustream_ssl_ctx *(*context_new)(bool server);
int (*context_set_crt_file)(struct ustream_ssl_ctx *ctx, const char *file);
int (*context_set_key_file)(struct ustream_ssl_ctx *ctx, const char *file);
+ int (*context_add_ca_crt_file)(struct ustream_ssl_ctx *ctx, const char *file);
void (*context_free)(struct ustream_ssl_ctx *ctx);
int (*init)(struct ustream_ssl *us, struct ustream *conn, struct ustream_ssl_ctx *ctx, bool server);
+ int (*set_peer_cn)(struct ustream_ssl *conn, const char *name);
};
extern const struct ustream_ssl_ops ustream_ssl_ops;
#define ustream_ssl_context_new ustream_ssl_ops.context_new
#define ustream_ssl_context_set_crt_file ustream_ssl_ops.context_set_crt_file
#define ustream_ssl_context_set_key_file ustream_ssl_ops.context_set_key_file
+#define ustream_ssl_context_add_ca_crt_file ustream_ssl_ops.context_add_ca_crt_file
#define ustream_ssl_context_free ustream_ssl_ops.context_free
#define ustream_ssl_init ustream_ssl_ops.init
+#define ustream_ssl_set_peer_cn ustream_ssl_ops.set_peer_cn
#endif