Files
libmicrohttpd/src/daemon/https/tls/auth_cert.c
T
2008-08-24 22:07:29 +00:00

1324 lines
32 KiB
C

/*
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation
*
* Author: Nikos Mavrogiannopoulos
*
* This file is part of GNUTLS.
*
* The GNUTLS 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 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA
*
*/
/* The certificate authentication functions which are needed in the handshake,
* and are common to RSA and DHE key exchange, are in this file.
*/
#include <gnutls_int.h>
#include "gnutls_auth_int.h"
#include "gnutls_errors.h"
#include <gnutls_cert.h>
#include <auth_cert.h>
#include "gnutls_dh.h"
#include "gnutls_num.h"
#include "libtasn1.h"
#include "gnutls_datum.h"
#include <gnutls_pk.h>
#include <gnutls_algorithms.h>
#include <gnutls_global.h>
#include <gnutls_record.h>
#include <gnutls_sig.h>
#include <gnutls_state.h>
#include <gnutls_pk.h>
#include <gnutls_x509.h>
#include "debug.h"
static gnutls_cert *alloc_and_load_x509_certs (gnutls_x509_crt_t * certs,
unsigned);
static gnutls_privkey *alloc_and_load_x509_key (gnutls_x509_privkey_t key);
/* Copies data from a internal certificate struct (gnutls_cert) to
* exported certificate struct (cert_auth_info_t)
*/
static int
_gnutls_copy_certificate_auth_info (cert_auth_info_t info,
gnutls_cert * cert, int ncerts)
{
/* Copy peer's information to auth_info_t
*/
int ret, i, j;
if (ncerts == 0)
{
info->raw_certificate_list = NULL;
info->ncerts = 0;
return 0;
}
info->raw_certificate_list =
gnutls_calloc (1, sizeof (gnutls_datum_t) * ncerts);
if (info->raw_certificate_list == NULL)
{
gnutls_assert ();
return GNUTLS_E_MEMORY_ERROR;
}
for (i = 0; i < ncerts; i++)
{
if (cert->raw.size > 0)
{
ret =
_gnutls_set_datum (&info->raw_certificate_list[i],
cert[i].raw.data, cert[i].raw.size);
if (ret < 0)
{
gnutls_assert ();
goto clear;
}
}
}
info->ncerts = ncerts;
return 0;
clear:
for (j = 0; j < i; j++)
_gnutls_free_datum (&info->raw_certificate_list[j]);
gnutls_free (info->raw_certificate_list);
info->raw_certificate_list = NULL;
return ret;
}
/* returns 0 if the algo_to-check exists in the pk_algos list,
* -1 otherwise.
*/
inline static int
_gnutls_check_pk_algo_in_list (const enum MHD_GNUTLS_PublicKeyAlgorithm
*pk_algos, int pk_algos_length,
enum MHD_GNUTLS_PublicKeyAlgorithm
algo_to_check)
{
int i;
for (i = 0; i < pk_algos_length; i++)
{
if (algo_to_check == pk_algos[i])
{
return 0;
}
}
return -1;
}
/* Returns the issuer's Distinguished name in odn, of the certificate
* specified in cert.
*/
static int
_gnutls_cert_get_issuer_dn (gnutls_cert * cert, gnutls_datum_t * odn)
{
ASN1_TYPE dn;
int len, result;
int start, end;
if ((result = asn1_create_element
(_gnutls_get_pkix (), "PKIX1.Certificate", &dn)) != ASN1_SUCCESS)
{
gnutls_assert ();
return mhd_gtls_asn2err (result);
}
result = asn1_der_decoding (&dn, cert->raw.data, cert->raw.size, NULL);
if (result != ASN1_SUCCESS)
{
/* couldn't decode DER */
gnutls_assert ();
asn1_delete_structure (&dn);
return mhd_gtls_asn2err (result);
}
result = asn1_der_decoding_startEnd (dn, cert->raw.data, cert->raw.size,
"tbsCertificate.issuer", &start, &end);
if (result != ASN1_SUCCESS)
{
/* couldn't decode DER */
gnutls_assert ();
asn1_delete_structure (&dn);
return mhd_gtls_asn2err (result);
}
asn1_delete_structure (&dn);
len = end - start + 1;
odn->size = len;
odn->data = &cert->raw.data[start];
return 0;
}
/* Locates the most appropriate x509 certificate using the
* given DN. If indx == -1 then no certificate was found.
*
* That is to guess which certificate to use, based on the
* CAs and sign algorithms supported by the peer server.
*/
static int
_find_x509_cert (const mhd_gtls_cert_credentials_t cred,
opaque * _data, size_t _data_size,
const enum MHD_GNUTLS_PublicKeyAlgorithm *pk_algos,
int pk_algos_length, int *indx)
{
unsigned size;
gnutls_datum_t odn;
opaque *data = _data;
ssize_t data_size = _data_size;
unsigned i, j;
int result, cert_pk;
*indx = -1;
do
{
DECR_LENGTH_RET (data_size, 2, 0);
size = mhd_gtls_read_uint16 (data);
DECR_LENGTH_RET (data_size, size, 0);
data += 2;
for (i = 0; i < cred->ncerts; i++)
{
for (j = 0; j < cred->cert_list_length[i]; j++)
{
if ((result =
_gnutls_cert_get_issuer_dn (&cred->cert_list[i][j],
&odn)) < 0)
{
gnutls_assert ();
return result;
}
if (odn.size != size)
continue;
/* If the DN matches and
* the *_SIGN algorithm matches
* the cert is our cert!
*/
cert_pk = cred->cert_list[i][0].subject_pk_algorithm;
if ((memcmp (odn.data, data, size) == 0) &&
(_gnutls_check_pk_algo_in_list
(pk_algos, pk_algos_length, cert_pk) == 0))
{
*indx = i;
break;
}
}
if (*indx != -1)
break;
}
if (*indx != -1)
break;
/* move to next record */
data += size;
}
while (1);
return 0;
}
/* Returns the number of issuers in the server's
* certificate request packet.
*/
static int
get_issuers_num (mhd_gtls_session_t session, opaque * data, ssize_t data_size)
{
int issuers_dn_len = 0, result;
unsigned size;
/* Count the number of the given issuers;
* This is used to allocate the issuers_dn without
* using realloc().
*/
if (data_size == 0 || data == NULL)
return 0;
if (data_size > 0)
do
{
/* This works like DECR_LEN()
*/
result = GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
DECR_LENGTH_COM (data_size, 2, goto error);
size = mhd_gtls_read_uint16 (data);
result = GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
DECR_LENGTH_COM (data_size, size, goto error);
data += 2;
if (size > 0)
{
issuers_dn_len++;
data += size;
}
if (data_size == 0)
break;
}
while (1);
return issuers_dn_len;
error:
return result;
}
/* Returns the issuers in the server's certificate request
* packet.
*/
static int
get_issuers (mhd_gtls_session_t session,
gnutls_datum_t * issuers_dn, int issuers_len,
opaque * data, size_t data_size)
{
int i;
unsigned size;
if (gnutls_certificate_type_get (session) != MHD_GNUTLS_CRT_X509)
return 0;
/* put the requested DNs to req_dn, only in case
* of X509 certificates.
*/
if (issuers_len > 0)
{
for (i = 0; i < issuers_len; i++)
{
/* The checks here for the buffer boundaries
* are not needed since the buffer has been
* parsed above.
*/
data_size -= 2;
size = mhd_gtls_read_uint16 (data);
data += 2;
issuers_dn[i].data = data;
issuers_dn[i].size = size;
data += size;
}
}
return 0;
}
/* Calls the client get callback.
*/
static int
call_get_cert_callback (mhd_gtls_session_t session,
gnutls_datum_t * issuers_dn,
int issuers_dn_length,
enum MHD_GNUTLS_PublicKeyAlgorithm *pk_algos,
int pk_algos_length)
{
unsigned i;
gnutls_cert *local_certs = NULL;
gnutls_privkey *local_key = NULL;
gnutls_retr_st st;
int ret;
enum MHD_GNUTLS_CertificateType type =
gnutls_certificate_type_get (session);
mhd_gtls_cert_credentials_t cred;
cred = (mhd_gtls_cert_credentials_t)
mhd_gtls_get_cred (session->key, MHD_GNUTLS_CRD_CERTIFICATE, NULL);
if (cred == NULL)
{
gnutls_assert ();
return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
}
memset (&st, 0, sizeof (st));
if (session->security_parameters.entity == GNUTLS_SERVER)
{
ret = cred->server_get_cert_callback (session, &st);
}
else
{ /* CLIENT */
ret =
cred->client_get_cert_callback (session,
issuers_dn, issuers_dn_length,
pk_algos, pk_algos_length, &st);
}
if (ret < 0)
{
gnutls_assert ();
return GNUTLS_E_INTERNAL_ERROR;
}
if (st.ncerts == 0)
return 0; /* no certificate was selected */
if (type != st.type)
{
gnutls_assert ();
ret = GNUTLS_E_INVALID_REQUEST;
goto cleanup;
}
if (type == MHD_GNUTLS_CRT_X509)
{
local_certs = alloc_and_load_x509_certs (st.cert.x509, st.ncerts);
if (local_certs != NULL)
local_key = alloc_and_load_x509_key (st.key.x509);
}
else
{ /* PGP */
gnutls_assert ();
ret = GNUTLS_E_INVALID_REQUEST;
goto cleanup;
}
mhd_gtls_selected_certs_set (session, local_certs,
(local_certs != NULL) ? st.ncerts : 0,
local_key, 1);
ret = 0;
cleanup:
if (st.type == MHD_GNUTLS_CRT_X509)
{
if (st.deinit_all)
{
for (i = 0; i < st.ncerts; i++)
{
gnutls_x509_crt_deinit (st.cert.x509[i]);
}
gnutls_free (st.cert.x509);
gnutls_x509_privkey_deinit (st.key.x509);
}
}
return ret;
}
/* Finds the appropriate certificate depending on the cA Distinguished name
* advertized by the server. If none matches then returns 0 and -1 as index.
* In case of an error a negative value, is returned.
*
* 20020128: added ability to select a certificate depending on the SIGN
* algorithm (only in automatic mode).
*/
static int
_select_client_cert (mhd_gtls_session_t session,
opaque * _data, size_t _data_size,
enum MHD_GNUTLS_PublicKeyAlgorithm *pk_algos,
int pk_algos_length)
{
int result;
int indx = -1;
mhd_gtls_cert_credentials_t cred;
opaque *data = _data;
ssize_t data_size = _data_size;
int issuers_dn_length;
gnutls_datum_t *issuers_dn = NULL;
cred = (mhd_gtls_cert_credentials_t)
mhd_gtls_get_cred (session->key, MHD_GNUTLS_CRD_CERTIFICATE, NULL);
if (cred == NULL)
{
gnutls_assert ();
return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
}
if (cred->client_get_cert_callback != NULL)
{
/* use a callback to get certificate
*/
if (session->security_parameters.cert_type != MHD_GNUTLS_CRT_X509)
issuers_dn_length = 0;
else
{
issuers_dn_length = get_issuers_num (session, data, data_size);
if (issuers_dn_length < 0)
{
gnutls_assert ();
return issuers_dn_length;
}
if (issuers_dn_length > 0)
{
issuers_dn =
gnutls_malloc (sizeof (gnutls_datum_t) * issuers_dn_length);
if (issuers_dn == NULL)
{
gnutls_assert ();
return GNUTLS_E_MEMORY_ERROR;
}
result =
get_issuers (session, issuers_dn, issuers_dn_length,
data, data_size);
if (result < 0)
{
gnutls_assert ();
goto cleanup;
}
}
}
result =
call_get_cert_callback (session, issuers_dn, issuers_dn_length,
pk_algos, pk_algos_length);
goto cleanup;
}
else
{
/* If we have no callbacks, try to guess.
*/
result = 0;
if (session->security_parameters.cert_type == MHD_GNUTLS_CRT_X509)
result =
_find_x509_cert (cred, _data, _data_size,
pk_algos, pk_algos_length, &indx);
if (result < 0)
{
gnutls_assert ();
return result;
}
if (indx >= 0)
{
mhd_gtls_selected_certs_set (session,
&cred->cert_list[indx][0],
cred->cert_list_length[indx],
&cred->pkey[indx], 0);
}
else
{
mhd_gtls_selected_certs_set (session, NULL, 0, NULL, 0);
}
result = 0;
}
cleanup:
gnutls_free (issuers_dn);
return result;
}
/* Generate client certificate
*/
int
mhd_gtls_gen_x509_crt (mhd_gtls_session_t session, opaque ** data)
{
int ret, i;
opaque *pdata;
gnutls_cert *apr_cert_list;
gnutls_privkey *apr_pkey;
int apr_cert_list_length;
/* find the appropriate certificate
*/
if ((ret =
mhd_gtls_get_selected_cert (session, &apr_cert_list,
&apr_cert_list_length, &apr_pkey)) < 0)
{
gnutls_assert ();
return ret;
}
ret = 3;
for (i = 0; i < apr_cert_list_length; i++)
{
ret += apr_cert_list[i].raw.size + 3;
/* hold size
* for uint24 */
}
/* if no certificates were found then send:
* 0B 00 00 03 00 00 00 // Certificate with no certs
* instead of:
* 0B 00 00 00 // empty certificate handshake
*
* ( the above is the whole handshake message, not
* the one produced here )
*/
(*data) = gnutls_malloc (ret);
pdata = (*data);
if (pdata == NULL)
{
gnutls_assert ();
return GNUTLS_E_MEMORY_ERROR;
}
mhd_gtls_write_uint24 (ret - 3, pdata);
pdata += 3;
for (i = 0; i < apr_cert_list_length; i++)
{
mhd_gtls_write_datum24 (pdata, apr_cert_list[i].raw);
pdata += (3 + apr_cert_list[i].raw.size);
}
return ret;
}
int
mhd_gtls_gen_cert_client_certificate (mhd_gtls_session_t session,
opaque ** data)
{
switch (session->security_parameters.cert_type)
{
case MHD_GNUTLS_CRT_X509:
return mhd_gtls_gen_x509_crt (session, data);
default:
gnutls_assert ();
return GNUTLS_E_INTERNAL_ERROR;
}
}
int
mhd_gtls_gen_cert_server_certificate (mhd_gtls_session_t session,
opaque ** data)
{
switch (session->security_parameters.cert_type)
{
case MHD_GNUTLS_CRT_X509:
return mhd_gtls_gen_x509_crt (session, data);
default:
gnutls_assert ();
return GNUTLS_E_INTERNAL_ERROR;
}
}
/* Process server certificate
*/
#define CLEAR_CERTS for(x=0;x<peer_certificate_list_size;x++) mhd_gtls_gcert_deinit(&peer_certificate_list[x])
int
mhd_gtls_proc_x509_server_certificate (mhd_gtls_session_t session,
opaque * data, size_t data_size)
{
int size, len, ret;
opaque *p = data;
cert_auth_info_t info;
mhd_gtls_cert_credentials_t cred;
ssize_t dsize = data_size;
int i, j, x;
gnutls_cert *peer_certificate_list;
int peer_certificate_list_size = 0;
gnutls_datum_t tmp;
cred = (mhd_gtls_cert_credentials_t)
mhd_gtls_get_cred (session->key, MHD_GNUTLS_CRD_CERTIFICATE, NULL);
if (cred == NULL)
{
gnutls_assert ();
return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
}
if ((ret =
mhd_gtls_auth_info_set (session, MHD_GNUTLS_CRD_CERTIFICATE,
sizeof (cert_auth_info_st), 1)) < 0)
{
gnutls_assert ();
return ret;
}
info = mhd_gtls_get_auth_info (session);
if (data == NULL || data_size == 0)
{
gnutls_assert ();
/* no certificate was sent */
return GNUTLS_E_NO_CERTIFICATE_FOUND;
}
DECR_LEN (dsize, 3);
size = mhd_gtls_read_uint24 (p);
p += 3;
/* some implementations send 0B 00 00 06 00 00 03 00 00 00
* instead of just 0B 00 00 03 00 00 00 as an empty certificate message.
*/
if (size == 0 || size == 3)
{
gnutls_assert ();
/* no certificate was sent */
return GNUTLS_E_NO_CERTIFICATE_FOUND;
}
i = dsize;
while (i > 0)
{
DECR_LEN (dsize, 3);
len = mhd_gtls_read_uint24 (p);
p += 3;
DECR_LEN (dsize, len);
peer_certificate_list_size++;
p += len;
i -= len + 3;
}
if (peer_certificate_list_size == 0)
{
gnutls_assert ();
return GNUTLS_E_NO_CERTIFICATE_FOUND;
}
/* Ok we now allocate the memory to hold the
* certificate list
*/
peer_certificate_list =
gnutls_malloc (sizeof (gnutls_cert) * (peer_certificate_list_size));
if (peer_certificate_list == NULL)
{
gnutls_assert ();
return GNUTLS_E_MEMORY_ERROR;
}
memset (peer_certificate_list, 0, sizeof (gnutls_cert) *
peer_certificate_list_size);
p = data + 3;
/* Now we start parsing the list (again).
* We don't use DECR_LEN since the list has
* been parsed before.
*/
for (j = 0; j < peer_certificate_list_size; j++)
{
len = mhd_gtls_read_uint24 (p);
p += 3;
tmp.size = len;
tmp.data = p;
if ((ret =
mhd_gtls_x509_raw_cert_to_gcert (&peer_certificate_list
[j], &tmp,
CERT_ONLY_EXTENSIONS)) < 0)
{
gnutls_assert ();
goto cleanup;
}
p += len;
}
if ((ret =
_gnutls_copy_certificate_auth_info (info,
peer_certificate_list,
peer_certificate_list_size)) < 0)
{
gnutls_assert ();
goto cleanup;
}
if ((ret =
_gnutls_check_key_usage (&peer_certificate_list[0],
gnutls_kx_get (session))) < 0)
{
gnutls_assert ();
goto cleanup;
}
ret = 0;
cleanup:
CLEAR_CERTS;
gnutls_free (peer_certificate_list);
return ret;
}
#define CLEAR_CERTS for(x=0;x<peer_certificate_list_size;x++) mhd_gtls_gcert_deinit(&peer_certificate_list[x])
int
mhd_gtls_proc_cert_server_certificate (mhd_gtls_session_t session,
opaque * data, size_t data_size)
{
switch (session->security_parameters.cert_type)
{
case MHD_GNUTLS_CRT_X509:
return mhd_gtls_proc_x509_server_certificate (session, data, data_size);
default:
gnutls_assert ();
return GNUTLS_E_INTERNAL_ERROR;
}
}
#define MAX_SIGN_ALGOS 2
typedef enum CertificateSigType
{ RSA_SIGN = 1, DSA_SIGN
} CertificateSigType;
/* Checks if we support the given signature algorithm
* (RSA or DSA). Returns the corresponding enum MHD_GNUTLS_PublicKeyAlgorithm
* if true;
*/
inline static int
_gnutls_check_supported_sign_algo (CertificateSigType algo)
{
switch (algo)
{
case RSA_SIGN:
return MHD_GNUTLS_PK_RSA;
}
return -1;
}
int
mhd_gtls_proc_cert_cert_req (mhd_gtls_session_t session, opaque * data,
size_t data_size)
{
int size, ret;
opaque *p;
mhd_gtls_cert_credentials_t cred;
ssize_t dsize;
int i, j;
enum MHD_GNUTLS_PublicKeyAlgorithm pk_algos[MAX_SIGN_ALGOS];
int pk_algos_length;
enum MHD_GNUTLS_Protocol ver = MHD_gnutls_protocol_get_version (session);
cred = (mhd_gtls_cert_credentials_t)
mhd_gtls_get_cred (session->key, MHD_GNUTLS_CRD_CERTIFICATE, NULL);
if (cred == NULL)
{
gnutls_assert ();
return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
}
if ((ret =
mhd_gtls_auth_info_set (session, MHD_GNUTLS_CRD_CERTIFICATE,
sizeof (cert_auth_info_st), 0)) < 0)
{
gnutls_assert ();
return ret;
}
p = data;
dsize = data_size;
DECR_LEN (dsize, 1);
size = p[0];
p++;
/* check if the sign algorithm is supported.
*/
pk_algos_length = j = 0;
for (i = 0; i < size; i++, p++)
{
DECR_LEN (dsize, 1);
if ((ret = _gnutls_check_supported_sign_algo (*p)) > 0)
{
if (j < MAX_SIGN_ALGOS)
{
pk_algos[j++] = ret;
pk_algos_length++;
}
}
}
if (pk_algos_length == 0)
{
gnutls_assert ();
return GNUTLS_E_UNKNOWN_PK_ALGORITHM;
}
if (ver == MHD_GNUTLS_TLS1_2)
{
/* read supported hashes */
int hash_num;
DECR_LEN (dsize, 1);
hash_num = p[0] & 0xFF;
p++;
DECR_LEN (dsize, hash_num);
p += hash_num;
}
/* read the certificate authorities */
DECR_LEN (dsize, 2);
size = mhd_gtls_read_uint16 (p);
p += 2;
DECR_LEN (dsize, size);
/* now we ask the user to tell which one
* he wants to use.
*/
if ((ret =
_select_client_cert (session, p, size, pk_algos, pk_algos_length)) < 0)
{
gnutls_assert ();
return ret;
}
/* We should reply with a certificate message,
* even if we have no certificate to send.
*/
session->key->certificate_requested = 1;
return 0;
}
int
mhd_gtls_gen_cert_client_cert_vrfy (mhd_gtls_session_t session,
opaque ** data)
{
int ret;
gnutls_cert *apr_cert_list;
gnutls_privkey *apr_pkey;
int apr_cert_list_length, size;
gnutls_datum_t signature;
*data = NULL;
/* find the appropriate certificate */
if ((ret =
mhd_gtls_get_selected_cert (session, &apr_cert_list,
&apr_cert_list_length, &apr_pkey)) < 0)
{
gnutls_assert ();
return ret;
}
if (apr_cert_list_length > 0)
{
if ((ret =
mhd_gtls_tls_sign_hdata (session,
&apr_cert_list[0],
apr_pkey, &signature)) < 0)
{
gnutls_assert ();
return ret;
}
}
else
{
return 0;
}
*data = gnutls_malloc (signature.size + 2);
if (*data == NULL)
{
_gnutls_free_datum (&signature);
return GNUTLS_E_MEMORY_ERROR;
}
size = signature.size;
mhd_gtls_write_uint16 (size, *data);
memcpy (&(*data)[2], signature.data, size);
_gnutls_free_datum (&signature);
return size + 2;
}
int
mhd_gtls_proc_cert_client_cert_vrfy (mhd_gtls_session_t session,
opaque * data, size_t data_size)
{
int size, ret;
ssize_t dsize = data_size;
opaque *pdata = data;
gnutls_datum_t sig;
cert_auth_info_t info = mhd_gtls_get_auth_info (session);
gnutls_cert peer_cert;
if (info == NULL || info->ncerts == 0)
{
gnutls_assert ();
/* we need this in order to get peer's certificate */
return GNUTLS_E_INTERNAL_ERROR;
}
DECR_LEN (dsize, 2);
size = mhd_gtls_read_uint16 (pdata);
pdata += 2;
DECR_LEN (dsize, size);
sig.data = pdata;
sig.size = size;
ret = mhd_gtls_raw_cert_to_gcert (&peer_cert,
session->security_parameters.cert_type,
&info->raw_certificate_list[0],
CERT_NO_COPY);
if (ret < 0)
{
gnutls_assert ();
return ret;
}
if ((ret = mhd_gtls_verify_sig_hdata (session, &peer_cert, &sig)) < 0)
{
gnutls_assert ();
mhd_gtls_gcert_deinit (&peer_cert);
return ret;
}
mhd_gtls_gcert_deinit (&peer_cert);
return 0;
}
#define CERTTYPE_SIZE 3
int
mhd_gtls_gen_cert_server_cert_req (mhd_gtls_session_t session, opaque ** data)
{
mhd_gtls_cert_credentials_t cred;
int size;
opaque *pdata;
enum MHD_GNUTLS_Protocol ver = MHD_gnutls_protocol_get_version (session);
/* Now we need to generate the RDN sequence. This is
* already in the CERTIFICATE_CRED structure, to improve
* performance.
*/
cred = (mhd_gtls_cert_credentials_t)
mhd_gtls_get_cred (session->key, MHD_GNUTLS_CRD_CERTIFICATE, NULL);
if (cred == NULL)
{
gnutls_assert ();
return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
}
size = CERTTYPE_SIZE + 2; /* 2 for enum MHD_GNUTLS_CertificateType + 2 for size of rdn_seq
*/
if (session->security_parameters.cert_type == MHD_GNUTLS_CRT_X509 &&
session->internals.ignore_rdn_sequence == 0)
size += cred->x509_rdn_sequence.size;
if (ver == MHD_GNUTLS_TLS1_2)
/* Need at least one byte to announce the number of supported hash
functions (see below). */
size += 1;
(*data) = gnutls_malloc (size);
pdata = (*data);
if (pdata == NULL)
{
gnutls_assert ();
return GNUTLS_E_MEMORY_ERROR;
}
pdata[0] = CERTTYPE_SIZE - 1;
pdata[1] = RSA_SIGN;
pdata[2] = DSA_SIGN; /* only these for now */
pdata += CERTTYPE_SIZE;
if (ver == MHD_GNUTLS_TLS1_2)
{
/* Supported hashes (nothing for now -- FIXME). */
*pdata = 0;
pdata++;
}
if (session->security_parameters.cert_type == MHD_GNUTLS_CRT_X509 &&
session->internals.ignore_rdn_sequence == 0)
{
mhd_gtls_write_datum16 (pdata, cred->x509_rdn_sequence);
/* pdata += cred->x509_rdn_sequence.size + 2; */
}
else
{
mhd_gtls_write_uint16 (0, pdata);
/* pdata+=2; */
}
return size;
}
/* This function will return the appropriate certificate to use.
* Fills in the apr_cert_list, apr_cert_list_length and apr_pkey.
* The return value is a negative value on error.
*
* It is normal to return 0 with no certificates in client side.
*
*/
int
mhd_gtls_get_selected_cert (mhd_gtls_session_t session,
gnutls_cert ** apr_cert_list,
int *apr_cert_list_length,
gnutls_privkey ** apr_pkey)
{
if (session->security_parameters.entity == GNUTLS_SERVER)
{
/* select_client_cert() has been called before.
*/
*apr_cert_list = session->internals.selected_cert_list;
*apr_pkey = session->internals.selected_key;
*apr_cert_list_length = session->internals.selected_cert_list_length;
if (*apr_cert_list_length == 0 || *apr_cert_list == NULL)
{
gnutls_assert ();
return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
}
}
else
{ /* CLIENT SIDE
*/
/* we have already decided which certificate
* to send.
*/
*apr_cert_list = session->internals.selected_cert_list;
*apr_cert_list_length = session->internals.selected_cert_list_length;
*apr_pkey = session->internals.selected_key;
}
return 0;
}
/* converts the given x509 certificate to gnutls_cert* and allocates
* space for them.
*/
static gnutls_cert *
alloc_and_load_x509_certs (gnutls_x509_crt_t * certs, unsigned ncerts)
{
gnutls_cert *local_certs;
int ret = 0;
unsigned i, j;
if (certs == NULL)
return NULL;
local_certs = gnutls_malloc (sizeof (gnutls_cert) * ncerts);
if (local_certs == NULL)
{
gnutls_assert ();
return NULL;
}
for (i = 0; i < ncerts; i++)
{
ret = mhd_gtls_x509_crt_to_gcert (&local_certs[i], certs[i], 0);
if (ret < 0)
break;
}
if (ret < 0)
{
gnutls_assert ();
for (j = 0; j < i; j++)
{
mhd_gtls_gcert_deinit (&local_certs[j]);
}
gnutls_free (local_certs);
return NULL;
}
return local_certs;
}
/* converts the given x509 key to gnutls_privkey* and allocates
* space for it.
*/
static gnutls_privkey *
alloc_and_load_x509_key (gnutls_x509_privkey_t key)
{
gnutls_privkey *local_key;
int ret = 0;
if (key == NULL)
return NULL;
local_key = gnutls_malloc (sizeof (gnutls_privkey));
if (local_key == NULL)
{
gnutls_assert ();
return NULL;
}
ret = _gnutls_x509_privkey_to_gkey (local_key, key);
if (ret < 0)
{
gnutls_assert ();
return NULL;
}
return local_key;
}
void
mhd_gtls_selected_certs_deinit (mhd_gtls_session_t session)
{
if (session->internals.selected_need_free != 0)
{
int i;
for (i = 0; i < session->internals.selected_cert_list_length; i++)
{
mhd_gtls_gcert_deinit (&session->internals.selected_cert_list[i]);
}
gnutls_free (session->internals.selected_cert_list);
session->internals.selected_cert_list = NULL;
session->internals.selected_cert_list_length = 0;
mhd_gtls_gkey_deinit (session->internals.selected_key);
if (session->internals.selected_key)
{
gnutls_free (session->internals.selected_key);
session->internals.selected_key = NULL;
}
}
return;
}
void
mhd_gtls_selected_certs_set (mhd_gtls_session_t session,
gnutls_cert * certs, int ncerts,
gnutls_privkey * key, int need_free)
{
mhd_gtls_selected_certs_deinit (session);
session->internals.selected_cert_list = certs;
session->internals.selected_cert_list_length = ncerts;
session->internals.selected_key = key;
session->internals.selected_need_free = need_free;
}
/* finds the most appropriate certificate in the cert list.
* The 'appropriate' is defined by the user.
*
* requested_algo holds the parameters required by the peer (RSA, DSA
* or -1 for any).
*
* Returns 0 on success and a negative value on error. The
* selected certificate will be in session->internals.selected_*.
*
*/
int
mhd_gtls_server_select_cert (mhd_gtls_session_t session,
enum MHD_GNUTLS_PublicKeyAlgorithm
requested_algo)
{
unsigned i;
int idx, ret;
mhd_gtls_cert_credentials_t cred;
cred = (mhd_gtls_cert_credentials_t)
mhd_gtls_get_cred (session->key, MHD_GNUTLS_CRD_CERTIFICATE, NULL);
if (cred == NULL)
{
gnutls_assert ();
return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
}
/* If the callback which retrieves certificate has been set,
* use it and leave.
*/
if (cred->server_get_cert_callback != NULL)
return call_get_cert_callback (session, NULL, 0, NULL, 0);
/* Otherwise... */
ret = 0;
idx = -1; /* default is use no certificate */
for (i = 0; i < cred->ncerts; i++)
{
/* find one compatible certificate
*/
if (requested_algo == GNUTLS_PK_ANY ||
requested_algo == cred->cert_list[i][0].subject_pk_algorithm)
{
/* if cert type matches
*/
if (session->security_parameters.cert_type ==
cred->cert_list[i][0].cert_type)
{
idx = i;
break;
}
}
}
/* store the certificate pointer for future use, in the handshake.
* (This will allow not calling this callback again.)
*/
if (idx >= 0 && ret == 0)
{
mhd_gtls_selected_certs_set (session,
&cred->cert_list[idx][0],
cred->cert_list_length[idx],
&cred->pkey[idx], 0);
}
else
/* Certificate does not support REQUESTED_ALGO. */
ret = GNUTLS_E_INSUFFICIENT_CREDENTIALS;
return ret;
}
/* Frees the mhd_gtls_rsa_info_st structure.
*/
void
mhd_gtls_free_rsa_info (rsa_info_st * rsa)
{
_gnutls_free_datum (&rsa->modulus);
_gnutls_free_datum (&rsa->exponent);
}