pki: use libtls for pki --est

This commit is contained in:
Andreas Steffen 2022-08-19 02:04:58 +02:00
parent c2dc5f69ca
commit 60a764bad9
5 changed files with 532 additions and 16 deletions

View File

@ -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

View File

@ -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}\""

View File

@ -20,13 +20,11 @@
#include "pki.h"
#include "pki_cert.h"
#include "est/est.h"
#include "est/est_tls.h"
#include <credentials/certificates/certificate.h>
#include <credentials/sets/mem_cred.h>
#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, "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;
}
}
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);

426
src/pki/est/est_tls.c Normal file
View File

@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
*
* 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 <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include "est_tls.h"
#include <utils/debug.h>
#include <utils/lexparser.h>
#include <tls_socket.h>
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 <hostname:port> used for http requests
*/
char *http_host;
/**
* Path string used for http requests
*/
char *http_path;
/**
* Optional <username:password> 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(&parameter, ':', &line) && eat_whitespace(&line))
{
if (matchcase("Content-Length", &parameter))
{
if (sscanf(line.ptr, "%u", &len) == 1)
{
*content_len = len;
}
}
else if (matchcase("Content-Transfer-Encoding", &parameter) &&
matchcase("Base64", &line))
{
*base64 = TRUE;
}
else if (matchcase("Retry-After", &parameter))
{
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 <hostname:port> 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;
}

69
src/pki/est/est_tls.h Normal file
View File

@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
*
* 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 <library.h>
#include <credentials/certificates/certificate.h>
#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_ @}*/