diff options
Diffstat (limited to 'src/libdnssec/pem.c')
-rw-r--r-- | src/libdnssec/pem.c | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/src/libdnssec/pem.c b/src/libdnssec/pem.c new file mode 100644 index 0000000..fa463f6 --- /dev/null +++ b/src/libdnssec/pem.c @@ -0,0 +1,182 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 3 of the License, or + (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/pem.h" +#include "libdnssec/shared/shared.h" + +_public_ +int dnssec_pem_to_x509(const dnssec_binary_t *pem, gnutls_x509_privkey_t *key) +{ + if (!pem || !key) { + return DNSSEC_EINVAL; + } + + gnutls_datum_t data = binary_to_datum(pem); + + gnutls_x509_privkey_t _key = NULL; + int r = gnutls_x509_privkey_init(&_key); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + int format = GNUTLS_X509_FMT_PEM; + char *password = NULL; + int flags = GNUTLS_PKCS_PLAIN; + r = gnutls_x509_privkey_import_pkcs8(_key, &data, format, password, flags); + if (r != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(_key); + return DNSSEC_PKCS8_IMPORT_ERROR; + } + + *key = _key; + + return DNSSEC_EOK; +} + +_public_ +int dnssec_pem_to_privkey(const dnssec_binary_t *pem, gnutls_privkey_t *key) +{ + if (!pem || !key) { + return DNSSEC_EINVAL; + } + + gnutls_x509_privkey_t key_x509 = NULL; + int r = dnssec_pem_to_x509(pem, &key_x509); + if (r != DNSSEC_EOK) { + return r; + } + + gnutls_privkey_t key_abs = NULL; + r = gnutls_privkey_init(&key_abs); + if (r != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(key_x509); + return DNSSEC_ENOMEM; + } + + int flags = GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE; + r = gnutls_privkey_import_x509(key_abs, key_x509, flags); + if (r != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(key_x509); + gnutls_privkey_deinit(key_abs); + return DNSSEC_ENOMEM; + } + + *key = key_abs; + + return DNSSEC_EOK; +} + +static int try_export_pem(gnutls_x509_privkey_t key, dnssec_binary_t *pem) +{ + assert(key); + + gnutls_x509_crt_fmt_t format = GNUTLS_X509_FMT_PEM; + char *password = NULL; + int flags = GNUTLS_PKCS_PLAIN; + + return gnutls_x509_privkey_export_pkcs8(key, format, password, flags, + pem->data, &pem->size); +} + +_public_ +int dnssec_pem_from_x509(gnutls_x509_privkey_t key, dnssec_binary_t *pem) +{ + if (!key || !pem) { + return DNSSEC_EINVAL; + } + + dnssec_binary_t _pem = { 0 }; + int r = try_export_pem(key, &_pem); + if (r != GNUTLS_E_SHORT_MEMORY_BUFFER || _pem.size == 0) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + r = dnssec_binary_alloc(&_pem, _pem.size); + if (r != DNSSEC_EOK) { + return r; + } + + r = try_export_pem(key, &_pem); + if (r != GNUTLS_E_SUCCESS) { + dnssec_binary_free(&_pem); + return DNSSEC_KEY_EXPORT_ERROR; + } + + *pem = _pem; + + return DNSSEC_EOK; +} + +static int privkey_export_x509(gnutls_privkey_t key, gnutls_x509_privkey_t *_key) +{ +#ifdef HAVE_EXPORT_X509 + if (gnutls_privkey_export_x509(key, _key) != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_EXPORT_ERROR; + } +#else // Needed for GnuTLS < 3.4.0 (CentOS 7) + struct privkey { // Extracted needed items only! + gnutls_privkey_type_t type; + gnutls_pk_algorithm_t pk_algorithm; + gnutls_x509_privkey_t x509; + }; + struct privkey *pkey = (struct privkey *)key; + + assert(pkey->type == GNUTLS_PRIVKEY_X509); + + if (gnutls_x509_privkey_init(_key) != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + if (gnutls_x509_privkey_cpy(*_key, pkey->x509) != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(*_key); + return DNSSEC_KEY_EXPORT_ERROR; + } +#endif + return DNSSEC_EOK; +} + +_public_ +int dnssec_pem_from_privkey(gnutls_privkey_t key, dnssec_binary_t *pem) +{ + if (!key || !pem) { + return DNSSEC_EINVAL; + } + + _cleanup_x509_privkey_ gnutls_x509_privkey_t _key = NULL; + + int r = privkey_export_x509(key, &_key); + if (r != DNSSEC_EOK) { + return r; + } + + dnssec_binary_t _pem = { 0 }; + r = dnssec_pem_from_x509(_key, &_pem); + if (r != DNSSEC_EOK) { + return r; + } + + *pem = _pem; + + return DNSSEC_EOK; +} |