lib/mix/tasks/nerves_key.signer.ex

defmodule Mix.Tasks.NervesKey.Signer do
  use Mix.Task

  @shortdoc "Manages NervesKey signing keys"

  @moduledoc """
  Manages NervesKey signing keys

  ## create

  Create a new NervesKey signer certificate and private key pair.  This
  creates a compressible X.509 certificate that can be stored in the
  ATECC508A's limited memory.

    mix nerves_key.signer create NAME --years-valid <YEARS>

  If --years-valid is unspecified, the new certificate will be valid for
  one year.
  """

  @switches [years_valid: :integer]

  def run(args) do
    {opts, args} = OptionParser.parse!(args, strict: @switches)

    case args do
      ["create", name] ->
        create(name, opts)

      _ ->
        usage()
    end
  end

  @spec usage() :: no_return()
  def usage() do
    Mix.raise("""
    Invalid arguments to `mix nerves_key.signer`.

    Usage:
      mix nerves_key.signer create NAME --years-valid <YEARS>

    Run `mix help nerves_key.signer` for more information.
    """)
  end

  @spec create(String.t(), keyword()) :: :ok
  def create(name, opts) do
    cert_path = name <> ".cert"
    key_path = name <> ".key"

    if File.exists?(cert_path) do
      Mix.raise("Refusing to overwrite #{cert_path}. Please remove or change the name")
    end

    if File.exists?(key_path) do
      Mix.raise("Refusing to overwrite #{key_path}. Please remove or change the name")
    end

    {cert, priv_key} = NervesKey.create_signing_key_pair(opts)
    pem_cert = X509.Certificate.to_pem(cert)
    pem_key = X509.PrivateKey.to_pem(priv_key)

    File.write!(cert_path, pem_cert)
    File.write!(key_path, pem_key)

    Mix.shell().info("""
    Created signer cert, #{cert_path} and private key, #{key_path}.

    Please store #{key_path} in a safe place.

    #{cert_path} is ready to be uploaded to the servers that need
    to authenticate devices signed by the private key.
    """)
  end
end