From: Andreas Steffen Date: Fri, 19 Aug 2022 00:04:58 +0000 (+0200) Subject: pki: use libtls for pki --est X-Git-Tag: 5.9.8dr1~2^2~11 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=60a764bad9e74080d8e45fcf3ea631a466343a61;p=thirdparty%2Fstrongswan.git pki: use libtls for pki --est --- diff --git a/configure.ac b/configure.ac index 7cbc96b6e5..4c44bd0d08 100644 --- a/configure.ac +++ b/configure.ac @@ -452,7 +452,7 @@ if test x$tnc_imc = xtrue -o x$tnc_imv = xtrue -o x$tnccs_11 = xtrue -o x$tnccs_ tnc_tnccs=true; fi -if test x$eap_tls = xtrue -o x$eap_ttls = xtrue -o x$eap_peap = xtrue -o x$tnc_tnccs = xtrue; then +if test x$eap_tls = xtrue -o x$eap_ttls = xtrue -o x$eap_peap = xtrue -o x$tnc_tnccs = xtrue -o x$pki = xtrue; then tls=true; fi diff --git a/src/pki/Makefile.am b/src/pki/Makefile.am index 382cd42c88..22fb11fe8d 100644 --- a/src/pki/Makefile.am +++ b/src/pki/Makefile.am @@ -20,15 +20,17 @@ pki_SOURCES = pki.c pki.h pki_cert.c pki_cert.h command.c command.h \ commands/self.c \ commands/signcrl.c \ commands/verify.c \ - est/est.h est/est.c \ + est/est.h est/est.c est/est_tls.h est/est_tls.c \ scep/scep.h scep/scep.c pki_LDADD = \ $(top_builddir)/src/libstrongswan/libstrongswan.la \ + $(top_builddir)/src/libtls/libtls.la \ $(PTHREADLIB) $(ATOMICLIB) $(DLLIB) pki.o : $(top_builddir)/config.status AM_CPPFLAGS = \ -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libtls \ -DPLUGINS=\""${pki_plugins}\"" diff --git a/src/pki/commands/est.c b/src/pki/commands/est.c index 55ba4dc090..ceee5b8106 100644 --- a/src/pki/commands/est.c +++ b/src/pki/commands/est.c @@ -20,13 +20,11 @@ #include "pki.h" #include "pki_cert.h" #include "est/est.h" +#include "est/est_tls.h" #include #include -#define HTTP_CODE_OK 200 -#define HTTP_CODE_ACCEPTED 202 - /* default polling time interval in EST manual mode */ #define DEFAULT_POLL_INTERVAL 60 /* seconds */ @@ -43,9 +41,10 @@ static int est() mem_cred_t *creds = NULL; private_key_t *client_key = NULL; est_op_t est_op = EST_SIMPLE_ENROLL; + est_tls_t *est_tls; u_int poll_interval = DEFAULT_POLL_INTERVAL; u_int max_poll_time = 0, poll_start = 0; - u_int http_code = 0; + u_int http_code = 0, retry_after = 0; int status = 1; /* initialize CA certificate storage */ @@ -165,6 +164,7 @@ static int est() DBG1(DBG_APP, "could not load client cert file '%s'", client_cert_file); goto end; } + creds->add_cert(creds, FALSE, client_cert->get_ref(client_cert)); /* load old client private key */ client_key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_ANY, @@ -174,19 +174,30 @@ static int est() DBG1(DBG_APP, "parsing client private key failed"); goto end; } + creds->add_key(creds, client_key->get_ref(client_key)); est_op = EST_SIMPLE_REENROLL; } - if (!est_https_request(url, est_op, TRUE, pkcs10_encoding, &est_response, - &http_code)) + est_tls = est_tls_create(url, client_cert, NULL); + if (!est_tls) { - DBG1(DBG_APP, "did not receive a valid EST response: HTTP %u", http_code); + DBG1(DBG_APP, "TLS connection to EST server was not established"); + goto end; + } + if (!est_tls->request(est_tls, est_op, pkcs10_encoding, &est_response, + &http_code, &retry_after)) + { + DBG1(DBG_APP, "EST request failed: HTTP %u", http_code); goto end; } /* in case of manual mode, we are going into a polling loop */ - if (http_code == HTTP_CODE_ACCEPTED) + if (http_code == EST_HTTP_CODE_ACCEPTED) { + if (retry_after > 0 && poll_interval < retry_after) + { + poll_interval = retry_after; + } if (max_poll_time > 0) { DBG1(DBG_APP, " EST request pending, polling every %d seconds" @@ -200,7 +211,7 @@ static int est() poll_start = time_monotonic(NULL); } - while (http_code == HTTP_CODE_ACCEPTED) + while (http_code == EST_HTTP_CODE_ACCEPTED) { if (max_poll_time > 0 && (time_monotonic(NULL) - poll_start) >= max_poll_time) @@ -211,16 +222,23 @@ static int est() DBG1(DBG_APP, " going to sleep for %d seconds", poll_interval); sleep(poll_interval); chunk_free(&est_response); - if (!est_https_request(url, est_op, TRUE, pkcs10_encoding, &est_response, - &http_code)) + + est_tls->destroy(est_tls); + est_tls = est_tls_create(url, client_cert, NULL); + if (!est_tls) + { + DBG1(DBG_APP, "TLS connection to EST server was not established"); + goto end; + } + if (!est_tls->request(est_tls, est_op, pkcs10_encoding, &est_response, + &http_code, &retry_after)) { - DBG1(DBG_APP, "did not receive a valid EST response: HTTP %u", - http_code); + DBG1(DBG_APP, "EST request failed: HTTP %u", http_code); goto end; } } - if (http_code == HTTP_CODE_OK) + if (http_code == EST_HTTP_CODE_OK) { status = pki_cert_extract_cert(est_response, form, creds) ? 0 : 1; } @@ -228,6 +246,7 @@ static int est() end: lib->credmgr->remove_set(lib->credmgr, &creds->set); creds->destroy(creds); + DESTROY_IF(est_tls); DESTROY_IF(client_cert); DESTROY_IF(client_key); DESTROY_IF(pkcs10); diff --git a/src/pki/est/est_tls.c b/src/pki/est/est_tls.c new file mode 100644 index 0000000000..2546bb0261 --- /dev/null +++ b/src/pki/est/est_tls.c @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2013-2022 Andreas Steffen + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#define _GNU_SOURCE /* for asprintf() */ +#include +#include +#include +#include +#include + +#include "est_tls.h" + +#include +#include +#include + +static const char *operations[] = { + "cacerts", + "simpleenroll", + "simplereenroll", + "fullcmc", + "serverkeygen", + "csrattrs" +}; + +static const char *request_types[] = { + "", + "application/pkcs10", + "application/pkcs10", + "application/pkcs7-mime", + "application/pkcs10", + "" +}; + +typedef struct private_est_tls_t private_est_tls_t; + +/** + * Private data of an est_tls_t object. + */ +struct private_est_tls_t { + + /** + * Public est_tls_t interface. + */ + est_tls_t public; + + /** + * EST Server (IP address and port) + */ + host_t *host; + + /** + * File descriptor for secure TCP socket + */ + int fd; + + /** + * TLS socket + */ + tls_socket_t *tls; + + /** + * Host string of the form used for http requests + */ + char *http_host; + + /** + * Path string used for http requests + */ + char *http_path; + + /** + * Optional for http basic authentication + */ + char *user_pass; +}; + +static chunk_t build_http_request(private_est_tls_t *this, est_op_t op, chunk_t in) +{ + char *http_header, http_auth[256]; + chunk_t request = chunk_empty, data; + int len; + + /* Use Basic Authentication? */ + if (this->user_pass) + { + snprintf(http_auth, sizeof(http_auth), "Authorization: Basic %s\r\n", + this->user_pass); + } + else + { + *http_auth = '\0'; + } + + if (strlen(request_types[op]) > 0) /* create HTTP POST request */ + { + data = chunk_to_base64(in, NULL); + + len = asprintf(&http_header, + "POST %s/.well-known/est/%s HTTP/1.1\r\n" + "Host: %s\r\n" + "%s" + "Content-Type: %s\r\n" + "Content-Transfer-Encoding: base64\r\n" + "Content-Length: %d\r\n" + "\r\n", + this->http_path, operations[op], this->http_host, http_auth, + request_types[op], (int)data.len); + if (len > 0) + { + request = chunk_cat("mm", chunk_create(http_header, len), data); + } + else + { + chunk_free(&data); + } + } + else /* create HTTP GET request */ + { + len = asprintf(&http_header, + "GET %s/.well-known/est/%s HTTP/1.1\r\n" + "Host: %s\r\n" + "%s", + this->http_path, operations[op], this->http_host, http_auth); + if (len > 0) + { + request = chunk_create(http_header, len); + } + } + return request; +} + +static bool parse_http_header(chunk_t *in, u_int *http_code, u_int *content_len, + bool *base64, u_int *retry_after) +{ + chunk_t line, version, parameter; + u_int len; + + /*initialize output parameters */ + *http_code = 0; + *content_len = 0; + *base64 = FALSE; + + /* Process HTTP protocol version and HTTP status code */ + if (!fetchline(in, &line) || !extract_token(&version, ' ', &line) || + !match("HTTP/1.1", &version) || sscanf(line.ptr, "%d", http_code) != 1) + { + DBG1(DBG_APP, "malformed http response header"); + return FALSE; + } + + /* Process HTTP header line by line until the HTTP body is reached */ + while (fetchline(in, &line)) + { + if (line.len == 0) + { + break; + } + if (extract_token(¶meter, ':', &line) && eat_whitespace(&line)) + { + if (matchcase("Content-Length", ¶meter)) + { + if (sscanf(line.ptr, "%u", &len) == 1) + { + *content_len = len; + } + } + else if (matchcase("Content-Transfer-Encoding", ¶meter) && + matchcase("Base64", &line)) + { + *base64 = TRUE; + } + else if (matchcase("Retry-After", ¶meter)) + { + if (sscanf(line.ptr, "%u", &len) == 1 && retry_after) + { + *retry_after = len; + } + } + } + } + + return (*http_code < 300); +} + + +METHOD(est_tls_t, request, bool, + private_est_tls_t *this, est_op_t op, chunk_t in, chunk_t *out, + u_int *http_code, u_int *retry_after) +{ + chunk_t http = chunk_empty, data = chunk_empty, response; + u_int content_len; + char buf[1024]; + bool base64; + int len; + + /* initialize output variables */ + *out = chunk_empty; + *http_code = 0; + *retry_after = 0; + + http = build_http_request(this, op, in); + + if (http.len == 0) + { + return FALSE; + } + DBG1(DBG_APP, "http: %B", &http); + + /* send https request */ + if (this->tls->write(this->tls, http.ptr, http.len) != http.len) + { + DBG1(DBG_APP, "TLS socket write failed"); + chunk_free(&http); + return FALSE; + } + chunk_free(&http); + + /* receive first part of https response */ + len = this->tls->read(this->tls, buf, sizeof(buf), TRUE); + if (len <= 0) + { + DBG1(DBG_APP, "TLS socket first read failed"); + return FALSE; + } + response = chunk_create(buf, len); + DBG1(DBG_APP, "response: %B", &response); + + if (!parse_http_header(&response, http_code, &content_len, &base64, + retry_after)) + { + return FALSE; + } + if (*http_code == EST_HTTP_CODE_OK) + { + if (content_len == 0) + { + DBG1(DBG_APP, "no content-length defined in http header"); + return FALSE; + } + if (response.len > content_len) + { + DBG1(DBG_APP, "http body is larger than content-length"); + return FALSE; + } + + data = chunk_alloc(content_len); + memcpy(data.ptr, response.ptr, response.len); + + if (data.len > response.len) + { + /* read remaining part of https response */ + len = this->tls->read(this->tls, data.ptr + response.len, + data.len - response.len, TRUE); + if (len < data.len - response.len) + { + DBG1(DBG_APP, "TLS socket second read failed"); + chunk_free(&data); + return FALSE; + } + } + + if (base64) + { + *out = chunk_from_base64(data, NULL); + chunk_free(&data); + } + else + { + *out = data; + } + } + return TRUE; +} + +METHOD(est_tls_t, destroy, void, + private_est_tls_t *this) +{ + DESTROY_IF(this->tls); + DESTROY_IF(this->host); + if (this->fd != -1) + { + close(this->fd); + } + free(this->http_host); + free(this->http_path); + free(this->user_pass); + free(this); +} + +static bool est_tls_init(private_est_tls_t *this, char *uri, + certificate_t *client_cert) +{ + identification_t *client_id = NULL, *server_id = NULL; + char *host_str, *port_str, *path_str; + int port = 443; + bool success = FALSE; + + /* check for "https://" prefix and remove it */ + if (strlen(uri) < 8 || !strncaseeq(uri, "https://", 8)) + { + DBG1(DBG_APP, "'%s' is not an https URI", uri); + return FALSE; + } + uri += 8; + + /* any trailing path or command? */ + path_str = strchr(uri, '/'); + + this->http_path = + strdup( (path_str == NULL || path_str[1] == '\0') ? "" : path_str ); + + if (path_str) + { + /* NUL-terminate host_str */ + *path_str = '\0'; + } + + /* duplicate string since we are going to manipulate it */ + host_str = strdup(uri); + + /* another duplicate for http requests */ + this->http_host = strdup(host_str); + + /* extract hostname and port from URI */ + port_str = strchr(host_str, ':'); + + if (port_str) + { + /* NUL-terminate hostname */ + *port_str++ = '\0'; + + /* extract port */ + if (sscanf(port_str, "%d", &port) != 1) + { + DBG1(DBG_APP, "parsing server port %s failed", port_str); + goto end; + } + } + + /* open TCP socket and connect to EST server */ + this->host = host_create_from_dns(host_str, 0, port); + if (!this->host) + { + DBG1(DBG_APP, "resolving hostname %s failed", host_str); + goto end; + } + + this->fd = socket(this->host->get_family(this->host), SOCK_STREAM, 0); + if (this->fd == -1) + { + DBG1(DBG_APP, "opening socket failed: %s", strerror(errno)); + goto end; + } + + if (connect(this->fd, this->host->get_sockaddr(this->host), + *this->host->get_sockaddr_len(this->host)) == -1) + { + DBG1(DBG_APP, "connecting to %#H failed: %s", + this->host, strerror(errno)); + goto end; + } + + if (client_cert) + { + client_id = client_cert->get_subject(client_cert); + } + server_id = identification_create_from_string(host_str); + + /* open TLS socket */ + this->tls = tls_socket_create(FALSE, server_id, client_id, this->fd, + NULL, TLS_UNSPEC, TLS_UNSPEC, 0); + server_id->destroy(server_id); + if (!this->tls) + { + DBG1(DBG_APP, "creating TLS socket failed"); + goto end; + } + success = TRUE; + +end: + free(host_str); + + return success; +} + +/** + * See header + */ +est_tls_t *est_tls_create(char *uri, certificate_t *client_cert, char *user_pass) +{ + private_est_tls_t *this; + + INIT(this, + .public = { + .request = _request, + .destroy = _destroy, + }, + ); + + if (user_pass) + { + this->user_pass = strdup(user_pass); + } + + if (!est_tls_init(this, uri, client_cert)) + { + destroy(this); + return NULL; + } + + return &this->public; +} diff --git a/src/pki/est/est_tls.h b/src/pki/est/est_tls.h new file mode 100644 index 0000000000..d05831c718 --- /dev/null +++ b/src/pki/est/est_tls.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 Andreas Steffen + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup est_tls est_tls + * @{ @ingroup pki + */ + +#ifndef EST_TLS_H_ +#define EST_TLS_H_ + +#include "est.h" + +#include +#include + +#define EST_HTTP_CODE_OK 200 +#define EST_HTTP_CODE_ACCEPTED 202 + +typedef struct est_tls_t est_tls_t; + +/** + * TLS Interface for sending and receiving HTTPS messages + */ +struct est_tls_t { + + /** + * Send a https request and get a response back + * + * @param option EST operation + * @param in HTTP POST input data + * @param out HTTP response + * @param http_code HTTP status code + * @param retry_after Retry time in seconds + * @result TRUE if successful + */ + bool (*request)(est_tls_t *this, est_op_t op, chunk_t in, chunk_t *out, + u_int *http_code, u_int *retry_after); + + /** + * Destroy an est_tls_t object. + */ + void (*destroy)(est_tls_t *this); +}; + +/** + * Create a est_tls instance. + * + * @param uri URI (https://...) + * @param client_cert Optional client certificate + * @param user_pass Optional username:password for HTTP Basic Authentication + */ +est_tls_t *est_tls_create(char *uri, certificate_t *client_cert, + char *user_pass); + +#endif /** EST_TLS_H_ @}*/