Source code

Revision control

Copy as Markdown

Other Tools

/*
* 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 <string.h>
#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<rnp::Hash>
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;
}