diff --git a/lib/crypt.cpp b/lib/crypt.cpp index 904fe2736ca..d3875dd78d3 100644 --- a/lib/crypt.cpp +++ b/lib/crypt.cpp @@ -1,5 +1,5 @@ // This file is part of BOINC. -// http://boinc.berkeley.edu +// https://boinc.berkeley.edu // Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,10 @@ #include #include #include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#include +#endif #include "boinc_stdio.h" #include "md5_file.h" @@ -62,7 +67,9 @@ int print_hex_data(FILE* f, DATA_BLOCK& x) { fprintf(f, "%02x", x.data[i]); if (i%32==31) fprintf(f, "\n"); } - if (x.len%32 != 0) fprintf(f, "\n"); + if (x.len%32 != 0) { + fprintf(f, "\n"); + } fprintf(f, ".\n"); return 0; } @@ -77,9 +84,13 @@ int sprint_hex_data(char* out_buf, DATA_BLOCK& x) { for (i=0; ibits = num_bits; len = size - sizeof(key->bits); while (1) { p = fgets(buf, 256, f); - if (!p) break; + if (!p) { + break; + } n = (strlen(p)-1)/2; - if (n == 0) break; + if (n == 0) { + break; + } for (i=0; idata[j++] = b; } } - if (j != len) return ERR_NULL; + if (j != len) { + return ERR_NULL; + } #else int fs = fscanf(f, "%d", &num_bits); - if (fs != 1) return ERR_NULL; + if (fs != 1) { + return ERR_NULL; + } key->bits = (unsigned short)num_bits; len = size - (int)sizeof(key->bits); for (i=0; idata[i] = (unsigned char)n; } fs = fscanf(f, "."); - if (fs == EOF) return ERR_NULL; + if (fs == EOF) { + return ERR_NULL; + } #endif return 0; } @@ -233,9 +265,13 @@ int sscan_key_hex(const char* buf, KEY* key, int size) { key->bits = (unsigned short)num_bits; //key->bits is a short //fprintf(stderr, "key->bits = %d\n", key->bits); - if (n != 1) return ERR_XML_PARSE; + if (n != 1) { + return ERR_XML_PARSE; + } buf = strchr(buf, '\n'); - if (!buf) return ERR_XML_PARSE; + if (!buf) { + return ERR_XML_PARSE; + } buf += 1; db.data = key->data; db.len = (unsigned)(size - sizeof(key->bits)); @@ -249,13 +285,119 @@ int sscan_key_hex(const char* buf, KEY* key, int size) { // The output block must be decrypted in its entirety. // int encrypt_private(R_RSA_PRIVATE_KEY& key, DATA_BLOCK& in, DATA_BLOCK& out) { - int n, modulus_len, retval; + int n, modulus_len; modulus_len = (key.bits+7)/8; n = in.len; if (n >= modulus_len-11) { n = modulus_len-11; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + // OpenSSL 3.x: use EVP_PKEY_sign with RSA PKCS1 padding + using BN_ptr = + std::unique_ptr; + using PKEY_ptr = + std::unique_ptr; + using PCTX_ptr = + std::unique_ptr; + using PARAMS_ptr = + std::unique_ptr; + using BLDP_ptr = + std::unique_ptr; + + int ret = ERR_CRYPTO; + + BN_ptr n_bn(BN_bin2bn(key.modulus, sizeof(key.modulus), NULL), BN_free); + BN_ptr e_bn(BN_bin2bn(key.publicExponent, sizeof(key.publicExponent), + NULL), BN_free); + BN_ptr d_bn(BN_bin2bn(key.exponent, sizeof(key.exponent), NULL), BN_free); + BN_ptr p_bn(BN_bin2bn(key.prime[0], sizeof(key.prime[0]), NULL), BN_free); + BN_ptr q_bn(BN_bin2bn(key.prime[1], sizeof(key.prime[1]), NULL), BN_free); + BN_ptr dmp1_bn(BN_bin2bn(key.primeExponent[0], sizeof(key.primeExponent[0]), + NULL), BN_free); + BN_ptr dmq1_bn(BN_bin2bn(key.primeExponent[1], sizeof(key.primeExponent[1]), + NULL), BN_free); + BN_ptr iqmp_bn(BN_bin2bn(key.coefficient, sizeof(key.coefficient), NULL), + BN_free); + + if (!n_bn || !e_bn || !d_bn || !p_bn || !q_bn || !dmp1_bn || !dmq1_bn || + !iqmp_bn) { + return ERR_CRYPTO; + } + + BLDP_ptr bld(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free); + if (!bld) { + return ERR_CRYPTO; + } + if (!OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_N, n_bn.get())) { + return ERR_CRYPTO; + } + if (!OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_E, e_bn.get())) { + return ERR_CRYPTO; + } + if (!OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_D, d_bn.get())) { + return ERR_CRYPTO; + } + if (!OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_FACTOR1, + p_bn.get())) { + return ERR_CRYPTO; + } + if (!OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_FACTOR2, + q_bn.get())) { + return ERR_CRYPTO; + } + if (!OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_EXPONENT1, + dmp1_bn.get())) { + return ERR_CRYPTO; + } + if (!OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_EXPONENT2, + dmq1_bn.get())) { + return ERR_CRYPTO; + } + if (!OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_COEFFICIENT, + iqmp_bn.get())) { + return ERR_CRYPTO; + } + + PARAMS_ptr params(OSSL_PARAM_BLD_to_param(bld.get()), OSSL_PARAM_free); + if (!params) { + return ERR_CRYPTO; + } + + PCTX_ptr from_ctx(EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL), + EVP_PKEY_CTX_free); + if (!from_ctx) { + return ERR_CRYPTO; + } + if (EVP_PKEY_fromdata_init(from_ctx.get()) <= 0) { + return ERR_CRYPTO; + } + + EVP_PKEY* raw_pkey = nullptr; + if (EVP_PKEY_fromdata(from_ctx.get(), &raw_pkey, EVP_PKEY_KEYPAIR, + params.get()) <= 0) { + return ERR_CRYPTO; + } + PKEY_ptr pkey(raw_pkey, EVP_PKEY_free); + + PCTX_ptr sctx(EVP_PKEY_CTX_new(pkey.get(), NULL), EVP_PKEY_CTX_free); + if (!sctx) { + return ERR_CRYPTO; + } + if (EVP_PKEY_sign_init(sctx.get()) <= 0) { + return ERR_CRYPTO; + } + if (EVP_PKEY_CTX_set_rsa_padding(sctx.get(), RSA_PKCS1_PADDING) <= 0) { + return ERR_CRYPTO; + } + + size_t outlen = (size_t)out.len; + if (EVP_PKEY_sign(sctx.get(), out.data, &outlen, in.data, (size_t)n) > 0) { + out.len = (unsigned int)outlen; + ret = 0; + } + return ret; +#else RSA* rp = RSA_new(); private_to_openssl(key, rp); retval = RSA_private_encrypt(n, in.data, out.data, rp, RSA_PKCS1_PADDING); @@ -266,13 +408,81 @@ int encrypt_private(R_RSA_PRIVATE_KEY& key, DATA_BLOCK& in, DATA_BLOCK& out) { out.len = RSA_size(rp); RSA_free(rp); return 0; +#endif } int decrypt_public(R_RSA_PUBLIC_KEY& key, DATA_BLOCK& in, DATA_BLOCK& out) { - int retval; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + // OpenSSL 3.x: use EVP_PKEY_verify_recover with RSA PKCS1 padding (RAII, no goto) + using BN_ptr = std::unique_ptr; + using PKEY_ptr = std::unique_ptr; + using PCTX_ptr = std::unique_ptr; + using PARAMS_ptr = std::unique_ptr; + using BLDP_ptr = std::unique_ptr; + + int ret = ERR_CRYPTO; + + BN_ptr n_bn(BN_bin2bn(key.modulus, sizeof(key.modulus), NULL), BN_free); + BN_ptr e_bn(BN_bin2bn(key.exponent, sizeof(key.exponent), NULL), BN_free); + if (!n_bn || !e_bn) { + return ERR_CRYPTO; + } + + BLDP_ptr bld(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free); + if (!bld) { + return ERR_CRYPTO; + } + if (!OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_N, n_bn.get())) { + return ERR_CRYPTO; + } + if (!OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_E, e_bn.get())) { + return ERR_CRYPTO; + } + PARAMS_ptr params(OSSL_PARAM_BLD_to_param(bld.get()), OSSL_PARAM_free); + if (!params) { + return ERR_CRYPTO; + } + + PCTX_ptr from_ctx(EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL), + EVP_PKEY_CTX_free); + if (!from_ctx) { + return ERR_CRYPTO; + } + if (EVP_PKEY_fromdata_init(from_ctx.get()) <= 0) { + return ERR_CRYPTO; + } + EVP_PKEY* raw_pkey = nullptr; + if (EVP_PKEY_fromdata(from_ctx.get(), &raw_pkey, EVP_PKEY_PUBLIC_KEY, + params.get()) <= 0) { + return ERR_CRYPTO; + } + PKEY_ptr pkey(raw_pkey, EVP_PKEY_free); + + PCTX_ptr vctx(EVP_PKEY_CTX_new(pkey.get(), NULL), EVP_PKEY_CTX_free); + if (!vctx) { + return ERR_CRYPTO; + } + if (EVP_PKEY_verify_recover_init(vctx.get()) <= 0) { + return ERR_CRYPTO; + } + if (EVP_PKEY_CTX_set_rsa_padding(vctx.get(), RSA_PKCS1_PADDING) <= 0) { + return ERR_CRYPTO; + } + + size_t outlen = (size_t)out.len; + if (EVP_PKEY_verify_recover(vctx.get(), out.data, &outlen, in.data, + (size_t)in.len) > 0) { + out.len = (unsigned int)outlen; + ret = 0; + } + return ret; +#else RSA* rp = RSA_new(); public_to_openssl(key, rp); - retval = RSA_public_decrypt(in.len, in.data, out.data, rp, RSA_PKCS1_PADDING); + retval = RSA_public_decrypt(in.len, in.data, out.data, rp, + RSA_PKCS1_PADDING); if (retval < 0) { RSA_free(rp); return ERR_CRYPTO; @@ -280,6 +490,7 @@ int decrypt_public(R_RSA_PUBLIC_KEY& key, DATA_BLOCK& in, DATA_BLOCK& out) { out.len = RSA_size(rp); RSA_free(rp); return 0; +#endif } int sign_file(const char* path, R_RSA_PRIVATE_KEY& key, DATA_BLOCK& signature) { @@ -289,15 +500,20 @@ int sign_file(const char* path, R_RSA_PRIVATE_KEY& key, DATA_BLOCK& signature) { int retval; retval = md5_file(path, md5_buf, file_length); - if (retval) return retval; + if (retval) { + return retval; + } in_block.data = (unsigned char*)md5_buf; in_block.len = (unsigned int)strlen(md5_buf); retval = encrypt_private(key, in_block, signature); - if (retval) return retval; + if (retval) { + return retval; + } return 0; } -int sign_block(DATA_BLOCK& data_block, R_RSA_PRIVATE_KEY& key, DATA_BLOCK& signature) { +int sign_block(DATA_BLOCK& data_block, R_RSA_PRIVATE_KEY& key, + DATA_BLOCK& signature) { char md5_buf[MD5_LEN]; int retval; DATA_BLOCK in_block; @@ -327,7 +543,9 @@ int generate_signature( signature_data.data = signature_buf; signature_data.len = SIGNATURE_SIZE_BINARY; retval = sign_block(block, key, signature_data); - if (retval) return retval; + if (retval) { + return retval; + } sprint_hex_data(signature_hex, signature_data); return 0; } @@ -379,7 +597,9 @@ int check_file_signature2( signature.data = signature_buf; signature.len = sizeof(signature_buf); retval = sscan_hex_data(signature_text, signature); - if (retval) return retval; + if (retval) { + return retval; + } return check_file_signature(md5, key, signature, answer); } @@ -396,16 +616,22 @@ int check_string_signature( DATA_BLOCK signature, clear_signature; retval = md5_block((const unsigned char*)text, (int)strlen(text), md5_buf); - if (retval) return retval; + if (retval) { + return retval; + } n = (int)strlen(md5_buf); signature.data = signature_buf; signature.len = sizeof(signature_buf); retval = sscan_hex_data(signature_text, signature); - if (retval) return retval; + if (retval) { + return retval; + } clear_signature.data = (unsigned char*)clear_buf; clear_signature.len = 256; retval = decrypt_public(key, signature, clear_signature); - if (retval) return retval; + if (retval) { + return retval; + } answer = !strncmp(md5_buf, clear_buf, n); return 0; } @@ -413,13 +639,16 @@ int check_string_signature( // Same, where public key is also encoded as text // int check_string_signature2( - const char* text, const char* signature_text, const char* key_text, bool& answer + const char* text, const char* signature_text, const char* key_text, + bool& answer ) { R_RSA_PUBLIC_KEY key; int retval; retval = sscan_key_hex(key_text, (KEY*)&key, sizeof(key)); - if (retval) return retval; + if (retval) { + return retval; + } return check_string_signature(text, signature_text, key, answer); } @@ -469,10 +698,12 @@ void openssl_to_keys( RSA_get0_factors(rp, &p, &q); RSA_get0_crt_params(rp, &dmp1, &dmq1, &iqmp); - if (n) + if (n) { bn_to_bin(n, pub.modulus, sizeof(pub.modulus)); - if (e) + } + if (e) { bn_to_bin(e, pub.exponent, sizeof(pub.exponent)); + } #else bn_to_bin(rp->n, pub.modulus, sizeof(pub.modulus)); bn_to_bin(rp->e, pub.exponent, sizeof(pub.exponent)); @@ -481,22 +712,30 @@ void openssl_to_keys( memset(&priv, 0, sizeof(priv)); priv.bits = (unsigned short)nbits; #ifdef HAVE_OPAQUE_RSA_DSA_DH - if (n) + if (n) { bn_to_bin(n, priv.modulus, sizeof(priv.modulus)); - if (e) + } + if (e) { bn_to_bin(e, priv.publicExponent, sizeof(priv.publicExponent)); - if (d) + } + if (d) { bn_to_bin(d, priv.exponent, sizeof(priv.exponent)); - if (p) + } + if (p) { bn_to_bin(p, priv.prime[0], sizeof(priv.prime[0])); - if (q) + } + if (q) { bn_to_bin(q, priv.prime[1], sizeof(priv.prime[1])); - if (dmp1) + } + if (dmp1) { bn_to_bin(dmp1, priv.primeExponent[0], sizeof(priv.primeExponent[0])); - if (dmq1) + } + if (dmq1) { bn_to_bin(dmq1, priv.primeExponent[1], sizeof(priv.primeExponent[1])); - if (iqmp) + } + if (iqmp) { bn_to_bin(iqmp, priv.coefficient, sizeof(priv.coefficient)); + } #else bn_to_bin(rp->n, priv.modulus, sizeof(priv.modulus)); bn_to_bin(rp->e, priv.publicExponent, sizeof(priv.publicExponent)); @@ -537,8 +776,10 @@ void private_to_openssl(R_RSA_PRIVATE_KEY& priv, RSA* rp) { rp->d = BN_bin2bn(priv.exponent, sizeof(priv.exponent), 0); rp->p = BN_bin2bn(priv.prime[0], sizeof(priv.prime[0]), 0); rp->q = BN_bin2bn(priv.prime[1], sizeof(priv.prime[1]), 0); - rp->dmp1 = BN_bin2bn(priv.primeExponent[0], sizeof(priv.primeExponent[0]), 0); - rp->dmq1 = BN_bin2bn(priv.primeExponent[1], sizeof(priv.primeExponent[1]), 0); + rp->dmp1 = BN_bin2bn(priv.primeExponent[0], sizeof(priv.primeExponent[0]), + 0); + rp->dmq1 = BN_bin2bn(priv.primeExponent[1], sizeof(priv.primeExponent[1]), + 0); rp->iqmp = BN_bin2bn(priv.coefficient, sizeof(priv.coefficient), 0); #endif } @@ -563,8 +804,9 @@ static int _bn2bin(const BIGNUM *from, unsigned char *to, int max) { return(0); } memset(to,0,(unsigned int)max); - if (!BN_bn2bin(from,&(to[max-i]))) + if (!BN_bn2bin(from,&(to[max-i]))) { return(0); + } return(1); } @@ -584,40 +826,56 @@ int openssl_to_private(RSA *from, R_RSA_PRIVATE_KEY *to) { RSA_get0_crt_params(from, &dmp1, &dmq1, &iqmp); to->bits = (unsigned short)BN_num_bits(n); - if (!_bn2bin(n,to->modulus,MAX_RSA_MODULUS_LEN)) + if (!_bn2bin(n,to->modulus,MAX_RSA_MODULUS_LEN)) { return(0); - if (!_bn2bin(e,to->publicExponent,MAX_RSA_MODULUS_LEN)) + } + if (!_bn2bin(e,to->publicExponent,MAX_RSA_MODULUS_LEN)) { return(0); - if (!_bn2bin(d,to->exponent,MAX_RSA_MODULUS_LEN)) + } + if (!_bn2bin(d,to->exponent,MAX_RSA_MODULUS_LEN)) { return(0); - if (!_bn2bin(p,to->prime[0],MAX_RSA_PRIME_LEN)) + } + if (!_bn2bin(p,to->prime[0],MAX_RSA_PRIME_LEN)) { return(0); - if (!_bn2bin(q,to->prime[1],MAX_RSA_PRIME_LEN)) + } + if (!_bn2bin(q,to->prime[1],MAX_RSA_PRIME_LEN)) { return(0); - if (!_bn2bin(dmp1,to->primeExponent[0],MAX_RSA_PRIME_LEN)) + } + if (!_bn2bin(dmp1,to->primeExponent[0],MAX_RSA_PRIME_LEN)) { return(0); - if (!_bn2bin(dmq1,to->primeExponent[1],MAX_RSA_PRIME_LEN)) + } + if (!_bn2bin(dmq1,to->primeExponent[1],MAX_RSA_PRIME_LEN)) { return(0); - if (!_bn2bin(iqmp,to->coefficient,MAX_RSA_PRIME_LEN)) + } + if (!_bn2bin(iqmp,to->coefficient,MAX_RSA_PRIME_LEN)) { return(0); + } #else to->bits = BN_num_bits(from->n); - if (!_bn2bin(from->n,to->modulus,MAX_RSA_MODULUS_LEN)) + if (!_bn2bin(from->n,to->modulus,MAX_RSA_MODULUS_LEN)) { return(0); - if (!_bn2bin(from->e,to->publicExponent,MAX_RSA_MODULUS_LEN)) + } + if (!_bn2bin(from->e,to->publicExponent,MAX_RSA_MODULUS_LEN)) { return(0); - if (!_bn2bin(from->d,to->exponent,MAX_RSA_MODULUS_LEN)) + } + if (!_bn2bin(from->d,to->exponent,MAX_RSA_MODULUS_LEN)) { return(0); - if (!_bn2bin(from->p,to->prime[0],MAX_RSA_PRIME_LEN)) + } + if (!_bn2bin(from->p,to->prime[0],MAX_RSA_PRIME_LEN)) { return(0); - if (!_bn2bin(from->q,to->prime[1],MAX_RSA_PRIME_LEN)) + } + if (!_bn2bin(from->q,to->prime[1],MAX_RSA_PRIME_LEN)) { return(0); - if (!_bn2bin(from->dmp1,to->primeExponent[0],MAX_RSA_PRIME_LEN)) + } + if (!_bn2bin(from->dmp1,to->primeExponent[0],MAX_RSA_PRIME_LEN)) { return(0); - if (!_bn2bin(from->dmq1,to->primeExponent[1],MAX_RSA_PRIME_LEN)) + } + if (!_bn2bin(from->dmq1,to->primeExponent[1],MAX_RSA_PRIME_LEN)) { return(0); - if (!_bn2bin(from->iqmp,to->coefficient,MAX_RSA_PRIME_LEN)) + } + if (!_bn2bin(from->iqmp,to->coefficient,MAX_RSA_PRIME_LEN)) { return(0); + } #endif return 1; } @@ -645,8 +903,9 @@ int check_validity_of_cert( lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); X509_LOOKUP_add_dir(lookup, (char *)caPath, X509_FILETYPE_PEM); if ((ctx = X509_STORE_CTX_new()) != 0) { - if (X509_STORE_CTX_init(ctx, store, cert, 0) == 1) + if (X509_STORE_CTX_init(ctx, store, cert, 0) == 1) { retval = X509_verify_cert(ctx); + } X509_STORE_CTX_free(ctx); } X509_STORE_free(store); @@ -669,6 +928,40 @@ int check_validity_of_cert( #else if (pubKey->type == EVP_PKEY_RSA) { #endif +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX *vctx = EVP_PKEY_CTX_new(pubKey, NULL); + if (!vctx) { + X509_free(cert); + EVP_PKEY_free(pubKey); + BIO_vfree(bio); + return 0; + } + if (EVP_PKEY_verify_init(vctx) <= 0) { + EVP_PKEY_CTX_free(vctx); + X509_free(cert); + EVP_PKEY_free(pubKey); + BIO_vfree(bio); + return 0; + } + if (EVP_PKEY_CTX_set_rsa_padding(vctx, RSA_PKCS1_PADDING) <= 0) { + EVP_PKEY_CTX_free(vctx); + X509_free(cert); + EVP_PKEY_free(pubKey); + BIO_vfree(bio); + return 0; + } + if (EVP_PKEY_CTX_set_signature_md(vctx, EVP_md5()) <= 0) { + EVP_PKEY_CTX_free(vctx); + X509_free(cert); + EVP_PKEY_free(pubKey); + BIO_vfree(bio); + return 0; + } + int vr = EVP_PKEY_verify(vctx, sfileMsg, sfsize, md5_md, + MD5_DIGEST_LENGTH); + retval = (vr == 1) ? 1 : 0; + EVP_PKEY_CTX_free(vctx); +#else BN_CTX *c = BN_CTX_new(); if (!c) { X509_free(cert); @@ -678,9 +971,6 @@ int check_validity_of_cert( } #ifdef HAVE_OPAQUE_RSA_DSA_DH RSA *rsa; - // CAUTION: In OpenSSL 3.0.0, EVP_PKEY_get0_RSA() now returns a - // pointer of type "const struct rsa_st*" to an immutable value. - // Do not try to modify the contents of the returned struct. rsa = (rsa_st*)EVP_PKEY_get0_RSA(pubKey); if (!RSA_blinding_on(rsa, c)) { #else @@ -693,13 +983,16 @@ int check_validity_of_cert( return 0; } #ifdef HAVE_OPAQUE_RSA_DSA_DH - retval = RSA_verify(NID_md5, md5_md, MD5_DIGEST_LENGTH, sfileMsg, sfsize, rsa); + retval = RSA_verify(NID_md5, md5_md, MD5_DIGEST_LENGTH, sfileMsg, + sfsize, rsa); RSA_blinding_off(rsa); #else - retval = RSA_verify(NID_md5, md5_md, MD5_DIGEST_LENGTH, sfileMsg, sfsize, pubKey->pkey.rsa); + retval = RSA_verify(NID_md5, md5_md, MD5_DIGEST_LENGTH, sfileMsg, + sfsize, pubKey->pkey.rsa); RSA_blinding_off(pubKey->pkey.rsa); #endif BN_CTX_free(c); +#endif } #ifdef HAVE_OPAQUE_EVP_PKEY if (EVP_PKEY_id(pubKey) == EVP_PKEY_DSA) { @@ -722,7 +1015,6 @@ char *check_validity( const char *certPath, const char *origFile, unsigned char *signature, char* caPath ) { - MD5_CTX md5CTX; int rbytes; unsigned char md5_md[MD5_DIGEST_LENGTH], rbuf[2048]; @@ -737,11 +1029,38 @@ char *check_validity( } FILE* of = boinc_fopen(origFile, "r"); if (!of) return NULL; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + if (!mdctx) { + fclose(of); return NULL; + } + unsigned int md_len = 0; + if (EVP_DigestInit_ex(mdctx, EVP_md5(), NULL) != 1) { + EVP_MD_CTX_free(mdctx); + fclose(of); + return NULL; + } + while (0 != (rbytes = (int)fread(rbuf, 1, sizeof(rbuf), of))) { + if (EVP_DigestUpdate(mdctx, rbuf, (size_t)rbytes) != 1) { + EVP_MD_CTX_free(mdctx); + fclose(of); + return NULL; + } + } + if (EVP_DigestFinal_ex(mdctx, md5_md, &md_len) != 1) { + EVP_MD_CTX_free(mdctx); + fclose(of); + return NULL; + } + EVP_MD_CTX_free(mdctx); +#else + MD5_CTX md5CTX; MD5_Init(&md5CTX); while (0 != (rbytes = (int)fread(rbuf, 1, sizeof(rbuf), of))) { MD5_Update(&md5CTX, rbuf, rbytes); } MD5_Final(md5_md, &md5CTX); +#endif fclose(of); DIRREF dir = dir_open(certPath); @@ -749,8 +1068,8 @@ char *check_validity( char file[MAXPATHLEN]; while (!dir_scan(file, dir, sizeof(file))) { char fpath[MAXPATHLEN]; - snprintf(fpath, sizeof(fpath), "%.*s/%.*s", DIR_LEN, certPath, FILE_LEN, file); - // TODO : replace '128' + snprintf(fpath, sizeof(fpath), "%.*s/%.*s", DIR_LEN, certPath, + FILE_LEN, file); if (check_validity_of_cert(fpath, md5_md, signature, 128, caPath)) { dir_close(dir); return strdup(fpath); @@ -764,7 +1083,6 @@ char *check_validity( int cert_verify_file( CERT_SIGS* signatures, const char* origFile, const char* trustLocation ) { - MD5_CTX md5CTX; int rbytes; unsigned char md5_md[MD5_DIGEST_LENGTH], rbuf[2048]; char buf[256]; @@ -788,11 +1106,39 @@ int cert_verify_file( if (!is_file(origFile)) return false; FILE* of = boinc_fopen(origFile, "r"); if (!of) return false; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + if (!mdctx) { + fclose(of); + return false; + } + unsigned int md_len = 0; + if (EVP_DigestInit_ex(mdctx, EVP_md5(), NULL) != 1) { + EVP_MD_CTX_free(mdctx); + fclose(of); + return false; + } + while (0 != (rbytes = (int)fread(rbuf, 1, sizeof(rbuf), of))) { + if (EVP_DigestUpdate(mdctx, rbuf, (size_t)rbytes) != 1) { + EVP_MD_CTX_free(mdctx); + fclose(of); + return false; + } + } + if (EVP_DigestFinal_ex(mdctx, md5_md, &md_len) != 1) { + EVP_MD_CTX_free(mdctx); + fclose(of); + return false; + } + EVP_MD_CTX_free(mdctx); +#else + MD5_CTX md5CTX; MD5_Init(&md5CTX); while (0 != (rbytes = (int)fread(rbuf, 1, sizeof(rbuf), of))) { MD5_Update(&md5CTX, rbuf, rbytes); } MD5_Final(md5_md, &md5CTX); +#endif fclose(of); for(unsigned int i=0;i < signatures->signatures.size(); i++) { sig_db.data = (unsigned char*)calloc(128, sizeof(char)); @@ -804,8 +1150,8 @@ int cert_verify_file( sscan_hex_data(signatures->signatures.at(i).signature, sig_db); file_counter = 0; while (1) { - snprintf(fbuf, MAXPATHLEN, "%s/%s.%d", trustLocation, signatures->signatures.at(i).hash, - file_counter); + snprintf(fbuf, MAXPATHLEN, "%s/%s.%d", trustLocation, + signatures->signatures.at(i).hash, file_counter); #ifndef _USING_FCGI_ FILE *f = fopen(fbuf, "r"); #else @@ -830,18 +1176,22 @@ int cert_verify_file( X509_free(cert); BIO_vfree(bio); if (strcmp(buf, signatures->signatures.at(i).subject)) { - printf("Subject does not match ('%s' <-> '%s')\n", buf, signatures->signatures.at(i).subject); + printf("Subject does not match ('%s' <-> '%s')\n", buf, + signatures->signatures.at(i).subject); file_counter++; continue; } - verified = check_validity_of_cert(fbuf, md5_md, sig_db.data, 128, trustLocation); - if (verified) + verified = check_validity_of_cert(fbuf, md5_md, sig_db.data, 128, + trustLocation); + if (verified) { break; + } file_counter++; } free(sig_db.data); - if (!verified) + if (!verified) { return false; + } } return verified; } diff --git a/tests/unit-tests/lib/CMakeLists.txt b/tests/unit-tests/lib/CMakeLists.txt index 22ed03674b7..d7fbeb391c8 100644 --- a/tests/unit-tests/lib/CMakeLists.txt +++ b/tests/unit-tests/lib/CMakeLists.txt @@ -2,6 +2,18 @@ file(GLOB SRCS *.cpp) add_executable(test_lib ${SRCS}) -TARGET_LINK_LIBRARIES(test_lib "${SCHED_LIB}" "${BOINC_CRYPT_LIB}" "${BOINC_LIB}" pthread GTest::gtest GTest::gtest_main) +# Ensure OpenSSL is linked for cryptography symbols used by libboinc_crypt and tests +find_package(OpenSSL REQUIRED) + +TARGET_LINK_LIBRARIES(test_lib + "${SCHED_LIB}" + "${BOINC_CRYPT_LIB}" + "${BOINC_LIB}" + OpenSSL::SSL + OpenSSL::Crypto + pthread + GTest::gtest + GTest::gtest_main +) add_test(NAME test_lib COMMAND test_lib) diff --git a/tests/unit-tests/lib/test_crypt.cpp b/tests/unit-tests/lib/test_crypt.cpp new file mode 100644 index 00000000000..aab763867ef --- /dev/null +++ b/tests/unit-tests/lib/test_crypt.cpp @@ -0,0 +1,289 @@ +// This file is part of BOINC. +// https://boinc.berkeley.edu +// Copyright (C) 2025 University of California +// +// BOINC 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 3 of the License, or (at your option) any later version. +// +// BOINC 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 BOINC. If not, see . + +#include "gtest/gtest.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "crypt.h" +#include "md5_file.h" + +using std::string; +using std::vector; + +namespace test_crypt { + +// Utilities +static EVP_PKEY* make_evp_rsa_1024() { + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr); + if (!ctx) return nullptr; + if (EVP_PKEY_keygen_init(ctx) <= 0) { + EVP_PKEY_CTX_free(ctx); + return nullptr; + } + if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 1024) <= 0) { + EVP_PKEY_CTX_free(ctx); + return nullptr; + } + EVP_PKEY* pkey = nullptr; + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + EVP_PKEY_CTX_free(ctx); + return nullptr; + } + EVP_PKEY_CTX_free(ctx); + return pkey; +} + +static void bn_to_fixed(const BIGNUM* bn, unsigned char* out, int out_len) { + memset(out, 0, out_len); + if (!bn) return; + int bytes = BN_num_bytes(bn); + if (bytes > out_len) { + // take the least significant bytes + std::vector tmp(bytes); + BN_bn2bin(bn, tmp.data()); + memcpy(out + (out_len - out_len), tmp.data() + (bytes - out_len), out_len); + } else { + BN_bn2bin(bn, out + (out_len - bytes)); + } +} + +static bool fill_keys_from_evp(EVP_PKEY* pkey, int nbits, R_RSA_PRIVATE_KEY& priv, R_RSA_PUBLIC_KEY& pub) { + BIGNUM *n=nullptr, *e=nullptr, *d=nullptr, *p=nullptr, *q=nullptr, *dmp1=nullptr, *dmq1=nullptr, *iqmp=nullptr; + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n)) return false; + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e)) { BN_free(n); return false; } + // Private params are optional on public-only keys + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_D, &d); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, &p); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_FACTOR2, &q); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT1, &dmp1); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT2, &dmq1); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_COEFFICIENT, &iqmp); + + pub.bits = (unsigned short)nbits; + bn_to_fixed(n, pub.modulus, sizeof(pub.modulus)); + bn_to_fixed(e, pub.exponent, sizeof(pub.exponent)); + + memset(&priv, 0, sizeof(priv)); + priv.bits = (unsigned short)nbits; + bn_to_fixed(n, priv.modulus, sizeof(priv.modulus)); + bn_to_fixed(e, priv.publicExponent, sizeof(priv.publicExponent)); + bn_to_fixed(d, priv.exponent, sizeof(priv.exponent)); + bn_to_fixed(p, priv.prime[0], sizeof(priv.prime[0])); + bn_to_fixed(q, priv.prime[1], sizeof(priv.prime[1])); + bn_to_fixed(dmp1, priv.primeExponent[0], sizeof(priv.primeExponent[0])); + bn_to_fixed(dmq1, priv.primeExponent[1], sizeof(priv.primeExponent[1])); + bn_to_fixed(iqmp, priv.coefficient, sizeof(priv.coefficient)); + + if (n) BN_free(n); + if (e) BN_free(e); + if (d) BN_free(d); + if (p) BN_free(p); + if (q) BN_free(q); + if (dmp1) BN_free(dmp1); + if (dmq1) BN_free(dmq1); + if (iqmp) BN_free(iqmp); + return true; +} + +static string key_to_string(KEY* key, int size) { + FILE* f = tmpfile(); + if (!f) return {}; + print_key_hex(f, key, size); + fflush(f); + fseek(f, 0, SEEK_END); + long sz = ftell(f); + rewind(f); + string out; + out.resize((size_t)sz); + fread(&out[0], 1, (size_t)sz, f); + fclose(f); + return out; +} + +static string data_to_hex_string(const vector& bytes) { + DATA_BLOCK db; + db.data = const_cast(bytes.data()); + db.len = (unsigned int)bytes.size(); + // Max chars: 2 per byte + newlines each 32 bytes + ".\n" and possible extra \n + size_t max_len = 2 * bytes.size() + (bytes.size() / 32 + 2) + 4; + vector buf(max_len + 8, 0); + sprint_hex_data(buf.data(), db); + return string(buf.data()); +} + +static vector hex_string_to_data(const string& hex) { + FILE* f = tmpfile(); + fwrite(hex.data(), 1, hex.size(), f); + rewind(f); + vector out(1024); + DATA_BLOCK db; + db.data = out.data(); + db.len = (unsigned int)out.size(); + scan_hex_data(f, db); + fclose(f); + out.resize(db.len); + return out; +} + +// bn_equal helper removed (unused) + +class CryptTest : public ::testing::Test { +protected: + void TearDown() override {} +}; + +TEST_F(CryptTest, SprintAndScanHexRoundTrip) { + // cover sprint_hex_data and scan_hex_data with >32 bytes to hit newline behavior + vector input(100); + for (size_t i = 0; i < input.size(); i++) input[i] = (unsigned char)i; + string hex = data_to_hex_string(input); + auto output = hex_string_to_data(hex); + ASSERT_EQ(output.size(), input.size()); + EXPECT_TRUE(std::equal(input.begin(), input.end(), output.begin())); +} + +TEST_F(CryptTest, OpenSSLToKeysAndBack) { + EVP_PKEY* pkey = make_evp_rsa_1024(); + ASSERT_NE(pkey, nullptr); + + R_RSA_PRIVATE_KEY priv{}; + R_RSA_PUBLIC_KEY pub{}; + const int nbits = 1024; // we generated 1024-bit key + ASSERT_TRUE(fill_keys_from_evp(pkey, nbits, priv, pub)); + + // Basic sanity on produced keys (exponent should be 65537; modulus non-zero) + bool all_zero = true; + for (unsigned char c : pub.modulus) if (c) { all_zero = false; break; } + EXPECT_FALSE(all_zero); + // publicExponent ends with 0x01 0x00 0x01 in big endian (position depends on length) + EXPECT_EQ(pub.exponent[MAX_RSA_MODULUS_LEN-1], 0x01); + EXPECT_EQ(pub.exponent[MAX_RSA_MODULUS_LEN-3], 0x01); + EXPECT_EQ(pub.bits, nbits); + EVP_PKEY_free(pkey); +} + +TEST_F(CryptTest, EncryptPrivateThenDecryptPublic) { + EVP_PKEY* pkey = make_evp_rsa_1024(); + ASSERT_NE(pkey, nullptr); + + R_RSA_PRIVATE_KEY priv{}; + R_RSA_PUBLIC_KEY pub{}; + ASSERT_TRUE(fill_keys_from_evp(pkey, 1024, priv, pub)); + + const char* msg = "test message"; + DATA_BLOCK in; + in.data = (unsigned char*)msg; + in.len = (unsigned int)strlen(msg); + + unsigned char sigbuf[SIGNATURE_SIZE_BINARY] = {0}; + DATA_BLOCK sig; + sig.data = sigbuf; + sig.len = sizeof(sigbuf); + ASSERT_EQ(encrypt_private(priv, in, sig), 0); + + unsigned char outbuf[SIGNATURE_SIZE_BINARY] = {0}; + DATA_BLOCK out; + out.data = outbuf; + out.len = sizeof(outbuf); + ASSERT_EQ(decrypt_public(pub, sig, out), 0); + + // Expect the decrypted prefix to equal original message + EXPECT_EQ(0, memcmp(outbuf, msg, in.len)); + + EVP_PKEY_free(pkey); +} + +TEST_F(CryptTest, GenerateAndVerifySignatureOnText) { + EVP_PKEY* pkey = make_evp_rsa_1024(); + ASSERT_NE(pkey, nullptr); + + R_RSA_PRIVATE_KEY priv{}; + R_RSA_PUBLIC_KEY pub{}; + ASSERT_TRUE(fill_keys_from_evp(pkey, 1024, priv, pub)); + + char text[] = "The quick brown fox jumps over the lazy dog"; + char sig_hex[SIGNATURE_SIZE_TEXT] = {0}; + ASSERT_EQ(generate_signature(text, sig_hex, priv), 0); + + // Build key text for the public key + string key_text = key_to_string((KEY*)&pub, sizeof(pub)); + + bool answer = false; + ASSERT_EQ(check_string_signature2(text, sig_hex, key_text.c_str(), answer), 0); + EXPECT_TRUE(answer); + + // Tamper 1 hex character and ensure verification fails + string bad_sig(sig_hex); + for (char& c : bad_sig) { + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) { + // flip nibble slightly + if (c == '0') c = '1'; + else if (c == '1') c = '0'; + else if (c == 'a') c = 'b'; + else if (c == 'b') c = 'a'; + else if (c == 'c') c = 'd'; + else if (c == 'd') c = 'c'; + else if (c == 'e') c = 'f'; + else if (c == 'f') c = 'e'; + else c = '0'; + break; + } + } + answer = true; // reset to ensure it flips + // Tampered signature should fail verification. The implementation + // returns a non-zero error (e.g., ERR_CRYPTO) on padding failure. + int rc = check_string_signature2(text, bad_sig.c_str(), key_text.c_str(), answer); + EXPECT_NE(rc, 0); + + EVP_PKEY_free(pkey); +} + +TEST_F(CryptTest, KeyStringParseRoundTrip) { + EVP_PKEY* pkey = make_evp_rsa_1024(); + ASSERT_NE(pkey, nullptr); + + R_RSA_PRIVATE_KEY priv{}; + R_RSA_PUBLIC_KEY pub{}; + ASSERT_TRUE(fill_keys_from_evp(pkey, 1024, priv, pub)); + + string priv_text = key_to_string((KEY*)&priv, sizeof(priv)); + string pub_text = key_to_string((KEY*)&pub, sizeof(pub)); + + R_RSA_PRIVATE_KEY priv2{}; + R_RSA_PUBLIC_KEY pub2{}; + ASSERT_EQ(sscan_key_hex(priv_text.c_str(), (KEY*)&priv2, sizeof(priv2)), 0); + ASSERT_EQ(sscan_key_hex(pub_text.c_str(), (KEY*)&pub2, sizeof(pub2)), 0); + + EXPECT_EQ(priv.bits, priv2.bits); + EXPECT_EQ(pub.bits, pub2.bits); + EXPECT_EQ(0, memcmp(&priv, &priv2, sizeof(priv))); + EXPECT_EQ(0, memcmp(&pub, &pub2, sizeof(pub))); + + EVP_PKEY_free(pkey); +} + +} // namespace test_crypt diff --git a/win_build/unittests.vcxproj b/win_build/unittests.vcxproj index c58188527ab..d9779dc5327 100644 --- a/win_build/unittests.vcxproj +++ b/win_build/unittests.vcxproj @@ -21,7 +21,7 @@ stdcpp17 - gtest_main.lib;gmock_main.lib;%(AdditionalDependencies) + gtest_main.lib;gmock_main.lib;libcrypto.lib;libssl.lib;wsock32.lib;wininet.lib;winmm.lib;libboinc.lib;Crypt32.Lib;%(AdditionalDependencies) $(VcpkgInstalledDir)/debug/lib/manual-link;%(AdditionalLibraryDirectories) $(VcpkgInstalledDir)/lib/manual-link;%(AdditionalLibraryDirectories) Console @@ -35,6 +35,7 @@ +