2015-04-24 11 views
5

Ho creato una coppia di chiavi RSA utilizzando la cifra EVP_aes_256_cbc(). La chiave privata è codificata PEM e ha una passphrase. Ciò richiede che l'utente inserisca una passphrase.Verificare in modo programmatico un certificato X509 e la corrispondenza della chiave privata

Ecco il creare chiamata chiave privata:

//Save private key 
    bio_priv = BIO_new_file(full_asymKeyFilePath.c_str(), "a+"); 
    if (PEM_write_bio_RSAPrivateKey(
     bio_priv, //BIO handle 
     rsa,  //Key handle 
     EVP_aes_256_cbc(),  //Cipher encoding format 
     pwd,  //Password 
     pwd_len,   //Password length 
     NULL,  //Callback 
     NULL  //Not sure 
     ) != 1) { 
      //report err 
    } 

Poi ho generato un certificato e firmato con la chiave privata.

//Sign the certificate with the generated key 
    if (!X509_sign(cert, evpKey, EVP_sha1())){ 
     //report err 
    } 

Più tardi, voglio verificare che questo certificato corrisponde questa coppia di chiavi RSA. Quando ho SSL_CTX_check_private_key(), mi viene richiesto di inserire una passphrase dalla console.

C'è un modo per inserire automaticamente la password in modo che non venga richiesta dalla console?

//Load server certificate, must be called before ever calling use private key 
    if (SSL_CTX_use_certificate_file(context, full_certFilePath.c_str(), SSL_FILETYPE_PEM) == 0){ //load all certs from PEM file into SSL_CTX 
     //err 
    } 

    //Load private key corresponding to the certificate 
    if (SSL_CTX_use_PrivateKey_file(context, full_asymKeyFilePath.c_str(), SSL_FILETYPE_PEM) == 0){ //load all certs from PEM file into SSL_CTX 
     //file type is not pem or private key was loaded before calling this function. Private key does not match the public key in the certificate 
     //err 
    } 

    //Verify that certificate and private key match 
    if (!SSL_CTX_check_private_key(context)){ //<====== Prompts me to enter pass :(
     //err 
    } 
+0

Ah, ho rilevato SSL_CTX_set_default_passwd_cb() dopo aver letto la documentazione. Lo proverò e pubblicherò i miei progressi più tardi. – GloriousLemon

+0

Possibile duplicato di [Verificare che un file sia certificato o una chiave] (http://stackoverflow.com/questions/22398477/check-that-a-file-is-certificate-or-a-key) – jww

risposta

6

Programmatically verify a X509 certificate and private key match. Private key has a PEM passphrase

Ci sono due risposte qui. Uno è per il certificato e il secondo è per la chiave privata. La chiave privata viene mostrata per prima perché viene utilizzata per convalidare il certificato (quindi è consigliabile visitarlo prima).

Inoltre, è importante chiamare le routine *_check_key perché OpenSSL controlla solo che una chiave sia ben codificata; e non controlla che sia effettivamente valido. Vedere, ad esempio, Private key generated by openssl does not satisfy n = p * q.


In OpenSSL, è necessario utilizzare il seguente per verificare la chiave privata è ben codificato:

FILE* file = fopen(...); 
EVP_PKEY* pkey = PEM_read_PrivateKey(file, NULL, PasswordCallback, NULL); 
unsigned long err = ERR_get_error(); 

if(pkey) 
    EVP_PKEY_free(pkey); 

Se pkey è NULL, poi c'è stato un problema e err in possesso di un codice motivo. Altrimenti, hai una chiave privata correttamente codificata (ma non necessariamente valida).

Se la chiave è correttamente codificata, è possibile verificare il tipo di chiave privata e convalidarla con quanto segue.

int type = EVP_PKEY_get_type(pkey); 
switch (type) 
{ 
case EVP_PKEY_RSA: 
case EVP_PKEY_RSA2: 
    RSA* rsa = EVP_PKEY_get1_RSA(pkey); 
    rc = RSA_check_key(rsa); 
    ASSERT(rc); 
    RSA_free(rsa); 

    break; 

case EVP_PKEY_DSA: 
case EVP_PKEY_DSA1: 
case EVP_PKEY_DSA2: 
case EVP_PKEY_DSA3: 
case EVP_PKEY_DSA4: 
    DSA* dsa = EVP_PKEY_get1_DSA(pkey); 
    rc = DSA_check_key(dsa); 
    ASSERT(rc); 
    DSA_free(dsa); 

    break; 

case EVP_PKEY_DH: 
    DH* dh = EVP_PKEY_get1_DH(pkey); 
    rc = DH_check_key(dh); 
    ASSERT(rc); 
    DH_free(dh); 

    break; 

case EVP_PKEY_EC: 
    EC_KEY* ec = EVP_PKEY_get1_EC_KEY(pkey); 
    rc = EC_KEY_check_key(ec); 
    ASSERT(rc); 
    EC_KEY_free(ec); 

    break; 

default: 
    ASSERT(0); 
} 

EVP_PKEY_get_type non fa parte di OpenSSL. Ecco come ho implementato è:

int EVP_PKEY_get_type(EVP_PKEY *pkey) 
{ 
    ASSERT(pkey); 
    if (!pkey) 
     return NID_undef; 

    return EVP_PKEY_type(pkey->type); 
} 

In OpenSSL, è necessario utilizzare il seguente per verificare il certificato è ben codificato:

FILE* file = fopen(...); 
X509* x509 = PEM_read_X509(file, NULL, NULL, NULL); 
unsigned long err = ERR_get_error(); 

Se x509 è NULL, poi c'è stato un problema e err contiene un codice motivo. Altrimenti, hai un certificato correttamente codificato (ma non necessariamente valido).

Si può quindi verificare il certificato con:

/* See above on validating the private key */ 
EVP_PKEY* pkey = ReadPrivateKey(...); 

int rc = X509_verify(x509, pkey); 
err = ERR_get_error(); 

Se rc != 1, poi c'è stato un problema e err tiene un codice motivo. Altrimenti, hai un certificato valido e una coppia di chiavi private.Se il certificato è valido, non è possibile utilizzare err perché err è valido solo se c'è un problema.

Se il certificato è firmato da un emittente (per esempio, una CA o intermedio), allora avete bisogno di utilizzare un X509_STORE per verificare la firma dell'emittente sul certificato (un sacco di controllo degli errori omesso):

const char* serverCertFilename = ...; 
const char* issuerCertFilename = ...;  

X509_STORE* store = X509_STORE_new(); 
ASSERT(store); 

static const long flags = X509_V_FLAG_X509_STRICT | X509_V_FLAG_CHECK_SS_SIGNATURE 
     | X509_V_FLAG_POLICY_CHECK; 
rc = X509_STORE_set_flags(store, flags); 
err = ERR_get_error(); 
ASSERT(rc); 

/* Some other object/functions owns 'lookup', but I'm not sure which (perhaps the store) */ 
X509_LOOKUP* lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); 
/* err = ERR_get_error(); // Does not set error codes. */ 
ASSERT(lookup);  

/* Cannot load this from memory. No API!!! */ 
rc = X509_LOOKUP_load_file(lookup, issuerCertFilename, X509_FILETYPE_PEM); 
/* err = ERR_get_error(); // Does not set error codes. */ 
ASSERT(rc); 

X509_STORE_CTX* ctx = X509_STORE_CTX_new(); 
ASSERT(ctx); 

X509* serverCert = ReadCertifcate(serverCertFilename); 
ASSERT(serverCert); 

rc = X509_STORE_CTX_init(ctx, store, serverCert, NULL); 
ret = err = ERR_get_error(); 
ASSERT(rc); 

/* Error codes at https://www.openssl.org/docs/crypto/X509_STORE_CTX_get_error.html */ 
rc = X509_verify_cert(ctx); 
err = X509_STORE_CTX_get_error(ctx); 

/* Do cleanup, return success/failure */ 

Is there are way to automatically input the password so that I don't get prompted from the console?

Sì. utilizzare la richiamata della password in PEM_read_PrivateKey. Lo PasswordCallback può semplicemente fornire una password nel buffer, oppure può richiedere all'utente e restituire la password nel buffer.

La mia password di richiamata è in qualche modo coinvolta. Esegue un singolo hash della password raw prima di passarlo alla libreria. Ciò garantisce che non venga utilizzata una password di "testo normale" (ma non rallenta gli attacchi consueti). Il tuo può richiedere all'utente una stringa o può restituire una stringa codificata.

La richiamata della mia password utilizza un'etichetta. L'etichetta mi consente di ricavare chiavi diverse a seconda dell'uso (anche se viene utilizzato lo stesso segreto "di base"). Specificando un diverso uso o etichetta, ottengo una diversa derivazione dei bit chiave. L'etichetta viene fornita tramite arg qui sotto e puoi impostarla con SSL_CTX_set_default_passwd_cb_userdata.

using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_destroy)>; 

int PasswordCallback(char *buffer, int size, int rwflag, void *arg) 
{ 
    UNUSED(rwflag); 

    int rc; 
    unsigned long err; 
    ostringstream oss; 

    const char* label = (char*) arg; 
    size_t lsize = (label ? strlen(label) : 0); 

    SecureVector sv = config.GetMasterKey(); 
    ASSERT(!sv.empty()); 
    if (sv.empty()) 
    { 
     ... 
     throw runtime_error(oss.str().c_str()); 
    } 

    EVP_MD_CTX_ptr ctx(EVP_MD_CTX_create(), ::EVP_MD_CTX_destroy); 
    ASSERT(ctx.get() != NULL); 

    const EVP_MD* hash = EVP_sha512(); 
    ASSERT(hash != NULL); 

    rc = EVP_DigestInit_ex(ctx.get(), hash, NULL); 
    err = ERR_get_error(); 

    ASSERT(rc == 1); 
    if (rc != 1) 
    { 
     ... 
     throw runtime_error(oss.str().c_str()); 
    } 

    rc = EVP_DigestUpdate(ctx.get(), sv.data(), sv.size()); 
    err = ERR_get_error(); 

    ASSERT(rc == 1); 
    if (rc != 1) 
    { 
     ... 
     throw runtime_error(oss.str().c_str()); 
    } 

    if (label && lsize) 
    { 
     rc = EVP_DigestUpdate(ctx.get(), label, lsize); 
     err = ERR_get_error(); 

     ASSERT(rc == 1); 
     if (rc != 1) 
     { 
      ... 
      throw runtime_error(oss.str().c_str()); 
     } 
    } 

    int n = std::min(size, EVP_MD_size(hash)); 
    if (n <= 0) 
     return 0; 

    rc = EVP_DigestFinal_ex(ctx.get(), (unsigned char*) buffer, (unsigned int*) &n); 
    err = ERR_get_error(); 

    ASSERT(rc == 1); 
    if (rc != 1) 
    { 
     ... 
     throw runtime_error(oss.str().c_str()); 
    } 

    return n; 
} 
+0

Grazie per il consiglio, lo apprezzo davvero. Quando ho eseguito: EVP_PKEY * pkey = PEM_read_PrivateKey (file, NULL, PasswordCallback, NULL); Ho ricevuto il seguente errore: OPENSSL_Uplink (000000000047E000,08): no OPENSSL_Applink Potresti avere un'idea del perché? – GloriousLemon

+1

@jww conosci un modo per verificare una chiave pubblica, perché secondo i documenti "non funziona su chiavi pubbliche RSA che hanno solo il modulo e gli elementi di esponente pubblico popolati" – lmiguelmh

+0

@lmiguelmh - per quanto ne so, l'unico requisito è 'e' e' d' deve essere co-prime (requisiti modulo su 'p' e' q'). Ma non hai 'd', quindi non puoi testare nulla. Forse puoi provare che 'n' è strano. – jww

Problemi correlati