diff options
-rw-r--r-- | devel/symbols.last | 3 | ||||
-rw-r--r-- | doc/Makefile.am | 6 | ||||
-rw-r--r-- | doc/manpages/Makefile.am | 3 | ||||
-rw-r--r-- | lib/includes/gnutls/pkcs7.h | 8 | ||||
-rw-r--r-- | lib/libgnutls.map | 3 | ||||
-rw-r--r-- | lib/pkix.asn | 6 | ||||
-rw-r--r-- | lib/x509/Makefile.am | 1 | ||||
-rw-r--r-- | lib/x509/pkcs7-digest.c | 340 | ||||
-rw-r--r-- | lib/x509/pkcs7-output.c | 31 | ||||
-rw-r--r-- | lib/x509/pkcs7.c | 6 | ||||
-rw-r--r-- | lib/x509/pkcs7_int.h | 1 | ||||
-rw-r--r-- | lib/x509/x509_int.h | 1 | ||||
-rw-r--r-- | src/cmstool-common.c | 2 | ||||
-rw-r--r-- | src/cmstool-common.h | 1 | ||||
-rw-r--r-- | src/cmstool-options.json | 10 | ||||
-rw-r--r-- | src/cmstool.c | 119 | ||||
-rw-r--r-- | tests/cert-tests/Makefile.am | 5 | ||||
-rwxr-xr-x | tests/cert-tests/cms-broken-dig.sh | 74 | ||||
-rwxr-xr-x | tests/cert-tests/cmstool.sh | 34 | ||||
-rw-r--r-- | tests/cert-tests/data/pkcs7-sha1.der | bin | 0 -> 96 bytes | |||
-rw-r--r-- | tests/cert-tests/data/pkcs7-sha1.der.out | 7 | ||||
-rw-r--r-- | tests/cert-tests/data/pkcs7-streebog256.der | bin | 0 -> 127 bytes | |||
-rw-r--r-- | tests/cert-tests/data/pkcs7-streebog256.der.out | 8 |
23 files changed, 665 insertions, 4 deletions
diff --git a/devel/symbols.last b/devel/symbols.last index bd0de671fd..353d1657dd 100644 --- a/devel/symbols.last +++ b/devel/symbols.last @@ -576,6 +576,7 @@ gnutls_pkcs7_attrs_deinit@GNUTLS_3_4 gnutls_pkcs7_deinit@GNUTLS_3_4 gnutls_pkcs7_delete_crl@GNUTLS_3_4 gnutls_pkcs7_delete_crt@GNUTLS_3_4 +gnutls_pkcs7_digest@GNUTLS_3_7_0 gnutls_pkcs7_export2@GNUTLS_3_4 gnutls_pkcs7_export@GNUTLS_3_4 gnutls_pkcs7_get_attr@GNUTLS_3_4 @@ -585,6 +586,7 @@ gnutls_pkcs7_get_crl_raw@GNUTLS_3_4 gnutls_pkcs7_get_crt_count@GNUTLS_3_4 gnutls_pkcs7_get_crt_raw2@GNUTLS_3_4 gnutls_pkcs7_get_crt_raw@GNUTLS_3_4 +gnutls_pkcs7_get_digest_algo@GNUTLS_3_7_0 gnutls_pkcs7_get_embedded_data@GNUTLS_3_4 gnutls_pkcs7_get_embedded_data_oid@GNUTLS_3_4 gnutls_pkcs7_get_signature_count@GNUTLS_3_4 @@ -600,6 +602,7 @@ gnutls_pkcs7_set_crt_raw@GNUTLS_3_4 gnutls_pkcs7_sign@GNUTLS_3_4 gnutls_pkcs7_signature_info_deinit@GNUTLS_3_4 gnutls_pkcs7_verify@GNUTLS_3_4 +gnutls_pkcs7_verify_digest@GNUTLS_3_7_0 gnutls_pkcs7_verify_direct@GNUTLS_3_4 gnutls_pkcs8_info@GNUTLS_3_4 gnutls_pkcs_schema_get_name@GNUTLS_3_4 diff --git a/doc/Makefile.am b/doc/Makefile.am index 3a4151036c..ad729af60d 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1583,6 +1583,8 @@ FUNCS += functions/gnutls_pkcs7_delete_crl FUNCS += functions/gnutls_pkcs7_delete_crl.short FUNCS += functions/gnutls_pkcs7_delete_crt FUNCS += functions/gnutls_pkcs7_delete_crt.short +FUNCS += functions/gnutls_pkcs7_digest +FUNCS += functions/gnutls_pkcs7_digest.short FUNCS += functions/gnutls_pkcs7_export FUNCS += functions/gnutls_pkcs7_export.short FUNCS += functions/gnutls_pkcs7_export2 @@ -1601,6 +1603,8 @@ FUNCS += functions/gnutls_pkcs7_get_crt_raw FUNCS += functions/gnutls_pkcs7_get_crt_raw.short FUNCS += functions/gnutls_pkcs7_get_crt_raw2 FUNCS += functions/gnutls_pkcs7_get_crt_raw2.short +FUNCS += functions/gnutls_pkcs7_get_digest_algo +FUNCS += functions/gnutls_pkcs7_get_digest_algo.short FUNCS += functions/gnutls_pkcs7_get_embedded_data FUNCS += functions/gnutls_pkcs7_get_embedded_data.short FUNCS += functions/gnutls_pkcs7_get_embedded_data_oid @@ -1631,6 +1635,8 @@ FUNCS += functions/gnutls_pkcs7_signature_info_deinit FUNCS += functions/gnutls_pkcs7_signature_info_deinit.short FUNCS += functions/gnutls_pkcs7_verify FUNCS += functions/gnutls_pkcs7_verify.short +FUNCS += functions/gnutls_pkcs7_verify_digest +FUNCS += functions/gnutls_pkcs7_verify_digest.short FUNCS += functions/gnutls_pkcs7_verify_direct FUNCS += functions/gnutls_pkcs7_verify_direct.short FUNCS += functions/gnutls_pkcs8_info diff --git a/doc/manpages/Makefile.am b/doc/manpages/Makefile.am index cddf789ec6..8340cbac1b 100644 --- a/doc/manpages/Makefile.am +++ b/doc/manpages/Makefile.am @@ -632,6 +632,7 @@ APIMANS += gnutls_pkcs7_attrs_deinit.3 APIMANS += gnutls_pkcs7_deinit.3 APIMANS += gnutls_pkcs7_delete_crl.3 APIMANS += gnutls_pkcs7_delete_crt.3 +APIMANS += gnutls_pkcs7_digest.3 APIMANS += gnutls_pkcs7_export.3 APIMANS += gnutls_pkcs7_export2.3 APIMANS += gnutls_pkcs7_get_attr.3 @@ -641,6 +642,7 @@ APIMANS += gnutls_pkcs7_get_crl_raw2.3 APIMANS += gnutls_pkcs7_get_crt_count.3 APIMANS += gnutls_pkcs7_get_crt_raw.3 APIMANS += gnutls_pkcs7_get_crt_raw2.3 +APIMANS += gnutls_pkcs7_get_digest_algo.3 APIMANS += gnutls_pkcs7_get_embedded_data.3 APIMANS += gnutls_pkcs7_get_embedded_data_oid.3 APIMANS += gnutls_pkcs7_get_signature_count.3 @@ -656,6 +658,7 @@ APIMANS += gnutls_pkcs7_set_crt_raw.3 APIMANS += gnutls_pkcs7_sign.3 APIMANS += gnutls_pkcs7_signature_info_deinit.3 APIMANS += gnutls_pkcs7_verify.3 +APIMANS += gnutls_pkcs7_verify_digest.3 APIMANS += gnutls_pkcs7_verify_direct.3 APIMANS += gnutls_pkcs8_info.3 APIMANS += gnutls_pkcs_schema_get_name.3 diff --git a/lib/includes/gnutls/pkcs7.h b/lib/includes/gnutls/pkcs7.h index 528427b484..e40dac23dd 100644 --- a/lib/includes/gnutls/pkcs7.h +++ b/lib/includes/gnutls/pkcs7.h @@ -140,6 +140,14 @@ int gnutls_pkcs7_get_crl_raw2(gnutls_pkcs7_t pkcs7, unsigned indx, gnutls_datum_t *crl); +int gnutls_pkcs7_digest(gnutls_pkcs7_t pkcs7, + const gnutls_datum_t *data, + gnutls_digest_algorithm_t dig, unsigned flags); + +int gnutls_pkcs7_verify_digest(gnutls_pkcs7_t pkcs7, + const gnutls_datum_t *data, unsigned flags); +int gnutls_pkcs7_get_digest_algo(gnutls_pkcs7_t pkcs7); + int gnutls_pkcs7_print(gnutls_pkcs7_t pkcs7, gnutls_certificate_print_formats_t format, gnutls_datum_t * out); diff --git a/lib/libgnutls.map b/lib/libgnutls.map index 9e869d038f..08aa2b7d30 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -1338,6 +1338,9 @@ GNUTLS_3_7_0 gnutls_handshake_set_read_function; gnutls_handshake_set_secret_function; gnutls_handshake_write; + gnutls_pkcs7_digest; + gnutls_pkcs7_get_digest_algo; + gnutls_pkcs7_verify_digest; gnutls_x509_trust_list_set_getissuer_function; gnutls_x509_trust_list_get_ptr; gnutls_x509_trust_list_set_ptr; diff --git a/lib/pkix.asn b/lib/pkix.asn index 48eaf39650..4f10c8e170 100644 --- a/lib/pkix.asn +++ b/lib/pkix.asn @@ -308,6 +308,12 @@ SignerIdentifier ::= CHOICE { pkcs-7-SignerInfos ::= SET OF pkcs-7-SignerInfo +pkcs-7-DigestedData ::= SEQUENCE { + version INTEGER, + digestAlgorithm AlgorithmIdentifier, + encapContentInfo pkcs-7-EncapsulatedContentInfo, + digest OCTET STRING +} -- BEGIN of RFC2986 diff --git a/lib/x509/Makefile.am b/lib/x509/Makefile.am index 4ac1cdea8c..1dcbd93f7d 100644 --- a/lib/x509/Makefile.am +++ b/lib/x509/Makefile.am @@ -55,6 +55,7 @@ libgnutls_x509_la_SOURCES = \ pkcs7.c \ pkcs7-attrs.c \ pkcs7-crypt.c pkcs7_int.h \ + pkcs7-digest.c \ pkcs7-sign.c \ privkey.c \ privkey_pkcs8.c \ diff --git a/lib/x509/pkcs7-digest.c b/lib/x509/pkcs7-digest.c new file mode 100644 index 0000000000..c68b2c628e --- /dev/null +++ b/lib/x509/pkcs7-digest.c @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2003-2015 Free Software Foundation, Inc. + * Copyright (C) 2015 Red Hat, Inc. + * Copyright (C) 2020 Dmitry Baryshkov + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * The GnuTLS 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 program. If not, see <https://www.gnu.org/licenses/> + * + */ + +/* Functions that relate on PKCS7 certificate lists parsing. + */ + +#include "gnutls_int.h" +#include <libtasn1.h> + +#include <common.h> +#include <pkcs7_int.h> +#include <gnutls/pkcs7.h> + +/* Decodes the PKCS #7 digested data, and returns an asn1_node, + * which holds them + */ +int _gnutls_pkcs7_decode_digested_data(gnutls_pkcs7_t pkcs7) +{ + asn1_node c2; + int len, result; + gnutls_datum_t tmp = {NULL, 0}; + + if ((result = asn1_create_element + (_gnutls_get_pkix(), "PKIX1.pkcs-7-DigestedData", + &c2)) != ASN1_SUCCESS) { + gnutls_assert(); + return _gnutls_asn2err(result); + } + + /* the Digested-data has been created, so + * decode them. + */ + result = _gnutls_x509_read_value(pkcs7->pkcs7, "content", &tmp); + if (result < 0) { + gnutls_assert(); + goto cleanup; + } + + result = asn1_der_decoding(&c2, tmp.data, tmp.size, NULL); + if (result != ASN1_SUCCESS) { + gnutls_assert(); + result = _gnutls_asn2err(result); + goto cleanup; + } + + /* read the encapsulated content */ + len = MAX_OID_SIZE - 1; + result = + asn1_read_value(c2, "encapContentInfo.eContentType", pkcs7->encap_data_oid, &len); + if (result != ASN1_SUCCESS) { + gnutls_assert(); + result = _gnutls_asn2err(result); + goto cleanup; + } + + if (strcmp(pkcs7->encap_data_oid, DATA_OID) != 0) { + _gnutls_debug_log + ("Unknown PKCS#7 Encapsulated Content OID '%s'; treating as raw data\n", + pkcs7->encap_data_oid); + + } + + /* Try reading as octet string according to rfc5652. If that fails, attempt + * a raw read according to rfc2315 */ + result = _gnutls_x509_read_string(c2, "encapContentInfo.eContent", &pkcs7->der_encap_data, ASN1_ETYPE_OCTET_STRING, 1); + if (result < 0) { + result = _gnutls_x509_read_value(c2, "encapContentInfo.eContent", &pkcs7->der_encap_data); + if (result < 0) { + pkcs7->der_encap_data.data = NULL; + pkcs7->der_encap_data.size = 0; + } else { + int tag_len, len_len; + unsigned char cls; + unsigned long tag; + + /* we skip the embedded element's tag and length - uncharted territorry - used by MICROSOFT_CERT_TRUST_LIST */ + result = asn1_get_tag_der(pkcs7->der_encap_data.data, pkcs7->der_encap_data.size, &cls, &tag_len, &tag); + if (result != ASN1_SUCCESS) { + gnutls_assert(); + result = _gnutls_asn2err(result); + goto cleanup; + } + + result = asn1_get_length_ber(pkcs7->der_encap_data.data+tag_len, pkcs7->der_encap_data.size-tag_len, &len_len); + if (result < 0) { + gnutls_assert(); + result = GNUTLS_E_ASN1_DER_ERROR; + goto cleanup; + } + + tag_len += len_len; + memmove(pkcs7->der_encap_data.data, &pkcs7->der_encap_data.data[tag_len], pkcs7->der_encap_data.size-tag_len); + pkcs7->der_encap_data.size-=tag_len; + } + } + + pkcs7->content_data = c2; + gnutls_free(tmp.data); + + return 0; + + cleanup: + gnutls_free(tmp.data); + if (c2) + asn1_delete_structure(&c2); + return result; +} + +/** + * gnutls_pkcs7_get_digest_algo: + * @pkcs7: should contain a #gnutls_pkcs7_t type + * + * This function will return digest algorithm used + * in the DigestedData of the PKCS #7 structure. + * + * Returns: On success, @gnutls_digest_algorithm_t value is returned, otherwise + * a negative error value. + * + * Since: 3.7.0 + **/ +int gnutls_pkcs7_get_digest_algo(gnutls_pkcs7_t pkcs7) +{ + int len, ret; + char oid[MAX_OID_SIZE]; + gnutls_digest_algorithm_t dig; + + if (pkcs7 == NULL || pkcs7->type != GNUTLS_PKCS7_DIGESTED) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + len = sizeof(oid) - 1; + ret = asn1_read_value(pkcs7->content_data, "digestAlgorithm.algorithm", oid, &len); + if (ret != ASN1_SUCCESS) + return gnutls_assert_val(GNUTLS_E_UNKNOWN_ALGORITHM); + + dig = gnutls_oid_to_digest(oid); + if (dig == GNUTLS_DIG_UNKNOWN) + return gnutls_assert_val(GNUTLS_E_UNKNOWN_ALGORITHM); + + return dig; +} + +/** + * gnutls_pkcs7_verify_digest: + * @pkcs7: should contain a #gnutls_pkcs7_t type + * @data: The data to be verified or %NULL + * @flags: Zero or an OR list of #gnutls_certificate_verify_flags + * + * This function will verify the provided data against the digest + * present in the DigestedData of the PKCS #7 structure. If the data + * provided are NULL then the data in the encapsulatedContent field + * will be used instead. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a + * negative error value. A verification error results to a + * %GNUTLS_E_HASH_FAILED and the lack of encapsulated data + * to verify to a %GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE. + * + * Since: 3.7.0 + **/ +int gnutls_pkcs7_verify_digest(gnutls_pkcs7_t pkcs7, + const gnutls_datum_t *data, unsigned flags) +{ + int len, ret; + gnutls_datum_t tmpdata = { NULL, 0 }; + char oid[MAX_OID_SIZE]; + uint8_t hash_output[MAX_HASH_SIZE]; + gnutls_digest_algorithm_t dig; + + if (pkcs7 == NULL || pkcs7->type != GNUTLS_PKCS7_DIGESTED) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + len = sizeof(oid) - 1; + ret = asn1_read_value(pkcs7->content_data, "digestAlgorithm.algorithm", oid, &len); + if (ret != ASN1_SUCCESS) + return gnutls_assert_val(GNUTLS_E_UNKNOWN_ALGORITHM); + + dig = gnutls_oid_to_digest(oid); + if (dig == GNUTLS_DIG_UNKNOWN) + return gnutls_assert_val(GNUTLS_E_UNKNOWN_ALGORITHM); + + if (_gnutls_digest_is_insecure(dig) && + !(flags & GNUTLS_VERIFY_ALLOW_BROKEN)) + return gnutls_assert_val(GNUTLS_E_HASH_FAILED); + + if (data == NULL || data->data == NULL) + ret = gnutls_hash_fast(dig, pkcs7->der_encap_data.data, pkcs7->der_encap_data.size, hash_output); + else + ret = gnutls_hash_fast(dig, data->data, data->size, hash_output); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls_x509_read_value(pkcs7->content_data, "digest", &tmpdata); + if (ret < 0) + return gnutls_assert_val(ret); + + if (tmpdata.size != gnutls_hash_get_len(dig) || + memcmp(tmpdata.data, hash_output, tmpdata.size)) { + ret = gnutls_assert_val(GNUTLS_E_HASH_FAILED); + } + _gnutls_free_datum(&tmpdata); + + return ret; +} + +/** + * gnutls_pkcs7_digest: + * @pkcs7: should contain a #gnutls_pkcs7_t type + * @data: The data to be signed or %NULL if the data are already embedded + * @dig: The digest algorithm to use for digesting + * @flags: Should be zero or one of %GNUTLS_PKCS7 flags + * + * This function will add a digest in the provided PKCS #7 structure + * for the provided data. + * + * The available flags are: + * %GNUTLS_PKCS7_EMBED_DATA. It is explained in the #gnutls_pkcs7_sign_flags + * definition. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a + * negative error value. + * + * Since: 3.7.0 + **/ +int gnutls_pkcs7_digest(gnutls_pkcs7_t pkcs7, + const gnutls_datum_t *data, + gnutls_digest_algorithm_t dig, unsigned flags) +{ + int ret, result; + gnutls_datum_t sigdata = { NULL, 0 }; + gnutls_datum_t signature = { NULL, 0 }; + const mac_entry_st *me = hash_to_entry(dig); + uint8_t hash_output[MAX_HASH_SIZE]; + uint8_t ver; + + if (pkcs7 == NULL || me == NULL) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + if (pkcs7->type != GNUTLS_PKCS7_DIGESTED) + asn1_delete_structure(&pkcs7->content_data); + + if (pkcs7->content_data == NULL) { + result = + asn1_create_element(_gnutls_get_pkix(), + "PKIX1.pkcs-7-DigestedData", + &pkcs7->content_data); + if (result != ASN1_SUCCESS) { + gnutls_assert(); + ret = _gnutls_asn2err(result); + goto cleanup; + } + + if (!(flags & GNUTLS_PKCS7_EMBED_DATA)) { + (void)asn1_write_value(pkcs7->content_data, + "encapContentInfo.eContent", NULL, 0); + } + pkcs7->type = GNUTLS_PKCS7_DIGESTED; + } + + result = asn1_write_value(pkcs7->content_data, "version", &ver, 1); + if (result != ASN1_SUCCESS) { + ret = _gnutls_asn2err(result); + goto cleanup; + } + + result = + asn1_write_value(pkcs7->content_data, + "encapContentInfo.eContentType", DATA_OID, + 0); + if (result != ASN1_SUCCESS) { + ret = _gnutls_asn2err(result); + goto cleanup; + } + + ver = 0; /* Change to 2 if eContentType is not id-data */ + + if ((flags & GNUTLS_PKCS7_EMBED_DATA) && data->data) { /* embed data */ + ret = + _gnutls_x509_write_string(pkcs7->content_data, + "encapContentInfo.eContent", data, + ASN1_ETYPE_OCTET_STRING); + if (ret < 0) { + goto cleanup; + } + } + + /* append digest info algorithm */ + asn1_write_value(pkcs7->content_data, + "digestAlgorithm.algorithm", + _gnutls_x509_digest_to_oid(me), 1); + if (result != ASN1_SUCCESS) { + gnutls_assert(); + ret = _gnutls_asn2err(result); + goto cleanup; + } + + (void)asn1_write_value(pkcs7->content_data, + "digestAlgorithm.parameters", NULL, 0); + + if (data == NULL || data->data == NULL) + ret = gnutls_hash_fast(dig, pkcs7->der_encap_data.data, pkcs7->der_encap_data.size, hash_output); + else + ret = gnutls_hash_fast(dig, data->data, data->size, hash_output); + if (ret < 0) + return gnutls_assert_val(ret); + + result = asn1_write_value(pkcs7->content_data, "digest", hash_output, me->output_size); + if (result != ASN1_SUCCESS) { + gnutls_assert(); + ret = _gnutls_asn2err(result); + goto cleanup; + } + + ret = 0; + + cleanup: + gnutls_free(sigdata.data); + gnutls_free(signature.data); + return ret; +} diff --git a/lib/x509/pkcs7-output.c b/lib/x509/pkcs7-output.c index 1021777419..1b7d9a0abc 100644 --- a/lib/x509/pkcs7-output.c +++ b/lib/x509/pkcs7-output.c @@ -299,6 +299,34 @@ static void _gnutls_pkcs7_print_signed(gnutls_pkcs7_t pkcs7, } } +static void _gnutls_pkcs7_print_digested(gnutls_pkcs7_t pkcs7, + gnutls_certificate_print_formats_t format, + gnutls_buffer_st * str) +{ + int ret, len; + char oid[MAX_OID_SIZE]; + gnutls_digest_algorithm_t dig; + + adds(str, "Content Type: Digested\n"); + + len = sizeof(oid) - 1; + ret = asn1_read_value(pkcs7->content_data, "digestAlgorithm.algorithm", oid, &len); + if (ret != ASN1_SUCCESS) { + gnutls_assert(); + adds(str, "Digest algorithm: unsupported\n"); + } else { + dig = gnutls_oid_to_digest(oid); + if (dig == GNUTLS_DIG_UNKNOWN) { + gnutls_assert(); + addf(str, "Digest algorithm: unsupported (%s)\n", oid); + } else { + addf(str, "Digest algorithm: %s\n", gnutls_digest_get_name(dig)); + } + } + + adds(str, "\n"); +} + /** * gnutls_pkcs7_print: * @pkcs7: The PKCS7 struct to be printed @@ -345,6 +373,9 @@ int gnutls_pkcs7_print(gnutls_pkcs7_t pkcs7, case GNUTLS_PKCS7_SIGNED: _gnutls_pkcs7_print_signed(pkcs7, format, &str); break; + case GNUTLS_PKCS7_DIGESTED: + _gnutls_pkcs7_print_digested(pkcs7, format, &str); + break; default: adds(&str, "Unsupported PKCS#7 Content Type\n"); break; diff --git a/lib/x509/pkcs7.c b/lib/x509/pkcs7.c index 9fff942793..dfb03b592a 100644 --- a/lib/x509/pkcs7.c +++ b/lib/x509/pkcs7.c @@ -226,6 +226,9 @@ gnutls_pkcs7_import(gnutls_pkcs7_t pkcs7, const gnutls_datum_t * data, } else if (strcmp(data_oid, SIGNED_DATA_OID) == 0) { pkcs7->type = GNUTLS_PKCS7_SIGNED; result = _gnutls_pkcs7_decode_signed_data(pkcs7); + } else if (strcmp(data_oid, DIGESTED_DATA_OID) == 0) { + pkcs7->type = GNUTLS_PKCS7_DIGESTED; + result = _gnutls_pkcs7_decode_digested_data(pkcs7); } else { gnutls_assert(); _gnutls_debug_log("Unknown PKCS7 Content OID '%s'\n", pkcs7->encap_data_oid); @@ -358,6 +361,9 @@ static int reencode(gnutls_pkcs7_t pkcs7) case GNUTLS_PKCS7_SIGNED: oid = SIGNED_DATA_OID; break; + case GNUTLS_PKCS7_DIGESTED: + oid = DIGESTED_DATA_OID; + break; default: return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); } diff --git a/lib/x509/pkcs7_int.h b/lib/x509/pkcs7_int.h index ccaa9b25b5..77d02c149b 100644 --- a/lib/x509/pkcs7_int.h +++ b/lib/x509/pkcs7_int.h @@ -132,5 +132,6 @@ _gnutls_pkcs7_data_enc_info(const gnutls_datum_t * data, const struct pkcs_ciphe struct pbkdf2_params *kdf_params, char **oid); int _gnutls_pkcs7_decode_signed_data(gnutls_pkcs7_t pkcs7); +int _gnutls_pkcs7_decode_digested_data(gnutls_pkcs7_t pkcs7); #endif /* GNUTLS_LIB_X509_PKCS7_INT_H */ diff --git a/lib/x509/x509_int.h b/lib/x509/x509_int.h index 5c9461d114..dc12509f17 100644 --- a/lib/x509/x509_int.h +++ b/lib/x509/x509_int.h @@ -119,6 +119,7 @@ typedef enum { GNUTLS_PKCS7_UNINITIALIZED = 0, GNUTLS_PKCS7_DATA = 1, GNUTLS_PKCS7_SIGNED = 2, + GNUTLS_PKCS7_DIGESTED = 5, } gnutls_pkcs7_content_type_t; typedef struct gnutls_pkcs7_attrs_st { diff --git a/src/cmstool-common.c b/src/cmstool-common.c index 22aaf6235d..c3a68add29 100644 --- a/src/cmstool-common.c +++ b/src/cmstool-common.c @@ -86,7 +86,7 @@ static gnutls_digest_algorithm_t get_dig(gnutls_x509_crt_t crt, common_info_st * return dig; } -static void load_data(common_info_st *cinfo, gnutls_datum_t *data) +void load_data(common_info_st *cinfo, gnutls_datum_t *data) { FILE *fp; size_t size; diff --git a/src/cmstool-common.h b/src/cmstool-common.h index 4ca60fe92b..298cd0851c 100644 --- a/src/cmstool-common.h +++ b/src/cmstool-common.h @@ -23,6 +23,7 @@ #include <certtool-common.h> +void load_data(common_info_st *cinfo, gnutls_datum_t *data); void pkcs7_info(common_info_st *cinfo, unsigned display_data); void pkcs7_generate(common_info_st *); void pkcs7_sign_common(common_info_st *, unsigned embed, gnutls_pkcs7_sign_flags flags); diff --git a/src/cmstool-options.json b/src/cmstool-options.json index 4338c543af..c3694b20ec 100644 --- a/src/cmstool-options.json +++ b/src/cmstool-options.json @@ -89,6 +89,16 @@ { "long-option": "smime-to-cms", "description": "Convert S/MIME to PKCS #7 structure" + }, + { + "long-option": "digest", + "description": "Digest using a PKCS #7 structure", + "detail": "This option generates a PKCS #7 structure containing a digest for the provided data from infile. The data are stored within the structure." + }, + { + "long-option": "verify-digest", + "description": "Verify the provided PKCS #7 digested structure", + "detail": "This option verifies the digested PKCS #7 structure. The --load-data option will utilize detached data." } ] }, diff --git a/src/cmstool.c b/src/cmstool.c index 7bdb465921..a003dcba77 100644 --- a/src/cmstool.c +++ b/src/cmstool.c @@ -133,6 +133,121 @@ static void pkcs7_verify(common_info_st * cinfo, const char *purpose, unsigned d return pkcs7_verify_common(cinfo, purpose, display_data, flags); } +static void pkcs7_digest(common_info_st * cinfo, unsigned embed) +{ + gnutls_pkcs7_t pkcs7; + int ret; + size_t size; + gnutls_datum_t data; + unsigned flags = 0; + + ret = gnutls_pkcs7_init(&pkcs7); + if (ret < 0) { + fprintf(stderr, "p7_init: %s\n", gnutls_strerror(ret)); + app_exit(1); + } + + data.data = (void *) fread_file(infile, 0, &size); + data.size = size; + + if (!data.data) { + fprintf(stderr, "%s", infile ? "file" : "standard input"); + app_exit(1); + } + + if (embed) + flags |= GNUTLS_PKCS7_EMBED_DATA; + + ret = gnutls_pkcs7_digest(pkcs7, &data, cinfo->hash, flags); + if (ret < 0) { + fprintf(stderr, "Error digesting: %s\n", gnutls_strerror(ret)); + app_exit(1); + } + + size = lbuffer_size; + ret = + gnutls_pkcs7_export(pkcs7, cinfo->outcert_format, lbuffer, &size); + if (ret < 0) { + fprintf(stderr, "pkcs7_export: %s\n", gnutls_strerror(ret)); + app_exit(1); + } + + fwrite(lbuffer, 1, size, outfile); + + gnutls_pkcs7_deinit(pkcs7); + app_exit(0); +} + +static void pkcs7_verify_digest(common_info_st * cinfo, unsigned display_data) +{ + gnutls_pkcs7_t pkcs7; + int ret, ecode; + size_t size; + gnutls_datum_t data, detached = {NULL,0}; + gnutls_datum_t tmp = {NULL,0}; + unsigned flags = 0; + + if (HAVE_OPT(VERIFY_ALLOW_BROKEN)) + flags |= GNUTLS_VERIFY_ALLOW_BROKEN; + + ret = gnutls_pkcs7_init(&pkcs7); + if (ret < 0) { + fprintf(stderr, "p7_init: %s\n", gnutls_strerror(ret)); + app_exit(1); + } + + data.data = (void *) fread_file(infile, 0, &size); + data.size = size; + + if (!data.data) { + fprintf(stderr, "%s", infile ? "file" : "standard input"); + app_exit(1); + } + + ret = gnutls_pkcs7_import(pkcs7, &data, cinfo->incert_format); + free(data.data); + if (ret < 0) { + fprintf(stderr, "import error: %s\n", + gnutls_strerror(ret)); + app_exit(1); + } + + if (cinfo->data_file) + load_data(cinfo, &detached); + + if (!display_data) { + fprintf(outfile, "eContent Type: %s\n", gnutls_pkcs7_get_embedded_data_oid(pkcs7)); + fprintf(outfile, "Digest: %s\n", gnutls_digest_get_name(gnutls_pkcs7_get_digest_algo(pkcs7))); + } + + if (!detached.data) { + ret = gnutls_pkcs7_get_embedded_data(pkcs7, 0, &tmp); + if (ret < 0) { + fprintf(stderr, "error getting embedded data: %s\n", gnutls_strerror(ret)); + app_exit(1); + } + + fwrite(tmp.data, 1, tmp.size, outfile); + gnutls_free(tmp.data); + tmp.data = NULL; + } else { + fwrite(detached.data, 1, detached.size, outfile); + } + + ret = gnutls_pkcs7_verify_digest(pkcs7, detached.data!=NULL?&detached:NULL, flags); + if (ret < 0) { + fprintf(stderr, "Digest status: verification failed: %s\n", gnutls_strerror(ret)); + ecode = 1; + } else { + fprintf(stderr, "Digest status: ok\n"); + ecode = 0; + } + + gnutls_pkcs7_deinit(pkcs7); + free(detached.data); + app_exit(ecode); +} + static void cmd_parser(int argc, char **argv) { int ret, privkey_op = 0; @@ -277,6 +392,10 @@ static void cmd_parser(int argc, char **argv) pkcs7_verify(&cinfo, OPT_ARG(VERIFY_PURPOSE), ENABLED_OPT(SHOW_DATA)); else if (HAVE_OPT(SMIME_TO_CMS)) smime_to_pkcs7(); + else if (HAVE_OPT(DIGEST)) + pkcs7_digest(&cinfo, 1); + else if (HAVE_OPT(VERIFY_DIGEST)) + pkcs7_verify_digest(&cinfo, ENABLED_OPT(SHOW_DATA)); else USAGE(1); diff --git a/tests/cert-tests/Makefile.am b/tests/cert-tests/Makefile.am index 88b6e309f4..d6c1ec0bcf 100644 --- a/tests/cert-tests/Makefile.am +++ b/tests/cert-tests/Makefile.am @@ -103,6 +103,8 @@ EXTRA_DIST = data/ca-no-pathlen.pem data/no-ca-or-pathlen.pem data/aki-cert.pem templates/template-no-ca-honor.tmpl templates/template-no-ca-explicit.tmpl \ data/rfc4134-3.1.der data/rfc4134-3.1.der.out \ data/rfc4134-3.2.der data/rfc4134-3.2.der.out \ + data/pkcs7-sha1.der data/pkcs7-sha1.der.out \ + data/pkcs7-streebog256.der data/pkcs7-streebog256.der.out \ data/crq-cert-no-ca-explicit.pem data/crq-cert-no-ca-honor.pem data/commonName.cer \ templates/simple-policy.tmpl data/simple-policy.pem @@ -116,7 +118,8 @@ dist_check_SCRIPTS = pathlen.sh aki.sh invalid-sig.sh email.sh \ key-id.sh pkcs8.sh pkcs8-decode.sh ecdsa.sh illegal-rsa.sh pkcs8-invalid.sh key-invalid.sh \ pkcs8-eddsa.sh certtool-subca.sh certtool-verify-profiles.sh x509-duplicate-ext.sh x25519-and-x448.sh -dist_check_SCRIPTS += cmstool.sh cms-cat.sh cms-broken-sigs.sh cms-constraints.sh cms-constraints2.sh cms-eddsa.sh cms-list-sign.sh +dist_check_SCRIPTS += cmstool.sh cms-cat.sh cms-broken-sigs.sh cms-constraints.sh cms-constraints2.sh cms-eddsa.sh \ + cms-list-sign.sh cms-broken-dig.sh dist_check_SCRIPTS += key-id.sh ecdsa.sh pkcs8-invalid.sh key-invalid.sh pkcs8-decode.sh pkcs8.sh pkcs8-eddsa.sh \ certtool-utf8.sh crq.sh diff --git a/tests/cert-tests/cms-broken-dig.sh b/tests/cert-tests/cms-broken-dig.sh new file mode 100755 index 0000000000..a8c4b577ae --- /dev/null +++ b/tests/cert-tests/cms-broken-dig.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +# Copyright (C) 2016 Red Hat, Inc. +# +# This file is part of GnuTLS. +# +# GnuTLS 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 3 of the License, or (at +# your option) any later version. +# +# GnuTLS 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. +# +# You should have received a copy of the GNU General Public License +# along with GnuTLS; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#set -e + +srcdir="${srcdir:-.}" +CMSTOOL="${CMSTOOL:-../../src/cmstool${EXEEXT}}" +DIFF="${DIFF:-diff -b -B}" + +if ! test -x "${CMSTOOL}"; then + exit 77 +fi + +# MD5 is not available under FIPS +if test "${GNUTLS_FORCE_FIPS_MODE}" = 1;then + exit 77 +fi + +if ! test -z "${VALGRIND}"; then + VALGRIND="${LIBTOOL:-libtool} --mode=execute ${VALGRIND} --error-exitcode=15" +fi + +OUTFILE=out-cms.$$.tmp +OUTFILE2=out2-cms.$$.tmp + +# Test digest with MD5 +FILE="digest" +${VALGRIND} "${CMSTOOL}" --digest --hash md5 --infile "${srcdir}/data/pkcs7-detached.txt" >"${OUTFILE}" +rc=$? + +if test "${rc}" != "0"; then + echo "${FILE}: PKCS7 struct digest with MD5 failed" + exit ${rc} +fi + +FILE="digest-verify" +${VALGRIND} "${CMSTOOL}" --verify-digest <"${OUTFILE}" +rc=$? + +if test "${rc}" != "1"; then + echo "${FILE}: PKCS7 struct digest succeeded verification with MD5" + exit ${rc} +fi + +FILE="digest-verify" +${VALGRIND} "${CMSTOOL}" --verify-digest --verify-allow-broken <"${OUTFILE}" +rc=$? + +if test "${rc}" != "0"; then + echo "${FILE}: PKCS7 struct digest failed with MD5 and allow-broken" + exit ${rc} +fi + +rm -f "${OUTFILE}" +rm -f "${OUTFILE2}" + +exit 0 diff --git a/tests/cert-tests/cmstool.sh b/tests/cert-tests/cmstool.sh index 5463d9a374..eeaf38d7d0 100755 --- a/tests/cert-tests/cmstool.sh +++ b/tests/cert-tests/cmstool.sh @@ -42,12 +42,12 @@ skip_if_no_datefudge if test "${ENABLE_GOST}" = "1" && test "${GNUTLS_FORCE_FIPS_MODE}" != "1" then - GOST_P7B="rfc4490.p7b" + GOST_P7B="rfc4490.p7b pkcs7-streebog256.der" else GOST_P7B="" fi -for FILE in single-ca.p7b full.p7b openssl.p7b openssl-keyid.p7b rfc4134-3.1.der rfc4134-3.2.der $GOST_P7B; do +for FILE in single-ca.p7b full.p7b openssl.p7b openssl-keyid.p7b rfc4134-3.1.der rfc4134-3.2.der pkcs7-sha1.der $GOST_P7B; do ${VALGRIND} "${CMSTOOL}" --inder --info --infile "${srcdir}/data/${FILE}"|grep -v "Signing time" >"${OUTFILE}" rc=$? @@ -336,6 +336,36 @@ then fi fi +# Test digest +FILE="digest" +${VALGRIND} "${CMSTOOL}" --digest --infile "${srcdir}/data/pkcs7-detached.txt" --hash sha512 >"${OUTFILE}" +rc=$? + +if test "${rc}" != "0"; then + echo "${FILE}: PKCS7 struct digest failed" + exit ${rc} +fi + +FILE="digest-verify" +${VALGRIND} "${CMSTOOL}" --verify-digest <"${OUTFILE}" +rc=$? + +if test "${rc}" != "0"; then + echo "${FILE}: PKCS7 struct digest failed verification" + exit ${rc} +fi + +#check extraction of embedded data in digest +FILE="digest-verify-data" +${VALGRIND} "${CMSTOOL}" --verify-digest --show-data --outfile "${OUTFILE2}" <"${OUTFILE}" +rc=$? + +if test "${rc}" != "0"; then + echo "${FILE}: PKCS7 struct signing failed verification with data" + exit ${rc} +fi + + rm -f "${OUTFILE}" rm -f "${OUTFILE2}" rm -f "${TMPFILE}" diff --git a/tests/cert-tests/data/pkcs7-sha1.der b/tests/cert-tests/data/pkcs7-sha1.der Binary files differnew file mode 100644 index 0000000000..dfbfaaaed6 --- /dev/null +++ b/tests/cert-tests/data/pkcs7-sha1.der diff --git a/tests/cert-tests/data/pkcs7-sha1.der.out b/tests/cert-tests/data/pkcs7-sha1.der.out new file mode 100644 index 0000000000..166064f0aa --- /dev/null +++ b/tests/cert-tests/data/pkcs7-sha1.der.out @@ -0,0 +1,7 @@ +Content Type: Digested +Digest algorithm: SHA1 + +-----BEGIN PKCS7----- +MF4GCSqGSIb3DQEHBaBRME8CAQAwBwYFKw4DAhowKwYJKoZIhvcNAQcBoB4EHFRo +aXMgaXMgc29tZSBzYW1wbGUgY29udGVudC4EFEBq7AhSebpuFgItngYpwCKWh91I +-----END PKCS7----- diff --git a/tests/cert-tests/data/pkcs7-streebog256.der b/tests/cert-tests/data/pkcs7-streebog256.der Binary files differnew file mode 100644 index 0000000000..58101c1212 --- /dev/null +++ b/tests/cert-tests/data/pkcs7-streebog256.der diff --git a/tests/cert-tests/data/pkcs7-streebog256.der.out b/tests/cert-tests/data/pkcs7-streebog256.der.out new file mode 100644 index 0000000000..2fd67be0dc --- /dev/null +++ b/tests/cert-tests/data/pkcs7-streebog256.der.out @@ -0,0 +1,8 @@ +Content Type: Digested +Digest algorithm: STREEBOG-256 + +-----BEGIN PKCS7----- +MH0GCSqGSIb3DQEHBaBwMG4CAQAwCgYIKoUDBwEBAgIwOwYJKoZIhvcNAQcBoC4E +LMru7fLw7uv87fvpIO/w6Ozl8CDk6/8g8fLw8+ry8/D7IERpZ2VzdERhdGEuBCD/ +esPQYsGkzxZV8uUMIAWt6SI8KtxBP8NyG8AGbJ8i/Q== +-----END PKCS7----- |