Current section
Files
Jump to
Current section
Files
lib/services/crl.ex
defmodule CA.EST.CRL do
@moduledoc "EST CRL generation and management."
require Record
Record.defrecord(:r_TBSCertList, :TBSCertList, Record.extract(:TBSCertList, from_lib: "public_key/include/public_key.hrl"))
Record.defrecord(:r_TBSCertList_revokedCertificates_SEQOF, :TBSCertList_revokedCertificates_SEQOF, Record.extract(:TBSCertList_revokedCertificates_SEQOF, from_lib: "public_key/include/public_key.hrl"))
Record.defrecord(:r_CertificateList, :CertificateList, Record.extract(:CertificateList, from_lib: "public_key/include/public_key.hrl"))
def revoked_file(profile) do
"#{CA.CSR.dir(profile)}/revoked.txt"
end
def read_revoked(profile) do
file = revoked_file(profile)
case File.read(file) do
{:ok, content} ->
content
|> String.split("\n")
|> Enum.map(&String.trim/1)
|> Enum.filter(&(&1 != ""))
|> Enum.map(&String.to_integer/1)
{:error, _} ->
[]
end
end
def revoke(profile, serial_number) do
file = revoked_file(profile)
File.mkdir_p!(Path.dirname(file))
File.write!(file, "#{serial_number}\n", [:append])
:ok
end
def generate(profile) do
{ca_key, ca} = CA.CSR.read_ca(profile)
issuer = elem(elem(ca, 1), 4)
{sig_oid, digest} = sig_alg_and_digest(profile)
sig_alg = {:AlgorithmIdentifier, sig_oid, :asn1_NOVALUE}
# Generate dates (thisUpdate and nextUpdate, e.g. nextUpdate 7 days in future)
now = DateTime.utc_now()
next_week = DateTime.add(now, 7 * 24 * 60 * 60, :second)
this_update_val = format_time(now)
next_update_val = format_time(next_week)
revoked_list =
read_revoked(profile)
|> Enum.map(fn serial ->
r_TBSCertList_revokedCertificates_SEQOF(
userCertificate: serial,
revocationDate: this_update_val,
crlEntryExtensions: :asn1_NOVALUE
)
end)
revoked_field =
case revoked_list do
[] -> :asn1_NOVALUE
list -> list
end
tbs = r_TBSCertList(
version: 1, # v2
signature: sig_alg,
issuer: issuer,
thisUpdate: this_update_val,
nextUpdate: next_update_val,
revokedCertificates: revoked_field,
crlExtensions: :asn1_NOVALUE
)
der_tbs = :public_key.der_encode(:TBSCertList, tbs)
signature = :public_key.sign(der_tbs, digest, ca_key)
cert_list = r_CertificateList(
tbsCertList: tbs,
signatureAlgorithm: sig_alg,
signature: signature
)
:public_key.der_encode(:CertificateList, cert_list)
end
def sig_alg_and_digest("secp256k1"), do: {{1, 2, 840, 10045, 4, 3, 2}, :sha256}
def sig_alg_and_digest("secp384r1"), do: {{1, 2, 840, 10045, 4, 3, 3}, :sha384}
def sig_alg_and_digest("secp521r1"), do: {{1, 2, 840, 10045, 4, 3, 4}, :sha512}
def sig_alg_and_digest(_), do: {{1, 2, 840, 10045, 4, 3, 3}, :sha384} # fallback
defp format_time(dt) do
year = rem(dt.year, 100) |> Integer.to_string() |> String.pad_leading(2, "0")
month = dt.month |> Integer.to_string() |> String.pad_leading(2, "0")
day = dt.day |> Integer.to_string() |> String.pad_leading(2, "0")
hour = dt.hour |> Integer.to_string() |> String.pad_leading(2, "0")
minute = dt.minute |> Integer.to_string() |> String.pad_leading(2, "0")
second = dt.second |> Integer.to_string() |> String.pad_leading(2, "0")
{:utcTime, String.to_charlist("#{year}#{month}#{day}#{hour}#{minute}#{second}Z")}
end
end