2015-03-13 18:39:26 +01:00
|
|
|
/*
|
|
|
|
* QEMU crypto TLS x509 credential support
|
|
|
|
*
|
|
|
|
* Copyright (c) 2015 Red Hat, Inc.
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
2019-02-13 16:54:59 +01:00
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2015-03-13 18:39:26 +01:00
|
|
|
*
|
|
|
|
* This library 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
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2016-01-26 19:16:55 +01:00
|
|
|
#include "qemu/osdep.h"
|
2015-03-13 18:39:26 +01:00
|
|
|
#include "crypto/tlscredsx509.h"
|
2018-05-03 21:50:23 +02:00
|
|
|
#include "tlscredspriv.h"
|
2015-10-15 17:14:42 +02:00
|
|
|
#include "crypto/secret.h"
|
include/qemu/osdep.h: Don't include qapi/error.h
Commit 57cb38b included qapi/error.h into qemu/osdep.h to get the
Error typedef. Since then, we've moved to include qemu/osdep.h
everywhere. Its file comment explains: "To avoid getting into
possible circular include dependencies, this file should not include
any other QEMU headers, with the exceptions of config-host.h,
compiler.h, os-posix.h and os-win32.h, all of which are doing a
similar job to this file and are under similar constraints."
qapi/error.h doesn't do a similar job, and it doesn't adhere to
similar constraints: it includes qapi-types.h. That's in excess of
100KiB of crap most .c files don't actually need.
Add the typedef to qemu/typedefs.h, and include that instead of
qapi/error.h. Include qapi/error.h in .c files that need it and don't
get it now. Include qapi-types.h in qom/object.h for uint16List.
Update scripts/clean-includes accordingly. Update it further to match
reality: replace config.h by config-target.h, add sysemu/os-posix.h,
sysemu/os-win32.h. Update the list of includes in the qemu/osdep.h
comment quoted above similarly.
This reduces the number of objects depending on qapi/error.h from "all
of them" to less than a third. Unfortunately, the number depending on
qapi-types.h shrinks only a little. More work is needed for that one.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
[Fix compilation without the spice devel packages. - Paolo]
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
2016-03-14 09:01:28 +01:00
|
|
|
#include "qapi/error.h"
|
2019-05-23 16:35:07 +02:00
|
|
|
#include "qemu/module.h"
|
2015-03-13 18:39:26 +01:00
|
|
|
#include "qom/object_interfaces.h"
|
|
|
|
#include "trace.h"
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_GNUTLS
|
|
|
|
|
2015-04-13 15:01:39 +02:00
|
|
|
#include <gnutls/x509.h>
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcrypto_tls_creds_check_cert_times(gnutls_x509_crt_t cert,
|
|
|
|
const char *certFile,
|
|
|
|
bool isServer,
|
|
|
|
bool isCA,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
time_t now = time(NULL);
|
|
|
|
|
|
|
|
if (now == ((time_t)-1)) {
|
|
|
|
error_setg_errno(errp, errno, "cannot get current time");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gnutls_x509_crt_get_expiration_time(cert) < now) {
|
|
|
|
error_setg(errp,
|
|
|
|
(isCA ?
|
|
|
|
"The CA certificate %s has expired" :
|
|
|
|
(isServer ?
|
|
|
|
"The server certificate %s has expired" :
|
|
|
|
"The client certificate %s has expired")),
|
|
|
|
certFile);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gnutls_x509_crt_get_activation_time(cert) > now) {
|
|
|
|
error_setg(errp,
|
|
|
|
(isCA ?
|
|
|
|
"The CA certificate %s is not yet active" :
|
|
|
|
(isServer ?
|
|
|
|
"The server certificate %s is not yet active" :
|
|
|
|
"The client certificate %s is not yet active")),
|
|
|
|
certFile);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcrypto_tls_creds_check_cert_basic_constraints(QCryptoTLSCredsX509 *creds,
|
|
|
|
gnutls_x509_crt_t cert,
|
|
|
|
const char *certFile,
|
|
|
|
bool isServer,
|
|
|
|
bool isCA,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
status = gnutls_x509_crt_get_basic_constraints(cert, NULL, NULL, NULL);
|
|
|
|
trace_qcrypto_tls_creds_x509_check_basic_constraints(
|
|
|
|
creds, certFile, status);
|
|
|
|
|
|
|
|
if (status > 0) { /* It is a CA cert */
|
|
|
|
if (!isCA) {
|
|
|
|
error_setg(errp, isServer ?
|
|
|
|
"The certificate %s basic constraints show a CA, "
|
|
|
|
"but we need one for a server" :
|
|
|
|
"The certificate %s basic constraints show a CA, "
|
|
|
|
"but we need one for a client",
|
|
|
|
certFile);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else if (status == 0) { /* It is not a CA cert */
|
|
|
|
if (isCA) {
|
|
|
|
error_setg(errp,
|
|
|
|
"The certificate %s basic constraints do not "
|
|
|
|
"show a CA",
|
|
|
|
certFile);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
|
|
|
|
/* Missing basicConstraints */
|
|
|
|
if (isCA) {
|
|
|
|
error_setg(errp,
|
|
|
|
"The certificate %s is missing basic constraints "
|
|
|
|
"for a CA",
|
|
|
|
certFile);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else { /* General error */
|
|
|
|
error_setg(errp,
|
|
|
|
"Unable to query certificate %s basic constraints: %s",
|
|
|
|
certFile, gnutls_strerror(status));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcrypto_tls_creds_check_cert_key_usage(QCryptoTLSCredsX509 *creds,
|
|
|
|
gnutls_x509_crt_t cert,
|
|
|
|
const char *certFile,
|
|
|
|
bool isCA,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
unsigned int usage = 0;
|
|
|
|
unsigned int critical = 0;
|
|
|
|
|
|
|
|
status = gnutls_x509_crt_get_key_usage(cert, &usage, &critical);
|
|
|
|
trace_qcrypto_tls_creds_x509_check_key_usage(
|
|
|
|
creds, certFile, status, usage, critical);
|
|
|
|
|
|
|
|
if (status < 0) {
|
|
|
|
if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
|
|
|
|
usage = isCA ? GNUTLS_KEY_KEY_CERT_SIGN :
|
|
|
|
GNUTLS_KEY_DIGITAL_SIGNATURE|GNUTLS_KEY_KEY_ENCIPHERMENT;
|
|
|
|
} else {
|
|
|
|
error_setg(errp,
|
|
|
|
"Unable to query certificate %s key usage: %s",
|
|
|
|
certFile, gnutls_strerror(status));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isCA) {
|
|
|
|
if (!(usage & GNUTLS_KEY_KEY_CERT_SIGN)) {
|
|
|
|
if (critical) {
|
|
|
|
error_setg(errp,
|
|
|
|
"Certificate %s usage does not permit "
|
|
|
|
"certificate signing", certFile);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!(usage & GNUTLS_KEY_DIGITAL_SIGNATURE)) {
|
|
|
|
if (critical) {
|
|
|
|
error_setg(errp,
|
|
|
|
"Certificate %s usage does not permit digital "
|
|
|
|
"signature", certFile);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!(usage & GNUTLS_KEY_KEY_ENCIPHERMENT)) {
|
|
|
|
if (critical) {
|
|
|
|
error_setg(errp,
|
|
|
|
"Certificate %s usage does not permit key "
|
|
|
|
"encipherment", certFile);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcrypto_tls_creds_check_cert_key_purpose(QCryptoTLSCredsX509 *creds,
|
|
|
|
gnutls_x509_crt_t cert,
|
|
|
|
const char *certFile,
|
|
|
|
bool isServer,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
size_t i;
|
|
|
|
unsigned int purposeCritical;
|
|
|
|
unsigned int critical;
|
|
|
|
char *buffer = NULL;
|
|
|
|
size_t size;
|
|
|
|
bool allowClient = false, allowServer = false;
|
|
|
|
|
|
|
|
critical = 0;
|
|
|
|
for (i = 0; ; i++) {
|
|
|
|
size = 0;
|
|
|
|
status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer,
|
|
|
|
&size, NULL);
|
|
|
|
|
|
|
|
if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
|
|
|
|
|
|
|
|
/* If there is no data at all, then we must allow
|
|
|
|
client/server to pass */
|
|
|
|
if (i == 0) {
|
|
|
|
allowServer = allowClient = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (status != GNUTLS_E_SHORT_MEMORY_BUFFER) {
|
|
|
|
error_setg(errp,
|
|
|
|
"Unable to query certificate %s key purpose: %s",
|
|
|
|
certFile, gnutls_strerror(status));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer = g_new0(char, size);
|
|
|
|
|
|
|
|
status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer,
|
|
|
|
&size, &purposeCritical);
|
|
|
|
|
|
|
|
if (status < 0) {
|
|
|
|
trace_qcrypto_tls_creds_x509_check_key_purpose(
|
|
|
|
creds, certFile, status, "<none>", purposeCritical);
|
|
|
|
g_free(buffer);
|
|
|
|
error_setg(errp,
|
|
|
|
"Unable to query certificate %s key purpose: %s",
|
|
|
|
certFile, gnutls_strerror(status));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
trace_qcrypto_tls_creds_x509_check_key_purpose(
|
|
|
|
creds, certFile, status, buffer, purposeCritical);
|
|
|
|
if (purposeCritical) {
|
|
|
|
critical = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (g_str_equal(buffer, GNUTLS_KP_TLS_WWW_SERVER)) {
|
|
|
|
allowServer = true;
|
|
|
|
} else if (g_str_equal(buffer, GNUTLS_KP_TLS_WWW_CLIENT)) {
|
|
|
|
allowClient = true;
|
|
|
|
} else if (g_str_equal(buffer, GNUTLS_KP_ANY)) {
|
|
|
|
allowServer = allowClient = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_free(buffer);
|
2015-11-13 18:45:27 +01:00
|
|
|
buffer = NULL;
|
2015-04-13 15:01:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isServer) {
|
|
|
|
if (!allowServer) {
|
|
|
|
if (critical) {
|
|
|
|
error_setg(errp,
|
|
|
|
"Certificate %s purpose does not allow "
|
|
|
|
"use with a TLS server", certFile);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!allowClient) {
|
|
|
|
if (critical) {
|
|
|
|
error_setg(errp,
|
|
|
|
"Certificate %s purpose does not allow use "
|
|
|
|
"with a TLS client", certFile);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcrypto_tls_creds_check_cert(QCryptoTLSCredsX509 *creds,
|
|
|
|
gnutls_x509_crt_t cert,
|
|
|
|
const char *certFile,
|
|
|
|
bool isServer,
|
|
|
|
bool isCA,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
if (qcrypto_tls_creds_check_cert_times(cert, certFile,
|
|
|
|
isServer, isCA,
|
|
|
|
errp) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qcrypto_tls_creds_check_cert_basic_constraints(creds,
|
|
|
|
cert, certFile,
|
|
|
|
isServer, isCA,
|
|
|
|
errp) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qcrypto_tls_creds_check_cert_key_usage(creds,
|
|
|
|
cert, certFile,
|
|
|
|
isCA, errp) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isCA &&
|
|
|
|
qcrypto_tls_creds_check_cert_key_purpose(creds,
|
|
|
|
cert, certFile,
|
|
|
|
isServer, errp) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcrypto_tls_creds_check_cert_pair(gnutls_x509_crt_t cert,
|
|
|
|
const char *certFile,
|
|
|
|
gnutls_x509_crt_t *cacerts,
|
|
|
|
size_t ncacerts,
|
|
|
|
const char *cacertFile,
|
|
|
|
bool isServer,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
unsigned int status;
|
|
|
|
|
|
|
|
if (gnutls_x509_crt_list_verify(&cert, 1,
|
|
|
|
cacerts, ncacerts,
|
|
|
|
NULL, 0,
|
|
|
|
0, &status) < 0) {
|
|
|
|
error_setg(errp, isServer ?
|
|
|
|
"Unable to verify server certificate %s against "
|
|
|
|
"CA certificate %s" :
|
|
|
|
"Unable to verify client certificate %s against "
|
|
|
|
"CA certificate %s",
|
|
|
|
certFile, cacertFile);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status != 0) {
|
|
|
|
const char *reason = "Invalid certificate";
|
|
|
|
|
|
|
|
if (status & GNUTLS_CERT_INVALID) {
|
|
|
|
reason = "The certificate is not trusted";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
|
|
|
|
reason = "The certificate hasn't got a known issuer";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status & GNUTLS_CERT_REVOKED) {
|
|
|
|
reason = "The certificate has been revoked";
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef GNUTLS_1_0_COMPAT
|
|
|
|
if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
|
|
|
|
reason = "The certificate uses an insecure algorithm";
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
error_setg(errp,
|
|
|
|
"Our own certificate %s failed validation against %s: %s",
|
|
|
|
certFile, cacertFile, reason);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static gnutls_x509_crt_t
|
|
|
|
qcrypto_tls_creds_load_cert(QCryptoTLSCredsX509 *creds,
|
|
|
|
const char *certFile,
|
|
|
|
bool isServer,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
gnutls_datum_t data;
|
|
|
|
gnutls_x509_crt_t cert = NULL;
|
2019-07-23 17:22:36 +02:00
|
|
|
g_autofree char *buf = NULL;
|
2015-04-13 15:01:39 +02:00
|
|
|
gsize buflen;
|
2019-12-04 10:36:08 +01:00
|
|
|
GError *gerr = NULL;
|
2015-04-13 15:01:39 +02:00
|
|
|
int ret = -1;
|
2016-04-05 21:33:48 +02:00
|
|
|
int err;
|
2015-04-13 15:01:39 +02:00
|
|
|
|
|
|
|
trace_qcrypto_tls_creds_x509_load_cert(creds, isServer, certFile);
|
|
|
|
|
2016-04-05 21:33:48 +02:00
|
|
|
err = gnutls_x509_crt_init(&cert);
|
|
|
|
if (err < 0) {
|
|
|
|
error_setg(errp, "Unable to initialize certificate: %s",
|
|
|
|
gnutls_strerror(err));
|
2015-04-13 15:01:39 +02:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!g_file_get_contents(certFile, &buf, &buflen, &gerr)) {
|
|
|
|
error_setg(errp, "Cannot load CA cert list %s: %s",
|
|
|
|
certFile, gerr->message);
|
|
|
|
g_error_free(gerr);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
data.data = (unsigned char *)buf;
|
|
|
|
data.size = strlen(buf);
|
|
|
|
|
2016-04-05 21:33:48 +02:00
|
|
|
err = gnutls_x509_crt_import(cert, &data, GNUTLS_X509_FMT_PEM);
|
|
|
|
if (err < 0) {
|
2015-04-13 15:01:39 +02:00
|
|
|
error_setg(errp, isServer ?
|
2016-04-05 21:33:48 +02:00
|
|
|
"Unable to import server certificate %s: %s" :
|
|
|
|
"Unable to import client certificate %s: %s",
|
|
|
|
certFile,
|
|
|
|
gnutls_strerror(err));
|
2015-04-13 15:01:39 +02:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
if (ret != 0) {
|
|
|
|
gnutls_x509_crt_deinit(cert);
|
|
|
|
cert = NULL;
|
|
|
|
}
|
|
|
|
return cert;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qcrypto_tls_creds_load_ca_cert_list(QCryptoTLSCredsX509 *creds,
|
|
|
|
const char *certFile,
|
|
|
|
gnutls_x509_crt_t *certs,
|
|
|
|
unsigned int certMax,
|
|
|
|
size_t *ncerts,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
gnutls_datum_t data;
|
2019-07-23 17:22:36 +02:00
|
|
|
g_autofree char *buf = NULL;
|
2015-04-13 15:01:39 +02:00
|
|
|
gsize buflen;
|
|
|
|
GError *gerr = NULL;
|
|
|
|
|
|
|
|
*ncerts = 0;
|
|
|
|
trace_qcrypto_tls_creds_x509_load_cert_list(creds, certFile);
|
|
|
|
|
|
|
|
if (!g_file_get_contents(certFile, &buf, &buflen, &gerr)) {
|
|
|
|
error_setg(errp, "Cannot load CA cert list %s: %s",
|
|
|
|
certFile, gerr->message);
|
|
|
|
g_error_free(gerr);
|
2019-07-23 17:22:36 +02:00
|
|
|
return -1;
|
2015-04-13 15:01:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
data.data = (unsigned char *)buf;
|
|
|
|
data.size = strlen(buf);
|
|
|
|
|
|
|
|
if (gnutls_x509_crt_list_import(certs, &certMax, &data,
|
|
|
|
GNUTLS_X509_FMT_PEM, 0) < 0) {
|
|
|
|
error_setg(errp,
|
|
|
|
"Unable to import CA certificate list %s",
|
|
|
|
certFile);
|
2019-07-23 17:22:36 +02:00
|
|
|
return -1;
|
2015-04-13 15:01:39 +02:00
|
|
|
}
|
|
|
|
*ncerts = certMax;
|
|
|
|
|
2019-07-23 17:22:36 +02:00
|
|
|
return 0;
|
2015-04-13 15:01:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#define MAX_CERTS 16
|
|
|
|
static int
|
|
|
|
qcrypto_tls_creds_x509_sanity_check(QCryptoTLSCredsX509 *creds,
|
|
|
|
bool isServer,
|
|
|
|
const char *cacertFile,
|
|
|
|
const char *certFile,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
gnutls_x509_crt_t cert = NULL;
|
|
|
|
gnutls_x509_crt_t cacerts[MAX_CERTS];
|
|
|
|
size_t ncacerts = 0;
|
|
|
|
size_t i;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
memset(cacerts, 0, sizeof(cacerts));
|
2015-11-18 16:42:26 +01:00
|
|
|
if (certFile &&
|
|
|
|
access(certFile, R_OK) == 0) {
|
2015-04-13 15:01:39 +02:00
|
|
|
cert = qcrypto_tls_creds_load_cert(creds,
|
|
|
|
certFile, isServer,
|
|
|
|
errp);
|
|
|
|
if (!cert) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (access(cacertFile, R_OK) == 0) {
|
|
|
|
if (qcrypto_tls_creds_load_ca_cert_list(creds,
|
|
|
|
cacertFile, cacerts,
|
|
|
|
MAX_CERTS, &ncacerts,
|
|
|
|
errp) < 0) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cert &&
|
|
|
|
qcrypto_tls_creds_check_cert(creds,
|
|
|
|
cert, certFile, isServer,
|
|
|
|
false, errp) < 0) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ncacerts; i++) {
|
|
|
|
if (qcrypto_tls_creds_check_cert(creds,
|
|
|
|
cacerts[i], cacertFile,
|
|
|
|
isServer, true, errp) < 0) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cert && ncacerts &&
|
|
|
|
qcrypto_tls_creds_check_cert_pair(cert, certFile, cacerts,
|
|
|
|
ncacerts, cacertFile,
|
|
|
|
isServer, errp) < 0) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
if (cert) {
|
|
|
|
gnutls_x509_crt_deinit(cert);
|
|
|
|
}
|
|
|
|
for (i = 0; i < ncacerts; i++) {
|
|
|
|
gnutls_x509_crt_deinit(cacerts[i]);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-03-13 18:39:26 +01:00
|
|
|
|
|
|
|
static int
|
|
|
|
qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
char *cacert = NULL, *cacrl = NULL, *cert = NULL,
|
|
|
|
*key = NULL, *dhparams = NULL;
|
|
|
|
int ret;
|
|
|
|
int rv = -1;
|
|
|
|
|
|
|
|
trace_qcrypto_tls_creds_x509_load(creds,
|
|
|
|
creds->parent_obj.dir ? creds->parent_obj.dir : "<nodir>");
|
|
|
|
|
|
|
|
if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
|
|
|
|
if (qcrypto_tls_creds_get_path(&creds->parent_obj,
|
|
|
|
QCRYPTO_TLS_CREDS_X509_CA_CERT,
|
|
|
|
true, &cacert, errp) < 0 ||
|
|
|
|
qcrypto_tls_creds_get_path(&creds->parent_obj,
|
|
|
|
QCRYPTO_TLS_CREDS_X509_CA_CRL,
|
|
|
|
false, &cacrl, errp) < 0 ||
|
|
|
|
qcrypto_tls_creds_get_path(&creds->parent_obj,
|
|
|
|
QCRYPTO_TLS_CREDS_X509_SERVER_CERT,
|
|
|
|
true, &cert, errp) < 0 ||
|
|
|
|
qcrypto_tls_creds_get_path(&creds->parent_obj,
|
|
|
|
QCRYPTO_TLS_CREDS_X509_SERVER_KEY,
|
|
|
|
true, &key, errp) < 0 ||
|
|
|
|
qcrypto_tls_creds_get_path(&creds->parent_obj,
|
|
|
|
QCRYPTO_TLS_CREDS_DH_PARAMS,
|
|
|
|
false, &dhparams, errp) < 0) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (qcrypto_tls_creds_get_path(&creds->parent_obj,
|
|
|
|
QCRYPTO_TLS_CREDS_X509_CA_CERT,
|
|
|
|
true, &cacert, errp) < 0 ||
|
|
|
|
qcrypto_tls_creds_get_path(&creds->parent_obj,
|
|
|
|
QCRYPTO_TLS_CREDS_X509_CLIENT_CERT,
|
|
|
|
false, &cert, errp) < 0 ||
|
|
|
|
qcrypto_tls_creds_get_path(&creds->parent_obj,
|
|
|
|
QCRYPTO_TLS_CREDS_X509_CLIENT_KEY,
|
|
|
|
false, &key, errp) < 0) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-13 15:01:39 +02:00
|
|
|
if (creds->sanityCheck &&
|
|
|
|
qcrypto_tls_creds_x509_sanity_check(creds,
|
|
|
|
creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
|
|
|
|
cacert, cert, errp) < 0) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2015-03-13 18:39:26 +01:00
|
|
|
ret = gnutls_certificate_allocate_credentials(&creds->data);
|
|
|
|
if (ret < 0) {
|
|
|
|
error_setg(errp, "Cannot allocate credentials: '%s'",
|
|
|
|
gnutls_strerror(ret));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = gnutls_certificate_set_x509_trust_file(creds->data,
|
|
|
|
cacert,
|
|
|
|
GNUTLS_X509_FMT_PEM);
|
|
|
|
if (ret < 0) {
|
|
|
|
error_setg(errp, "Cannot load CA certificate '%s': %s",
|
|
|
|
cacert, gnutls_strerror(ret));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cert != NULL && key != NULL) {
|
2015-10-15 17:14:42 +02:00
|
|
|
char *password = NULL;
|
|
|
|
if (creds->passwordid) {
|
|
|
|
password = qcrypto_secret_lookup_as_utf8(creds->passwordid,
|
|
|
|
errp);
|
|
|
|
if (!password) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ret = gnutls_certificate_set_x509_key_file2(creds->data,
|
|
|
|
cert, key,
|
|
|
|
GNUTLS_X509_FMT_PEM,
|
|
|
|
password,
|
|
|
|
0);
|
|
|
|
g_free(password);
|
2015-03-13 18:39:26 +01:00
|
|
|
if (ret < 0) {
|
|
|
|
error_setg(errp, "Cannot load certificate '%s' & key '%s': %s",
|
|
|
|
cert, key, gnutls_strerror(ret));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cacrl != NULL) {
|
|
|
|
ret = gnutls_certificate_set_x509_crl_file(creds->data,
|
|
|
|
cacrl,
|
|
|
|
GNUTLS_X509_FMT_PEM);
|
|
|
|
if (ret < 0) {
|
|
|
|
error_setg(errp, "Cannot load CRL '%s': %s",
|
|
|
|
cacrl, gnutls_strerror(ret));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
|
|
|
|
if (qcrypto_tls_creds_get_dh_params_file(&creds->parent_obj, dhparams,
|
|
|
|
&creds->parent_obj.dh_params,
|
|
|
|
errp) < 0) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
gnutls_certificate_set_dh_params(creds->data,
|
|
|
|
creds->parent_obj.dh_params);
|
|
|
|
}
|
|
|
|
|
|
|
|
rv = 0;
|
|
|
|
cleanup:
|
|
|
|
g_free(cacert);
|
|
|
|
g_free(cacrl);
|
|
|
|
g_free(cert);
|
|
|
|
g_free(key);
|
|
|
|
g_free(dhparams);
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds)
|
|
|
|
{
|
|
|
|
if (creds->data) {
|
|
|
|
gnutls_certificate_free_credentials(creds->data);
|
|
|
|
creds->data = NULL;
|
|
|
|
}
|
2015-11-18 15:41:35 +01:00
|
|
|
if (creds->parent_obj.dh_params) {
|
|
|
|
gnutls_dh_params_deinit(creds->parent_obj.dh_params);
|
|
|
|
creds->parent_obj.dh_params = NULL;
|
|
|
|
}
|
2015-03-13 18:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#else /* ! CONFIG_GNUTLS */
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
error_setg(errp, "TLS credentials support requires GNUTLS");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcrypto_tls_creds_x509_unload(QCryptoTLSCredsX509 *creds G_GNUC_UNUSED)
|
|
|
|
{
|
|
|
|
/* nada */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endif /* ! CONFIG_GNUTLS */
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcrypto_tls_creds_x509_prop_set_loaded(Object *obj,
|
|
|
|
bool value,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj);
|
|
|
|
|
|
|
|
if (value) {
|
|
|
|
qcrypto_tls_creds_x509_load(creds, errp);
|
|
|
|
} else {
|
|
|
|
qcrypto_tls_creds_x509_unload(creds);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_GNUTLS
|
|
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
qcrypto_tls_creds_x509_prop_get_loaded(Object *obj,
|
|
|
|
Error **errp G_GNUC_UNUSED)
|
|
|
|
{
|
|
|
|
QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj);
|
|
|
|
|
|
|
|
return creds->data != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#else /* ! CONFIG_GNUTLS */
|
|
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
qcrypto_tls_creds_x509_prop_get_loaded(Object *obj G_GNUC_UNUSED,
|
|
|
|
Error **errp G_GNUC_UNUSED)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endif /* ! CONFIG_GNUTLS */
|
|
|
|
|
|
|
|
|
2015-04-13 15:01:39 +02:00
|
|
|
static void
|
|
|
|
qcrypto_tls_creds_x509_prop_set_sanity(Object *obj,
|
|
|
|
bool value,
|
|
|
|
Error **errp G_GNUC_UNUSED)
|
|
|
|
{
|
|
|
|
QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj);
|
|
|
|
|
|
|
|
creds->sanityCheck = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-10-15 17:14:42 +02:00
|
|
|
static void
|
|
|
|
qcrypto_tls_creds_x509_prop_set_passwordid(Object *obj,
|
|
|
|
const char *value,
|
|
|
|
Error **errp G_GNUC_UNUSED)
|
|
|
|
{
|
|
|
|
QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj);
|
|
|
|
|
|
|
|
creds->passwordid = g_strdup(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static char *
|
|
|
|
qcrypto_tls_creds_x509_prop_get_passwordid(Object *obj,
|
|
|
|
Error **errp G_GNUC_UNUSED)
|
|
|
|
{
|
|
|
|
QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj);
|
|
|
|
|
|
|
|
return g_strdup(creds->passwordid);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-04-13 15:01:39 +02:00
|
|
|
static bool
|
|
|
|
qcrypto_tls_creds_x509_prop_get_sanity(Object *obj,
|
|
|
|
Error **errp G_GNUC_UNUSED)
|
|
|
|
{
|
|
|
|
QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj);
|
|
|
|
|
|
|
|
return creds->sanityCheck;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-03-13 18:39:26 +01:00
|
|
|
static void
|
|
|
|
qcrypto_tls_creds_x509_complete(UserCreatable *uc, Error **errp)
|
|
|
|
{
|
qom: Put name parameter before value / visitor parameter
The object_property_set_FOO() setters take property name and value in
an unusual order:
void object_property_set_FOO(Object *obj, FOO_TYPE value,
const char *name, Error **errp)
Having to pass value before name feels grating. Swap them.
Same for object_property_set(), object_property_get(), and
object_property_parse().
Convert callers with this Coccinelle script:
@@
identifier fun = {
object_property_get, object_property_parse, object_property_set_str,
object_property_set_link, object_property_set_bool,
object_property_set_int, object_property_set_uint, object_property_set,
object_property_set_qobject
};
expression obj, v, name, errp;
@@
- fun(obj, v, name, errp)
+ fun(obj, name, v, errp)
Chokes on hw/arm/musicpal.c's lcd_refresh() with the unhelpful error
message "no position information". Convert that one manually.
Fails to convert hw/arm/armsse.c, because Coccinelle gets confused by
ARMSSE being used both as typedef and function-like macro there.
Convert manually.
Fails to convert hw/rx/rx-gdbsim.c, because Coccinelle gets confused
by RXCPU being used both as typedef and function-like macro there.
Convert manually. The other files using RXCPU that way don't need
conversion.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
Message-Id: <20200707160613.848843-27-armbru@redhat.com>
[Straightforwad conflict with commit 2336172d9b "audio: set default
value for pcspk.iobase property" resolved]
2020-07-07 18:05:54 +02:00
|
|
|
object_property_set_bool(OBJECT(uc), "loaded", true, errp);
|
2015-03-13 18:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcrypto_tls_creds_x509_init(Object *obj)
|
|
|
|
{
|
2015-04-13 15:01:39 +02:00
|
|
|
QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj);
|
|
|
|
|
|
|
|
creds->sanityCheck = true;
|
2015-03-13 18:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcrypto_tls_creds_x509_finalize(Object *obj)
|
|
|
|
{
|
|
|
|
QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj);
|
|
|
|
|
2015-10-15 17:14:42 +02:00
|
|
|
g_free(creds->passwordid);
|
2015-03-13 18:39:26 +01:00
|
|
|
qcrypto_tls_creds_x509_unload(creds);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcrypto_tls_creds_x509_class_init(ObjectClass *oc, void *data)
|
|
|
|
{
|
|
|
|
UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
|
|
|
|
|
|
|
|
ucc->complete = qcrypto_tls_creds_x509_complete;
|
2015-08-24 19:46:57 +02:00
|
|
|
|
|
|
|
object_class_property_add_bool(oc, "loaded",
|
|
|
|
qcrypto_tls_creds_x509_prop_get_loaded,
|
qom: Drop parameter @errp of object_property_add() & friends
The only way object_property_add() can fail is when a property with
the same name already exists. Since our property names are all
hardcoded, failure is a programming error, and the appropriate way to
handle it is passing &error_abort.
Same for its variants, except for object_property_add_child(), which
additionally fails when the child already has a parent. Parentage is
also under program control, so this is a programming error, too.
We have a bit over 500 callers. Almost half of them pass
&error_abort, slightly fewer ignore errors, one test case handles
errors, and the remaining few callers pass them to their own callers.
The previous few commits demonstrated once again that ignoring
programming errors is a bad idea.
Of the few ones that pass on errors, several violate the Error API.
The Error ** argument must be NULL, &error_abort, &error_fatal, or a
pointer to a variable containing NULL. Passing an argument of the
latter kind twice without clearing it in between is wrong: if the
first call sets an error, it no longer points to NULL for the second
call. ich9_pm_add_properties(), sparc32_ledma_realize(),
sparc32_dma_realize(), xilinx_axidma_realize(), xilinx_enet_realize()
are wrong that way.
When the one appropriate choice of argument is &error_abort, letting
users pick the argument is a bad idea.
Drop parameter @errp and assert the preconditions instead.
There's one exception to "duplicate property name is a programming
error": the way object_property_add() implements the magic (and
undocumented) "automatic arrayification". Don't drop @errp there.
Instead, rename object_property_add() to object_property_try_add(),
and add the obvious wrapper object_property_add().
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200505152926.18877-15-armbru@redhat.com>
[Two semantic rebase conflicts resolved]
2020-05-05 17:29:22 +02:00
|
|
|
qcrypto_tls_creds_x509_prop_set_loaded);
|
2015-08-24 19:46:57 +02:00
|
|
|
object_class_property_add_bool(oc, "sanity-check",
|
|
|
|
qcrypto_tls_creds_x509_prop_get_sanity,
|
qom: Drop parameter @errp of object_property_add() & friends
The only way object_property_add() can fail is when a property with
the same name already exists. Since our property names are all
hardcoded, failure is a programming error, and the appropriate way to
handle it is passing &error_abort.
Same for its variants, except for object_property_add_child(), which
additionally fails when the child already has a parent. Parentage is
also under program control, so this is a programming error, too.
We have a bit over 500 callers. Almost half of them pass
&error_abort, slightly fewer ignore errors, one test case handles
errors, and the remaining few callers pass them to their own callers.
The previous few commits demonstrated once again that ignoring
programming errors is a bad idea.
Of the few ones that pass on errors, several violate the Error API.
The Error ** argument must be NULL, &error_abort, &error_fatal, or a
pointer to a variable containing NULL. Passing an argument of the
latter kind twice without clearing it in between is wrong: if the
first call sets an error, it no longer points to NULL for the second
call. ich9_pm_add_properties(), sparc32_ledma_realize(),
sparc32_dma_realize(), xilinx_axidma_realize(), xilinx_enet_realize()
are wrong that way.
When the one appropriate choice of argument is &error_abort, letting
users pick the argument is a bad idea.
Drop parameter @errp and assert the preconditions instead.
There's one exception to "duplicate property name is a programming
error": the way object_property_add() implements the magic (and
undocumented) "automatic arrayification". Don't drop @errp there.
Instead, rename object_property_add() to object_property_try_add(),
and add the obvious wrapper object_property_add().
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200505152926.18877-15-armbru@redhat.com>
[Two semantic rebase conflicts resolved]
2020-05-05 17:29:22 +02:00
|
|
|
qcrypto_tls_creds_x509_prop_set_sanity);
|
2015-08-24 19:46:57 +02:00
|
|
|
object_class_property_add_str(oc, "passwordid",
|
|
|
|
qcrypto_tls_creds_x509_prop_get_passwordid,
|
qom: Drop parameter @errp of object_property_add() & friends
The only way object_property_add() can fail is when a property with
the same name already exists. Since our property names are all
hardcoded, failure is a programming error, and the appropriate way to
handle it is passing &error_abort.
Same for its variants, except for object_property_add_child(), which
additionally fails when the child already has a parent. Parentage is
also under program control, so this is a programming error, too.
We have a bit over 500 callers. Almost half of them pass
&error_abort, slightly fewer ignore errors, one test case handles
errors, and the remaining few callers pass them to their own callers.
The previous few commits demonstrated once again that ignoring
programming errors is a bad idea.
Of the few ones that pass on errors, several violate the Error API.
The Error ** argument must be NULL, &error_abort, &error_fatal, or a
pointer to a variable containing NULL. Passing an argument of the
latter kind twice without clearing it in between is wrong: if the
first call sets an error, it no longer points to NULL for the second
call. ich9_pm_add_properties(), sparc32_ledma_realize(),
sparc32_dma_realize(), xilinx_axidma_realize(), xilinx_enet_realize()
are wrong that way.
When the one appropriate choice of argument is &error_abort, letting
users pick the argument is a bad idea.
Drop parameter @errp and assert the preconditions instead.
There's one exception to "duplicate property name is a programming
error": the way object_property_add() implements the magic (and
undocumented) "automatic arrayification". Don't drop @errp there.
Instead, rename object_property_add() to object_property_try_add(),
and add the obvious wrapper object_property_add().
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200505152926.18877-15-armbru@redhat.com>
[Two semantic rebase conflicts resolved]
2020-05-05 17:29:22 +02:00
|
|
|
qcrypto_tls_creds_x509_prop_set_passwordid);
|
2015-03-13 18:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static const TypeInfo qcrypto_tls_creds_x509_info = {
|
|
|
|
.parent = TYPE_QCRYPTO_TLS_CREDS,
|
|
|
|
.name = TYPE_QCRYPTO_TLS_CREDS_X509,
|
|
|
|
.instance_size = sizeof(QCryptoTLSCredsX509),
|
|
|
|
.instance_init = qcrypto_tls_creds_x509_init,
|
|
|
|
.instance_finalize = qcrypto_tls_creds_x509_finalize,
|
|
|
|
.class_size = sizeof(QCryptoTLSCredsX509Class),
|
|
|
|
.class_init = qcrypto_tls_creds_x509_class_init,
|
|
|
|
.interfaces = (InterfaceInfo[]) {
|
|
|
|
{ TYPE_USER_CREATABLE },
|
|
|
|
{ }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qcrypto_tls_creds_x509_register_types(void)
|
|
|
|
{
|
|
|
|
type_register_static(&qcrypto_tls_creds_x509_info);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type_init(qcrypto_tls_creds_x509_register_types);
|