/* * Copyright (C) 2012 Martin Willi * Copyright (C) 2012 revosec 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 . * * 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 #include #include #include #include #include #include #include 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; /** * 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; /** * EncryptedDigest */ chunk_t encrypted_digest; /** * Digesting algorithm OID */ int digest_alg; /** * Public key encryption algorithm OID */ int enc_alg; } signerinfo_t; /** * Destroy a signerinfo_t entry */ void signerinfo_destroy(signerinfo_t *this) { DESTROY_IF(this->attributes); DESTROY_IF(this->serial); DESTROY_IF(this->issuer); free(this->encrypted_digest.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, "digestEncryptionAlgorithm", ASN1_EOC, ASN1_RAW }, /* 21 */ { 3, "encryptedDigest", 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_DIGEST_ENC_ALGORITHM 21 #define PKCS7_ENCRYPTED_DIGEST 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; signature_scheme_t scheme; 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; scheme = signature_scheme_from_oid(info->digest_alg); if (scheme == SIGN_UNKNOWN) { DBG1(DBG_LIB, "unsupported signature scheme"); continue; } if (!info->attributes) { DBG1(DBG_LIB, "no authenticatedAttributes object found"); continue; } if (info->enc_alg != OID_RSA_ENCRYPTION) { DBG1(DBG_LIB, "only RSA digest encryption supported"); continue; } enumerator = lib->credmgr->create_trusted_enumerator(lib->credmgr, KEY_RSA, 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, scheme, chunk, info->encrypted_digest)) { 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); 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, .enc_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_DIGEST_ENC_ALGORITHM: info->enc_alg = asn1_parse_algorithmIdentifier(object, level, NULL); break; case PKCS7_ENCRYPTED_DIGEST: info->encrypted_digest = 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, signerInfo, encoding = chunk_empty; chunk_t messageDigest, signingTime, attributes; signature_scheme_t scheme; hasher_t *hasher; time_t now; int digest_oid; digest_oid = hasher_algorithm_to_oid(alg); scheme = signature_scheme_from_oid(digest_oid); if (!this->content->get_data(this->content, &data)) { return FALSE; } 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 support", hash_algorithm_names, alg); free(data.ptr); return FALSE; } 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, scheme, attributes, &encryptedDigest)) { free(data.ptr); return FALSE; } authenticatedAttributes = chunk_clone(attributes); *authenticatedAttributes.ptr = ASN1_CONTEXT_C_0; free(data.ptr); if (encryptedDigest.ptr) { encryptedDigest = asn1_wrap(ASN1_OCTET_STRING, "m", encryptedDigest); } signerInfo = asn1_wrap(ASN1_SEQUENCE, "cmmmmm", ASN1_INTEGER_1, build_issuerAndSerialNumber(cert), asn1_algorithmIdentifier(digest_oid), authenticatedAttributes, asn1_algorithmIdentifier(OID_RSA_ENCRYPTION), encryptedDigest); if (!cert->get_encoding(cert, CERT_ASN1_DER, &encoding)) { free(signerInfo.ptr); return FALSE; } if (!this->content->get_encoding(this->content, &data)) { free(encoding.ptr); free(signerInfo.ptr); return FALSE; } 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", asn1_algorithmIdentifier(digest_oid)), data, asn1_wrap(ASN1_CONTEXT_C_0, "m", encoding), asn1_wrap(ASN1_SET, "m", signerInfo)))); pkcs9->destroy(pkcs9); /* TODO: create signerInfos entry */ return TRUE; } /** * 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_SHA1; 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_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->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; }