# `ADS.Secure`
[🔗](https://github.com/tallakt/yaads/blob/v0.1.0/lib/ads/secure.ex#L1)

Secure ADS: ADS inside TLS 1.2, to port 8016, from TwinCAT 3.1 build 4024 on.

Beckhoff's manual, "TwinCAT 3 Secure ADS", describes the three ways the two ends agree on keys,
and how to set them up on the PLC; what goes inside the TLS connection it leaves out. That part
is as described by https://github.com/kevinherron/beckhoff-secure-ads, and is yet to be checked
against a PLC:

  1. TLS 1.2, with a certificate on both sides, or a pre-shared key.
  2. The client sends a TlsConnectInfo, and the PLC answers with one (`connect_info/4`).
  3. AMS packets follow, each without the AMS/TCP header in front, as the AMS header holds
     their length (see `ADS.AMS`).

| Mode | Keys | On the PLC |
|---|---|---|
| Pre-shared key | an identity and a password, and TLS-PSK with the key `psk_key/2` | an entry under Server/Tls/Psk in its StaticRoutes.xml |
| Shared CA | a certificate for each side from a CA both trust | the CA, its own certificate and key under Server/Tls |
| Self-signed | each side its own certificate, the PLC's known by its SHA-256 fingerprint | a route, added once with the login of a user on the PLC (`add_route:`) |

A TlsConnectInfo is 64 bytes, with a user's name and password after them when adding a route:

| Bytes | Field |
|---|---|
| 2 | length, 64 and the credentials |
| 2 | flags |
| 1 | version, 1 |
| 1 | error, in the PLC's answer: 0, or 1 for the TLS version, 2 a certificate's name, 3 an unknown certificate, 4 an unknown user |
| 6 | the NetId of the sender |
| 1, 1 | lengths of the user name and the password |
| 18 | reserved, zeros |
| 32 | the sender's host name, with zeros after |
| ... | the user name and the password |

Its strings are in Windows-1252, as are the PSK identity and password.

The flags are, from the lowest bit: `:response`, in the PLC's answer; `:ams_allowed`;
`:server_info`; `:own_file`; `:self_signed`, for self-signed certificates; `:ip_address`, for a
route that names the client by its address rather than its host name; `:ignore_cn`, for
certificates whose common name isn't the name of their host; and `:add_remote`, to add a route.

`ADS.Client` takes all this as `secure:`; this module has the parts, for tests and those who
bring their own transport.

# `connect_info`

```elixir
@type connect_info() :: %{
  flags: [flag()],
  version: byte(),
  error:
    nil
    | :version
    | :cn_mismatch
    | :unknown_certificate
    | :unknown_user
    | byte(),
  net_id: ADS.NetId.t(),
  hostname: String.t(),
  credentials: {String.t(), String.t()} | nil
}
```

# `flag`

```elixir
@type flag() ::
  :response
  | :ams_allowed
  | :server_info
  | :own_file
  | :self_signed
  | :ip_address
  | :ignore_cn
  | :add_remote
```

# `connect_info`

```elixir
@spec connect_info(
  [flag()],
  ADS.NetId.t(),
  String.t(),
  {String.t(), String.t()} | nil,
  ADS.Codepage.t()
) :: binary()
```

A TlsConnectInfo, with credentials to add a route, or nil; the strings in the code page (see
`ADS.Codepage`). The host name is cut to 31 bytes.

    iex> info = ADS.Secure.connect_info([:self_signed], <<10, 0, 0, 7, 1, 1>>, "scada")
    iex> byte_size(info)
    64
    iex> {:ok, %{flags: [:self_signed], hostname: "scada"}, ""} = ADS.Secure.decode_connect_info(info)

# `decode_connect_info`

```elixir
@spec decode_connect_info(binary(), ADS.Codepage.t()) ::
  {:ok, connect_info(), binary()} | :more | :error
```

The first TlsConnectInfo in the bytes: `{:ok, info, rest}`, `:more` for the start of one, or
`:error` for one that can't be, longer than 512 bytes or shorter than 64. The strings are in
the code page.

# `fingerprint`

```elixir
@spec fingerprint(binary()) :: String.t()
```

A certificate's fingerprint as Secure ADS compares them: the SHA-256 of its DER, as 64
hexadecimal digits; the same as `ADS.Certificate.fingerprint/1`.

# `psk_key`

```elixir
@spec psk_key(String.t(), String.t(), ADS.Codepage.t()) :: &lt;&lt;_::256&gt;&gt;
```

The pre-shared key of an identity and a password, as TwinCAT makes it: the SHA-256 of the
identity in upper case followed by the password, both in the code page.

---

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