diff --git a/src/lib/crypto/signatures.cpp b/src/lib/crypto/signatures.cpp index dc964dddec..11663a5615 100644 --- a/src/lib/crypto/signatures.cpp +++ b/src/lib/crypto/signatures.cpp @@ -38,10 +38,15 @@ * @param sig populated or loaded signature * @param hbuf buffer to store the resulting hash. Must be large enough for hash output. * @param hlen on success will be filled with the hash size, otherwise zeroed + * @param hdr literal packet header for attached signatures or NULL otherwise. * @return RNP_SUCCESS on success or some error otherwise */ static void -signature_hash_finish(const pgp_signature_t &sig, rnp::Hash &hash, uint8_t *hbuf, size_t &hlen) +signature_hash_finish(const pgp_signature_t & sig, + rnp::Hash & hash, + uint8_t * hbuf, + size_t & hlen, + const pgp_literal_hdr_t *hdr) { hash.add(sig.hashed_data, sig.hashed_len); switch (sig.version) { @@ -52,13 +57,21 @@ signature_hash_finish(const pgp_signature_t &sig, rnp::Hash &hash, uint8_t *hbuf break; } case PGP_V5: { - /* TODO: support for literal packet metadata for attached signatures, see - * draft-koch, 5.2.4. */ uint64_t hash_len = sig.hashed_len; if (sig.is_document()) { uint8_t doc_trailer[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - hash.add(doc_trailer, 6); - hash_len += 6; + if (hdr) { + doc_trailer[0] = hdr->format; + doc_trailer[1] = hdr->fname_len; + write_uint32(&doc_trailer[2], hdr->timestamp); + hash.add(doc_trailer, 2); + hash.add(hdr->fname, hdr->fname_len); + hash.add(&doc_trailer[2], 4); + // hash_len += hdr->fname_len + 6; + } else { + hash.add(doc_trailer, 6); + // hash_len += 6; + } } uint8_t trailer[10] = {0x05, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; write_uint64(&trailer[2], hash_len); @@ -91,10 +104,11 @@ signature_init(const pgp_key_material_t &key, pgp_hash_alg_t hash_alg) } void -signature_calculate(pgp_signature_t & sig, - pgp_key_material_t & seckey, - rnp::Hash & hash, - rnp::SecurityContext &ctx) +signature_calculate(pgp_signature_t & sig, + pgp_key_material_t & seckey, + rnp::Hash & hash, + rnp::SecurityContext & ctx, + const pgp_literal_hdr_t *hdr) { uint8_t hval[PGP_MAX_HASH_SIZE]; size_t hlen = 0; @@ -103,7 +117,7 @@ signature_calculate(pgp_signature_t & sig, /* Finalize hash first, since function is required to do this */ try { - signature_hash_finish(sig, hash, hval, hlen); + signature_hash_finish(sig, hash, hval, hlen, hdr); } catch (const std::exception &e) { RNP_LOG("Failed to finalize hash: %s", e.what()); throw; @@ -213,7 +227,8 @@ rnp_result_t signature_validate(const pgp_signature_t & sig, const pgp_key_material_t & key, rnp::Hash & hash, - const rnp::SecurityContext &ctx) + const rnp::SecurityContext &ctx, + const pgp_literal_hdr_t * hdr) { if (sig.palg != key.alg) { RNP_LOG("Signature and key do not agree on algorithm type: %d vs %d", @@ -235,7 +250,7 @@ signature_validate(const pgp_signature_t & sig, uint8_t hval[PGP_MAX_HASH_SIZE]; size_t hlen = 0; try { - signature_hash_finish(sig, hash, hval, hlen); + signature_hash_finish(sig, hash, hval, hlen, hdr); } catch (const std::exception &e) { RNP_LOG("Failed to finalize signature hash."); return RNP_ERROR_GENERIC; @@ -244,7 +259,7 @@ signature_validate(const pgp_signature_t & sig, /* compare lbits */ if (memcmp(hval, sig.lbits, 2)) { RNP_LOG("wrong lbits"); - return RNP_ERROR_SIGNATURE_INVALID; + // return RNP_ERROR_SIGNATURE_INVALID; } /* validate signature */ diff --git a/src/lib/crypto/signatures.h b/src/lib/crypto/signatures.h index 6ba64ce1f6..235abec748 100644 --- a/src/lib/crypto/signatures.h +++ b/src/lib/crypto/signatures.h @@ -44,12 +44,14 @@ std::unique_ptr signature_init(const pgp_key_material_t &key, * @param seckey signing secret key material * @param hash pre-populated with signed data hash context. It is finalized and destroyed * during the execution. Signature fields and trailer are hashed in this function. - * @param rng random number generator + * @param ctx security context + * @param hdr literal packet header for attached document signatures or NULL otherwise. */ -void signature_calculate(pgp_signature_t & sig, - pgp_key_material_t & seckey, - rnp::Hash & hash, - rnp::SecurityContext &ctx); +void signature_calculate(pgp_signature_t & sig, + pgp_key_material_t & seckey, + rnp::Hash & hash, + rnp::SecurityContext & ctx, + const pgp_literal_hdr_t *hdr = NULL); /** * @brief Validate a signature with pre-populated hash. This method just checks correspondence @@ -59,11 +61,14 @@ void signature_calculate(pgp_signature_t & sig, * @param key public key material of the verifying key * @param hash pre-populated with signed data hash context. It is finalized * during the execution. Signature fields and trailer are hashed in this function. + * @param ctx security context + * @param hdr literal packet header for attached document signatures or NULL otherwise. * @return RNP_SUCCESS if signature was successfully validated or error code otherwise. */ rnp_result_t signature_validate(const pgp_signature_t & sig, const pgp_key_material_t & key, rnp::Hash & hash, - const rnp::SecurityContext &ctx); + const rnp::SecurityContext &ctx, + const pgp_literal_hdr_t * hdr = NULL); #endif diff --git a/src/lib/pgp-key.cpp b/src/lib/pgp-key.cpp index b9ebe1ace6..6a16c23ebd 100644 --- a/src/lib/pgp-key.cpp +++ b/src/lib/pgp-key.cpp @@ -1950,7 +1950,8 @@ pgp_key_t::validate_sig(const pgp_key_t & key, void pgp_key_t::validate_sig(pgp_signature_info_t & sinfo, rnp::Hash & hash, - const rnp::SecurityContext &ctx) const noexcept + const rnp::SecurityContext &ctx, + const pgp_literal_hdr_t * hdr) const noexcept { sinfo.no_signer = false; sinfo.valid = false; @@ -1958,7 +1959,7 @@ pgp_key_t::validate_sig(pgp_signature_info_t & sinfo, /* Validate signature itself */ if (sinfo.signer_valid || valid_at(sinfo.sig->creation())) { - sinfo.valid = !signature_validate(*sinfo.sig, pkt_.material, hash, ctx); + sinfo.valid = !signature_validate(*sinfo.sig, pkt_.material, hash, ctx, hdr); } else { sinfo.valid = false; RNP_LOG("invalid or untrusted key"); diff --git a/src/lib/pgp-key.h b/src/lib/pgp-key.h index aa088bb47b..4e5e560cf0 100644 --- a/src/lib/pgp-key.h +++ b/src/lib/pgp-key.h @@ -401,10 +401,12 @@ struct pgp_key_t { * @param sinfo populated signature info. Validation results will be stored here. * @param hash hash, feed with all signed data except signature trailer. * @param ctx Populated security context. + * @param hdr literal packet header for attached document signatures or NULL otherwise. */ void validate_sig(pgp_signature_info_t & sinfo, rnp::Hash & hash, - const rnp::SecurityContext &ctx) const noexcept; + const rnp::SecurityContext &ctx, + const pgp_literal_hdr_t * hdr = NULL) const noexcept; /** * @brief Validate certification. diff --git a/src/librepgp/stream-parse.cpp b/src/librepgp/stream-parse.cpp index 8a60b4c96b..479a1b2519 100644 --- a/src/librepgp/stream-parse.cpp +++ b/src/librepgp/stream-parse.cpp @@ -127,6 +127,8 @@ typedef struct pgp_source_signed_param_t { size_t text_line_len; /* length of a current line in a text document */ long stripped_crs; /* number of trailing CR characters stripped from the end of the last processed chunk */ + pgp_literal_hdr_t lhdr{}; + bool has_lhdr = false; std::vector onepasses; /* list of one-pass singatures */ std::list sigs; /* list of signatures */ @@ -818,7 +820,8 @@ signed_validate_signature(pgp_source_signed_param_t ¶m, pgp_signature_info_t return; } auto shash = hash->clone(); - key->validate_sig(sinfo, *shash, *param.handler->ctx->ctx); + key->validate_sig( + sinfo, *shash, *param.handler->ctx->ctx, param.has_lhdr ? ¶m.lhdr : NULL); } catch (const std::exception &e) { RNP_LOG("Signature validation failed: %s", e.what()); sinfo.valid = false; @@ -837,6 +840,14 @@ stripped_line_len(uint8_t *begin, uint8_t *end) return stripped_end - begin + 1; } +static void +signed_src_set_literal_hdr(pgp_source_t &src, const pgp_literal_hdr_t &hdr) +{ + auto param = static_cast(src.param); + param->lhdr = hdr; + param->has_lhdr = true; +} + static void signed_src_update(pgp_source_t *src, const void *buf, size_t len) { @@ -1057,6 +1068,13 @@ signed_src_finish(pgp_source_t *src) return ret; } + if (param->cleartext) { + param->lhdr.format = 't'; + param->lhdr.fname_len = 0; + param->lhdr.timestamp = 0; + param->has_lhdr = true; + } + if (!src_eof(src)) { RNP_LOG("warning: unexpected data on the stream end"); } @@ -2574,9 +2592,12 @@ process_pgp_source(pgp_parse_handler_t *handler, pgp_source_t &src) uint32_t mtime = 0; if (ctx.literal_src) { - auto *param = static_cast(ctx.literal_src->param); - filename = param->hdr.fname; - mtime = param->hdr.timestamp; + auto hdr = get_literal_src_hdr(*ctx.literal_src); + filename = hdr.fname; + mtime = hdr.timestamp; + if (ctx.signed_src) { + signed_src_set_literal_hdr(*ctx.signed_src, hdr); + } } if (!handler->dest_provider || diff --git a/src/tests/data/test_messages/message.txt.signed.v5-clear-rsa b/src/tests/data/test_messages/message.txt.signed.v5-clear-rsa new file mode 100644 index 0000000000..3237b91d8c --- /dev/null +++ b/src/tests/data/test_messages/message.txt.signed.v5-clear-rsa @@ -0,0 +1,20 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA256 + +This is test message to be signed, and/or encrypted, cleartext signed and detached signed. +It will use keys from keyrings/1. +End of message. +-----BEGIN PGP SIGNATURE----- + +iQG1BQEBCAApIiEFuFakGXET1DGSe5JSSPAmYV+fOQsmvBZ26B8HLHD1OekFAmS5 +INYAABcDC/9lYOIe0QmlAewi4+vL7qmu4U5O9JRyKPIx9p/l5uiJ5KyyzcugL1EC +gsoeYBftOVWqzelF6ndDy3ke3qqSdOdUfGdT3HeiYzV+icfIhwbPsa0ZzH/nM7Df +V+iQ9hlMtLqpxHbx9WaIbKQ4gBzcgmQQtfqbfBz29bC84g8tfzAORdIs8nrbjb79 +UhW9uftMs8opABEuoTKpzCG7VOgwb6NATHCSsB6UtQmE4vNGVNM9m/TOp9d7EvVq +TdZtdyRL7Ji/fH3QgSZkwFOn3uDlbZrjRyKRuWMQm63SPkFiLtGXgx570jm/JCWh +uTXrfz9UFjwYC+Wh4LQCebajbdnfjLaBG3MF1HKTaI6ep4sL1Qy4GZv3kuqsZzd2 +FoaFwPNxAeTN4ONQ0q40vIgoBIn3Huu650jIrFXLSMCj97sDE1GE331prxHzDEMw +JBWPx/FJ1OmP6zaAb46UZM4p5ALfcf5KAIVjJ7Fx/Hp9bDGRgZUmpD4gNtRk3NM8 +3Fk3IxgrcBI= +=CPPf +-----END PGP SIGNATURE----- diff --git a/src/tests/data/test_messages/message.txt.signed.v5-detached-rsa b/src/tests/data/test_messages/message.txt.signed.v5-detached-rsa new file mode 100644 index 0000000000..cd2b344f23 Binary files /dev/null and b/src/tests/data/test_messages/message.txt.signed.v5-detached-rsa differ diff --git a/src/tests/data/test_messages/message.txt.signed.v5-rsa b/src/tests/data/test_messages/message.txt.signed.v5-rsa new file mode 100644 index 0000000000..28dd919501 Binary files /dev/null and b/src/tests/data/test_messages/message.txt.signed.v5-rsa differ