Sunday, 16 April 2017

Load Client Certificates from MAC Operating system

It is not secure to authenticate an application using plain username and password. Many applications started supporting certificate based authentication than basic (username + password) authentication.

What is Certificate based authentication?
Certificate based authentication uses a digital certificate to identify a device, user and provide access to resources. Usually this digital certificate is issued by a certificate authority, it contains information like
         a. certificate expiry date
         b. Certificate authority that issued this certificate
         c. Name of the client etc.,
        
While doing certificate authentication, client has to submit the certificate to the server, server validate the authenticity of the certificate and provide access to the client, if the certificate is valid.

How to load client certificates from Windows Operating system?
The Windows-MY keystore contains the user's private keys and the associated certificate chains.

How to load client certificates from MAC Operating system?
By loading the certificates from key store 'KEYCHAINSTORE', we can load the client certificates from MAC Operating system.

I developed an application 'SystemCertificateLoader.java', it provides following utility methods to load client certificates from windows and mac operating system.

public static List<Certificate> loadClientCertificatesFromWindowsOperatingSystem()
Load client certificates from windows operating system.

public static List<Certificate> loadClientCertificatesFromMacOperatingSystem()
Load client certificates from MAC operating system.

public static List<X509Certificate> getCertificates(List<Certificate> certificates)
Get all the list of X509Certificate from list of certificates.

Find the following complete working application.

SystemCertificateLoader.java
package com.sample;

import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class SystemCertificateLoader {
 private static final Logger logger = LogManager.getLogger();

 /**
  * Windows-MY keystore contains the user's private keys and the associated
  * certificate chains. By using 'SunMSCAPI' provider we can access client
  * certificates from windows operating system.
  * 
  * @return
  */
 public static List<Certificate> loadClientCertificatesFromWindowsOperatingSystem() {
  return getCertificates("WINDOWS-MY", "SunMSCAPI");
 }

 /**
  * KEYCHAINSTORE keystore contains the user's private keys and the associated
  * certificate chains. By using 'Apple' provider we can access client
  * certificates from MAC operating system.
  * 
  * @return
  */
 public static List<Certificate> loadClientCertificatesFromMacOperatingSystem() {
  return getCertificates("KEYCHAINSTORE", "Apple");
 }

 public static List<Certificate> getCertificates(String keyStoreName, String providerName) {

  if (keyStoreName == null || keyStoreName.isEmpty()) {
   return Collections.emptyList();
  }

  if (providerName == null || providerName.isEmpty()) {
   return Collections.emptyList();
  }

  List<Certificate> certificates = new ArrayList<>();

  try {
   KeyStore systemKeyStore = KeyStore.getInstance(keyStoreName, providerName);
   systemKeyStore.load(null, null);

   Enumeration<String> aliases = systemKeyStore.aliases();

   while (aliases.hasMoreElements()) {

    try {
     String alias = aliases.nextElement();
     Certificate certificate = systemKeyStore.getCertificate(alias);
     certificates.add(certificate);
    } catch (KeyStoreException e) {
     logger.error(e);
    }

   }

  } catch (KeyStoreException | NoSuchProviderException e) {
   logger.error(e);
  } catch (NoSuchAlgorithmException | CertificateException | IOException e) {
   logger.error(e);
  }
  return certificates;
 }


 /**
  * Return all the list of X509Certificate from list of certificates.
  * @param certificates
  * @return
  */
 public static List<X509Certificate> getCertificates(List<Certificate> certificates) {

  if (certificates == null || certificates.isEmpty()) {
   return Collections.emptyList();
  }

  List<X509Certificate> x509Certificates = new ArrayList<>();

  for (Certificate certificate : certificates) {

   if (!(certificate instanceof X509Certificate)) {
    continue;
   }

   x509Certificates.add((X509Certificate) certificate);

  }

  return x509Certificates;

 }

}

Test.java
package com.sample;

import java.io.IOException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.List;

public class Test {

 public static void main(String args[]) throws IOException {

  List<Certificate> certificates = SystemCertificateLoader.loadClientCertificatesFromWindowsOperatingSystem();

  List<X509Certificate> x509Certificates = SystemCertificateLoader.getCertificates(certificates);

  for (X509Certificate x509Certificate : x509Certificates) {
   System.out.println("DN: " + x509Certificate.getSubjectDN() + ". CN: " + x509Certificate.getIssuerDN());
  }

 }
}