my_bool ma_schannel_verify_certs(SC_CTX *sctx, DWORD dwCertFlags) { SECURITY_STATUS sRet; DWORD flags; MARIADB_PVIO *pvio= sctx->mysql->net.pvio; PCCERT_CONTEXT pServerCert= NULL; if ((sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert)) != SEC_E_OK) { ma_schannel_set_sec_error(pvio, sRet); return 0; } flags= CERT_STORE_SIGNATURE_FLAG | CERT_STORE_TIME_VALIDITY_FLAG; if (sctx->client_ca_ctx) { if (!(sRet= CertVerifySubjectCertificateContext(pServerCert, sctx->client_ca_ctx, &flags))) { ma_schannel_set_win_error(pvio); return 0; } if (flags) { if ((flags & CERT_STORE_SIGNATURE_FLAG) != 0) pvio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Certificate signature check failed"); else if ((flags & CERT_STORE_REVOCATION_FLAG) != 0) pvio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "certificate was revoked"); else if ((flags & CERT_STORE_TIME_VALIDITY_FLAG) != 0) pvio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "certificate has expired"); else pvio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown error during certificate validation"); return 0; } } /* Check if none of the certificates in the certificate chain have been revoked. */ if (sctx->client_crl_ctx) { PCRL_INFO Info[1]; Info[0]= sctx->client_crl_ctx->pCrlInfo; if (!(CertVerifyCRLRevocation(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pServerCert->pCertInfo, 1, Info)) ) { pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "CRL Revocation failed"); return 0; } } return 1; }
/* Create a crl context from crlfile SYNOPSIS ma_schannel_create_crl_context() pem_file name of certificate or ca file DESCRIPTION Loads a certification revocation list file, creates a certification context and loads the binary representation into crl context. The returned context must be freed by caller. If the function failed, error can be retrieved by GetLastError(). RETURNS NULL If loading of the file or creating context failed PCCRL_CONTEXT A pointer to a certification context structure */ PCCRL_CONTEXT ma_schannel_create_crl_context(MARIADB_PVIO *pvio, const char *pem_file) { DWORD der_buffer_length; LPBYTE der_buffer= NULL; PCCRL_CONTEXT ctx= NULL; /* load ca pem file into memory */ if (!(der_buffer= ma_schannel_load_pem(pvio, pem_file, (DWORD *)&der_buffer_length))) goto end; if (!(ctx= CertCreateCRLContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der_buffer, der_buffer_length))) ma_schannel_set_win_error(pvio); end: if (der_buffer) LocalFree(der_buffer); return ctx; }
/* Create a certification context from ca or cert file SYNOPSIS ma_schannel_create_cert_context() pvio pvio object pem_file name of certificate or ca file DESCRIPTION Loads a PEM file (certificate authority or certificate) creates a certification context and loads the binary representation into context. The returned context must be freed by caller. If the function failed, error can be retrieved by GetLastError(). RETURNS NULL If loading of the file or creating context failed CERT_CONTEXT * A pointer to a certification context structure */ CERT_CONTEXT *ma_schannel_create_cert_context(MARIADB_PVIO *pvio, const char *pem_file) { DWORD der_buffer_length; LPBYTE der_buffer= NULL; CERT_CONTEXT *ctx= NULL; /* create DER binary object from ca/certification file */ if (!(der_buffer= ma_schannel_load_pem(pvio, pem_file, (DWORD *)&der_buffer_length))) goto end; if (!(ctx= (CERT_CONTEXT *)CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der_buffer, der_buffer_length))) ma_schannel_set_win_error(pvio); end: if (der_buffer) LocalFree(der_buffer); return ctx; }
my_bool ma_schannel_load_private_key(MARIADB_PVIO *pvio, CERT_CONTEXT *ctx, char *key_file) { DWORD der_buffer_len= 0; LPBYTE der_buffer= NULL; DWORD priv_key_len= 0; LPBYTE priv_key= NULL; HCRYPTPROV crypt_prov= 0; HCRYPTKEY crypt_key= 0; CERT_KEY_CONTEXT kpi={ 0 }; my_bool rc= 0; /* load private key into der binary object */ if (!(der_buffer= ma_schannel_load_pem(pvio, key_file, &der_buffer_len))) return 0; /* determine required buffer size for decoded private key */ if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, der_buffer, der_buffer_len, 0, NULL, NULL, &priv_key_len)) { ma_schannel_set_win_error(pvio); goto end; } /* allocate buffer for decoded private key */ if (!(priv_key= LocalAlloc(0, priv_key_len))) { pvio->set_error(pvio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); goto end; } /* decode */ if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, der_buffer, der_buffer_len, 0, NULL, priv_key, &priv_key_len)) { ma_schannel_set_win_error(pvio); goto end; } /* Acquire context */ if (!CryptAcquireContext(&crypt_prov, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { ma_schannel_set_win_error(pvio); goto end; } /* ... and import the private key */ if (!CryptImportKey(crypt_prov, priv_key, priv_key_len, 0, 0, (HCRYPTKEY *)&crypt_key)) { ma_schannel_set_win_error(pvio); goto end; } kpi.hCryptProv= crypt_prov; kpi.dwKeySpec = AT_KEYEXCHANGE; kpi.cbSize= sizeof(kpi); /* assign private key to certificate context */ if (CertSetCertificateContextProperty(ctx, CERT_KEY_CONTEXT_PROP_ID, 0, &kpi)) rc= 1; else ma_schannel_set_win_error(pvio); end: if (der_buffer) LocalFree(der_buffer); if (priv_key) { if (crypt_key) CryptDestroyKey(crypt_key); LocalFree(priv_key); if (!rc) if (crypt_prov) CryptReleaseContext(crypt_prov, 0); } return rc; }
/* Load a pem or clr file and convert it to a binary DER object SYNOPSIS ma_schannel_load_pem() PemFileName name of the pem file (in) buffer_len length of the converted DER binary DESCRIPTION Loads a X509 file (ca, certification, key or clr) into memory and converts it to a DER binary object. This object can be decoded and loaded into a schannel crypto context. If the function failed, error can be retrieved by GetLastError() The returned binary object must be freed by caller. RETURN VALUE NULL if the conversion failed or file was not found LPBYTE * a pointer to a binary der object buffer_len will contain the length of binary der object */ static LPBYTE ma_schannel_load_pem(MARIADB_PVIO *pvio, const char *PemFileName, DWORD *buffer_len) { HANDLE hfile; char *buffer= NULL; DWORD dwBytesRead= 0; LPBYTE der_buffer= NULL; DWORD der_buffer_length; if (buffer_len == NULL) return NULL; if ((hfile= CreateFile(PemFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL )) == INVALID_HANDLE_VALUE) { ma_schannel_set_win_error(pvio); return NULL; } if (!(*buffer_len = GetFileSize(hfile, NULL))) { pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Invalid pem format"); goto end; } if (!(buffer= LocalAlloc(0, *buffer_len + 1))) { pvio->set_error(pvio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); goto end; } if (!ReadFile(hfile, buffer, *buffer_len, &dwBytesRead, NULL)) { ma_schannel_set_win_error(pvio); goto end; } CloseHandle(hfile); /* calculate the length of DER binary */ if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER, NULL, &der_buffer_length, NULL, NULL)) { ma_schannel_set_win_error(pvio); goto end; } /* allocate DER binary buffer */ if (!(der_buffer= (LPBYTE)LocalAlloc(0, der_buffer_length))) { pvio->set_error(pvio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); goto end; } /* convert to DER binary */ if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER, der_buffer, &der_buffer_length, NULL, NULL)) { ma_schannel_set_win_error(pvio); goto end; } *buffer_len= der_buffer_length; LocalFree(buffer); return der_buffer; end: if (hfile != INVALID_HANDLE_VALUE) CloseHandle(hfile); if (buffer) LocalFree(buffer); if (der_buffer) LocalFree(der_buffer); *buffer_len= 0; return NULL; }
/* {{{ static int ma_tls_set_client_certs(MARIADB_TLS *ctls) */ static int ma_tls_set_client_certs(MARIADB_TLS *ctls) { MYSQL *mysql= ctls->pvio->mysql; char *certfile= mysql->options.ssl_cert, *keyfile= mysql->options.ssl_key, *cafile= mysql->options.ssl_ca; PCERT_CONTEXT ca_ctx= NULL; PCRL_CONTEXT crl_ctx = NULL; SC_CTX *sctx= (SC_CTX *)ctls->ssl; MARIADB_PVIO *pvio= ctls->pvio; if (cafile) { if (!(ca_ctx = ma_schannel_create_cert_context(pvio, cafile))) goto end; /* Add ca to in-memory certificate store */ if (CertAddCertificateContextToStore(ca_CertStore, ca_ctx, CERT_STORE_ADD_NEWER, NULL) != TRUE && GetLastError() != CRYPT_E_EXISTS) { ma_schannel_set_win_error(sctx->mysql); goto end; } ca_Check= 0; CertFreeCertificateContext(ca_ctx); } if (!certfile && keyfile) certfile= keyfile; if (!keyfile && certfile) keyfile= certfile; if (certfile && certfile[0]) if (!(sctx->client_cert_ctx = ma_schannel_create_cert_context(ctls->pvio, certfile))) goto end; if (sctx->client_cert_ctx && keyfile[0]) if (!ma_schannel_load_private_key(pvio, sctx->client_cert_ctx, keyfile)) goto end; if (mysql->options.extension && mysql->options.extension->ssl_crl) { if (!(crl_ctx= (CRL_CONTEXT *)ma_schannel_create_crl_context(pvio, mysql->options.extension->ssl_crl))) goto end; /* Add ca to in-memory certificate store */ if (CertAddCRLContextToStore(crl_CertStore, crl_ctx, CERT_STORE_ADD_NEWER, NULL) != TRUE && GetLastError() != CRYPT_E_EXISTS) { ma_schannel_set_win_error(sctx->mysql); goto end; } crl_Check = 1; CertFreeCertificateContext(ca_ctx); } return 0; end: if (ca_ctx) CertFreeCertificateContext(ca_ctx); if (sctx->client_cert_ctx) CertFreeCertificateContext(sctx->client_cert_ctx); if (crl_ctx) CertFreeCRLContext(crl_ctx); sctx->client_cert_ctx= NULL; return 1; }