/* * Copyright (c) 2018-2022, [Ribose Inc](https://www.ribose.com). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include "crypto/signatures.h" #include "librepgp/stream-packet.h" #include "librepgp/stream-sig.h" #include "utils.h" #include "sec_profile.hpp" /** * @brief Add signature fields to the hash context and finish it. * @param hash initialized hash context fed with signed data (document, key, etc). * It is finalized in this function. * @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 * @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) { hash.add(sig.hashed_data, sig.hashed_len); if (sig.version > PGP_V3) { uint8_t trailer[6] = {0x04, 0xff, 0x00, 0x00, 0x00, 0x00}; STORE32BE(&trailer[2], sig.hashed_len); hash.add(trailer, 6); } hlen = hash.finish(hbuf); } std::unique_ptr signature_init(const pgp_key_material_t &key, pgp_hash_alg_t hash_alg) { auto hash = rnp::Hash::create(hash_alg); if (key.alg == PGP_PKA_SM2) { #if defined(ENABLE_SM2) rnp_result_t r = sm2_compute_za(key.ec, *hash); if (r != RNP_SUCCESS) { RNP_LOG("failed to compute SM2 ZA field"); throw rnp::rnp_exception(r); } #else RNP_LOG("SM2 ZA computation not available"); throw rnp::rnp_exception(RNP_ERROR_NOT_IMPLEMENTED); #endif } return hash; } void signature_calculate(pgp_signature_t & sig, pgp_key_material_t & seckey, rnp::Hash & hash, rnp::SecurityContext &ctx) { uint8_t hval[PGP_MAX_HASH_SIZE]; size_t hlen = 0; rnp_result_t ret = RNP_ERROR_GENERIC; const pgp_hash_alg_t hash_alg = hash.alg(); /* Finalize hash first, since function is required to do this */ try { signature_hash_finish(sig, hash, hval, hlen); } catch (const std::exception &e) { RNP_LOG("Failed to finalize hash: %s", e.what()); throw; } if (!seckey.secret) { RNP_LOG("Secret key is required."); throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); } if (sig.palg != seckey.alg) { RNP_LOG("Signature and secret key do not agree on algorithm type."); throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); } /* Validate key material if didn't before */ seckey.validate(ctx, false); if (!seckey.valid()) { RNP_LOG("Attempt to sign with invalid key material."); throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); } /* copy left 16 bits to signature */ memcpy(sig.lbits, hval, 2); /* sign */ pgp_signature_material_t material = {}; switch (sig.palg) { case PGP_PKA_RSA: case PGP_PKA_RSA_ENCRYPT_ONLY: case PGP_PKA_RSA_SIGN_ONLY: ret = rsa_sign_pkcs1(&ctx.rng, &material.rsa, sig.halg, hval, hlen, &seckey.rsa); if (ret) { RNP_LOG("rsa signing failed"); } break; case PGP_PKA_EDDSA: ret = eddsa_sign(&ctx.rng, &material.ecc, hval, hlen, &seckey.ec); if (ret) { RNP_LOG("eddsa signing failed"); } break; case PGP_PKA_DSA: ret = dsa_sign(&ctx.rng, &material.dsa, hval, hlen, &seckey.dsa); if (ret != RNP_SUCCESS) { RNP_LOG("DSA signing failed"); } break; /* * ECDH is signed with ECDSA. This must be changed when ECDH will support * X25519, but I need to check how it should be done exactly. */ case PGP_PKA_ECDH: case PGP_PKA_ECDSA: case PGP_PKA_SM2: { const ec_curve_desc_t *curve = get_curve_desc(seckey.ec.curve); if (!curve) { RNP_LOG("Unknown curve"); ret = RNP_ERROR_BAD_PARAMETERS; break; } if (!curve_supported(seckey.ec.curve)) { RNP_LOG("EC sign: curve %s is not supported.", curve->pgp_name); ret = RNP_ERROR_NOT_SUPPORTED; break; } /* "-2" because ECDSA on P-521 must work with SHA-512 digest */ if (BITS_TO_BYTES(curve->bitlen) - 2 > hlen) { RNP_LOG("Message hash too small"); ret = RNP_ERROR_BAD_PARAMETERS; break; } if (sig.palg == PGP_PKA_SM2) { #if defined(ENABLE_SM2) ret = sm2_sign(&ctx.rng, &material.ecc, hash_alg, hval, hlen, &seckey.ec); if (ret) { RNP_LOG("SM2 signing failed"); } #else RNP_LOG("SM2 signing is not available."); ret = RNP_ERROR_NOT_IMPLEMENTED; #endif break; } ret = ecdsa_sign(&ctx.rng, &material.ecc, hash_alg, hval, hlen, &seckey.ec); if (ret) { RNP_LOG("ECDSA signing failed"); } break; } default: RNP_LOG("Unsupported algorithm %d", sig.palg); break; } if (ret) { throw rnp::rnp_exception(ret); } try { sig.write_material(material); } catch (const std::exception &e) { RNP_LOG("%s", e.what()); throw; } } rnp_result_t signature_validate(const pgp_signature_t & sig, const pgp_key_material_t & key, rnp::Hash & hash, const rnp::SecurityContext &ctx) { if (sig.palg != key.alg) { RNP_LOG("Signature and key do not agree on algorithm type: %d vs %d", (int) sig.palg, (int) key.alg); return RNP_ERROR_BAD_PARAMETERS; } /* Check signature security */ auto action = sig.is_document() ? rnp::SecurityAction::VerifyData : rnp::SecurityAction::VerifyKey; if (ctx.profile.hash_level(sig.halg, sig.creation(), action) < rnp::SecurityLevel::Default) { RNP_LOG("Insecure hash algorithm %d, marking signature as invalid.", sig.halg); return RNP_ERROR_SIGNATURE_INVALID; } /* Finalize hash */ uint8_t hval[PGP_MAX_HASH_SIZE]; size_t hlen = 0; try { signature_hash_finish(sig, hash, hval, hlen); } catch (const std::exception &e) { RNP_LOG("Failed to finalize signature hash."); return RNP_ERROR_GENERIC; } /* compare lbits */ if (memcmp(hval, sig.lbits, 2)) { RNP_LOG("wrong lbits"); return RNP_ERROR_SIGNATURE_INVALID; } /* validate signature */ pgp_signature_material_t material = {}; try { sig.parse_material(material); } catch (const std::exception &e) { RNP_LOG("%s", e.what()); return RNP_ERROR_OUT_OF_MEMORY; } rnp_result_t ret = RNP_ERROR_GENERIC; switch (sig.palg) { case PGP_PKA_DSA: ret = dsa_verify(&material.dsa, hval, hlen, &key.dsa); break; case PGP_PKA_EDDSA: ret = eddsa_verify(&material.ecc, hval, hlen, &key.ec); break; case PGP_PKA_SM2: #if defined(ENABLE_SM2) ret = sm2_verify(&material.ecc, hash.alg(), hval, hlen, &key.ec); #else RNP_LOG("SM2 verification is not available."); ret = RNP_ERROR_NOT_IMPLEMENTED; #endif break; case PGP_PKA_RSA: case PGP_PKA_RSA_SIGN_ONLY: ret = rsa_verify_pkcs1(&material.rsa, sig.halg, hval, hlen, &key.rsa); break; case PGP_PKA_RSA_ENCRYPT_ONLY: RNP_LOG("RSA encrypt-only signature considered as invalid."); ret = RNP_ERROR_SIGNATURE_INVALID; break; case PGP_PKA_ECDSA: if (!curve_supported(key.ec.curve)) { RNP_LOG("ECDSA verify: curve %d is not supported.", (int) key.ec.curve); ret = RNP_ERROR_NOT_SUPPORTED; break; } ret = ecdsa_verify(&material.ecc, hash.alg(), hval, hlen, &key.ec); break; case PGP_PKA_ELGAMAL: case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: RNP_LOG("ElGamal are considered as invalid."); ret = RNP_ERROR_SIGNATURE_INVALID; break; default: RNP_LOG("Unknown algorithm"); ret = RNP_ERROR_BAD_PARAMETERS; } return ret; }