summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Baryshkov <dbaryshkov@gmail.com>2020-05-17 01:22:01 +0300
committerDmitry Baryshkov <dmitry.baryshkov@linaro.org>2022-09-11 17:54:58 +0300
commite03f293fa88f71062f039106d72aec6f4e67fe25 (patch)
tree52e566686a53d6fd9013c570a86bb9f23b0d051e
parent7d745de8e1e1e80514bbcc973eec35a1c483d22e (diff)
downloadgnutls-e03f293fa88f71062f039106d72aec6f4e67fe25.tar.gz
pkcs7: add support for DigestedData CMS files
Add support for creating and verifying DigestedData CMS files. Signed-off-by: Dmitry Baryshkov <dbaryshkov@gmail.com>
-rw-r--r--devel/symbols.last3
-rw-r--r--doc/Makefile.am6
-rw-r--r--doc/manpages/Makefile.am3
-rw-r--r--lib/includes/gnutls/pkcs7.h8
-rw-r--r--lib/libgnutls.map3
-rw-r--r--lib/pkix.asn6
-rw-r--r--lib/x509/Makefile.am1
-rw-r--r--lib/x509/pkcs7-digest.c340
-rw-r--r--lib/x509/pkcs7-output.c31
-rw-r--r--lib/x509/pkcs7.c6
-rw-r--r--lib/x509/pkcs7_int.h1
-rw-r--r--lib/x509/x509_int.h1
-rw-r--r--src/cmstool-common.c2
-rw-r--r--src/cmstool-common.h1
-rw-r--r--src/cmstool-options.json10
-rw-r--r--src/cmstool.c119
-rw-r--r--tests/cert-tests/Makefile.am5
-rwxr-xr-xtests/cert-tests/cms-broken-dig.sh74
-rwxr-xr-xtests/cert-tests/cmstool.sh34
-rw-r--r--tests/cert-tests/data/pkcs7-sha1.derbin0 -> 96 bytes
-rw-r--r--tests/cert-tests/data/pkcs7-sha1.der.out7
-rw-r--r--tests/cert-tests/data/pkcs7-streebog256.derbin0 -> 127 bytes
-rw-r--r--tests/cert-tests/data/pkcs7-streebog256.der.out8
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
new file mode 100644
index 0000000000..dfbfaaaed6
--- /dev/null
+++ b/tests/cert-tests/data/pkcs7-sha1.der
Binary files 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
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
new file mode 100644
index 0000000000..58101c1212
--- /dev/null
+++ b/tests/cert-tests/data/pkcs7-streebog256.der
Binary files 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
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-----