]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
pkcs7: add support for DigestedData CMS files
authorDmitry Baryshkov <dbaryshkov@gmail.com>
Sat, 16 May 2020 22:22:01 +0000 (01:22 +0300)
committerDmitry Baryshkov <dmitry.baryshkov@linaro.org>
Sun, 11 Sep 2022 14:54:58 +0000 (17:54 +0300)
Add support for creating and verifying DigestedData CMS files.

Signed-off-by: Dmitry Baryshkov <dbaryshkov@gmail.com>
23 files changed:
devel/symbols.last
doc/Makefile.am
doc/manpages/Makefile.am
lib/includes/gnutls/pkcs7.h
lib/libgnutls.map
lib/pkix.asn
lib/x509/Makefile.am
lib/x509/pkcs7-digest.c [new file with mode: 0644]
lib/x509/pkcs7-output.c
lib/x509/pkcs7.c
lib/x509/pkcs7_int.h
lib/x509/x509_int.h
src/cmstool-common.c
src/cmstool-common.h
src/cmstool-options.json
src/cmstool.c
tests/cert-tests/Makefile.am
tests/cert-tests/cms-broken-dig.sh [new file with mode: 0755]
tests/cert-tests/cmstool.sh
tests/cert-tests/data/pkcs7-sha1.der [new file with mode: 0644]
tests/cert-tests/data/pkcs7-sha1.der.out [new file with mode: 0644]
tests/cert-tests/data/pkcs7-streebog256.der [new file with mode: 0644]
tests/cert-tests/data/pkcs7-streebog256.der.out [new file with mode: 0644]

index bd0de671fd022fb2e4e27f9438c4c218339e26de..353d1657ddade1c6431d78540240ae653f5db5b3 100644 (file)
@@ -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
index 3a4151036c3a593242ca0d183283eafd717362bd..ad729af60db0f0ddb4efa18826b7d6e42f2d81c3 100644 (file)
@@ -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
index cddf789ec6334f78ee95a377bcd8489fa2dbf79d..8340cbac1baa85040c979ee03beba5715c36003e 100644 (file)
@@ -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
index 528427b484757180acfb2d1640983da643d0c17d..e40dac23dda6f5341303b5ecdf0e7294f7806667 100644 (file)
@@ -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);
index 9e869d038f43fbf06cc72ee0cd347e3a41004272..08aa2b7d3039c72b064412db76d7b4345c870831 100644 (file)
@@ -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;
index 48eaf396506bad9204e729e9278206a70874a6bf..4f10c8e1705371f91eae240a02b838371a5969c5 100644 (file)
@@ -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
 
index 4ac1cdea8c6b846d765145d50f92960da96bb5ff..1dcbd93f7d42bd1116c1dfa00403678c717a0605 100644 (file)
@@ -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 (file)
index 0000000..c68b2c6
--- /dev/null
@@ -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;
+}
index 1021777419151bd1041d6289bae3c8eaa980db62..1b7d9a0abcba304004a14656008fb83a8ceac04f 100644 (file)
@@ -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;
index 9fff94279318f3d532d0a3b7493175094f8f2227..dfb03b592a300fe68355154dcc9e94368b3cb6ee 100644 (file)
@@ -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);
                }
index ccaa9b25b55ff8fe253323e9eae59278fe8bb1b6..77d02c149b54abb3cdbf6e9c13b040e75f53a9b5 100644 (file)
@@ -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 */
index 5c9461d1145110ee44999c7a810058fb6278c453..dc12509f17ec88a4e961d7661be45916eb67b9df 100644 (file)
@@ -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 {
index 22aaf6235dcf881af3e63fe34b823d10245a9a07..c3a68add29647bcb3ddef3338428330daa31ee1a 100644 (file)
@@ -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;
index 4ca60fe92b705739a726a4bec9e1c72b60bd59ed..298cd0851c4b0bbaa45da41025ee680d18c37712 100644 (file)
@@ -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);
index 4338c543af8e3b007114d1ade379bab40896b7ff..c3694b20ec848c50e19707f16a8c1b6b8b33944c 100644 (file)
         {
           "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."
         }
       ]
     },
index 7bdb465921bfae089fd1f5671bccb8511243625e..a003dcba775a68d1afb8ad13363e646271ae5c5e 100644 (file)
@@ -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);
 
index 88b6e309f48ac1cc12e149108ab9e520b64c1dfc..d6c1ec0bcf91da82c4fe3534d463e651e7848348 100644 (file)
@@ -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 (executable)
index 0000000..a8c4b57
--- /dev/null
@@ -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
index 5463d9a3746a08343740fda8c7320a031dc0ec05..eeaf38d7d0664273c1ecb3d4d7f4a9f8e1a94522 100755 (executable)
@@ -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
new file mode 100644 (file)
index 0000000..dfbfaaa
Binary files /dev/null and b/tests/cert-tests/data/pkcs7-sha1.der differ
diff --git a/tests/cert-tests/data/pkcs7-sha1.der.out b/tests/cert-tests/data/pkcs7-sha1.der.out
new file mode 100644 (file)
index 0000000..166064f
--- /dev/null
@@ -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
new file mode 100644 (file)
index 0000000..58101c1
Binary files /dev/null and b/tests/cert-tests/data/pkcs7-streebog256.der differ
diff --git a/tests/cert-tests/data/pkcs7-streebog256.der.out b/tests/cert-tests/data/pkcs7-streebog256.der.out
new file mode 100644 (file)
index 0000000..2fd67be
--- /dev/null
@@ -0,0 +1,8 @@
+Content Type: Digested
+Digest algorithm: STREEBOG-256
+
+-----BEGIN PKCS7-----
+MH0GCSqGSIb3DQEHBaBwMG4CAQAwCgYIKoUDBwEBAgIwOwYJKoZIhvcNAQcBoC4E
+LMru7fLw7uv87fvpIO/w6Ozl8CDk6/8g8fLw8+ry8/D7IERpZ2VzdERhdGEuBCD/
+esPQYsGkzxZV8uUMIAWt6SI8KtxBP8NyG8AGbJ8i/Q==
+-----END PKCS7-----