Search code examples
opensslerlangelixirx509certificate

Elixir / Erlang: Certificate Chain Validation


I have a use case where the certificate chain has to be validated for PKI. I have two certs, one is the device cert and the other is the certificate_chain. Both are in pem format. The:public_key.pkix_path_validation/3 seems promising but I don’t know how to give the chain in der format. I am converting the device cert to der using X509.Certificate.to_der but how will I convert the chain to der, since it has 3 certificates( Root_CA, Intermediate_CA, Signing_CA) and when I convert it using the X509 library and give it to :public_key.pkix_path_validation/3 . Basically I want to achieve the alternative to “openssl verify -CAfile certs/root_ca.pem -untrusted cert_chain.pem certs/device_cert.pem” in elixir.

I made some progress and wrote a method to read the certificate and pass it for validation my method to read the certificate for chain validation is

  defmodule Cert do
  def stubChainValidation do
    certRaw = File.read!("software_signing.pem")
    {:ok, certEncoded} = X509.Certificate.from_pem(certRaw)
    certChainRaw = File.read!("chain.pem")
    certChain = :public_key.pem_decode(certChainRaw)

    cert_chain_decoded =
      Enum.map(
        cert_chain,
        fn {_, bin, _} -> bin end
      )

    :public_key.pkix_path_validation(certEncoded, 
cert_chain_decoded, [{:max_path_length, 0}])
  end
end

When I run this function I get the output of Invalid issuer

{:error, {:bad_cert, :invalid_issuer}}

Solution

  • After spending nearly one complete week I Figured out what I was doing wrong, Erlang expects the parameters in a different way, you need to pass the root and then the chain which contains the intermediate and signing certificate.

    I am attaching a sample implementation for the Erlang Community just in case someone gets stuck doing the validation in the future.

    defmodule Cert do
      def verify_fun(_, {:extension, _}, state) do
        {:unknown, state}
      end
    
      def verify_fun(_, {:bad_cert, reason}, _state) do
        {:fail, reason}
      end
    
      def verify_fun(_, {:revoked, _}, _state) do
        {:fail, :revoked}
      end
    
      def verify_fun(_cert, _event, state) do
        {:unknown, state}
      end
    
      def stubChainValidation do
        Application.ensure_all_started(:inets)
        Application.ensure_all_started(:ssl)
        Application.ensure_all_started(:public_key)
        certRaw = File.read!("./certs/root_ca.pem")
        {:ok, certEncoded} = X509.Certificate.from_pem(certRaw)
    
        certChainRaw = File.read!("./certs/chain_2.pem")
        certChain = :public_key.pem_decode(certChainRaw)
    
        cert_chain_decoded =
          Enum.map(
            certChain,
            fn {_, bin, _} -> bin end
          )
    
    case :public_key.pkix_path_validation(certEncoded, cert_chain_decoded, [
           {:verify_fun, {&verify_fun/3, {}}}
         ]) do
      {:ok, {_public_key_info, _policy_tree}} ->
        {:ok, cert_chain_decoded}
    
      {:error, {:bad_cert, reason}} ->
        {:error, reason}
        end
      end
    end