/*
* Copyright (C) 2002-2012 Free Software Foundation, Inc.
*
* Author: Timo Schulz, Nikos Mavrogiannopoulos
*
* This file is part of GnuTLS.
*
* The GnuTLS 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 version 2.1 of
* the License, or (at your option) any later version.
*
* 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 program. If not, see
*
*/
#include "gnutls_int.h"
#include "errors.h"
#include "mpi.h"
#include "num.h"
#include "datum.h"
#include "global.h"
#include "openpgp.h"
#include "read-file.h"
#include
#include
#include
#include
#include
/* Map an OpenCDK error type to a GnuTLS error type. */
int _gnutls_map_cdk_rc(int rc)
{
switch (rc) {
case CDK_Success:
return 0;
case CDK_EOF:
return GNUTLS_E_PARSING_ERROR;
case CDK_Too_Short:
return GNUTLS_E_SHORT_MEMORY_BUFFER;
case CDK_General_Error:
return GNUTLS_E_INTERNAL_ERROR;
case CDK_File_Error:
return GNUTLS_E_FILE_ERROR;
case CDK_MPI_Error:
return GNUTLS_E_MPI_SCAN_FAILED;
case CDK_Error_No_Key:
return GNUTLS_E_OPENPGP_GETKEY_FAILED;
case CDK_Armor_Error:
return GNUTLS_E_BASE64_DECODING_ERROR;
case CDK_Inv_Value:
return GNUTLS_E_INVALID_REQUEST;
default:
return GNUTLS_E_INTERNAL_ERROR;
}
}
/**
* gnutls_certificate_set_openpgp_key:
* @res: is a #gnutls_certificate_credentials_t type.
* @crt: contains an openpgp public key
* @pkey: is an openpgp private key
*
* This function sets a certificate/private key pair in the
* gnutls_certificate_credentials_t type. This function may be
* called more than once (in case multiple keys/certificates exist
* for the server).
*
* Note that this function requires that the preferred key ids have
* been set and be used. See gnutls_openpgp_crt_set_preferred_key_id().
* Otherwise the master key will be used.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned,
* otherwise a negative error code is returned.
**/
int
gnutls_certificate_set_openpgp_key(gnutls_certificate_credentials_t res,
gnutls_openpgp_crt_t crt,
gnutls_openpgp_privkey_t pkey)
{
int ret, ret2, i;
gnutls_privkey_t privkey;
gnutls_pcert_st *ccert = NULL;
char name[MAX_CN];
size_t max_size;
gnutls_str_array_t names;
_gnutls_str_array_init(&names);
/* this should be first */
ret = gnutls_privkey_init(&privkey);
if (ret < 0) {
gnutls_assert();
return ret;
}
ret =
gnutls_privkey_import_openpgp(privkey, pkey,
GNUTLS_PRIVKEY_IMPORT_COPY);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
ccert = gnutls_calloc(1, sizeof(gnutls_pcert_st));
if (ccert == NULL) {
gnutls_assert();
ret = GNUTLS_E_MEMORY_ERROR;
goto cleanup;
}
max_size = sizeof(name);
ret = 0;
for (i = 0; !(ret < 0); i++) {
ret = gnutls_openpgp_crt_get_name(crt, i, name, &max_size);
if (ret >= 0) {
ret2 =
_gnutls_str_array_append(&names, name,
max_size);
if (ret2 < 0) {
gnutls_assert();
ret = ret2;
goto cleanup;
}
}
}
ret = gnutls_pcert_import_openpgp(ccert, crt, 0);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
ret = certificate_credentials_append_pkey(res, privkey);
if (ret >= 0)
ret =
certificate_credential_append_crt_list(res, names,
ccert, 1);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
res->ncerts++;
ret = _gnutls_check_key_cert_match(res);
if (ret < 0) {
gnutls_assert();
return ret;
}
return 0;
cleanup:
gnutls_privkey_deinit(privkey);
gnutls_free(ccert);
_gnutls_str_array_clear(&names);
return ret;
}
/**
* gnutls_certificate_get_openpgp_key:
* @res: is a #gnutls_certificate_credentials_t type.
* @index: The index of the key to obtain.
* @key: Location to store the key.
*
* Obtains a OpenPGP private key that has been stored in @res with one of
* gnutls_certificate_set_openpgp_key(),
* gnutls_certificate_set_openpgp_key_file(),
* gnutls_certificate_set_openpgp_key_file2(),
* gnutls_certificate_set_openpgp_key_mem(), or
* gnutls_certificate_set_openpgp_key_mem2().
* The returned key must be deallocated with gnutls_openpgp_privkey_deinit()
* when no longer needed.
*
* If there is no key with the given index,
* %GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE is returned. If the key with the
* given index is not a X.509 key, %GNUTLS_E_INVALID_REQUEST is returned.
*
* Returns: %GNUTLS_E_SUCCESS (0) on success, or a negative error code.
*
* Since: 3.4.0
*/
int
gnutls_certificate_get_openpgp_key(gnutls_certificate_credentials_t res,
unsigned index,
gnutls_openpgp_privkey_t *key)
{
if (index >= res->ncerts) {
gnutls_assert();
return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
}
return gnutls_privkey_export_openpgp(res->pkey[index], key);
}
/**
* gnutls_certificate_get_openpgp_crt:
* @res: is a #gnutls_certificate_credentials_t type.
* @index: The index of the certificate list to obtain.
* @crt_list: Where to store the certificate list.
* @key: Will hold the number of certificates.
*
* Obtains a X.509 certificate list that has been stored in @res with one of
* gnutls_certificate_set_openpgp_key(),
* gnutls_certificate_set_openpgp_key_file(),
* gnutls_certificate_set_openpgp_key_file2(),
* gnutls_certificate_set_openpgp_key_mem(), or
* gnutls_certificate_set_openpgp_key_mem2(). Each certificate in the
* returned certificate list must be deallocated with
* gnutls_openpgp_crt_deinit(), and the list itself must be freed with
* gnutls_free().
*
* If there is no certificate with the given index,
* %GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE is returned. If the certificate
* with the given index is not a X.509 certificate, %GNUTLS_E_INVALID_REQUEST
* is returned.
*
* Returns: %GNUTLS_E_SUCCESS (0) on success, or a negative error code.
*
* Since: 3.4.0
*/
int
gnutls_certificate_get_openpgp_crt(gnutls_certificate_credentials_t res,
unsigned index,
gnutls_openpgp_crt_t **crt_list,
unsigned *crt_list_size)
{
int ret;
unsigned i;
if (index >= res->ncerts) {
gnutls_assert();
return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
}
*crt_list_size = res->certs[index].cert_list_length;
*crt_list = gnutls_malloc(
res->certs[index].cert_list_length * sizeof (gnutls_openpgp_crt_t));
if (*crt_list == NULL) {
gnutls_assert();
return GNUTLS_E_MEMORY_ERROR;
}
for (i = 0; i < res->certs[index].cert_list_length; ++i) {
ret = gnutls_pcert_export_openpgp(&res->certs[index].cert_list[i], crt_list[i]);
if (ret < 0) {
while (i--)
gnutls_openpgp_crt_deinit(*crt_list[i]);
gnutls_free(*crt_list);
*crt_list = NULL;
return gnutls_assert_val(ret);
}
}
return 0;
}
/*-
* gnutls_openpgp_get_key:
* @key: the destination context to save the key.
* @keyring: the datum struct that contains all keyring information.
* @attr: The attribute (keyid, fingerprint, ...).
* @by: What attribute is used.
*
* This function can be used to retrieve keys by different pattern
* from a binary or a file keyring.
-*/
int
gnutls_openpgp_get_key(gnutls_datum_t * key,
gnutls_openpgp_keyring_t keyring, key_attr_t by,
uint8_t * pattern)
{
cdk_kbnode_t knode = NULL;
unsigned long keyid[2];
unsigned char *buf;
void *desc;
size_t len;
int rc = 0;
cdk_keydb_search_t st;
if (!key || !keyring || by == KEY_ATTR_NONE) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
memset(key, 0, sizeof *key);
if (by == KEY_ATTR_SHORT_KEYID) {
keyid[0] = _gnutls_read_uint32(pattern);
desc = keyid;
} else if (by == KEY_ATTR_KEYID) {
keyid[0] = _gnutls_read_uint32(pattern);
keyid[1] = _gnutls_read_uint32(pattern + 4);
desc = keyid;
} else
desc = pattern;
rc = cdk_keydb_search_start(&st, keyring->db, by, desc);
if (!rc)
rc = cdk_keydb_search(st, keyring->db, &knode);
cdk_keydb_search_release(st);
if (rc) {
rc = _gnutls_map_cdk_rc(rc);
goto leave;
}
if (!cdk_kbnode_find(knode, CDK_PKT_PUBLIC_KEY)) {
rc = GNUTLS_E_OPENPGP_GETKEY_FAILED;
goto leave;
}
/* We let the function allocate the buffer to avoid
to call the function twice. */
rc = cdk_kbnode_write_to_mem_alloc(knode, &buf, &len);
if (!rc)
_gnutls_datum_append(key, buf, len);
gnutls_free(buf);
leave:
cdk_kbnode_release(knode);
return rc;
}
/**
* gnutls_certificate_set_openpgp_key_mem:
* @res: the destination context to save the data.
* @cert: the datum that contains the public key.
* @key: the datum that contains the secret key.
* @format: the format of the keys
*
* This function is used to load OpenPGP keys into the GnuTLS credential
* structure. The datum should contain at least one valid non encrypted subkey.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value.
**/
int
gnutls_certificate_set_openpgp_key_mem(gnutls_certificate_credentials_t
res, const gnutls_datum_t * cert,
const gnutls_datum_t * key,
gnutls_openpgp_crt_fmt_t format)
{
return gnutls_certificate_set_openpgp_key_mem2(res, cert, key,
NULL, format);
}
/**
* gnutls_certificate_set_openpgp_key_file:
* @res: the destination context to save the data.
* @certfile: the file that contains the public key.
* @keyfile: the file that contains the secret key.
* @format: the format of the keys
*
* This function is used to load OpenPGP keys into the GnuTLS
* credentials structure. The file should contain at least one valid non encrypted subkey.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value.
**/
int
gnutls_certificate_set_openpgp_key_file(gnutls_certificate_credentials_t
res, const char *certfile,
const char *keyfile,
gnutls_openpgp_crt_fmt_t format)
{
return gnutls_certificate_set_openpgp_key_file2(res, certfile,
keyfile, NULL,
format);
}
static int get_keyid(gnutls_openpgp_keyid_t keyid, const char *str)
{
size_t keyid_size = GNUTLS_OPENPGP_KEYID_SIZE;
size_t len = strlen(str);
gnutls_datum_t tmp;
int ret;
if (len != 16) {
_gnutls_debug_log
("The OpenPGP subkey ID has to be 16 hexadecimal characters.\n");
return GNUTLS_E_INVALID_REQUEST;
}
tmp.data = (void*)str;
tmp.size = len;
ret = gnutls_hex_decode(&tmp, keyid, &keyid_size);
if (ret < 0) {
_gnutls_debug_log("Error converting hex string: %s.\n",
str);
return GNUTLS_E_INVALID_REQUEST;
}
return 0;
}
/**
* gnutls_certificate_set_openpgp_key_mem2:
* @res: the destination context to save the data.
* @cert: the datum that contains the public key.
* @key: the datum that contains the secret key.
* @subkey_id: a hex encoded subkey id
* @format: the format of the keys
*
* This function is used to load OpenPGP keys into the GnuTLS
* credentials structure. The datum should contain at least one valid non encrypted subkey.
*
* The special keyword "auto" is also accepted as @subkey_id. In that
* case the gnutls_openpgp_crt_get_auth_subkey() will be used to
* retrieve the subkey.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value.
*
* Since: 2.4.0
**/
int
gnutls_certificate_set_openpgp_key_mem2(gnutls_certificate_credentials_t
res, const gnutls_datum_t * cert,
const gnutls_datum_t * key,
const char *subkey_id,
gnutls_openpgp_crt_fmt_t format)
{
gnutls_openpgp_privkey_t pkey;
gnutls_openpgp_crt_t crt;
int ret;
uint8_t keyid[GNUTLS_OPENPGP_KEYID_SIZE];
ret = gnutls_openpgp_privkey_init(&pkey);
if (ret < 0) {
gnutls_assert();
return ret;
}
ret = gnutls_openpgp_privkey_import(pkey, key, format, NULL, 0);
if (ret < 0) {
gnutls_assert();
gnutls_openpgp_privkey_deinit(pkey);
return ret;
}
ret = gnutls_openpgp_crt_init(&crt);
if (ret < 0) {
gnutls_assert();
gnutls_openpgp_privkey_deinit(pkey);
return ret;
}
ret = gnutls_openpgp_crt_import(crt, cert, format);
if (ret < 0) {
gnutls_assert();
gnutls_openpgp_privkey_deinit(pkey);
gnutls_openpgp_crt_deinit(crt);
return ret;
}
if (subkey_id != NULL) {
if (strcasecmp(subkey_id, "auto") == 0)
ret =
gnutls_openpgp_crt_get_auth_subkey(crt, keyid,
1);
else
ret = get_keyid(keyid, subkey_id);
if (ret < 0)
gnutls_assert();
if (ret >= 0) {
ret =
gnutls_openpgp_crt_set_preferred_key_id(crt,
keyid);
if (ret >= 0)
ret =
gnutls_openpgp_privkey_set_preferred_key_id
(pkey, keyid);
}
if (ret < 0) {
gnutls_assert();
gnutls_openpgp_privkey_deinit(pkey);
gnutls_openpgp_crt_deinit(crt);
return ret;
}
}
ret = gnutls_certificate_set_openpgp_key(res, crt, pkey);
gnutls_openpgp_crt_deinit(crt);
gnutls_openpgp_privkey_deinit(pkey);
return ret;
}
/**
* gnutls_certificate_set_openpgp_key_file2:
* @res: the destination context to save the data.
* @certfile: the file that contains the public key.
* @keyfile: the file that contains the secret key.
* @subkey_id: a hex encoded subkey id
* @format: the format of the keys
*
* This function is used to load OpenPGP keys into the GnuTLS credential
* structure. The file should contain at least one valid non encrypted subkey.
*
* The special keyword "auto" is also accepted as @subkey_id. In that
* case the gnutls_openpgp_crt_get_auth_subkey() will be used to
* retrieve the subkey.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value.
*
* Since: 2.4.0
**/
int
gnutls_certificate_set_openpgp_key_file2(gnutls_certificate_credentials_t
res, const char *certfile,
const char *keyfile,
const char *subkey_id,
gnutls_openpgp_crt_fmt_t format)
{
struct stat statbuf;
gnutls_datum_t key, cert;
int rc;
size_t size;
if (!res || !keyfile || !certfile) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
if (stat(certfile, &statbuf) || stat(keyfile, &statbuf)) {
gnutls_assert();
return GNUTLS_E_FILE_ERROR;
}
cert.data = (void *) read_binary_file(certfile, &size);
cert.size = (unsigned int) size;
if (cert.data == NULL) {
gnutls_assert();
return GNUTLS_E_FILE_ERROR;
}
key.data = (void *) read_binary_file(keyfile, &size);
key.size = (unsigned int) size;
if (key.data == NULL) {
gnutls_assert();
free(cert.data);
return GNUTLS_E_FILE_ERROR;
}
rc = gnutls_certificate_set_openpgp_key_mem2(res, &cert, &key,
subkey_id, format);
free(cert.data);
free(key.data);
if (rc < 0) {
gnutls_assert();
return rc;
}
return 0;
}
int gnutls_openpgp_count_key_names(const gnutls_datum_t * cert)
{
cdk_kbnode_t knode, p, ctx;
cdk_packet_t pkt;
int nuids;
if (cert == NULL) {
gnutls_assert();
return 0;
}
if (cdk_kbnode_read_from_mem(&knode, 0, cert->data, cert->size)) {
gnutls_assert();
return 0;
}
ctx = NULL;
for (nuids = 0;;) {
p = cdk_kbnode_walk(knode, &ctx, 0);
if (!p)
break;
pkt = cdk_kbnode_get_packet(p);
if (pkt->pkttype == CDK_PKT_USER_ID)
nuids++;
}
cdk_kbnode_release(knode);
return nuids;
}
/**
* gnutls_certificate_set_openpgp_keyring_file:
* @c: A certificate credentials structure
* @file: filename of the keyring.
* @format: format of keyring.
*
* The function is used to set keyrings that will be used internally
* by various OpenPGP functions. For example to find a key when it
* is needed for an operations. The keyring will also be used at the
* verification functions.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value.
**/
int
gnutls_certificate_set_openpgp_keyring_file
(gnutls_certificate_credentials_t c, const char *file,
gnutls_openpgp_crt_fmt_t format)
{
gnutls_datum_t ring;
size_t size;
int rc;
if (!c || !file) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
ring.data = (void *) read_binary_file(file, &size);
ring.size = (unsigned int) size;
if (ring.data == NULL) {
gnutls_assert();
return GNUTLS_E_FILE_ERROR;
}
rc = gnutls_certificate_set_openpgp_keyring_mem(c, ring.data,
ring.size, format);
free(ring.data);
return rc;
}
/**
* gnutls_certificate_set_openpgp_keyring_mem:
* @c: A certificate credentials structure
* @data: buffer with keyring data.
* @dlen: length of data buffer.
* @format: the format of the keyring
*
* The function is used to set keyrings that will be used internally
* by various OpenPGP functions. For example to find a key when it
* is needed for an operations. The keyring will also be used at the
* verification functions.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a
* negative error value.
**/
int
gnutls_certificate_set_openpgp_keyring_mem(gnutls_certificate_credentials_t
c, const uint8_t * data,
size_t dlen,
gnutls_openpgp_crt_fmt_t format)
{
gnutls_datum_t ddata;
int rc;
ddata.data = (void *) data;
ddata.size = dlen;
if (!c || !data || !dlen) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
rc = gnutls_openpgp_keyring_init(&c->keyring);
if (rc < 0) {
gnutls_assert();
return rc;
}
rc = gnutls_openpgp_keyring_import(c->keyring, &ddata, format);
if (rc < 0) {
gnutls_assert();
gnutls_openpgp_keyring_deinit(c->keyring);
return rc;
}
return 0;
}
/*-
* _gnutls_openpgp_request_key - Receives a key from a database, key server etc
* @ret - a pointer to gnutls_datum_t type.
* @cred - a gnutls_certificate_credentials_t type.
* @key_fingerprint - The keyFingerprint
* @key_fingerprint_size - the size of the fingerprint
*
* Retrieves a key from a local database, keyring, or a key server. The
* return value is locally allocated.
*
-*/
int
_gnutls_openpgp_request_key(gnutls_session_t session, gnutls_datum_t * ret,
const gnutls_certificate_credentials_t cred,
uint8_t * key_fpr, int key_fpr_size)
{
int rc = 0;
if (!ret || !cred || !key_fpr) {
gnutls_assert();
return GNUTLS_E_INVALID_REQUEST;
}
if (key_fpr_size != 16 && key_fpr_size != 20)
return GNUTLS_E_HASH_FAILED; /* only MD5 and SHA1 are supported */
rc = gnutls_openpgp_get_key(ret, cred->keyring, KEY_ATTR_FPR,
key_fpr);
if (rc >= 0) { /* key was found */
rc = 0;
goto error;
} else
rc = GNUTLS_E_OPENPGP_GETKEY_FAILED;
/* If the callback function was set, then try this one. */
if (session->internals.openpgp_recv_key_func != NULL) {
rc = session->internals.openpgp_recv_key_func(session,
key_fpr,
key_fpr_size,
ret);
if (rc < 0) {
gnutls_assert();
rc = GNUTLS_E_OPENPGP_GETKEY_FAILED;
goto error;
}
}
error:
return rc;
}
/**
* gnutls_openpgp_set_recv_key_function:
* @session: a TLS session
* @func: the callback
*
* This function will set a key retrieval function for OpenPGP keys. This
* callback is only useful in server side, and will be used if the peer
* sent a key fingerprint instead of a full key.
*
* The retrieved key must be allocated using gnutls_malloc().
*
**/
void
gnutls_openpgp_set_recv_key_function(gnutls_session_t session,
gnutls_openpgp_recv_key_func func)
{
session->internals.openpgp_recv_key_func = func;
}