I was working on building software to communicate with external device. The connection should be secured. Here comes to use ssl certificates.
To do so, I needed to 3 things:
1) Create all needed certificates for Certificate Authority(CA), server and client to simulate the connection.
2) Create server and client using Nodejs to test the certificate.
3) In my c++ project, I was using CPP Rest API. I wanted it to embedded my client certificate in the connection to the server to authorize the connection and manage to communicate to the server successfully.
I will go through each step:
Create Certificates
1) Download and install Openssl
- For more detailed information, please check here
2) Create certificate authority[CA] configuration file
It is optional step but it is easy to pass the information to openssl using a file rather than inserting that each time.
I tried to create a simple example here
- You can check the file format here
3) Create CA certifcate and key
openssl req -new -x509 -config cert-authority.cnf -keyout cert-authority-key.pem -out cert-authority-crt.pem
Output: cert-authority-key.pem, cert-authority-crt.pem
Server
1) Create server private key
openssl genrsa -out server-key.pem 4096
Output: server-key.pem
2) Create server configuration file
I tried to create a simple example here
3) Create server certifacate signing request
openssl req -new -config server.cnf -key server-key.pem -out server-csr.pem
Output: server-csr.pem
4) Sign server certificate
openssl x509 -req -extfile server.cnf -passin "pass:12345" -in server-csr.pem -CA cert-authority-crt.pem -CAkey cert-authority-key.pem -CAcreateserial -out server-crt.pem
Client
1) Create client private key
openssl genrsa -out client-key.pem 4096
Output: client-key.pem
2) Create client configuration file
I tried to create a simple example here
3) Create client certifacate signing request
openssl req -new -config client.cnf -key client-key.pem -out client-csr.pem
Output: client-csr.pem
4) Sign client certificate
openssl x509 -req -extfile client.cnf -passin "pass:12345" -in client-csr.pem -CA cert-authority-crt.pem -CAkey cert-authority-key.pem -CAcreateserial -out client-crt.pem
5) Verify client certificate
you can verify client certificate using CA or server certificates as following:
openssl verify -CAfile cert-authority-crt.pem client-crt.pem
Use Nodejs to create server and client
Server
var fs = require('fs'); var https = require('https'); var options = { key: fs.readFileSync('server-key.pem'), cert: fs.readFileSync('server-crt.pem'), ca: fs.readFileSync('cert-authority-crt.pem'), strictSSL: true, requestCert: true, rejectUnauthorized: true }; var srv = https.createServer(options, function (req, res) { console.log('Recieve an request from authorized client!'); res.writeHead(200); res.end("Hello secured world!"); }).listen(3000, function() { console.log('Hello! I am at https://localhost:'+ srv.address().port); });
Client
var fs = require('fs'); var https = require('https'); var options = { hostname: 'localhost', port: 3000, path: '/', method: 'GET', key: fs.readFileSync(__dirname +'/client-key.pem'), cert: fs.readFileSync(__dirname +'/client-crt.pem'), ca: fs.readFileSync(__dirname +'/cert-authority-crt.pem') }; var req = https.request(options, function(res) { res.on('data', function(data) { process.stdout.write(data); }); }); req.end(); req.on('error', function(e) { console.error(e); });
C++ Code
1) Notes before going into the code
After you create your certificates we need to install your certificate authority into your machine.
openssl pkcs12 -export -out cert-authority.p12 -inkey cert-authority-key.pem -in cert-authority-cert.pem
Double-click on cert-authority.p12 and install the authourity under "Trusted Root Certification Authorities"
Now convert your client certificate using the same way to loaded later in c++:
openssl pkcs12 -export -out client.p12 -inkey client-key.pem -in client-cert.pem
Finally do not include the following library in you linker
Crypt32.lib winhttp.lib
2) Load certificate Function
void loadOrFindCertificate() { if (_pCertContext) return; HANDLE _certFileHandle = NULL; /*Open File*/ _certFileHandle = CreateFile(L"client.p12", GENERIC_READ, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (INVALID_HANDLE_VALUE == _certFileHandle) return; DWORD certEncodedSize = GetFileSize(_certFileHandle, NULL); /*Check if file size */ if (!certEncodedSize) { CloseHandle(_certFileHandle); return; } BYTE* certEncoded = new BYTE[(int)certEncodedSize]; /*Read File */ auto result = ReadFile(_certFileHandle, certEncoded, certEncodedSize, &certEncodedSize, 0); if (!result) { CloseHandle(_certFileHandle); return; } CRYPT_DATA_BLOB data; data.cbData = certEncodedSize; data.pbData = certEncoded; // Convert key-pair data to the in-memory certificate store WCHAR pszPassword[] = L"12345"; HCERTSTORE hCertStore = PFXImportCertStore(&data, pszPassword, 0); SecureZeroMemory(pszPassword, sizeof(pszPassword)); if (!hCertStore) { CloseHandle(_certFileHandle); return; } //get handle of loaded certificate _pCertContext = CertFindCertificateInStore (hCertStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL); CloseHandle(_certFileHandle); }
3) Test with sending request to server
// Create http_client configuration. web::http::client::http_client_config config; config.set_timeout(std::chrono::seconds(2)); config.set_validate_certificates(true); auto func = [&](web::http::client::native_handle handle) { loadOrFindCertificate(); //Attach certificate with request if (_pCertContext) WinHttpSetOption(handle, WINHTTP_OPTION_CLIENT_CERT_CONTEXT, (LPVOID)_pCertContext, sizeof(CERT_CONTEXT)); }; config.set_nativehandle_options(func); // Create http_client to send the request. web::http::client::http_client client(U("https://localhost:4150"), config); // Build request URI and start the request. auto requestTask = client.request(web::http::methods::GET) // Handle response headers arriving. .then([=](web::http::http_response response) { auto status(response.status_code()); printf("Received response status code:%u\n", status); /* Extract plain text only if status code signals success */ if (status >= 200 && status < 300) return response.extract_string(true); else return Concurrency::task<utility::string_t>([=] { return utility::string_t(); }); }).then([=](utility::string_t val) { printf("Received response message:%s\n", utility::conversions::to_utf8string(val).c_str()); }); // Wait for all the outstanding I/O to complete and handle any exceptions try { requestTask.wait(); } catch (const std::exception &e) { printf("Error exception:%s\n", e.what()); }
Source Code
Create server/client Certificates using openssl
Create server and client using NodeJS
Load certificate with CPP REST API
Top comments (0)