From d192d170f09d8c2f1e3e3c3bff4ea0d50f142d55 Mon Sep 17 00:00:00 2001 From: fitblip Date: Mon, 5 Mar 2018 00:40:34 -0800 Subject: [PATCH] Add in arguments for supporting base64 serialization and domain name extraction --- lib/easy_ssl.ex | 51 +++++++++++++++++++++++++++++++++++++----- mix.exs | 2 +- test/easy_ssl_test.exs | 24 ++++++++++++++++++++ 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/lib/easy_ssl.ex b/lib/easy_ssl.ex index 0584a78..75a5f92 100644 --- a/lib/easy_ssl.ex +++ b/lib/easy_ssl.ex @@ -58,15 +58,56 @@ defmodule EasySSL do } } """ - def parse_der(certificate_der) when is_binary(certificate_der) do + def parse_der(certificate_der, opts \\ [all_domains: false, serialize: false]) when is_binary(certificate_der) do cert = :public_key.pkix_decode_cert(certificate_der, :otp) |> get_field(:tbsCertificate) - %{} + serialized_certificate = %{} |> Map.put(:fingerprint, certificate_der |> fingerprint_cert) |> Map.put(:serial_number, cert |> get_field(:serialNumber) |> Integer.to_string(16)) |> Map.put(:subject, cert |> parse_subject) |> Map.put(:extensions, cert |> parse_extensions) |> Map.merge(parse_expiry(cert)) + + Enum.reduce(opts, serialized_certificate, fn {option, flag}, serialized_certificate -> + case option do + :all_domains when flag == true -> + serialized_certificate + |> Map.put(:all_domains, get_all_domain_names(cert, serialized_certificate)) + + :serialize when flag == true -> + serialized_certificate + |> Map.put(:as_der, Base.encode64(certificate_der)) + _ -> serialized_certificate + end + end) + end + + def get_all_domain_names(cert, serialized_cert) do + domain_names = MapSet.new() + + domain_names = case serialized_cert[:subject][:CN] do + nil -> domain_names + _ -> MapSet.put(domain_names, serialized_cert[:subject][:CN]) + end + + extensions = cert |> get_field(:extensions) + + extensions + |> Enum.reduce(domain_names, fn extension, domain_names -> + case extension do + {:Extension, {2, 5, 29, 17}, _critical, san_entries} -> + san_entries + |> Enum.reduce(domain_names, fn entry, names -> + case entry do + {:dNSName, dns_name} -> MapSet.put(names, dns_name |> to_string) + _ -> names + end + end) + :asn1_NOVALUE -> domain_names + _ -> domain_names + end + end) + |> MapSet.to_list end @doc """ @@ -104,7 +145,7 @@ defmodule EasySSL do } """ def parse_pem(cert_charlist) when is_list(cert_charlist) do parse_pem(cert_charlist |> to_string) end - def parse_pem(cert_pem) do + def parse_pem(cert_pem, opts \\ [all_domains: false, return_base64: false]) do cert_regex = ~r/^\-{5}BEGIN\sCERTIFICATE\-{5}\n(?[^\-]+)\-{5}END\sCERTIFICATE\-{5}/ match = Regex.named_captures(cert_regex, cert_pem) @@ -115,7 +156,7 @@ defmodule EasySSL do match["certificate"] |> String.replace("\n", "") |> Base.decode64! - |> parse_der + |> parse_der(opts) end defp get_field(record, field) do @@ -284,7 +325,7 @@ defmodule EasySSL do # Basically ignore those {:directoryName, _sequence} -> san_list - {:otherName, other_name} -> san_list + {:otherName, _sequence} -> san_list _ -> raise("Unhandled SAN entry type #{inspect entry}") diff --git a/mix.exs b/mix.exs index 99634b2..1532c48 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule EasySSL.MixProject do [ app: :easy_ssl, name: "EasySSL", - version: "1.0.2", + version: "1.0.3", elixir: "~> 1.6", description: "SSL/X509 parsing for humans.", deps: deps(), diff --git a/test/easy_ssl_test.exs b/test/easy_ssl_test.exs index 8bbfaee..5785d8c 100644 --- a/test/easy_ssl_test.exs +++ b/test/easy_ssl_test.exs @@ -57,4 +57,28 @@ defmodule EasySSLTest do assert_has_normal_atom_keys(cert) end + test "parses and adds all domains to the top level leaf node" do + cert_bytes = File.read!(@der_cert_dir <> "twitter.com.der") + + serialized_cert = cert_bytes + |> EasySSL.parse_der() + refute Enum.member?(Map.keys(serialized_cert), :as_der) + refute Enum.member?(Map.keys(serialized_cert), :all_domains) + + serialized_cert = cert_bytes + |> EasySSL.parse_der(all_domains: true) + refute Enum.member?(Map.keys(serialized_cert), :as_der) + assert Enum.member?(Map.keys(serialized_cert), :all_domains) + + serialized_cert = cert_bytes + |> EasySSL.parse_der(serialize: true) + assert Enum.member?(Map.keys(serialized_cert), :as_der) + refute Enum.member?(Map.keys(serialized_cert), :all_domains) + + serialized_cert = cert_bytes + |> EasySSL.parse_der(serialize: true, all_domains: true) + assert Enum.member?(Map.keys(serialized_cert), :as_der) + assert Enum.member?(Map.keys(serialized_cert), :all_domains) + end + end