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)