The pattern currently is to call get_cache(), generate the encoding if that failed and then store it with cache(). The latter adopts the passed encoding and frees any stored encoding. However, the latter means that if two threads concurrently fail to get a cached encoding and then both generate and store one, one of the threads might use an encoding that was freed by the other thread. Since encodings are not expected to change, we can avoid this issue by not replacing an existing cache entry and instead return that (while freeing the passed value instead of the cached one). Closes strongswan/strongswan#1231
951 lines
22 KiB
C
951 lines
22 KiB
C
/*
|
|
* Copyright (C) 2011-2015 Tobias Brunner
|
|
* Copyright (C) 2010 Martin Willi
|
|
*
|
|
* Copyright (C) secunet Security Networks AG
|
|
*
|
|
* 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 2 of the License, or (at your
|
|
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "pkcs11_public_key.h"
|
|
|
|
#include "pkcs11.h"
|
|
#include "pkcs11_private_key.h"
|
|
#include "pkcs11_manager.h"
|
|
|
|
#include <asn1/oid.h>
|
|
#include <asn1/asn1.h>
|
|
#include <asn1/asn1_parser.h>
|
|
#include <utils/debug.h>
|
|
|
|
typedef struct private_pkcs11_public_key_t private_pkcs11_public_key_t;
|
|
|
|
/**
|
|
* Private data of an pkcs11_public_key_t object.
|
|
*/
|
|
struct private_pkcs11_public_key_t {
|
|
|
|
/**
|
|
* Public pkcs11_public_key_t interface.
|
|
*/
|
|
pkcs11_public_key_t public;
|
|
|
|
/**
|
|
* Type of the key
|
|
*/
|
|
key_type_t type;
|
|
|
|
/**
|
|
* Key size in bits
|
|
*/
|
|
size_t k;
|
|
|
|
/**
|
|
* PKCS#11 library this key uses
|
|
*/
|
|
pkcs11_library_t *lib;
|
|
|
|
/**
|
|
* Slot the token is in
|
|
*/
|
|
CK_SLOT_ID slot;
|
|
|
|
/**
|
|
* Session we use
|
|
*/
|
|
CK_SESSION_HANDLE session;
|
|
|
|
/**
|
|
* Object handle to the key
|
|
*/
|
|
CK_OBJECT_HANDLE object;
|
|
|
|
/**
|
|
* References to this key
|
|
*/
|
|
refcount_t ref;
|
|
};
|
|
|
|
/**
|
|
* Helper function that returns the base point order length in bits of the
|
|
* given named curve.
|
|
*
|
|
* Currently only a subset of defined curves is supported (namely the 5 curves
|
|
* over Fp recommended by NIST). IKEv2 only supports 3 out of these.
|
|
*
|
|
* 0 is returned if the given curve is not supported.
|
|
*/
|
|
static size_t basepoint_order_len(int oid)
|
|
{
|
|
switch (oid)
|
|
{
|
|
case OID_PRIME192V1:
|
|
return 192;
|
|
case OID_SECT224R1:
|
|
return 224;
|
|
case OID_PRIME256V1:
|
|
return 256;
|
|
case OID_SECT384R1:
|
|
return 384;
|
|
case OID_SECT521R1:
|
|
return 521;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses the given ecParameters (ASN.1) and returns the key length.
|
|
*/
|
|
static bool keylen_from_ecparams(chunk_t ecparams, size_t *keylen)
|
|
{
|
|
if (!asn1_parse_simple_object(&ecparams, ASN1_OID, 0, "named curve"))
|
|
{
|
|
return FALSE;
|
|
}
|
|
*keylen = basepoint_order_len(asn1_known_oid(ecparams));
|
|
return *keylen > 0;
|
|
}
|
|
|
|
/**
|
|
* ASN.1 definition of a subjectPublicKeyInfo structure when used with ECDSA
|
|
* we currently only support named curves.
|
|
*/
|
|
static const asn1Object_t pkinfoObjects[] = {
|
|
{ 0, "subjectPublicKeyInfo", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */
|
|
{ 1, "algorithmIdentifier", ASN1_SEQUENCE, ASN1_NONE }, /* 1 */
|
|
{ 2, "algorithm", ASN1_OID, ASN1_BODY }, /* 2 */
|
|
{ 2, "namedCurve", ASN1_OID, ASN1_RAW }, /* 3 */
|
|
{ 1, "subjectPublicKey", ASN1_BIT_STRING, ASN1_BODY }, /* 4 */
|
|
{ 0, "exit", ASN1_EOC, ASN1_EXIT }
|
|
};
|
|
#define PKINFO_SUBJECT_PUBLIC_KEY_ALGORITHM 2
|
|
#define PKINFO_SUBJECT_PUBLIC_KEY_NAMEDCURVE 3
|
|
#define PKINFO_SUBJECT_PUBLIC_KEY 4
|
|
|
|
/**
|
|
* Extract the DER encoded Parameters and ECPoint from the given DER encoded
|
|
* subjectPublicKeyInfo.
|
|
* Memory for ecpoint is allocated.
|
|
*/
|
|
static bool parse_ecdsa_public_key(chunk_t blob, chunk_t *ecparams,
|
|
chunk_t *ecpoint, size_t *keylen)
|
|
{
|
|
asn1_parser_t *parser;
|
|
chunk_t object;
|
|
int objectID;
|
|
bool success = FALSE;
|
|
|
|
parser = asn1_parser_create(pkinfoObjects, blob);
|
|
|
|
while (parser->iterate(parser, &objectID, &object))
|
|
{
|
|
switch (objectID)
|
|
{
|
|
case PKINFO_SUBJECT_PUBLIC_KEY_ALGORITHM:
|
|
{
|
|
if (asn1_known_oid(object) != OID_EC_PUBLICKEY)
|
|
{
|
|
goto end;
|
|
}
|
|
break;
|
|
}
|
|
case PKINFO_SUBJECT_PUBLIC_KEY_NAMEDCURVE:
|
|
{
|
|
*ecparams = object;
|
|
if (!keylen_from_ecparams(object, keylen))
|
|
{
|
|
goto end;
|
|
}
|
|
break;
|
|
}
|
|
case PKINFO_SUBJECT_PUBLIC_KEY:
|
|
{
|
|
if (object.len > 0 && *object.ptr == 0x00)
|
|
{ /* skip initial bit string octet defining 0 unused bits */
|
|
object = chunk_skip(object, 1);
|
|
}
|
|
/* the correct way to encode an EC_POINT in PKCS#11 is as
|
|
* ASN.1 octet string */
|
|
*ecpoint = asn1_wrap(ASN1_OCTET_STRING, "c", object);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
success = parser->success(parser);
|
|
end:
|
|
parser->destroy(parser);
|
|
return success;
|
|
}
|
|
|
|
|
|
METHOD(public_key_t, get_type, key_type_t,
|
|
private_pkcs11_public_key_t *this)
|
|
{
|
|
return this->type;
|
|
}
|
|
|
|
METHOD(public_key_t, get_keysize, int,
|
|
private_pkcs11_public_key_t *this)
|
|
{
|
|
return this->k;
|
|
}
|
|
|
|
METHOD(public_key_t, verify, bool,
|
|
private_pkcs11_public_key_t *this, signature_scheme_t scheme, void *params,
|
|
chunk_t data, chunk_t sig)
|
|
{
|
|
CK_MECHANISM_PTR mechanism;
|
|
CK_SESSION_HANDLE session;
|
|
CK_RV rv;
|
|
hash_algorithm_t hash_alg;
|
|
chunk_t hash = chunk_empty, parse, r, s;
|
|
size_t len;
|
|
|
|
mechanism = pkcs11_signature_scheme_to_mech(this->lib, this->slot, scheme,
|
|
this->type, this->k, &hash_alg);
|
|
if (!mechanism)
|
|
{
|
|
DBG1(DBG_LIB, "signature scheme %N not supported",
|
|
signature_scheme_names, scheme);
|
|
return FALSE;
|
|
}
|
|
switch (scheme)
|
|
{
|
|
case SIGN_ECDSA_WITH_SHA1_DER:
|
|
case SIGN_ECDSA_WITH_SHA256_DER:
|
|
case SIGN_ECDSA_WITH_SHA384_DER:
|
|
case SIGN_ECDSA_WITH_SHA512_DER:
|
|
/* PKCS#11 expects the ECDSA signatures as simple concatenation of
|
|
* r and s, so unwrap the ASN.1 encoded sequence */
|
|
parse = sig;
|
|
if (asn1_unwrap(&parse, &parse) != ASN1_SEQUENCE ||
|
|
asn1_unwrap(&parse, &r) != ASN1_INTEGER ||
|
|
asn1_unwrap(&parse, &s) != ASN1_INTEGER)
|
|
{
|
|
return FALSE;
|
|
}
|
|
r = chunk_skip_zero(r);
|
|
s = chunk_skip_zero(s);
|
|
len = (get_keysize(this) + 7) / 8;
|
|
if (r.len > len || s.len > len)
|
|
{
|
|
return FALSE;
|
|
}
|
|
/* concatenate r and s (forced to the defined length) */
|
|
sig = chunk_alloca(2*len);
|
|
memset(sig.ptr, 0, sig.len);
|
|
memcpy(sig.ptr + (len - r.len), r.ptr, r.len);
|
|
memcpy(sig.ptr + len + (len - s.len), s.ptr, s.len);
|
|
break;
|
|
default:
|
|
sig = chunk_skip_zero(sig);
|
|
break;
|
|
}
|
|
rv = this->lib->f->C_OpenSession(this->slot, CKF_SERIAL_SESSION, NULL, NULL,
|
|
&session);
|
|
if (rv != CKR_OK)
|
|
{
|
|
DBG1(DBG_CFG, "opening PKCS#11 session failed: %N", ck_rv_names, rv);
|
|
return FALSE;
|
|
}
|
|
rv = this->lib->f->C_VerifyInit(session, mechanism, this->object);
|
|
if (rv != CKR_OK)
|
|
{
|
|
this->lib->f->C_CloseSession(session);
|
|
DBG1(DBG_LIB, "C_VerifyInit() failed: %N", ck_rv_names, rv);
|
|
return FALSE;
|
|
}
|
|
if (hash_alg != HASH_UNKNOWN)
|
|
{
|
|
hasher_t *hasher;
|
|
|
|
hasher = lib->crypto->create_hasher(lib->crypto, hash_alg);
|
|
if (!hasher || !hasher->allocate_hash(hasher, data, &hash))
|
|
{
|
|
DESTROY_IF(hasher);
|
|
this->lib->f->C_CloseSession(session);
|
|
return FALSE;
|
|
}
|
|
hasher->destroy(hasher);
|
|
switch (scheme)
|
|
{
|
|
case SIGN_RSA_EMSA_PKCS1_SHA1:
|
|
case SIGN_RSA_EMSA_PKCS1_SHA2_256:
|
|
case SIGN_RSA_EMSA_PKCS1_SHA2_384:
|
|
case SIGN_RSA_EMSA_PKCS1_SHA2_512:
|
|
/* encode PKCS#1 digestInfo if the token does not support it */
|
|
hash = asn1_wrap(ASN1_SEQUENCE, "mm",
|
|
asn1_algorithmIdentifier(
|
|
hasher_algorithm_to_oid(hash_alg)),
|
|
asn1_wrap(ASN1_OCTET_STRING, "m", hash));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
data = hash;
|
|
}
|
|
rv = this->lib->f->C_Verify(session, data.ptr, data.len, sig.ptr, sig.len);
|
|
this->lib->f->C_CloseSession(session);
|
|
chunk_free(&hash);
|
|
if (rv != CKR_OK)
|
|
{
|
|
DBG1(DBG_LIB, "C_Verify() failed: %N", ck_rv_names, rv);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
METHOD(public_key_t, encrypt, bool,
|
|
private_pkcs11_public_key_t *this, encryption_scheme_t scheme,
|
|
void *params, chunk_t plain, chunk_t *crypt)
|
|
{
|
|
CK_MECHANISM_PTR mechanism;
|
|
CK_SESSION_HANDLE session;
|
|
CK_BYTE_PTR buf;
|
|
CK_ULONG len;
|
|
CK_RV rv;
|
|
|
|
mechanism = pkcs11_encryption_scheme_to_mech(scheme);
|
|
if (!mechanism)
|
|
{
|
|
DBG1(DBG_LIB, "encryption scheme %N not supported",
|
|
encryption_scheme_names, scheme);
|
|
return FALSE;
|
|
}
|
|
rv = this->lib->f->C_OpenSession(this->slot, CKF_SERIAL_SESSION, NULL, NULL,
|
|
&session);
|
|
if (rv != CKR_OK)
|
|
{
|
|
DBG1(DBG_CFG, "opening PKCS#11 session failed: %N", ck_rv_names, rv);
|
|
return FALSE;
|
|
}
|
|
rv = this->lib->f->C_EncryptInit(session, mechanism, this->object);
|
|
if (rv != CKR_OK)
|
|
{
|
|
this->lib->f->C_CloseSession(session);
|
|
DBG1(DBG_LIB, "C_EncryptInit() failed: %N", ck_rv_names, rv);
|
|
return FALSE;
|
|
}
|
|
len = (get_keysize(this) + 7) / 8;
|
|
buf = malloc(len);
|
|
rv = this->lib->f->C_Encrypt(session, plain.ptr, plain.len, buf, &len);
|
|
this->lib->f->C_CloseSession(session);
|
|
if (rv != CKR_OK)
|
|
{
|
|
DBG1(DBG_LIB, "C_Encrypt() failed: %N", ck_rv_names, rv);
|
|
free(buf);
|
|
return FALSE;
|
|
}
|
|
*crypt = chunk_create(buf, len);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Encode ECDSA key using a given encoding type
|
|
*/
|
|
static bool encode_ecdsa(private_pkcs11_public_key_t *this,
|
|
cred_encoding_type_t type, chunk_t *encoding)
|
|
{
|
|
enumerator_t *enumerator;
|
|
bool success = FALSE;
|
|
CK_ATTRIBUTE attr[] = {
|
|
{CKA_EC_PARAMS, NULL, 0},
|
|
{CKA_EC_POINT, NULL, 0},
|
|
};
|
|
|
|
if (type != PUBKEY_SPKI_ASN1_DER && type != PUBKEY_PEM)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
enumerator = this->lib->create_object_attr_enumerator(this->lib,
|
|
this->session, this->object, attr, countof(attr));
|
|
if (enumerator && enumerator->enumerate(enumerator, NULL) &&
|
|
attr[0].ulValueLen > 0 && attr[1].ulValueLen > 0)
|
|
{
|
|
chunk_t ecparams, ecpoint;
|
|
ecparams = chunk_create(attr[0].pValue, attr[0].ulValueLen);
|
|
ecpoint = chunk_create(attr[1].pValue, attr[1].ulValueLen);
|
|
/* encode as subjectPublicKeyInfo */
|
|
*encoding = asn1_wrap(ASN1_SEQUENCE, "mm",
|
|
asn1_wrap(ASN1_SEQUENCE, "mc",
|
|
asn1_build_known_oid(OID_EC_PUBLICKEY), ecparams),
|
|
asn1_bitstring("c", ecpoint));
|
|
success = TRUE;
|
|
if (type == PUBKEY_PEM)
|
|
{
|
|
chunk_t asn1 = *encoding;
|
|
success = lib->encoding->encode(lib->encoding, PUBKEY_PEM,
|
|
NULL, encoding, CRED_PART_ECDSA_PUB_ASN1_DER,
|
|
asn1, CRED_PART_END);
|
|
chunk_clear(&asn1);
|
|
}
|
|
}
|
|
DESTROY_IF(enumerator);
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* Compute fingerprint of an ECDSA key
|
|
*/
|
|
static bool fingerprint_ecdsa(private_pkcs11_public_key_t *this,
|
|
cred_encoding_type_t type, chunk_t *fp)
|
|
{
|
|
hasher_t *hasher;
|
|
chunk_t asn1;
|
|
|
|
switch (type)
|
|
{
|
|
case KEYID_PUBKEY_SHA1:
|
|
if (!this->lib->get_ck_attribute(this->lib, this->session,
|
|
this->object, CKA_EC_POINT, &asn1))
|
|
{
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case KEYID_PUBKEY_INFO_SHA1:
|
|
if (!encode_ecdsa(this, PUBKEY_SPKI_ASN1_DER, &asn1))
|
|
{
|
|
return FALSE;
|
|
}
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1);
|
|
if (!hasher || !hasher->allocate_hash(hasher, asn1, fp))
|
|
{
|
|
DESTROY_IF(hasher);
|
|
chunk_clear(&asn1);
|
|
return FALSE;
|
|
}
|
|
hasher->destroy(hasher);
|
|
chunk_clear(&asn1);
|
|
lib->encoding->cache(lib->encoding, type, this, fp);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Encode RSA key using a given encoding type
|
|
*/
|
|
static bool encode_rsa(private_pkcs11_public_key_t *this,
|
|
cred_encoding_type_t type, void *cache, chunk_t *encoding)
|
|
{
|
|
enumerator_t *enumerator;
|
|
bool success = FALSE;
|
|
CK_ATTRIBUTE attr[] = {
|
|
{CKA_MODULUS, NULL, 0},
|
|
{CKA_PUBLIC_EXPONENT, NULL, 0},
|
|
};
|
|
|
|
enumerator = this->lib->create_object_attr_enumerator(this->lib,
|
|
this->session, this->object, attr, countof(attr));
|
|
if (enumerator && enumerator->enumerate(enumerator, NULL) &&
|
|
attr[0].ulValueLen > 0 && attr[1].ulValueLen > 0)
|
|
{
|
|
chunk_t n, e;
|
|
/* some tokens/libraries add unnecessary 0x00 prefixes */
|
|
n = chunk_skip_zero(chunk_create(attr[0].pValue, attr[0].ulValueLen));
|
|
if (n.ptr[0] & 0x80)
|
|
{ /* add leading 0x00, encoders might expect it in two's complement */
|
|
n = chunk_cata("cc", chunk_from_chars(0x00), n);
|
|
}
|
|
e = chunk_skip_zero(chunk_create(attr[1].pValue, attr[1].ulValueLen));
|
|
if (e.ptr[0] & 0x80)
|
|
{
|
|
e = chunk_cata("cc", chunk_from_chars(0x00), e);
|
|
}
|
|
success = lib->encoding->encode(lib->encoding, type, cache, encoding,
|
|
CRED_PART_RSA_MODULUS, n, CRED_PART_RSA_PUB_EXP, e, CRED_PART_END);
|
|
}
|
|
DESTROY_IF(enumerator);
|
|
return success;
|
|
}
|
|
|
|
METHOD(public_key_t, get_encoding, bool,
|
|
private_pkcs11_public_key_t *this, cred_encoding_type_t type,
|
|
chunk_t *encoding)
|
|
{
|
|
switch (this->type)
|
|
{
|
|
case KEY_RSA:
|
|
return encode_rsa(this, type, NULL, encoding);
|
|
case KEY_ECDSA:
|
|
return encode_ecdsa(this, type, encoding);
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
METHOD(public_key_t, get_fingerprint, bool,
|
|
private_pkcs11_public_key_t *this, cred_encoding_type_t type, chunk_t *fp)
|
|
{
|
|
if (lib->encoding->get_cache(lib->encoding, type, this, fp))
|
|
{
|
|
return TRUE;
|
|
}
|
|
switch (this->type)
|
|
{
|
|
case KEY_RSA:
|
|
return encode_rsa(this, type, this, fp);
|
|
case KEY_ECDSA:
|
|
return fingerprint_ecdsa(this, type, fp);
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
METHOD(public_key_t, get_ref, public_key_t*,
|
|
private_pkcs11_public_key_t *this)
|
|
{
|
|
ref_get(&this->ref);
|
|
return &this->public.key;
|
|
}
|
|
|
|
METHOD(public_key_t, destroy, void,
|
|
private_pkcs11_public_key_t *this)
|
|
{
|
|
if (ref_put(&this->ref))
|
|
{
|
|
lib->encoding->clear_cache(lib->encoding, this);
|
|
this->lib->f->C_CloseSession(this->session);
|
|
free(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create an empty PKCS#11 public key
|
|
*/
|
|
static private_pkcs11_public_key_t *create(key_type_t type, size_t k,
|
|
pkcs11_library_t *p11, CK_SLOT_ID slot,
|
|
CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object)
|
|
{
|
|
private_pkcs11_public_key_t *this;
|
|
|
|
INIT(this,
|
|
.public = {
|
|
.key = {
|
|
.get_type = _get_type,
|
|
.verify = _verify,
|
|
.encrypt = _encrypt,
|
|
.equals = public_key_equals,
|
|
.get_keysize = _get_keysize,
|
|
.get_fingerprint = _get_fingerprint,
|
|
.has_fingerprint = public_key_has_fingerprint,
|
|
.get_encoding = _get_encoding,
|
|
.get_ref = _get_ref,
|
|
.destroy = _destroy,
|
|
},
|
|
},
|
|
.type = type,
|
|
.k = k,
|
|
.lib = p11,
|
|
.slot = slot,
|
|
.session = session,
|
|
.object = object,
|
|
.ref = 1,
|
|
);
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Find a key object, including PKCS11 library and slot
|
|
*/
|
|
static private_pkcs11_public_key_t* find_key(key_type_t type, size_t keylen,
|
|
CK_ATTRIBUTE_PTR tmpl, int count)
|
|
{
|
|
private_pkcs11_public_key_t *this = NULL;
|
|
pkcs11_manager_t *manager;
|
|
enumerator_t *enumerator, *keys;
|
|
pkcs11_library_t *p11;
|
|
CK_SLOT_ID slot;
|
|
|
|
manager = lib->get(lib, "pkcs11-manager");
|
|
if (!manager)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
enumerator = manager->create_token_enumerator(manager);
|
|
while (enumerator->enumerate(enumerator, &p11, &slot))
|
|
{
|
|
CK_OBJECT_HANDLE object;
|
|
CK_SESSION_HANDLE session;
|
|
CK_RV rv;
|
|
|
|
rv = p11->f->C_OpenSession(slot, CKF_SERIAL_SESSION, NULL, NULL,
|
|
&session);
|
|
if (rv != CKR_OK)
|
|
{
|
|
DBG1(DBG_CFG, "opening PKCS#11 session failed: %N", ck_rv_names, rv);
|
|
continue;
|
|
}
|
|
keys = p11->create_object_enumerator(p11, session, tmpl, count,
|
|
NULL, 0);
|
|
if (keys->enumerate(keys, &object))
|
|
{
|
|
this = create(type, keylen, p11, slot, session, object);
|
|
keys->destroy(keys);
|
|
break;
|
|
}
|
|
keys->destroy(keys);
|
|
p11->f->C_CloseSession(session);
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Find an RSA key object
|
|
*/
|
|
static private_pkcs11_public_key_t* find_rsa_key(chunk_t n, chunk_t e,
|
|
size_t keylen)
|
|
{
|
|
CK_OBJECT_CLASS class = CKO_PUBLIC_KEY;
|
|
CK_KEY_TYPE type = CKK_RSA;
|
|
CK_ATTRIBUTE tmpl[] = {
|
|
{CKA_CLASS, &class, sizeof(class)},
|
|
{CKA_KEY_TYPE, &type, sizeof(type)},
|
|
{CKA_MODULUS, n.ptr, n.len},
|
|
{CKA_PUBLIC_EXPONENT, e.ptr, e.len},
|
|
};
|
|
return find_key(KEY_RSA, keylen, tmpl, countof(tmpl));
|
|
}
|
|
|
|
/**
|
|
* Find an ECDSA key object
|
|
*/
|
|
static private_pkcs11_public_key_t* find_ecdsa_key(chunk_t ecparams,
|
|
chunk_t ecpoint,
|
|
size_t keylen)
|
|
{
|
|
CK_OBJECT_CLASS class = CKO_PUBLIC_KEY;
|
|
CK_KEY_TYPE type = CKK_ECDSA;
|
|
CK_ATTRIBUTE tmpl[] = {
|
|
{CKA_CLASS, &class, sizeof(class)},
|
|
{CKA_KEY_TYPE, &type, sizeof(type)},
|
|
{CKA_EC_PARAMS, ecparams.ptr, ecparams.len},
|
|
{CKA_EC_POINT, ecpoint.ptr, ecpoint.len},
|
|
};
|
|
return find_key(KEY_ECDSA, keylen, tmpl, countof(tmpl));
|
|
}
|
|
|
|
/**
|
|
* Create a key object in a suitable token session
|
|
*/
|
|
static private_pkcs11_public_key_t* create_key(key_type_t type, size_t keylen,
|
|
CK_MECHANISM_TYPE_PTR mechanisms, int mcount,
|
|
CK_ATTRIBUTE_PTR tmpl, int count)
|
|
{
|
|
private_pkcs11_public_key_t *this = NULL;
|
|
pkcs11_manager_t *manager;
|
|
enumerator_t *enumerator, *mechs;
|
|
pkcs11_library_t *p11;
|
|
CK_SLOT_ID slot;
|
|
|
|
manager = lib->get(lib, "pkcs11-manager");
|
|
if (!manager)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
enumerator = manager->create_token_enumerator(manager);
|
|
while (enumerator->enumerate(enumerator, &p11, &slot))
|
|
{
|
|
CK_MECHANISM_TYPE mech;
|
|
CK_MECHANISM_INFO info;
|
|
CK_OBJECT_HANDLE object;
|
|
CK_SESSION_HANDLE session;
|
|
CK_RV rv;
|
|
|
|
mechs = p11->create_mechanism_enumerator(p11, slot);
|
|
while (mechs->enumerate(mechs, &mech, &info))
|
|
{
|
|
bool found = FALSE;
|
|
int i;
|
|
if (!(info.flags & CKF_VERIFY))
|
|
{
|
|
continue;
|
|
}
|
|
for (i = 0; i < mcount; i++)
|
|
{
|
|
if (mechanisms[i] == mech)
|
|
{
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
continue;
|
|
}
|
|
rv = p11->f->C_OpenSession(slot, CKF_SERIAL_SESSION, NULL, NULL,
|
|
&session);
|
|
if (rv != CKR_OK)
|
|
{
|
|
DBG1(DBG_CFG, "opening PKCS#11 session failed: %N",
|
|
ck_rv_names, rv);
|
|
continue;
|
|
}
|
|
rv = p11->f->C_CreateObject(session, tmpl, count, &object);
|
|
if (rv == CKR_OK)
|
|
{
|
|
this = create(type, keylen, p11, slot, session, object);
|
|
DBG2(DBG_CFG, "created %N public key on token '%s':%d ",
|
|
key_type_names, type, p11->get_name(p11), slot);
|
|
}
|
|
else
|
|
{
|
|
DBG1(DBG_CFG, "creating %N public key on token '%s':%d "
|
|
"failed: %N", key_type_names, type, p11->get_name(p11),
|
|
slot, ck_rv_names, rv);
|
|
p11->f->C_CloseSession(session);
|
|
}
|
|
break;
|
|
}
|
|
mechs->destroy(mechs);
|
|
if (this)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Create an RSA key object in a suitable token session
|
|
*/
|
|
static private_pkcs11_public_key_t* create_rsa_key(chunk_t n, chunk_t e,
|
|
size_t keylen)
|
|
{
|
|
CK_OBJECT_CLASS class = CKO_PUBLIC_KEY;
|
|
CK_KEY_TYPE type = CKK_RSA;
|
|
CK_ATTRIBUTE tmpl[] = {
|
|
{CKA_CLASS, &class, sizeof(class)},
|
|
{CKA_KEY_TYPE, &type, sizeof(type)},
|
|
{CKA_MODULUS, n.ptr, n.len},
|
|
{CKA_PUBLIC_EXPONENT, e.ptr, e.len},
|
|
};
|
|
CK_MECHANISM_TYPE mechs[] = {
|
|
CKM_RSA_PKCS,
|
|
CKM_SHA1_RSA_PKCS,
|
|
CKM_SHA256_RSA_PKCS,
|
|
CKM_SHA384_RSA_PKCS,
|
|
CKM_SHA512_RSA_PKCS,
|
|
CKM_MD5_RSA_PKCS,
|
|
};
|
|
return create_key(KEY_RSA, keylen, mechs, countof(mechs), tmpl,
|
|
countof(tmpl));
|
|
}
|
|
|
|
/**
|
|
* Create an ECDSA key object in a suitable token session
|
|
*/
|
|
static private_pkcs11_public_key_t* create_ecdsa_key(chunk_t ecparams,
|
|
chunk_t ecpoint,
|
|
size_t keylen)
|
|
{
|
|
CK_OBJECT_CLASS class = CKO_PUBLIC_KEY;
|
|
CK_KEY_TYPE type = CKK_ECDSA;
|
|
CK_ATTRIBUTE tmpl[] = {
|
|
{CKA_CLASS, &class, sizeof(class)},
|
|
{CKA_KEY_TYPE, &type, sizeof(type)},
|
|
{CKA_EC_PARAMS, ecparams.ptr, ecparams.len},
|
|
{CKA_EC_POINT, ecpoint.ptr, ecpoint.len},
|
|
};
|
|
CK_MECHANISM_TYPE mechs[] = {
|
|
CKM_ECDSA,
|
|
CKM_ECDSA_SHA1,
|
|
};
|
|
return create_key(KEY_ECDSA, keylen, mechs,
|
|
countof(mechs), tmpl, countof(tmpl));
|
|
}
|
|
|
|
/**
|
|
* See header
|
|
*/
|
|
pkcs11_public_key_t *pkcs11_public_key_load(key_type_t type, va_list args)
|
|
{
|
|
private_pkcs11_public_key_t *this;
|
|
chunk_t n, e, blob;
|
|
size_t keylen = 0;
|
|
|
|
n = e = blob = chunk_empty;
|
|
while (TRUE)
|
|
{
|
|
switch (va_arg(args, builder_part_t))
|
|
{
|
|
case BUILD_BLOB_ASN1_DER:
|
|
blob = va_arg(args, chunk_t);
|
|
continue;
|
|
case BUILD_RSA_MODULUS:
|
|
n = va_arg(args, chunk_t);
|
|
continue;
|
|
case BUILD_RSA_PUB_EXP:
|
|
e = va_arg(args, chunk_t);
|
|
continue;
|
|
case BUILD_END:
|
|
break;
|
|
default:
|
|
return NULL;
|
|
}
|
|
break;
|
|
}
|
|
if (type == KEY_RSA && e.ptr && n.ptr)
|
|
{
|
|
if (n.len && n.ptr[0] == 0)
|
|
{ /* trim leading zero byte in modulus */
|
|
n = chunk_skip(n, 1);
|
|
}
|
|
keylen = n.len * 8;
|
|
this = find_rsa_key(n, e, keylen);
|
|
if (this)
|
|
{
|
|
return &this->public;
|
|
}
|
|
this = create_rsa_key(n, e, keylen);
|
|
if (this)
|
|
{
|
|
return &this->public;
|
|
}
|
|
}
|
|
else if (type == KEY_ECDSA && blob.ptr)
|
|
{
|
|
chunk_t ecparams, ecpoint;
|
|
ecparams = ecpoint = chunk_empty;
|
|
if (parse_ecdsa_public_key(blob, &ecparams, &ecpoint, &keylen))
|
|
{
|
|
this = find_ecdsa_key(ecparams, ecpoint, keylen);
|
|
if (!this)
|
|
{
|
|
this = create_ecdsa_key(ecparams, ecpoint, keylen);
|
|
}
|
|
chunk_free(&ecpoint);
|
|
if (this)
|
|
{
|
|
return &this->public;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static private_pkcs11_public_key_t *find_key_by_keyid(pkcs11_library_t *p11,
|
|
int slot, key_type_t key_type,
|
|
chunk_t keyid)
|
|
{
|
|
CK_OBJECT_CLASS class = CKO_PUBLIC_KEY;
|
|
CK_KEY_TYPE type;
|
|
CK_ATTRIBUTE tmpl[] = {
|
|
{CKA_CLASS, &class, sizeof(class)},
|
|
{CKA_ID, keyid.ptr, keyid.len},
|
|
{CKA_KEY_TYPE, &type, sizeof(type)},
|
|
};
|
|
CK_OBJECT_HANDLE object;
|
|
CK_ATTRIBUTE attr[] = {
|
|
{CKA_KEY_TYPE, &type, sizeof(type)},
|
|
};
|
|
CK_SESSION_HANDLE session;
|
|
CK_RV rv;
|
|
enumerator_t *enumerator;
|
|
int count = countof(tmpl);
|
|
bool found = FALSE;
|
|
size_t keylen;
|
|
|
|
switch (key_type)
|
|
{
|
|
case KEY_RSA:
|
|
type = CKK_RSA;
|
|
break;
|
|
case KEY_ECDSA:
|
|
type = CKK_ECDSA;
|
|
break;
|
|
default:
|
|
/* don't specify key type on KEY_ANY */
|
|
count--;
|
|
break;
|
|
}
|
|
|
|
rv = p11->f->C_OpenSession(slot, CKF_SERIAL_SESSION, NULL, NULL, &session);
|
|
if (rv != CKR_OK)
|
|
{
|
|
DBG1(DBG_CFG, "opening public key session on '%s':%d failed: %N",
|
|
p11->get_name(p11), slot, ck_rv_names, rv);
|
|
return NULL;
|
|
}
|
|
|
|
enumerator = p11->create_object_enumerator(p11, session, tmpl, count, attr,
|
|
countof(attr));
|
|
if (enumerator->enumerate(enumerator, &object) &&
|
|
attr[0].ulValueLen != CK_UNAVAILABLE_INFORMATION)
|
|
{
|
|
switch (type)
|
|
{
|
|
case CKK_ECDSA:
|
|
{
|
|
chunk_t ecparams;
|
|
if (p11->get_ck_attribute(p11, session, object, CKA_EC_PARAMS,
|
|
&ecparams) &&
|
|
keylen_from_ecparams(ecparams, &keylen))
|
|
{
|
|
chunk_free(&ecparams);
|
|
key_type = KEY_ECDSA;
|
|
found = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case CKK_RSA:
|
|
{
|
|
chunk_t n;
|
|
if (p11->get_ck_attribute(p11, session, object, CKA_MODULUS,
|
|
&n) && n.len > 0)
|
|
{
|
|
keylen = n.len * 8;
|
|
chunk_free(&n);
|
|
key_type = KEY_RSA;
|
|
found = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
DBG1(DBG_CFG, "PKCS#11 key type %d not supported", type);
|
|
break;
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
|
|
if (found)
|
|
{
|
|
return create(key_type, keylen, p11, slot, session, object);
|
|
}
|
|
p11->f->C_CloseSession(session);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* See header.
|
|
*/
|
|
public_key_t *pkcs11_public_key_connect(pkcs11_library_t *p11, int slot,
|
|
key_type_t type, chunk_t keyid)
|
|
{
|
|
private_pkcs11_public_key_t *this;
|
|
|
|
this = find_key_by_keyid(p11, slot, type, keyid);
|
|
if (!this)
|
|
{
|
|
return NULL;
|
|
}
|
|
return &this->public.key;
|
|
}
|