DEV Community

Mohamed Barakat
Mohamed Barakat

Posted on

3 1

Working with self-signed Certificates

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

Postgres on Neon - Get the Free Plan

No credit card required. The database you love, on a serverless platform designed to help you build faster.

Get Postgres on Neon

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series