Files
strongswan-ext/src/libstrongswan/plugins/pkcs7/pkcs7_signed_data.c
T
Tobias Brunner 5e76bc1634 pkcs7: Add support for CMS-style signatures (RSA-PSS, ECDSA)
For the legacy schemes with rsaEncryption nothing changes, but if an
actual signature scheme is encoded we use that to find the key and
verify the signature.

The descriptions for the PKCS#7 structure are adapted for CMS.
2023-03-30 10:46:46 +02:00

718 lines
18 KiB
C

/*
* Copyright (C) 2023 Tobias Brunner
* Copyright (C) 2012 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 "pkcs7_signed_data.h"
#include "pkcs7_attributes.h"
#include <time.h>
#include <utils/debug.h>
#include <asn1/oid.h>
#include <asn1/asn1.h>
#include <asn1/asn1_parser.h>
#include <credentials/sets/mem_cred.h>
#include <credentials/certificates/x509.h>
#include <credentials/keys/private_key.h>
typedef struct private_pkcs7_signed_data_t private_pkcs7_signed_data_t;
/**
* Private data of a PKCS#7 signed-data container.
*/
struct private_pkcs7_signed_data_t {
/**
* Implements pkcs7_t.
*/
pkcs7_t public;
/**
* Signed content data
*/
container_t *content;
/**
* Signature scheme
*/
signature_params_t *scheme;
/**
* Encoded PKCS#7 signed-data
*/
chunk_t encoding;
/**
* list of signerInfos, signerinfo_t
*/
linked_list_t *signerinfos;
/**
* Contained certificates
*/
mem_cred_t *creds;
};
/**
* A single signerInfo
*/
typedef struct {
/**
* Signed attributes of signerInfo
*/
pkcs7_attributes_t *attributes;
/**
* Serial of signing certificate
*/
identification_t *serial;
/**
* Issuer of signing certificate
*/
identification_t *issuer;
/**
* Signature
*/
chunk_t signature;
/**
* Digesting algorithm OID
*/
int digest_alg;
/**
* Signature algorithm
*/
signature_params_t sig_alg;
} signerinfo_t;
/**
* Destroy a signerinfo_t entry
*/
static void signerinfo_destroy(signerinfo_t *this)
{
DESTROY_IF(this->attributes);
DESTROY_IF(this->serial);
DESTROY_IF(this->issuer);
signature_params_clear(&this->sig_alg);
free(this->signature.ptr);
free(this);
}
/**
* ASN.1 definition of the PKCS#7 signedData type
*/
static const asn1Object_t signedDataObjects[] = {
{ 0, "signedData", ASN1_SEQUENCE, ASN1_NONE }, /* 0 */
{ 1, "version", ASN1_INTEGER, ASN1_BODY }, /* 1 */
{ 1, "digestAlgorithms", ASN1_SET, ASN1_LOOP }, /* 2 */
{ 2, "algorithm", ASN1_EOC, ASN1_RAW }, /* 3 */
{ 1, "end loop", ASN1_EOC, ASN1_END }, /* 4 */
{ 1, "contentInfo", ASN1_EOC, ASN1_RAW }, /* 5 */
{ 1, "certificates", ASN1_CONTEXT_C_0, ASN1_OPT |
ASN1_LOOP }, /* 6 */
{ 2, "certificate", ASN1_SEQUENCE, ASN1_OBJ }, /* 7 */
{ 1, "end opt or loop", ASN1_EOC, ASN1_END }, /* 8 */
{ 1, "crls", ASN1_CONTEXT_C_1, ASN1_OPT |
ASN1_LOOP }, /* 9 */
{ 2, "crl", ASN1_SEQUENCE, ASN1_OBJ }, /* 10 */
{ 1, "end opt or loop", ASN1_EOC, ASN1_END }, /* 11 */
{ 1, "signerInfos", ASN1_SET, ASN1_LOOP }, /* 12 */
{ 2, "signerInfo", ASN1_SEQUENCE, ASN1_NONE }, /* 13 */
{ 3, "version", ASN1_INTEGER, ASN1_BODY }, /* 14 */
{ 3, "issuerAndSerialNumber", ASN1_SEQUENCE, ASN1_BODY }, /* 15 */
{ 4, "issuer", ASN1_SEQUENCE, ASN1_OBJ }, /* 16 */
{ 4, "serial", ASN1_INTEGER, ASN1_BODY }, /* 17 */
{ 3, "digestAlgorithm", ASN1_EOC, ASN1_RAW }, /* 18 */
{ 3, "authenticatedAttributes", ASN1_CONTEXT_C_0, ASN1_OPT |
ASN1_OBJ }, /* 19 */
{ 3, "end opt", ASN1_EOC, ASN1_END }, /* 20 */
{ 3, "signatureAlgorithm", ASN1_EOC, ASN1_RAW }, /* 21 */
{ 3, "signature", ASN1_OCTET_STRING, ASN1_BODY }, /* 22 */
{ 3, "unauthenticatedAttributes", ASN1_CONTEXT_C_1, ASN1_OPT }, /* 23 */
{ 3, "end opt", ASN1_EOC, ASN1_END }, /* 24 */
{ 1, "end loop", ASN1_EOC, ASN1_END }, /* 25 */
{ 0, "exit", ASN1_EOC, ASN1_EXIT }
};
#define PKCS7_VERSION 1
#define PKCS7_DIGEST_ALG 3
#define PKCS7_CONTENT_INFO 5
#define PKCS7_CERT 7
#define PKCS7_SIGNER_INFO 13
#define PKCS7_SIGNER_INFO_VERSION 14
#define PKCS7_ISSUER 16
#define PKCS7_SERIAL_NUMBER 17
#define PKCS7_DIGEST_ALGORITHM 18
#define PKCS7_AUTH_ATTRIBUTES 19
#define PKCS7_SIGNATURE_ALGORITHM 21
#define PKCS7_SIGNATURE 22
METHOD(container_t, get_type, container_type_t,
private_pkcs7_signed_data_t *this)
{
return CONTAINER_PKCS7_SIGNED_DATA;
}
/**
* Signature enumerator implementation
*/
typedef struct {
/** implements enumerator */
enumerator_t public;
/** inner signerinfos enumerator */
enumerator_t *inner;
/** currently enumerated auth_cfg */
auth_cfg_t *auth;
/** currently enumerating signerinfo */
signerinfo_t *info;
/** reference to container */
private_pkcs7_signed_data_t *this;
} signature_enumerator_t;
METHOD(enumerator_t, enumerate, bool,
signature_enumerator_t *this, va_list args)
{
signerinfo_t *info;
hash_algorithm_t algorithm;
enumerator_t *enumerator;
certificate_t *cert;
public_key_t *key;
auth_cfg_t *auth, **out;
chunk_t chunk, hash, content;
hasher_t *hasher;
bool valid;
VA_ARGS_VGET(args, out);
while (this->inner->enumerate(this->inner, &info))
{
/* clean up previous round */
DESTROY_IF(this->auth);
this->auth = NULL;
if (!info->attributes)
{
DBG1(DBG_LIB, "no authenticatedAttributes object found");
continue;
}
if (info->sig_alg.scheme == SIGN_UNKNOWN)
{
DBG1(DBG_LIB, "unsupported signature scheme");
continue;
}
enumerator = lib->credmgr->create_trusted_enumerator(lib->credmgr,
key_type_from_signature_scheme(info->sig_alg.scheme),
info->serial, FALSE);
while (enumerator->enumerate(enumerator, &cert, &auth))
{
if (info->issuer->equals(info->issuer, cert->get_issuer(cert)))
{
key = cert->get_public_key(cert);
if (key)
{
chunk = info->attributes->get_encoding(info->attributes);
if (key->verify(key, info->sig_alg.scheme,
info->sig_alg.params, chunk, info->signature))
{
this->auth = auth->clone(auth);
key->destroy(key);
break;
}
key->destroy(key);
}
}
}
enumerator->destroy(enumerator);
if (!this->auth)
{
DBG1(DBG_LIB, "unable to verify pkcs7 attributes signature");
continue;
}
chunk = info->attributes->get_attribute(info->attributes,
OID_PKCS9_MESSAGE_DIGEST);
if (!chunk.len)
{
DBG1(DBG_LIB, "messageDigest attribute not found");
continue;
}
if (!this->this->content->get_data(this->this->content, &content))
{
continue;
}
algorithm = hasher_algorithm_from_oid(info->digest_alg);
hasher = lib->crypto->create_hasher(lib->crypto, algorithm);
if (!hasher || !hasher->allocate_hash(hasher, content, &hash))
{
free(content.ptr);
DESTROY_IF(hasher);
DBG1(DBG_LIB, "hash algorithm %N not supported",
hash_algorithm_names, algorithm);
continue;
}
free(content.ptr);
hasher->destroy(hasher);
DBG3(DBG_LIB, "hash: %B", &hash);
valid = chunk_equals_const(chunk, hash);
free(hash.ptr);
if (!valid)
{
DBG1(DBG_LIB, "invalid messageDigest");
continue;
}
*out = this->auth;
this->info = info;
return TRUE;
}
this->info = NULL;
return FALSE;
}
METHOD(enumerator_t, enumerator_destroy, void,
signature_enumerator_t *this)
{
lib->credmgr->remove_local_set(lib->credmgr, &this->this->creds->set);
this->inner->destroy(this->inner);
DESTROY_IF(this->auth);
free(this);
}
METHOD(container_t, create_signature_enumerator, enumerator_t*,
private_pkcs7_signed_data_t *this)
{
signature_enumerator_t *enumerator;
INIT(enumerator,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate,
.destroy = _enumerator_destroy,
},
.inner = this->signerinfos->create_enumerator(this->signerinfos),
.this = this,
);
lib->credmgr->add_local_set(lib->credmgr, &this->creds->set, FALSE);
return &enumerator->public;
}
METHOD(pkcs7_t, get_attribute, bool,
private_pkcs7_signed_data_t *this, int oid, enumerator_t *enumerator, chunk_t *value)
{
signature_enumerator_t *e;
chunk_t chunk;
e = (signature_enumerator_t*)enumerator;
if (e->info)
{
chunk = e->info->attributes->get_attribute(e->info->attributes, oid);
if (chunk.len)
{
*value = chunk_clone(chunk);
return TRUE;
}
}
return FALSE;
}
METHOD(pkcs7_t, create_cert_enumerator, enumerator_t*,
private_pkcs7_signed_data_t *this)
{
return this->creds->set.create_cert_enumerator(&this->creds->set,
CERT_ANY, KEY_ANY, NULL, FALSE);
}
METHOD(container_t, get_data, bool,
private_pkcs7_signed_data_t *this, chunk_t *data)
{
if (this->content)
{
return this->content->get_data(this->content, data);
}
return FALSE;
}
METHOD(container_t, get_encoding, bool,
private_pkcs7_signed_data_t *this, chunk_t *data)
{
*data = chunk_clone(this->encoding);
return TRUE;
}
METHOD(container_t, destroy, void,
private_pkcs7_signed_data_t *this)
{
this->creds->destroy(this->creds);
this->signerinfos->destroy_function(this->signerinfos,
(void*)signerinfo_destroy);
signature_params_destroy(this->scheme);
DESTROY_IF(this->content);
free(this->encoding.ptr);
free(this);
}
/**
* Create an empty PKCS#7 signed-data container.
*/
static private_pkcs7_signed_data_t* create_empty()
{
private_pkcs7_signed_data_t *this;
INIT(this,
.public = {
.container = {
.get_type = _get_type,
.create_signature_enumerator = _create_signature_enumerator,
.get_data = _get_data,
.get_encoding = _get_encoding,
.destroy = _destroy,
},
.get_attribute = _get_attribute,
.create_cert_enumerator = _create_cert_enumerator,
},
.creds = mem_cred_create(),
.signerinfos = linked_list_create(),
);
return this;
}
/**
* Parse PKCS#7 signed data
*/
static bool parse(private_pkcs7_signed_data_t *this, chunk_t content)
{
asn1_parser_t *parser;
chunk_t object;
int objectID, version;
signerinfo_t *info = NULL;
bool success = FALSE;
parser = asn1_parser_create(signedDataObjects, content);
parser->set_top_level(parser, 0);
while (parser->iterate(parser, &objectID, &object))
{
u_int level = parser->get_level(parser);
switch (objectID)
{
case PKCS7_VERSION:
version = object.len ? (int)*object.ptr : 0;
DBG2(DBG_LIB, " v%d", version);
break;
case PKCS7_CONTENT_INFO:
this->content = lib->creds->create(lib->creds,
CRED_CONTAINER, CONTAINER_PKCS7,
BUILD_BLOB_ASN1_DER, object, BUILD_END);
break;
case PKCS7_CERT:
{
certificate_t *cert;
DBG2(DBG_LIB, " parsing pkcs7-wrapped certificate");
cert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509,
BUILD_BLOB_ASN1_DER, object,
BUILD_END);
if (cert)
{
this->creds->add_cert(this->creds, FALSE, cert);
}
break;
}
case PKCS7_SIGNER_INFO:
INIT(info,
.digest_alg = OID_UNKNOWN,
);
this->signerinfos->insert_last(this->signerinfos, info);
break;
case PKCS7_SIGNER_INFO_VERSION:
version = object.len ? (int)*object.ptr : 0;
DBG2(DBG_LIB, " v%d", version);
break;
case PKCS7_ISSUER:
info->issuer = identification_create_from_encoding(
ID_DER_ASN1_DN, object);
break;
case PKCS7_SERIAL_NUMBER:
info->serial = identification_create_from_encoding(
ID_KEY_ID, object);
break;
case PKCS7_AUTH_ATTRIBUTES:
*object.ptr = ASN1_SET;
info->attributes = pkcs7_attributes_create_from_chunk(
object, level+1);
*object.ptr = ASN1_CONTEXT_C_0;
break;
case PKCS7_DIGEST_ALGORITHM:
info->digest_alg = asn1_parse_algorithmIdentifier(object,
level, NULL);
break;
case PKCS7_SIGNATURE_ALGORITHM:
if (!signature_params_parse(object, level, &info->sig_alg))
{
if (asn1_parse_algorithmIdentifier(object, -1,
NULL) == OID_RSA_ENCRYPTION &&
info->digest_alg != OID_UNKNOWN)
{
/* derive the signature scheme from the digest algorithm
* for the classic PKCS#7 RSA mechansim */
info->sig_alg.scheme = signature_scheme_from_oid(
info->digest_alg);
}
}
break;
case PKCS7_SIGNATURE:
info->signature = chunk_clone(object);
break;
}
}
success = parser->success(parser);
parser->destroy(parser);
return success;
}
/**
* See header.
*/
pkcs7_t *pkcs7_signed_data_load(chunk_t encoding, chunk_t content)
{
private_pkcs7_signed_data_t *this = create_empty();
this->encoding = chunk_clone(encoding);
if (!parse(this, content))
{
destroy(this);
return NULL;
}
return &this->public;
}
/**
* build a DER-encoded issuerAndSerialNumber object
*/
static chunk_t build_issuerAndSerialNumber(certificate_t *cert)
{
identification_t *issuer = cert->get_issuer(cert);
chunk_t serial = chunk_empty;
if (cert->get_type(cert) == CERT_X509)
{
x509_t *x509 = (x509_t*)cert;
serial = x509->get_serial(x509);
}
return asn1_wrap(ASN1_SEQUENCE, "cm",
issuer->get_encoding(issuer),
asn1_integer("c", serial));
}
/**
* Generate a new PKCS#7 signed-data container
*/
static bool generate(private_pkcs7_signed_data_t *this, private_key_t *key,
certificate_t *cert, hash_algorithm_t alg,
pkcs7_attributes_t *pkcs9)
{
chunk_t authenticatedAttributes = chunk_empty;
chunk_t encryptedDigest = chunk_empty;
chunk_t data = chunk_empty, encoding = chunk_empty;
chunk_t digest_alg = chunk_empty, sig_scheme = chunk_empty;
chunk_t signerInfo = chunk_empty, messageDigest, signingTime, attributes;
hasher_t *hasher;
time_t now;
/* select signature scheme, if not already specified */
if (!this->scheme)
{
INIT(this->scheme,
.scheme = signature_scheme_from_oid(
hasher_signature_algorithm_to_oid(alg,
key->get_type(key))),
);
}
if (this->scheme->scheme == SIGN_UNKNOWN)
{
return FALSE;
}
if (!signature_params_build(this->scheme, &sig_scheme))
{
return FALSE;
}
if (!this->content->get_data(this->content, &data))
{
goto err;
}
hasher = lib->crypto->create_hasher(lib->crypto, alg);
if (!hasher || !hasher->allocate_hash(hasher, data, &messageDigest))
{
DESTROY_IF(hasher);
DBG1(DBG_LIB, " hash algorithm %N not supported",
hash_algorithm_names, alg);
goto err;
}
chunk_free(&data);
hasher->destroy(hasher);
pkcs9->add_attribute(pkcs9,
OID_PKCS9_MESSAGE_DIGEST,
asn1_wrap(ASN1_OCTET_STRING, "m", messageDigest));
/* take the current time as signingTime */
now = time(NULL);
signingTime = asn1_from_time(&now, ASN1_UTCTIME);
pkcs9->add_attribute(pkcs9, OID_PKCS9_SIGNING_TIME, signingTime);
pkcs9->add_attribute(pkcs9, OID_PKCS9_CONTENT_TYPE,
asn1_build_known_oid(OID_PKCS7_DATA));
attributes = pkcs9->get_encoding(pkcs9);
if (!key->sign(key, this->scheme->scheme, this->scheme->params, attributes,
&encryptedDigest))
{
goto err;
}
authenticatedAttributes = chunk_clone(attributes);
*authenticatedAttributes.ptr = ASN1_CONTEXT_C_0;
if (encryptedDigest.ptr)
{
encryptedDigest = asn1_wrap(ASN1_OCTET_STRING, "m", encryptedDigest);
}
digest_alg = asn1_algorithmIdentifier(hasher_algorithm_to_oid(alg));
signerInfo = asn1_wrap(ASN1_SEQUENCE, "cmcmmm",
ASN1_INTEGER_1,
build_issuerAndSerialNumber(cert),
digest_alg,
authenticatedAttributes,
sig_scheme,
encryptedDigest);
sig_scheme = chunk_empty;
if (!cert->get_encoding(cert, CERT_ASN1_DER, &encoding))
{
goto err;
}
if (!this->content->get_encoding(this->content, &data))
{
goto err;
}
this->encoding = asn1_wrap(ASN1_SEQUENCE, "mm",
asn1_build_known_oid(OID_PKCS7_SIGNED_DATA),
asn1_wrap(ASN1_CONTEXT_C_0, "m",
asn1_wrap(ASN1_SEQUENCE, "cmmmm",
ASN1_INTEGER_1,
asn1_wrap(ASN1_SET, "m", digest_alg),
data,
asn1_wrap(ASN1_CONTEXT_C_0, "m", encoding),
asn1_wrap(ASN1_SET, "m", signerInfo))));
pkcs9->destroy(pkcs9);
/* TODO: create signerInfos entry */
return TRUE;
err:
chunk_free(&data);
chunk_free(&digest_alg);
chunk_free(&sig_scheme);
chunk_free(&signerInfo);
chunk_free(&encoding);
return FALSE;
}
/**
* See header.
*/
pkcs7_t *pkcs7_signed_data_gen(container_type_t type, va_list args)
{
private_pkcs7_signed_data_t *this;
chunk_t blob = chunk_empty;
hash_algorithm_t alg = HASH_SHA256;
signature_params_t *scheme = NULL;
private_key_t *key = NULL;
certificate_t *cert = NULL;
pkcs7_attributes_t *pkcs9;
chunk_t value;
int oid;
pkcs9 = pkcs7_attributes_create();
while (TRUE)
{
switch (va_arg(args, builder_part_t))
{
case BUILD_SIGNING_KEY:
key = va_arg(args, private_key_t*);
continue;
case BUILD_SIGNING_CERT:
cert = va_arg(args, certificate_t*);
continue;
case BUILD_DIGEST_ALG:
alg = va_arg(args, int);
continue;
case BUILD_BLOB:
blob = va_arg(args, chunk_t);
continue;
case BUILD_SIGNATURE_SCHEME:
scheme = va_arg(args, signature_params_t*);
continue;
case BUILD_PKCS7_ATTRIBUTE:
oid = va_arg(args, int);
value = va_arg(args, chunk_t);
pkcs9->add_attribute(pkcs9, oid, chunk_clone(value));
continue;
case BUILD_END:
break;
default:
pkcs9->destroy(pkcs9);
return NULL;
}
break;
}
if (blob.len && key && cert)
{
this = create_empty();
this->scheme = signature_params_clone(scheme);
this->creds->add_cert(this->creds, FALSE, cert->get_ref(cert));
this->content = lib->creds->create(lib->creds,
CRED_CONTAINER, CONTAINER_PKCS7_DATA,
BUILD_BLOB, blob, BUILD_END);
if (this->content && generate(this, key, cert, alg, pkcs9))
{
return &this->public;
}
pkcs9->destroy(pkcs9);
destroy(this);
}
else
{
pkcs9->destroy(pkcs9);
}
return NULL;
}