Commit 1cc5b5c7 authored by David Woodhouse's avatar David Woodhouse

Kill OPENCONNECT_X509, let certain functions only operate on peer_cert

This makes the certificate handling a whole lot saner.
Signed-off-by: default avatarDavid Woodhouse <David.Woodhouse@intel.com>
parent be40969c
......@@ -1648,43 +1648,51 @@ static int get_cert_fingerprint(struct openconnect_info *vpninfo,
}
int get_cert_md5_fingerprint(struct openconnect_info *vpninfo,
OPENCONNECT_X509 *cert, char *buf)
void *cert, char *buf)
{
return get_cert_fingerprint(vpninfo, cert, GNUTLS_DIG_MD5, buf);
}
int openconnect_get_cert_sha1(struct openconnect_info *vpninfo,
OPENCONNECT_X509 *cert, char *buf)
const char *openconnect_get_peer_cert_hash(struct openconnect_info *vpninfo)
{
return get_cert_fingerprint(vpninfo, cert, GNUTLS_DIG_SHA1, buf);
if (!vpninfo->peer_cert_hash) {
char buf[41];
if (get_cert_fingerprint(vpninfo, vpninfo->peer_cert,
GNUTLS_DIG_SHA1, buf))
return NULL;
vpninfo->peer_cert_hash = strdup(buf);
}
return vpninfo->peer_cert_hash;
}
char *openconnect_get_cert_details(struct openconnect_info *vpninfo,
OPENCONNECT_X509 *cert)
char *openconnect_get_peer_cert_details(struct openconnect_info *vpninfo)
{
gnutls_datum_t buf;
if (gnutls_x509_crt_print(cert, GNUTLS_CRT_PRINT_FULL, &buf))
if (gnutls_x509_crt_print(vpninfo->peer_cert, GNUTLS_CRT_PRINT_FULL, &buf))
return NULL;
return (char *)buf.data;
}
int openconnect_get_cert_DER(struct openconnect_info *vpninfo,
OPENCONNECT_X509 *cert, unsigned char **buf)
int openconnect_get_peer_cert_DER(struct openconnect_info *vpninfo,
unsigned char **buf)
{
size_t l = 0;
unsigned char *ret = NULL;
if (gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, ret, &l) !=
GNUTLS_E_SHORT_MEMORY_BUFFER)
if (gnutls_x509_crt_export(vpninfo->peer_cert, GNUTLS_X509_FMT_DER,
ret, &l) != GNUTLS_E_SHORT_MEMORY_BUFFER)
return -EIO;
ret = gnutls_malloc(l);
if (!ret)
return -ENOMEM;
if (gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, ret, &l)) {
if (gnutls_x509_crt_export(vpninfo->peer_cert, GNUTLS_X509_FMT_DER,
ret, &l)) {
gnutls_free(ret);
return -EIO;
}
......@@ -1705,7 +1713,7 @@ static int verify_peer(gnutls_session_t session)
gnutls_x509_crt_t cert;
unsigned int status, cert_list_size;
const char *reason = NULL;
int err;
int err = 0;
if (vpninfo->peer_cert) {
gnutls_x509_crt_deinit(vpninfo->peer_cert);
......@@ -1718,6 +1726,19 @@ static int verify_peer(gnutls_session_t session)
return GNUTLS_E_CERTIFICATE_ERROR;
}
err = gnutls_x509_crt_init(&cert);
if (err) {
vpn_progress(vpninfo, PRG_ERR, _("Error initialising X509 cert structure\n"));
return GNUTLS_E_CERTIFICATE_ERROR;
}
err = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
if (err) {
vpn_progress(vpninfo, PRG_ERR, _("Error importing server's cert\n"));
gnutls_x509_crt_deinit(cert);
return GNUTLS_E_CERTIFICATE_ERROR;
}
if (vpninfo->servercert) {
unsigned char sha1bin[SHA1_SIZE];
char fingerprint[(SHA1_SIZE * 2) + 1];
......@@ -1737,7 +1758,7 @@ static int verify_peer(gnutls_session_t session)
_("Server SSL certificate didn't match: %s\n"), fingerprint);
return GNUTLS_E_CERTIFICATE_ERROR;
}
return 0;
goto done;
}
err = gnutls_certificate_verify_peers2(session, &status);
......@@ -1764,19 +1785,6 @@ static int verify_peer(gnutls_session_t session)
why we don't just set a bit for that too. */
reason = _("signature verification failed");
err = gnutls_x509_crt_init(&cert);
if (err) {
vpn_progress(vpninfo, PRG_ERR, _("Error initialising X509 cert structure\n"));
return GNUTLS_E_CERTIFICATE_ERROR;
}
err = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
if (err) {
vpn_progress(vpninfo, PRG_ERR, _("Error importing server's cert\n"));
gnutls_x509_crt_deinit(cert);
return GNUTLS_E_CERTIFICATE_ERROR;
}
if (reason)
goto done;
......@@ -1826,20 +1834,21 @@ static int verify_peer(gnutls_session_t session)
reason = _("certificate does not match hostname");
}
done:
vpninfo->peer_cert = cert;
free(vpninfo->peer_cert_hash);
vpninfo->peer_cert_hash = 0;
if (reason) {
vpn_progress(vpninfo, PRG_INFO,
_("Server certificate verify failed: %s\n"),
reason);
if (vpninfo->validate_peer_cert)
err = vpninfo->validate_peer_cert(vpninfo->cbdata,
cert,
reason) ? GNUTLS_E_CERTIFICATE_ERROR : 0;
else
err = GNUTLS_E_CERTIFICATE_ERROR;
}
vpninfo->peer_cert = cert;
return err;
}
......@@ -2086,6 +2095,9 @@ void openconnect_close_https(struct openconnect_info *vpninfo, int final)
gnutls_x509_crt_deinit(vpninfo->peer_cert);
vpninfo->peer_cert = NULL;
}
free(vpninfo->peer_cert_hash);
vpninfo->peer_cert_hash = NULL;
if (vpninfo->https_sess) {
gnutls_deinit(vpninfo->https_sess);
vpninfo->https_sess = NULL;
......
......@@ -32,7 +32,6 @@ struct libctx {
jobject jobj;
jobject async_lock;
struct openconnect_info *vpninfo;
OPENCONNECT_X509 *cert;
int cmd_fd;
int loglevel;
};
......@@ -195,7 +194,7 @@ static int add_string_pair(struct libctx *ctx, jclass jcls, jobject jobj,
return 0;
}
static int validate_peer_cert_cb(void *privdata, OPENCONNECT_X509 *cert, const char *reason)
static int validate_peer_cert_cb(void *privdata, const char *reason)
{
struct libctx *ctx = privdata;
jstring jreason;
......@@ -209,7 +208,6 @@ static int validate_peer_cert_cb(void *privdata, OPENCONNECT_X509 *cert, const c
if (!jreason)
goto out;
ctx->cert = cert;
mid = get_obj_mid(ctx, ctx->jobj, "onValidatePeerCert", "(Ljava/lang/String;)I");
if (mid)
ret = (*ctx->jenv)->CallIntMethod(ctx->jenv, ctx->jobj, mid, jreason);
......@@ -685,15 +683,10 @@ JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_obtainCo
JNIEnv *jenv, jobject jobj)
{
struct libctx *ctx = getctx(jenv, jobj);
int ret;
if (!ctx)
return 0;
ctx->cert = NULL;
ret = openconnect_obtain_cookie(ctx->vpninfo);
if (ret == 0)
ctx->cert = openconnect_get_peer_cert(ctx->vpninfo);
return ret;
return openconnect_obtain_cookie(ctx->vpninfo);
}
/* special handling: caller-allocated buffer */
......@@ -701,14 +694,15 @@ JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getCe
JNIEnv *jenv, jobject jobj)
{
struct libctx *ctx = getctx(jenv, jobj);
char buf[41];
const char *hash;
jstring jresult = NULL;
if (!ctx || !ctx->cert)
if (!ctx)
return NULL;
if (openconnect_get_cert_sha1(ctx->vpninfo, ctx->cert, buf))
hash = openconnect_get_peer_cert_hash(ctx->vpninfo);
if (!hash)
return NULL;
jresult = dup_to_jstring(ctx->jenv, buf);
jresult = dup_to_jstring(ctx->jenv, hash);
if (!jresult)
OOM(ctx->jenv);
return jresult;
......@@ -722,9 +716,9 @@ JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getCe
char *buf = NULL;
jstring jresult = NULL;
if (!ctx || !ctx->cert)
if (!ctx)
return NULL;
buf = openconnect_get_cert_details(ctx->vpninfo, ctx->cert);
buf = openconnect_get_peer_cert_details(ctx->vpninfo);
if (!buf)
return NULL;
......@@ -745,9 +739,9 @@ JNIEXPORT jbyteArray JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_ge
int ret;
jbyteArray jresult = NULL;
if (!ctx || !ctx->cert)
if (!ctx)
return NULL;
ret = openconnect_get_cert_DER(ctx->vpninfo, ctx->cert, &buf);
ret = openconnect_get_peer_cert_DER(ctx->vpninfo, &buf);
if (ret < 0)
return NULL;
......
OPENCONNECT_4.0 {
OPENCONNECT_5.0 {
global:
openconnect_free_cert_info;
openconnect_set_option_value;
openconnect_clear_cookie;
openconnect_get_cert_sha1;
openconnect_get_peer_cert_hash;
openconnect_get_cookie;
openconnect_get_hostname;
openconnect_get_peer_cert;
openconnect_get_port;
openconnect_get_urlpath;
openconnect_get_version;
......@@ -25,8 +24,8 @@ OPENCONNECT_4.0 {
openconnect_vpninfo_free;
openconnect_set_cert_expiry_warning;
openconnect_set_cancel_fd;
openconnect_get_cert_details;
openconnect_get_cert_DER;
openconnect_get_peer_cert_details;
openconnect_get_peer_cert_DER;
openconnect_init_ssl;
openconnect_has_tss_blob_support;
openconnect_has_pkcs11_support;
......@@ -54,15 +53,11 @@ OPENCONNECT_4.0 {
openconnect_set_dpd;
openconnect_set_proxy_auth;
openconnect_set_token_callbacks;
};
OPENCONNECT_4.1 {
global:
openconnect_set_system_trust;
openconnect_get_dtls_cipher;
openconnect_get_cstp_cipher;
openconnect_set_csd_environ;
} OPENCONNECT_4.0;
};
OPENCONNECT_PRIVATE {
......
......@@ -236,6 +236,7 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo)
#endif
vpninfo->peer_cert = NULL;
}
free(vpninfo->peer_cert_hash);
free(vpninfo->localname);
free(vpninfo->useragent);
free(vpninfo->authgroup);
......@@ -394,11 +395,6 @@ int openconnect_set_client_cert(struct openconnect_info *vpninfo,
return 0;
}
OPENCONNECT_X509 *openconnect_get_peer_cert(struct openconnect_info *vpninfo)
{
return vpninfo->peer_cert;
}
int openconnect_get_port(struct openconnect_info *vpninfo)
{
return vpninfo->port;
......
......@@ -66,9 +66,7 @@ static int write_new_config(void *_vpninfo,
const char *buf, int buflen);
static void __attribute__ ((format(printf, 3, 4)))
write_progress(void *_vpninfo, int level, const char *fmt, ...);
static int validate_peer_cert(void *_vpninfo,
OPENCONNECT_X509 *peer_cert,
const char *reason);
static int validate_peer_cert(void *_vpninfo, const char *reason);
static int process_auth_form_cb(void *_vpninfo,
struct oc_auth_form *form);
static void init_token(struct openconnect_info *vpninfo,
......@@ -1333,11 +1331,8 @@ int main(int argc, char **argv)
/* --authenticate */
printf("COOKIE='%s'\n", vpninfo->cookie);
printf("HOST='%s'\n", openconnect_get_hostname(vpninfo));
if (vpninfo->peer_cert) {
char buf[41] = {0, };
openconnect_get_cert_sha1(vpninfo, vpninfo->peer_cert, buf);
printf("FINGERPRINT='%s'\n", buf);
}
printf("FINGERPRINT='%s'\n",
openconnect_get_peer_cert_hash(vpninfo));
openconnect_vpninfo_free(vpninfo);
exit(0);
} else if (cookieonly) {
......@@ -1531,20 +1526,16 @@ struct accepted_cert {
char host[0];
} *accepted_certs;
static int validate_peer_cert(void *_vpninfo, OPENCONNECT_X509 *peer_cert,
const char *reason)
static int validate_peer_cert(void *_vpninfo, const char *reason)
{
struct openconnect_info *vpninfo = _vpninfo;
char fingerprint[SHA1_SIZE * 2 + 1];
const char *fingerprint;
struct accepted_cert *this;
int ret;
if (nocertcheck)
return 0;
ret = openconnect_get_cert_sha1(vpninfo, peer_cert, fingerprint);
if (ret)
return ret;
fingerprint = openconnect_get_peer_cert_hash(vpninfo);
for (this = accepted_certs; this; this = this->next) {
if (!strcasecmp(this->host, vpninfo->hostname) &&
......@@ -1587,9 +1578,9 @@ static int validate_peer_cert(void *_vpninfo, OPENCONNECT_X509 *peer_cert,
}
free(response);
details = openconnect_get_cert_details(vpninfo, peer_cert);
details = openconnect_get_peer_cert_details(vpninfo);
fputs(details, stderr);
free(details);
openconnect_free_cert_info(vpninfo, details);
fprintf(stderr, _("SHA1 fingerprint: %s\n"), fingerprint);
}
}
......
......@@ -282,7 +282,8 @@ struct openconnect_info {
openconnect_unlock_token_vfn unlock_token;
void *tok_cbdata;
OPENCONNECT_X509 *peer_cert;
void *peer_cert;
char *peer_cert_hash;
char *cookie; /* Pointer to within cookies list */
struct oc_vpn_option *cookies;
......@@ -621,7 +622,7 @@ void openconnect_clear_cookies(struct openconnect_info *vpninfo);
int openconnect_open_https(struct openconnect_info *vpninfo);
void openconnect_close_https(struct openconnect_info *vpninfo, int final);
int cstp_handshake(struct openconnect_info *vpninfo, unsigned init);
int get_cert_md5_fingerprint(struct openconnect_info *vpninfo, OPENCONNECT_X509 *cert,
int get_cert_md5_fingerprint(struct openconnect_info *vpninfo, void *cert,
char *buf);
int openconnect_sha1(unsigned char *result, void *data, int len);
int openconnect_md5(unsigned char *result, void *data, int len);
......
......@@ -28,10 +28,15 @@
#define uid_t unsigned
#endif
#define OPENCONNECT_API_VERSION_MAJOR 4
#define OPENCONNECT_API_VERSION_MINOR 1
#define OPENCONNECT_API_VERSION_MAJOR 5
#define OPENCONNECT_API_VERSION_MINOR 0
/*
* API version 5.0:
* - Remove OPENCONNECT_X509 and openconnect_get_peer_cert().
* - Change openconnect_get_cert_der() to openconnect_get_peer_cert_DER() etc.
* - Add openconnect_check_peer_cert_hash().
*
* API version 4.1:
* - Add openconnect_get_cstp_cipher(), openconnect_get_dtls_cipher(),
* openconnect_set_system_trust(), openconnect_set_csd_environ().
......@@ -263,8 +268,6 @@ struct oc_stats {
struct openconnect_info;
#define OPENCONNECT_X509 void
typedef enum {
OC_TOKEN_MODE_NONE,
OC_TOKEN_MODE_STOKEN,
......@@ -293,19 +296,20 @@ typedef enum {
int openconnect_set_csd_environ(struct openconnect_info *vpninfo,
const char *name, const char *value);
/* The buffer 'buf' must be at least 41 bytes. It will receive a hex string
with trailing NUL, representing the SHA1 fingerprint of the certificate. */
int openconnect_get_cert_sha1(struct openconnect_info *vpninfo,
OPENCONNECT_X509 *cert, char *buf);
/* This string is static, valid only while the connection lasts. If you
* are going to cache this to remember which certs the user has accepted,
* make sure you also store the host/port for which it was accepted and
* don't just accept this cert from *anywhere*. */
const char *openconnect_get_peer_cert_hash(struct openconnect_info *vpninfo);
/* The buffers returned by these two functions must be freed with
openconnect_free_cert_info(), especially on Windows. */
char *openconnect_get_cert_details(struct openconnect_info *vpninfo,
OPENCONNECT_X509 *cert);
char *openconnect_get_peer_cert_details(struct openconnect_info *vpninfo);
/* Returns the length of the created DER output, in a newly-allocated buffer
that will need to be freed by openconnect_free_cert_info(). */
int openconnect_get_cert_DER(struct openconnect_info *vpninfo,
OPENCONNECT_X509 *cert, unsigned char **buf);
int openconnect_get_peer_cert_DER(struct openconnect_info *vpninfo,
unsigned char **buf);
void openconnect_free_cert_info(struct openconnect_info *vpninfo,
void *buf);
/* Contains a comma-separated list of authentication methods to enabled.
......@@ -399,12 +403,6 @@ int openconnect_get_ip_info(struct openconnect_info *,
const struct oc_vpn_option **cstp_options,
const struct oc_vpn_option **dtls_options);
/* This is *not* yours and must not be destroyed with X509_free(). It
will be valid when a cookie has been obtained successfully, and will
be valid until the connection is destroyed or another attempt it made
to use it. */
OPENCONNECT_X509 *openconnect_get_peer_cert(struct openconnect_info *);
int openconnect_get_port(struct openconnect_info *);
const char *openconnect_get_cookie(struct openconnect_info *);
void openconnect_clear_cookie(struct openconnect_info *);
......@@ -480,7 +478,6 @@ int openconnect_mainloop(struct openconnect_info *vpninfo,
if the certificate is (or has in the past been) explicitly accepted
by the user, and non-zero to abort the connection. */
typedef int (*openconnect_validate_peer_cert_vfn) (void *privdata,
OPENCONNECT_X509 *cert,
const char *reason);
/* On a successful connection, the server may provide us with a new XML
configuration file. This contains the list of servers that can be
......
......@@ -57,14 +57,14 @@ int openconnect_md5(unsigned char *result, void *data, int len)
return 0;
}
int openconnect_get_cert_DER(struct openconnect_info *vpninfo,
OPENCONNECT_X509 *cert, unsigned char **buf)
int openconnect_get_peer_cert_DER(struct openconnect_info *vpninfo,
unsigned char **buf)
{
BIO *bp = BIO_new(BIO_s_mem());
BUF_MEM *certinfo;
size_t l;
if (!i2d_X509_bio(bp, cert)) {
if (!i2d_X509_bio(bp, vpninfo->peer_cert)) {
BIO_free(bp);
return -EIO;
}
......@@ -870,7 +870,7 @@ static int load_certificate(struct openconnect_info *vpninfo)
}
static int get_cert_fingerprint(struct openconnect_info *vpninfo,
OPENCONNECT_X509 *cert, const EVP_MD *type,
X509 *cert, const EVP_MD *type,
char *buf)
{
unsigned char md[EVP_MAX_MD_SIZE];
......@@ -886,25 +886,30 @@ static int get_cert_fingerprint(struct openconnect_info *vpninfo,
}
int get_cert_md5_fingerprint(struct openconnect_info *vpninfo,
OPENCONNECT_X509 *cert, char *buf)
void *cert, char *buf)
{
return get_cert_fingerprint(vpninfo, cert, EVP_md5(), buf);
}
int openconnect_get_cert_sha1(struct openconnect_info *vpninfo,
OPENCONNECT_X509 *cert, char *buf)
const char *openconnect_get_peer_cert_hash(struct openconnect_info *vpninfo)
{
return get_cert_fingerprint(vpninfo, cert, EVP_sha1(), buf);
if (!vpninfo->peer_cert_hash) {
char buf[41];
if (get_cert_fingerprint(vpninfo, vpninfo->peer_cert,
EVP_sha1(), buf))
return NULL;
vpninfo->peer_cert_hash = strdup(buf);
}
return vpninfo->peer_cert_hash;
}
static int check_server_cert(struct openconnect_info *vpninfo, X509 *cert)
{
char fingerprint[EVP_MAX_MD_SIZE * 2 + 1];
int ret;
const char *fingerprint;
ret = openconnect_get_cert_sha1(vpninfo, cert, fingerprint);
if (ret)
return ret;
fingerprint = openconnect_get_peer_cert_hash(vpninfo);
if (strcasecmp(vpninfo->servercert, fingerprint)) {
vpn_progress(vpninfo, PRG_ERR,
......@@ -1190,22 +1195,19 @@ static int match_cert_hostname(struct openconnect_info *vpninfo, X509 *peer_cert
static int verify_peer(struct openconnect_info *vpninfo, SSL *https_ssl)
{
X509 *peer_cert;
int ret;
peer_cert = SSL_get_peer_certificate(https_ssl);
if (vpninfo->servercert) {
/* If given a cert fingerprint on the command line, that's
all we look for */
ret = check_server_cert(vpninfo, peer_cert);
ret = check_server_cert(vpninfo, vpninfo->peer_cert);
} else {
int vfy = SSL_get_verify_result(https_ssl);
const char *err_string = NULL;
if (vfy != X509_V_OK)
err_string = X509_verify_cert_error_string(vfy);
else if (match_cert_hostname(vpninfo, peer_cert))
else if (match_cert_hostname(vpninfo, vpninfo->peer_cert))
err_string = _("certificate does not match hostname");
if (err_string) {
......@@ -1215,7 +1217,6 @@ static int verify_peer(struct openconnect_info *vpninfo, SSL *https_ssl)
if (vpninfo->validate_peer_cert)
ret = vpninfo->validate_peer_cert(vpninfo->cbdata,
peer_cert,
err_string);
else
ret = -EINVAL;
......@@ -1223,7 +1224,6 @@ static int verify_peer(struct openconnect_info *vpninfo, SSL *https_ssl)
ret = 0;
}
}
X509_free(peer_cert);
return ret;
}
......@@ -1334,6 +1334,8 @@ int openconnect_open_https(struct openconnect_info *vpninfo)
X509_free(vpninfo->peer_cert);
vpninfo->peer_cert = NULL;
}
free (vpninfo->peer_cert_hash);
vpninfo->peer_cert_hash = NULL;
ssl_sock = connect_https_socket(vpninfo);
if (ssl_sock < 0)
......@@ -1491,6 +1493,8 @@ int openconnect_open_https(struct openconnect_info *vpninfo)
}
}
vpninfo->peer_cert = SSL_get_peer_certificate(https_ssl);
if (verify_peer(vpninfo, https_ssl)) {
SSL_free(https_ssl);
closesocket(ssl_sock);
......@@ -1504,9 +1508,6 @@ int openconnect_open_https(struct openconnect_info *vpninfo)
vpninfo->ssl_write = openconnect_openssl_write;
vpninfo->ssl_gets = openconnect_openssl_gets;
/* Stash this now, because it might not be available later if the
server has disconnected. */
vpninfo->peer_cert = SSL_get_peer_certificate(vpninfo->https_ssl);
vpn_progress(vpninfo, PRG_INFO, _("Connected to HTTPS on %s\n"),
vpninfo->hostname);
......@@ -1525,6 +1526,9 @@ void openconnect_close_https(struct openconnect_info *vpninfo, int final)
X509_free(vpninfo->peer_cert);
vpninfo->peer_cert = NULL;
}
free (vpninfo->peer_cert_hash);
vpninfo->peer_cert_hash = NULL;
if (vpninfo->https_ssl) {
SSL_free(vpninfo->https_ssl);
vpninfo->https_ssl = NULL;
......@@ -1562,15 +1566,14 @@ int openconnect_init_ssl(void)
return 0;
}
char *openconnect_get_cert_details(struct openconnect_info *vpninfo,
OPENCONNECT_X509 *cert)
char *openconnect_get_peer_cert_details(struct openconnect_info *vpninfo)
{
BIO *bp = BIO_new(BIO_s_mem());
BUF_MEM *certinfo;
char zero = 0;
char *ret;
X509_print_ex(bp, cert, 0, 0);
X509_print_ex(bp, vpninfo->peer_cert, 0, 0);
BIO_write(bp, &zero, 1);
BIO_get_mem_ptr(bp, &certinfo);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment