DEV Community

Mohamed Barakat
Mohamed Barakat

Posted on

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

Top comments (0)