Files
parrhesia/lib/parrhesia/test_support/tls_certs.ex

146 lines
3.1 KiB
Elixir

defmodule Parrhesia.TestSupport.TLSCerts do
@moduledoc false
@spec create_ca!(String.t(), String.t()) :: map()
def create_ca!(dir, name) do
keyfile = Path.join(dir, "#{name}-ca.key.pem")
certfile = Path.join(dir, "#{name}-ca.cert.pem")
openssl!([
"req",
"-x509",
"-newkey",
"rsa:2048",
"-nodes",
"-sha256",
"-days",
"2",
"-subj",
"/CN=#{name} Test CA",
"-keyout",
keyfile,
"-out",
certfile
])
%{keyfile: keyfile, certfile: certfile}
end
@spec issue_server_cert!(String.t(), map(), String.t()) :: map()
def issue_server_cert!(dir, ca, name) do
issue_cert!(
dir,
ca,
name,
"localhost",
["DNS:localhost", "IP:127.0.0.1"],
"serverAuth"
)
end
@spec issue_client_cert!(String.t(), map(), String.t()) :: map()
def issue_client_cert!(dir, ca, name) do
issue_cert!(dir, ca, name, name, [], "clientAuth")
end
@spec spki_pin!(String.t()) :: String.t()
def spki_pin!(certfile) do
certfile
|> der_cert!()
|> spki_pin()
end
@spec cert_sha256!(String.t()) :: String.t()
def cert_sha256!(certfile) do
certfile
|> der_cert!()
|> then(&Base.encode64(:crypto.hash(:sha256, &1)))
end
defp issue_cert!(dir, ca, name, common_name, san_entries, extended_key_usage) do
keyfile = Path.join(dir, "#{name}.key.pem")
csrfile = Path.join(dir, "#{name}.csr.pem")
certfile = Path.join(dir, "#{name}.cert.pem")
extfile = Path.join(dir, "#{name}.ext.cnf")
openssl!([
"req",
"-new",
"-newkey",
"rsa:2048",
"-nodes",
"-subj",
"/CN=#{common_name}",
"-keyout",
keyfile,
"-out",
csrfile
])
File.write!(extfile, extension_config(san_entries, extended_key_usage))
openssl!([
"x509",
"-req",
"-in",
csrfile,
"-CA",
ca.certfile,
"-CAkey",
ca.keyfile,
"-CAcreateserial",
"-out",
certfile,
"-days",
"2",
"-sha256",
"-extfile",
extfile,
"-extensions",
"v3_req"
])
%{keyfile: keyfile, certfile: certfile}
end
defp extension_config(san_entries, extended_key_usage) do
san_block =
case san_entries do
[] -> ""
entries -> "subjectAltName = #{Enum.join(entries, ",")}\n"
end
"""
[v3_req]
basicConstraints = CA:FALSE
keyUsage = digitalSignature,keyEncipherment
extendedKeyUsage = #{extended_key_usage}
#{san_block}
"""
end
defp der_cert!(certfile) do
certfile
|> File.read!()
|> :public_key.pem_decode()
|> List.first()
|> elem(1)
end
defp spki_pin(cert_der) do
cert = :public_key.pkix_decode_cert(cert_der, :plain)
spki = cert |> elem(1) |> elem(7)
:public_key.der_encode(:SubjectPublicKeyInfo, spki)
|> then(&:crypto.hash(:sha256, &1))
|> Base.encode64()
end
defp openssl!(args) do
case System.cmd("/usr/bin/openssl", args, stderr_to_stdout: true) do
{output, 0} -> output
{output, status} -> raise "openssl failed with status #{status}: #{output}"
end
end
end