/* libp11, a simple layer on to of PKCS#11 API
 * Copyright (C) 2005 Olaf Kirch <okir@lst.de>
 * Copyright (C) 2016-2018 Michał Trojnara <Michal.Trojnara@stunnel.org>
 *
 *  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
 *  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., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */

/*
 * p11_cert.c - Handle certificates residing on a PKCS11 token
 *
 * Copyright (C) 2002, Olaf Kirch <okir@lst.de>
 */

#include "libp11-int.h"
#include <string.h>

static int pkcs11_find_certs(PKCS11_TOKEN *);
static int pkcs11_next_cert(PKCS11_CTX *, PKCS11_TOKEN *, CK_SESSION_HANDLE);
static int pkcs11_init_cert(PKCS11_CTX *ctx, PKCS11_TOKEN *token,
	CK_SESSION_HANDLE session, CK_OBJECT_HANDLE o, PKCS11_CERT **);

/*
 * Enumerate all certs on the card
 */
int pkcs11_enumerate_certs(PKCS11_TOKEN *token,
		PKCS11_CERT **certp, unsigned int *countp)
{
	PKCS11_SLOT *slot = TOKEN2SLOT(token);
	PKCS11_CTX *ctx = SLOT2CTX(slot);
	PKCS11_TOKEN_private *tpriv = PRIVTOKEN(token);
	PKCS11_SLOT_private *spriv = PRIVSLOT(slot);
	PKCS11_CTX_private *cpriv = PRIVCTX(ctx);
	int rv;

	/* Make sure we have a session */
	if (!spriv->haveSession && PKCS11_open_session(slot, 0))
		return -1;

	CRYPTO_THREAD_write_lock(cpriv->rwlock);
	rv = pkcs11_find_certs(token);
	CRYPTO_THREAD_unlock(cpriv->rwlock);
	if (rv < 0) {
		pkcs11_destroy_certs(token);
		return -1;
	}

	if (certp)
		*certp = tpriv->certs;
	if (countp)
		*countp = tpriv->ncerts;
	return 0;
}

/**
 * Remove a certificate from the associated token
 */
int pkcs11_remove_certificate(PKCS11_CERT *cert){
	PKCS11_SLOT *slot = CERT2SLOT(cert);
	PKCS11_CTX *ctx = CERT2CTX(cert);
	PKCS11_SLOT_private *spriv = PRIVSLOT(slot);
	CK_OBJECT_HANDLE obj;
	CK_ULONG count;
	CK_ATTRIBUTE search_parameters[32];
	unsigned int n = 0;
	int rv;

	/* First, make sure we have a session */
	if (!spriv->haveSession && PKCS11_open_session(slot, 1)){
		return -1;
	}
	
	pkcs11_addattr_int(search_parameters + n++, CKA_CLASS, CKO_CERTIFICATE);
	if (cert->id && cert->id_len){
		pkcs11_addattr(search_parameters + n++, CKA_ID, cert->id, cert->id_len);
	}
	if (cert->label){
	 	pkcs11_addattr_s(search_parameters + n++, CKA_LABEL, cert->label);
	}

	rv = CRYPTOKI_call(ctx,
		C_FindObjectsInit(spriv->session, search_parameters, n));
	CRYPTOKI_checkerr(CKR_F_PKCS11_REMOVE_CERTIFICATE, rv);
	
	rv = CRYPTOKI_call(ctx, C_FindObjects(spriv->session, &obj, 1, &count));
	CRYPTOKI_checkerr(CKR_F_PKCS11_REMOVE_CERTIFICATE, rv);

	CRYPTOKI_call(ctx, C_FindObjectsFinal(spriv->session));
	if (count!=1){
		pkcs11_zap_attrs(search_parameters, n);
		return -1;
	}
	rv = CRYPTOKI_call(ctx, C_DestroyObject(spriv->session, obj));
	if (rv != CKR_OK){
		pkcs11_zap_attrs(search_parameters, n);
		return -1;
	}
	pkcs11_zap_attrs(search_parameters, n);
	return 0;
}

/*
 * Find certificate matching a key
 */
PKCS11_CERT *pkcs11_find_certificate(PKCS11_KEY *key)
{
	PKCS11_KEY_private *kpriv;
	PKCS11_CERT_private *cpriv;
	PKCS11_CERT *cert;
	unsigned int n, count;

	kpriv = PRIVKEY(key);
	if (PKCS11_enumerate_certs(KEY2TOKEN(key), &cert, &count))
		return NULL;
	for (n = 0; n < count; n++, cert++) {
		cpriv = PRIVCERT(cert);
		if (cpriv->id_len == kpriv->id_len
				&& !memcmp(cpriv->id, kpriv->id, kpriv->id_len))
			return cert;
	}
	return NULL;
}

/*
 * Find all certs of a given type (public or private)
 */
static int pkcs11_find_certs(PKCS11_TOKEN *token)
{
	PKCS11_SLOT *slot = TOKEN2SLOT(token);
	PKCS11_CTX *ctx = SLOT2CTX(slot);
	PKCS11_SLOT_private *spriv = PRIVSLOT(slot);
	CK_OBJECT_CLASS cert_search_class;
	CK_ATTRIBUTE cert_search_attrs[] = {
		{CKA_CLASS, &cert_search_class, sizeof(cert_search_class)},
	};
	int rv, res = -1;

	/* Tell the PKCS11 lib to enumerate all matching objects */
	cert_search_class = CKO_CERTIFICATE;
	rv = CRYPTOKI_call(ctx, C_FindObjectsInit(spriv->session, cert_search_attrs, 1));
	CRYPTOKI_checkerr(CKR_F_PKCS11_FIND_CERTS, rv);

	do {
		res = pkcs11_next_cert(ctx, token, spriv->session);
	} while (res == 0);

	CRYPTOKI_call(ctx, C_FindObjectsFinal(spriv->session));

	return (res < 0) ? -1 : 0;
}

static int pkcs11_next_cert(PKCS11_CTX *ctx, PKCS11_TOKEN *token,
		CK_SESSION_HANDLE session)
{
	CK_OBJECT_HANDLE obj;
	CK_ULONG count;
	int rv;

	/* Get the next matching object */
	rv = CRYPTOKI_call(ctx, C_FindObjects(session, &obj, 1, &count));
	CRYPTOKI_checkerr(CKR_F_PKCS11_NEXT_CERT, rv);

	if (count == 0)
		return 1;

	if (pkcs11_init_cert(ctx, token, session, obj, NULL))
		return -1;

	return 0;
}

static int pkcs11_init_cert(PKCS11_CTX *ctx, PKCS11_TOKEN *token,
		CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, PKCS11_CERT ** ret)
{
	PKCS11_TOKEN_private *tpriv;
	PKCS11_CERT_private *cpriv;
	PKCS11_CERT *cert, *tmp;
	unsigned char *data;
	CK_CERTIFICATE_TYPE cert_type;
	size_t size;
	int i;

	(void)ctx;
	(void)session;

	/* Ignore unknown certificate types */
	size = sizeof(CK_CERTIFICATE_TYPE);
	if (pkcs11_getattr_var(token, obj, CKA_CERTIFICATE_TYPE, (CK_BYTE *)&cert_type, &size))
		return -1;
	if (cert_type != CKC_X_509)
		return 0;

	/* Prevent re-adding existing PKCS#11 object handles */
	/* TODO: Rewrite the O(n) algorithm as O(log n),
	 * or it may be too slow with a large number of certificates */
	for (i=0; i < PRIVTOKEN(token)->ncerts; ++i)
		if (PRIVCERT(PRIVTOKEN(token)->certs + i)->object == obj)
			return 0;

	/* Allocate memory */
	cpriv = OPENSSL_malloc(sizeof(PKCS11_CERT_private));
	if (!cpriv)
		return -1;
	memset(cpriv, 0, sizeof(PKCS11_CERT_private));
	tpriv = PRIVTOKEN(token);
	tmp = OPENSSL_realloc(tpriv->certs,
		(tpriv->ncerts + 1) * sizeof(PKCS11_CERT));
	if (!tmp)
		return -1;
	tpriv->certs = tmp;
	cert = tpriv->certs + tpriv->ncerts++;
	memset(cert, 0, sizeof(PKCS11_CERT));

	/* Fill public properties */
	pkcs11_getattr_alloc(token, obj, CKA_LABEL, (CK_BYTE **)&cert->label, NULL);
	size = 0;
	if (!pkcs11_getattr_alloc(token, obj, CKA_VALUE, &data, &size)) {
		const unsigned char *p = data;

		cert->x509 = d2i_X509(NULL, &p, (long)size);
		OPENSSL_free(data);
	}
	cert->id_len = 0;
	pkcs11_getattr_alloc(token, obj, CKA_ID, &cert->id, &cert->id_len);

	/* Fill private properties */
	cert->_private = cpriv;
	cpriv->object = obj;
	cpriv->parent = token;
	cpriv->id_len = sizeof cpriv->id;
	if (pkcs11_getattr_var(token, obj, CKA_ID, cpriv->id, &cpriv->id_len))
		cpriv->id_len = 0;

	if (ret)
		*ret = cert;
	return 0;
}

/*
 * Destroy all certs
 */
void pkcs11_destroy_certs(PKCS11_TOKEN *token)
{
	PKCS11_TOKEN_private *tpriv = PRIVTOKEN(token);

	while (tpriv->ncerts > 0) {
		PKCS11_CERT *cert = &tpriv->certs[--(tpriv->ncerts)];

		if (cert->x509)
			X509_free(cert->x509);
		OPENSSL_free(cert->label);
		if (cert->id)
			OPENSSL_free(cert->id);
		if (cert->_private)
			OPENSSL_free(cert->_private);
	}
	if (tpriv->certs)
		OPENSSL_free(tpriv->certs);
	tpriv->certs = NULL;
	tpriv->ncerts = 0;
}

/*
 * Store certificate
 */
int pkcs11_store_certificate(PKCS11_TOKEN *token, X509 *x509, char *label,
		unsigned char *id, size_t id_len, PKCS11_CERT ** ret_cert)
{
	PKCS11_SLOT *slot = TOKEN2SLOT(token);
	PKCS11_CTX *ctx = SLOT2CTX(slot);
	PKCS11_SLOT_private *spriv = PRIVSLOT(slot);
	CK_OBJECT_HANDLE object;
	CK_ATTRIBUTE attrs[32];
	unsigned int n = 0;
	int rv;
	int signature_nid;
	const EVP_MD* evp_md;
	CK_MECHANISM_TYPE ckm_md;
	unsigned char md[EVP_MAX_MD_SIZE];
	unsigned int md_len;

	/* First, make sure we have a session */
	if (!PRIVSLOT(slot)->haveSession && PKCS11_open_session(slot, 1))
		return -1;

	/* Now build the template */
	pkcs11_addattr_int(attrs + n++, CKA_CLASS, CKO_CERTIFICATE);
	pkcs11_addattr_bool(attrs + n++, CKA_TOKEN, TRUE);
	pkcs11_addattr_int(attrs + n++, CKA_CERTIFICATE_TYPE, CKC_X_509);
	pkcs11_addattr_obj(attrs + n++, CKA_SUBJECT,
		(pkcs11_i2d_fn)i2d_X509_NAME, X509_get_subject_name(x509));
	pkcs11_addattr_obj(attrs + n++, CKA_ISSUER,
		(pkcs11_i2d_fn)i2d_X509_NAME, X509_get_issuer_name(x509));

	/* Get digest algorithm from x509 certificate */
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)
	signature_nid = X509_get_signature_nid(x509);
#else
	signature_nid = OBJ_obj2nid(x509->sig_alg->algorithm);
#endif
	evp_md = EVP_get_digestbynid(signature_nid);
	switch (EVP_MD_type(evp_md)) {
	default:
	case NID_sha1:
		ckm_md = CKM_SHA_1;
		break;
	case NID_sha224:
		ckm_md = CKM_SHA224;
		break;
	case NID_sha256:
		ckm_md = CKM_SHA256;
		break;
	case NID_sha512:
		ckm_md = CKM_SHA512;
		break;
	case NID_sha384:
		ckm_md = CKM_SHA384;
		break;
	}

	/* Set hash algorithm; default is SHA-1 */
	pkcs11_addattr_int(attrs + n++, CKA_NAME_HASH_ALGORITHM, ckm_md);
	if(X509_pubkey_digest(x509,evp_md,md,&md_len))
		pkcs11_addattr(attrs + n++, CKA_HASH_OF_SUBJECT_PUBLIC_KEY,md,md_len);

	pkcs11_addattr_obj(attrs + n++, CKA_VALUE, (pkcs11_i2d_fn)i2d_X509, x509);
	if (label)
		pkcs11_addattr_s(attrs + n++, CKA_LABEL, label);
	if (id && id_len)
		pkcs11_addattr(attrs + n++, CKA_ID, id, id_len);

	/* Now call the pkcs11 module to create the object */
	rv = CRYPTOKI_call(ctx, C_CreateObject(spriv->session, attrs, n, &object));

	/* Zap all memory allocated when building the template */
	pkcs11_zap_attrs(attrs, n);

	CRYPTOKI_checkerr(CKR_F_PKCS11_STORE_CERTIFICATE, rv);

	/* Gobble the key object */
	return pkcs11_init_cert(ctx, token, spriv->session, object, ret_cert);
}

/* vim: set noexpandtab: */
