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:
- TLS 1.2, with a certificate on both sides, or a pre-shared key.
- The client sends a TlsConnectInfo, and the PLC answers with one (
connect_info/4). - 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.
Summary
Functions
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.
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.
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.
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.
Types
@type flag() ::
:response
| :ams_allowed
| :server_info
| :own_file
| :self_signed
| :ip_address
| :ignore_cn
| :add_remote
Functions
@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)
@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.
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.
@spec psk_key(String.t(), String.t(), ADS.Codepage.t()) :: <<_::256>>
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.