mirror of
https://github.com/strongswan/strongswan.git
synced 2025-10-06 00:00:47 -04:00
pki: use libtls for pki --est
This commit is contained in:
parent
c2dc5f69ca
commit
60a764bad9
@ -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
|
||||
|
||||
|
@ -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}\""
|
||||
|
@ -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
426
src/pki/est/est_tls.c
Normal 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(¶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 <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
69
src/pki/est/est_tls.h
Normal 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_ @}*/
|
Loading…
x
Reference in New Issue
Block a user