Mohamed Barakat
Posted on May 24, 2019
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
Posted on May 24, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.