# `Nfse.Certificate`
[🔗](https://github.com/ideia4it/nfse/blob/v0.1.0/lib/nfse/certificate.ex#L1)

Certificado A1 (um `.pfx` PKCS#12) aberto nas peças em DER que `:ssl` e `:public_key` pedem,
mais o que a biblioteca precisa saber dele: CNPJ ou CPF, validade e `clientAuth`.

O OTP não tem leitor de PKCS#12 (`:public_key` não exporta um, e `PFX` é um tipo indefinido em
`OTP-PUB-KEY` e `PKCS-FRAME`), então `from_pfx/3` chama `openssl pkcs12`. Duas regras tornam
isso seguro, e nenhuma é opcional:

  * o bundle entra pelo stdin do processo filho, nunca por arquivo temporário, porque o
    arquivo guardaria a chave privada sem cifra;
  * a senha entra por variável de ambiente do filho, nunca por `argv`, porque o `argv` é
    legível por qualquer usuário no `ps`.

A maior parte dos A1 da ICP-Brasil ainda é exportada com `PBE-SHA1-RC2-40`, que o OpenSSL 3
moveu para o provider legacy. A primeira tentativa vai sem `-legacy` e só então repete com ele:
um export moderno custa uma chamada e um antigo custa duas. O motivo da ordem é que o openssl
se recusa a rodar quando não consegue carregar um provider pedido na linha de comando, então
começar por `-legacy` perderia os bundles modernos em todo host sem o pacote.

Em produção esse provider vem no pacote `openssl-provider-legacy`. Sem ele, todo certificado
real falha ao abrir enquanto os fixtures no Mac do desenvolvedor continuam funcionando, por
isso `:legacy_provider_missing` é um erro próprio e não `:invalid_file`. Um subprocesso que
nunca responde recebe o mesmo tratamento e volta como `:timeout`, e um host sem o executável
`openssl` no `PATH` volta como `:openssl_missing`: nenhum dos três é problema do arquivo, e
mandar exportar o certificado de novo levaria a pessoa atrás da coisa errada.

Os erros são `%Nfse.Error{kind: :certificate}` e `details` é um destes:

| `details` | Quando |
|---|---|
| `:invalid_password` | a senha não abre o bundle |
| `:invalid_file` | o arquivo não é um PKCS#12 completo, ou falta chave ou folha |
| `:legacy_provider_missing` | o bundle usa cifra legada e o openssl não tem o provider |
| `:openssl_missing` | o executável `openssl` não está no `PATH` |
| `:timeout` | o openssl não respondeu dentro do prazo |
| `:unsupported_key` | a chave não é RSA, e a assinatura da DPS exige RSA |

# `t`

```elixir
@type t() :: %Nfse.Certificate{
  certificate: binary(),
  chain: [binary()],
  client_auth: boolean(),
  cnpj: String.t() | nil,
  cpf: String.t() | nil,
  key: {:RSAPrivateKey, binary()},
  not_after: DateTime.t(),
  not_before: DateTime.t()
}
```

# `expired?`

```elixir
@spec expired?(t(), DateTime.t()) :: boolean()
```

Diz se `now` já passou do fim da validade.

# `from_der`

```elixir
@spec from_der(binary(), {:RSAPrivateKey, binary()}, [binary()]) ::
  {:ok, t()} | {:error, Nfse.Error.t()}
```

Monta o certificado a partir de peças em DER que o chamador já tem, sem passar pelo openssl.

# `from_pfx`

```elixir
@spec from_pfx(binary(), String.t(), [{:timeout, pos_integer()}]) ::
  {:ok, t()} | {:error, Nfse.Error.t()}
```

Abre `bundle` com `password`.

A folha é escolhida casando a chave pública de cada certificado com a chave privada: a ordem
dentro de um `.pfx` não é especificada, e pegar o primeiro certificado devolve uma
intermediária com frequência suficiente para importar.

## Opções

  * `:timeout` - prazo em milissegundos para a chamada inteira, incluindo a repetição com
    `-legacy`. Padrão: `15_000`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
