515 lines
15 KiB
C
515 lines
15 KiB
C
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "fips.h"
|
|
#include "../utils.h"
|
|
#include "../error.h"
|
|
|
|
/*
|
|
* SSL Format
|
|
* RSA PUBLIC KEY -> PKCS#1 format
|
|
* PUBLIC KEY -> PEM Format (SPKI)
|
|
*/
|
|
|
|
static int DEBUG = 0;
|
|
|
|
int fips(const char *pkey, struct audit_fips *st_audit_fips, struct keyinfo *st_keyinfo, const int type, const int is_pubkey){
|
|
int res;
|
|
|
|
if(type == TYPE_RSA){
|
|
if (is_pubkey == 1)
|
|
res = fips_pubkey_rsa(st_audit_fips, st_keyinfo, pkey);
|
|
else
|
|
res = fips_privkey_rsa(st_audit_fips, st_keyinfo, pkey);
|
|
}
|
|
else if (type == TYPE_ELLIPTIC){
|
|
if (is_pubkey){
|
|
EC_KEY *ec = fips_load_pubkey_ecc(pkey);
|
|
if (!ec)
|
|
return FIPS_ERR_LOAD_ECC_PUBKEY;
|
|
|
|
res = fips_pubkey_ecc(ec, st_audit_fips, st_keyinfo, pkey);
|
|
}
|
|
}
|
|
else if (type == TYPE_X509)
|
|
res = fips_x509(st_audit_fips, st_keyinfo, pkey);
|
|
|
|
return res;
|
|
}
|
|
|
|
/********************************************************/
|
|
/* RSA part */
|
|
/********************************************************/
|
|
|
|
/*
|
|
* This function load public RSA key and make an audit on it
|
|
*/
|
|
static int fips_pubkey_rsa(struct audit_fips *st_audit_fips, struct keyinfo *st_keyinfo, const char *pkey) {
|
|
int res;
|
|
|
|
/*
|
|
* Now, we check if the public key is compliant with FIPS
|
|
* The program check the length of the key, the exponent
|
|
*/
|
|
|
|
RSA *rsa = NULL;
|
|
res = loadkeys_rsa(&rsa, pkey, &st_keyinfo->st_rsa.format);
|
|
|
|
if (res > 0 || !rsa){
|
|
printf("Failed to read the public key\n");
|
|
return res;
|
|
}
|
|
|
|
/* We have loaded our RSA key, we can audit it */
|
|
audit_rsa_keys(rsa, st_audit_fips, st_keyinfo, pkey);
|
|
|
|
// Cleaning
|
|
RSA_free(rsa);
|
|
return 0;
|
|
}
|
|
/*
|
|
* This function audit RSA private key
|
|
*/
|
|
static int fips_privkey_rsa(struct audit_fips *st_audit_fips, struct keyinfo *st_keyinfo, const char *pkey) {
|
|
int res;
|
|
RSA *rsa = NULL;
|
|
|
|
res = load_priv_rsa_keys(&rsa, pkey);
|
|
|
|
if (res > 0 || !rsa){
|
|
printf("Failed to read the private key\n");
|
|
return res;
|
|
}
|
|
|
|
audit_rsa_keys(rsa, st_audit_fips, st_keyinfo, pkey);
|
|
|
|
// Clean
|
|
RSA_free(rsa);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function load the RSA key and store to the RSA * object
|
|
* Detect and specify the correct RSA format public key
|
|
*/
|
|
static int loadkeys_rsa(RSA **rsa, const char *pkey, int *format){
|
|
BIO *bio = BIO_new(BIO_s_file());
|
|
if (BIO_read_filename(bio, pkey) == 0){
|
|
printf("Failed to read BIO\n");
|
|
return FIPS_ERR_READ_BIO;
|
|
}
|
|
|
|
/*
|
|
* Works with PEM_read_RSAPublicKey, but when we try to read the file
|
|
* we cannot. This function "block" the access to the file
|
|
*/
|
|
/*rsa = PEM_read_RSAPublicKey(f, NULL, NULL, NULL); */
|
|
|
|
#if OPENSSL_VERSION_NUMBER > 0x03000000f
|
|
EVP_PKEY *evp = PEM_read_bio_PUBKEY_ex(bio, NULL, NULL, NULL, NULL, NULL);
|
|
if (!evp){
|
|
BIO_free(bio);
|
|
return FIPS_ERR_LOAD_KEY;
|
|
}
|
|
|
|
BIO_free(bio);
|
|
|
|
//printf("Keysize: %d\n", EVP_PKEY_bits(evp));
|
|
/* EVP_PKEY_get1_RSA is deprecated, need to find another way to get the RSA key */
|
|
*rsa = EVP_PKEY_get1_RSA(evp);
|
|
if (!(*rsa)){
|
|
EVP_PKEY_free(evp);
|
|
return FIPS_ERR_LOAD_RSA_KEY;
|
|
}
|
|
EVP_PKEY_free(evp);
|
|
*format = 0;
|
|
|
|
#else
|
|
// Deprecated in OpenSSL v3
|
|
/*
|
|
* RSAPublicKey read publickey at the PEM format
|
|
* RSA_PUBKEY read publickey at the PKCS1 format
|
|
*/
|
|
/* In case it's OpenSSL v1, we get the RSA * object from BIO */
|
|
*rsa = PEM_read_bio_RSAPublicKey(bio, NULL, NULL, NULL);
|
|
|
|
// If we cannot read it, we try with the PKCS1 format
|
|
if (!(*rsa)){
|
|
// print_error();
|
|
|
|
/*
|
|
* We need to reset or reseek the BIO, otherwise, we cannot read it
|
|
* https://docs.openssl.org/3.0/man3/BIO_ctrl/#synopsis
|
|
*/
|
|
//BIO_reset((*rsa)->bio); /* Works too */
|
|
BIO_seek(bio, 0);
|
|
|
|
*(rsa) = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL,NULL);
|
|
|
|
if (!(*rsa)){
|
|
if (DEBUG)
|
|
printf("Cannot read the SPKI format of the public key\n");
|
|
BIO_free(bio);
|
|
return FIPS_ERR_LOAD_KEY;
|
|
}
|
|
|
|
*format = RSA_FORMAT_SPKI;
|
|
}
|
|
else
|
|
*format = RSA_FORMAT_PKCS1;
|
|
|
|
BIO_free(bio);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function load RSA Private key
|
|
*/
|
|
static int load_priv_rsa_keys(RSA **rsa, const char *pkey){
|
|
BIO *bio = BIO_new(BIO_s_file());
|
|
if (!bio){
|
|
if (DEBUG)
|
|
printf("Failed to create new BIO\n");
|
|
return FIPS_ERR_NEW_BIO;
|
|
}
|
|
if(BIO_read_filename(bio, pkey) == 0){
|
|
printf("Failed to read BIO\n");
|
|
BIO_free(bio);
|
|
return FIPS_ERR_READ_BIO;
|
|
}
|
|
|
|
#if OPENSSL_VERSION_NUMBER > 0x03000000f
|
|
EVP_PKEY *evp = PEM_read_bio_PrivateKey_ex(bio, NULL, NULL, NULL, NULL, NULL);
|
|
|
|
if (!evp){
|
|
if (DEBUG)
|
|
printf("Failed to read BIO PrivateKey\n");
|
|
BIO_free(bio);
|
|
return FIPS_ERR_READ_BIO;
|
|
}
|
|
BIO_free(bio);
|
|
|
|
*rsa = EVP_PKEY_get1_RSA(evp);
|
|
if (!*rsa){
|
|
EVP_PKEY_free(evp);
|
|
return FIPS_ERR_LOAD_RSA_KEY;
|
|
}
|
|
EVP_PKEY_free(evp);
|
|
#else /* For OpenSSL v1 */
|
|
*rsa = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
|
|
if (!*rsa){
|
|
if (DEBUG)
|
|
printf("Failed to read BIO RSAPrivateKey\n");
|
|
BIO_free(bio);
|
|
return FIPS_ERR_LOAD_RSA_PRIV_KEY;
|
|
}
|
|
BIO_free(bio);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function audit the RSA keys, both public and private
|
|
* For the audit, the function check the exponent (modulus) and the key size
|
|
*/
|
|
static void audit_rsa_keys(RSA *rsa, struct audit_fips *st_audit_fips, struct keyinfo *st_keyinfo, const char *pkey){
|
|
int res;
|
|
|
|
st_keyinfo->st_rsa.keysize = RSA_size(rsa);
|
|
|
|
st_keyinfo->algo = ALGO_RSA;
|
|
|
|
// The return value is a const, shouldn't be freed
|
|
const BIGNUM *e = RSA_get0_e(rsa);
|
|
char *exponent = BN_bn2dec(e);
|
|
//free(exponent);
|
|
OPENSSL_free(exponent);
|
|
|
|
/* Exponent has been set up, we can check it */
|
|
res = check_exponent(e, st_audit_fips->audit_rsa.audit_exponent.result, &st_keyinfo->st_rsa.exponent);
|
|
|
|
/*
|
|
* Audit the key size. For a better security, the key size is at least 2048 bits
|
|
*/
|
|
if (st_keyinfo->st_rsa.keysize * 8 < 2048){
|
|
sprintf(st_audit_fips->audit_rsa.audit_keysize.result, "The key size is lower than 2048. The key should be at least 2048 bits.");
|
|
st_audit_fips->audit_rsa.audit_keysize.audit = FALSE;
|
|
}
|
|
else{
|
|
sprintf(st_audit_fips->audit_rsa.audit_keysize.result, "The key size is upper or equal than 2048. The audit is passed with success.");
|
|
st_audit_fips->audit_rsa.audit_keysize.audit = TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* In this function, we are going to check the exponent
|
|
* For testing if the exponent is odd or even, we apply a modulo 2 on the exponent
|
|
* If the result is 1, means the key has a remainder and the key is odd, if the result is 0, the exponent is even.
|
|
* Regarding to the FIPS 186-5, the exponent must be odd.
|
|
* The function check also the size of the exponent.
|
|
* When the key has been generated with OpenSSL, by default the exponent is 65537.
|
|
* The exponent e size must be 2 ** 16 < e < 2 ** 256
|
|
*/
|
|
static int check_exponent(const BIGNUM *e, char *buf, unsigned long *exponent){
|
|
BIGNUM *rem = BN_new(), *a = BN_new(), *m = BN_new();
|
|
char nExponent[4];
|
|
BN_CTX *ctx;
|
|
char *r;
|
|
int error = 0;
|
|
|
|
sprintf(nExponent, "%d", 2);
|
|
BN_dec2bn(&m, nExponent);
|
|
ctx = BN_CTX_new();
|
|
BN_div(NULL, rem, e, m, ctx);
|
|
r = BN_bn2dec(rem);
|
|
|
|
/*
|
|
* According to the FIPS 186-5, the exponent size must be 2 ** 16 < e < 2 ** 256
|
|
* The exponent must be odd too
|
|
*/
|
|
char *exp = BN_bn2dec(e);
|
|
*exponent = (char2dec(exp[0]) * 10000) + (char2dec(exp[1]) * 1000) + (char2dec(exp[2])* 100) + (char2dec(exp[3]) * 10) + (char2dec(exp[4]));
|
|
|
|
if (strcmp(r, "0") == 0){
|
|
strncpy(buf, "The exponent is even, should be odd", BUF_SIZE_AUDIT);
|
|
error += 1;
|
|
}
|
|
|
|
// Check the exponent size
|
|
double minSize = pow(2, 16);
|
|
double maxSize = pow(2, 256);
|
|
if (*exponent < minSize || *exponent > maxSize){
|
|
strncpy(buf, "The exponent size is not correct. The minimum size is 2 ** 16 and maximum size 2 ** 256.", BUF_SIZE_AUDIT);
|
|
error += 1;
|
|
}
|
|
|
|
// If no error
|
|
if (error == 0)
|
|
strncpy(buf, "The exponent is correct, the FIPS compliance is respected.", BUF_SIZE_AUDIT);
|
|
|
|
// Cleaning
|
|
free(r);
|
|
BN_free(rem);
|
|
BN_free(a);
|
|
BN_free(m);
|
|
BN_CTX_free(ctx);
|
|
OPENSSL_free(exp);
|
|
return 0;
|
|
}
|
|
/********************************************************/
|
|
/* ECC part */
|
|
/********************************************************/
|
|
static int fips_pubkey_ecc(EC_KEY *ec, struct audit_fips *st_audit_fips, struct keyinfo *st_keyinfo, const char *pkey){
|
|
st_keyinfo->algo = ALGO_EC;
|
|
memset(&st_keyinfo->s_ecc, 0, sizeof(struct ecc*));
|
|
|
|
st_keyinfo->s_ecc.ec = ec;
|
|
|
|
int res = get_domain_parameters(&st_keyinfo->s_ecc);
|
|
|
|
if (res != 0)
|
|
return res;
|
|
|
|
audit_ecc(st_audit_fips, st_keyinfo->s_ecc.nid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function audit the ECC keys.
|
|
* According to the RCC 7748 and NIST recommendation,
|
|
* curve schemes: P-521, Curve25519 or Curve448 should be used
|
|
* The key length recommended is at least 256 bit.
|
|
*/
|
|
static void audit_ecc(struct audit_fips *st_audit, const int nid){
|
|
/*
|
|
* Recommended curve name (See file /usr/include/openssl/obj_mac):
|
|
* NID 716 = secp521r1
|
|
* NID 1034 = X25519 (Curve25519)
|
|
* NID 1035 = X448 (Curve448)
|
|
*/
|
|
if (nid != 716 && nid != 1034 && nid != 1035){
|
|
sprintf(st_audit->audit_ecc.audit_curve.result, "The curve scheme should be P-521, Curve25519 or Curve448.");
|
|
st_audit->audit_ecc.audit_curve.audit = FALSE;
|
|
}
|
|
else{
|
|
sprintf(st_audit->audit_ecc.audit_curve.result, "The curve scheme is enough strong and respect NIST recommendation.");
|
|
st_audit->audit_ecc.audit_curve.audit = TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function load the public ECC key and return the key store in the variable EVP_PKEY
|
|
*/
|
|
static EC_KEY *fips_load_pubkey_ecc(const char *pkey){
|
|
BIO *bio = BIO_new(BIO_s_file());
|
|
if (BIO_read_filename(bio, pkey) == 0){
|
|
printf("Failed to read BIO\n");
|
|
return NULL;
|
|
}
|
|
EC_KEY *ec = PEM_read_bio_EC_PUBKEY(bio, NULL, NULL, NULL);
|
|
|
|
if (!ec){
|
|
if (DEBUG)
|
|
printf("Cannot read the ECC Public key\n");
|
|
BIO_free(bio);
|
|
return NULL;
|
|
}
|
|
/* We don't use it anymore, we freeing it */
|
|
BIO_free(bio);
|
|
return ec;
|
|
|
|
}
|
|
|
|
/*
|
|
* This function get domain parameters from the ECC key
|
|
*/
|
|
static int get_domain_parameters(struct ecc *st_ecc){
|
|
EC_GROUP *group = EC_KEY_get0_group(st_ecc->ec);
|
|
if(!group){
|
|
if (DEBUG)
|
|
printf("Failed to load ECC Group\n");
|
|
EC_KEY_free(st_ecc->ec);
|
|
return FIPS_ERR_GET_ECC_GROUP;
|
|
}
|
|
|
|
// Get cofactor
|
|
BIGNUM *b_cofactor = EC_GROUP_get0_cofactor(group);
|
|
if(!b_cofactor){
|
|
printf("Cannot get cofactor\n");
|
|
return FIPS_ERR_GET_ECC_DOMAPARAM;
|
|
}
|
|
st_ecc->cofactor = BN_bn2dec(b_cofactor);
|
|
//BN_free(b_cofactor);
|
|
|
|
// Get field
|
|
#if OPENSSL_VERSION_NUMBER > 0x03000000f
|
|
|
|
#endif
|
|
|
|
// Get order bit
|
|
st_ecc->order_bits = EC_GROUP_order_bits(group);
|
|
|
|
// Get order
|
|
BIGNUM *b_order = EC_GROUP_get0_order(group);
|
|
if(!b_order){
|
|
printf("Cannot get order\n");
|
|
return FIPS_ERR_GET_ECC_DOMAPARAM;
|
|
}
|
|
st_ecc->order = BN_bn2hex(b_order);
|
|
//OPENSSL_free(order);
|
|
//BN_free(b_order);
|
|
|
|
// Get curve name
|
|
st_ecc->nid = EC_GROUP_get_curve_name(group);
|
|
st_ecc->curve = OBJ_nid2sn(st_ecc->nid);
|
|
//OPENSSL_free(name); /* If I free, the program crash, because it's a const ?? */
|
|
|
|
// Get generator
|
|
EC_POINT *g = EC_GROUP_get0_generator(group);
|
|
if (!g){
|
|
if (DEBUG)
|
|
printf("Failed to get ECC generator\n");
|
|
return FIPS_ERR_GET_ECC_GENERATOR;
|
|
}
|
|
|
|
st_ecc->g = EC_POINT_point2hex(group, g, POINT_CONVERSION_UNCOMPRESSED, NULL);
|
|
/*
|
|
* It's cannot mandatory to clean EC_GROUP and EC_POINT and other objects
|
|
* We freeing them in certificate.c file.
|
|
* Also, for cleaning EC_GROUP and EC_POINT, just freeing EC_KEY is enough
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
/********************************************************/
|
|
/* X.509 part */
|
|
/********************************************************/
|
|
|
|
/*
|
|
* This function load X509 certificate
|
|
*/
|
|
static int fips_x509(struct audit_fips *st_audit_fips, struct keyinfo *st_keyinfo, const char *pkey){
|
|
BIO *bio = BIO_new(BIO_s_file());
|
|
if (!bio){
|
|
printf("Failed to create new BIO\n");
|
|
return FIPS_ERR_NEW_BIO;
|
|
}
|
|
if (BIO_read_filename(bio, pkey) == 0){
|
|
printf("Failed to read BIO\n");
|
|
return FIPS_ERR_READ_BIO;
|
|
}
|
|
|
|
X509 *x = PEM_read_bio_X509(bio, NULL, 0, NULL);
|
|
if (!x){
|
|
printf("Failed to read the X509 certificate\n");
|
|
BIO_free(bio);
|
|
return FIPS_ERR_LOAD_X509;
|
|
}
|
|
BIO_free(bio); /* We don't need it anymore, we freeing it */
|
|
|
|
EVP_PKEY *evp = X509_get_pubkey(x);
|
|
if (!evp){
|
|
printf("Failed to get public certificate\n");
|
|
X509_free(x);
|
|
return FIPS_ERR_LOAD_RSA_KEY;
|
|
}
|
|
|
|
/* Get certificate info, such as issuer, validity, etc. */
|
|
|
|
X509_free(x);
|
|
|
|
/* Key type identification */
|
|
int type = EVP_PKEY_base_id(evp);
|
|
|
|
switch (type) {
|
|
case EVP_PKEY_RSA: ;
|
|
RSA *rsa = EVP_PKEY_get1_RSA(evp);
|
|
if (!rsa)
|
|
return FIPS_ERR_LOAD_RSA_KEY;
|
|
// We have the RSA key, we can audit it
|
|
audit_rsa_keys(rsa, st_audit_fips, st_keyinfo, pkey);
|
|
RSA_free(rsa);
|
|
break;
|
|
case EVP_PKEY_EC: ;
|
|
/* We free EC_KEY in certificate.c file */
|
|
EC_KEY *ec = EVP_PKEY_get1_EC_KEY(evp);
|
|
if (!ec)
|
|
return FIPS_ERR_LOAD_ECC_PUBKEY;
|
|
int res = fips_pubkey_ecc(ec, st_audit_fips, st_keyinfo, pkey);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
EVP_PKEY_free(evp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Return 1 if the version is upper than 1 and less than 3
|
|
* Return 3 for the version v3
|
|
*/
|
|
static int openssl_version(){
|
|
unsigned long version = OPENSSL_VERSION_NUMBER;
|
|
|
|
if (DEBUG)
|
|
printf("OpenSSL Version: %lx\n", version);
|
|
if (version <= 0x03000000f)
|
|
return 1;
|
|
if (version >= 0x03000000f)
|
|
return 3;
|
|
}
|
|
/*
|
|
* In case we have an error with OpenSSL librairy, we can print the error message
|
|
*/
|
|
static void print_error(){
|
|
unsigned long err = ERR_get_error();
|
|
char b[256];
|
|
ERR_error_string(err, b);
|
|
printf("%s\n", b);
|
|
}
|
|
|