lmiguelmh
lmiguelmh

Reputation: 3222

Verify a RSA public key in OpenSSL?

I have an EVP_PKEY with only the public part of a RSA key. I extracted the public part from a SubjectPublicKeyInfo structure in DER encoding. This is what I have now:

unsigned char publicKey[] = {0x30, 0x5a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, ...}
size_t publicKeyLength = 92;
unsigned char* publicKeyCopy = new unsigned char[publicKeyLength];
memcpy(publicKeyCopy, publicKey, publicKeyLength);

RSA *rsa;
rsa = d2i_RSA_PUBKEY(NULL, (unsigned char const **) &pubKey, pubKeyLen);
EVP_PKEY *pkey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(pkey, rsa);

I know that you can use RSA_check_key to verify a RSA private key but the docs say that "It does not work on RSA public keys that have only the modulus and public exponent elements populated".

So, is it possible to verify a key without the private part? Because as you can see I have only the public part of the EVP_PKEY. I wonder, is this even possible? What would you verify in a public part of an EVP_PKEY?

You can see the answer for this question Programmatically verify a X509 certificate and private key match but there the full key is validated (private and public parts).

Beware The original code posted in this question has a BUG. This is because internally d2i_RSA_PUBKEY uses d2i_PUBKEY and d2i_PUBKEY uses d2i_X509_PUBKEY (in x_pubkey.c). If you read the documentation for d2i_X509 you will see the next "WARNING: The use of temporary variable is mandatory. A common mistake is to attempt to use a buffer directly...". So the corrected code will have to use a temporary copy of publicKeyCopy and after the use you could safely delete publicKeyCopy:

Upvotes: 2

Views: 5678

Answers (3)

Sameer Khanna
Sameer Khanna

Reputation: 49

I had a similiar problem and I thought it may be prudent to display my solution to this issue. Unlike lmiguelmh's solution, this one does work in C.

int checkRsaPublic(RSA *rsa, int debug) {
    if (!rsa) {
        printf("ERROR: RSA key not defined!\n");
        return 0;
    }
    //key
    const BIGNUM *n;
    const BIGNUM *e;
    const BIGNUM *d;

    //factors
    const BIGNUM *p;
    const BIGNUM *q;

    //crt_params
    const BIGNUM *dmp1;
    const BIGNUM *dmq1;
    const BIGNUM *iqmp;

    RSA_get0_key(rsa, &n, &e, &d);
    RSA_get0_factors(rsa, &p, &q);
    RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);

    if (debug) {
        if (n) {
            printf("n is %s\n", BN_bn2hex(n));
        }
        if (e) {
            printf("e is %s\n", BN_bn2hex(e));
        }
        if (d) {
            printf("d is %s\n", BN_bn2hex(d));
        }
        if (p) {
            printf("p is %s\n", BN_bn2hex(p));
        }
        if (q) {
            printf("q is %s\n", BN_bn2hex(q));
        }
        if (dmp1) {
            printf("dmp1 is %s\n", BN_bn2hex(dmp1));
        }
        if (dmq1) {
            printf("dmq1 is %s\n", BN_bn2hex(dmq1));
        }
        if (iqmp) {
            printf("iqmp is %s\n", BN_bn2hex(iqmp));
        }
    }

    //RSA_check_key : doesn't have n (modulus) and e (public exponent)
    if (d || !n || !e) {
        printf("ERROR: RSA public key not well defined!\n");
        return 0;
    }

    if (BN_is_odd(e) && !BN_is_one(e)) {
        return 1;
    }

    printf("ERROR: Invalid public exponent.");
    return 0;
}

Upvotes: 0

jww
jww

Reputation: 102396

Beware The original code posted in this question has a BUG...

I'm just going to comment on this, and show you how to handle it.

unsigned char publicKey[] = {0x30, 0x5a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, ...}
size_t publicKeyLength = sizeof(publicKey);

unsigned char* t = publicKey;
rsa = d2i_RSA_PUBKEY(NULL, &t, pubKeyLen);

Internally, the temporary pointer t is incremented, so its wasted. It will point to some place after the buffer if everything works as expected. What you should find is (size_t)t - (size_t)publicKey == publicKeyLength after the function executes.

Because you used a temporary pointer, the original pointer publicKey is still good. And you can use t to parse the next key if there are consecutive keys in memory.

There's no need to copy the data.


I think a second option is to use a memory BIO and d2i_RSA_PUBKEY_bio. Something like:

BIO* bio = BIO_new_mem_buf(publicKey, (int)publicKeyLength);
ASSERT(bio != NULL);

RSA* rsa = d2i_RSA_PUBKEY_bio(bio, NULL);
ASSERT(rsa != NULL);

/* ... */

RSA_free(rsa);
BIO_free(bio);

The get1 bumps the reference count, so you need to call free on both the EVP_PKEY* and RSA*.

Upvotes: 1

lmiguelmh
lmiguelmh

Reputation: 3222

With the help of @jww in this answer https://stackoverflow.com/a/29885771/2692914. I came up with this solution, I hope it is ok:

bool isValidPublicKeyOnly(EVP_PKEY *pkey) {
    //EVP_PKEY_get_type from https://stackoverflow.com/a/29885771/2692914
    int type = EVP_PKEY_get_type(pkey); //checks nullptr
    if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA2) {
        //not RSA
        return false;
    }

    RSA *rsa = EVP_PKEY_get1_RSA(pkey);
    if (!rsa) {
        return false;
    }

    bool isValid = isValidRSAPublicKeyOnly(rsa);
    RSA_free(rsa);
    return isValid;
}

bool isValidRSAPublicKeyOnly(RSA *rsa) {
    //from rsa_ameth.c do_rsa_print : has a private key
    //from rsa_chk.c RSA_check_key : doesn't have n (modulus) and e (public exponent)
    if (!rsa || rsa->d || !rsa->n || !rsa->e) {
        return false;
    }
    //from http://rt.openssl.org/Ticket/Display.html?user=guest&pass=guest&id=1454
    //doesnt have a valid public exponent
    return BN_is_odd(rsa->e) && !BN_is_one(rsa->e);
}

Upvotes: 0

Related Questions