Packages
grizzly
8.4.0
9.1.4
9.1.2
9.1.1
9.1.0
9.0.0
8.15.3
8.15.2
8.15.1
8.15.0
8.14.0
8.13.0
8.12.0
8.11.3
8.11.2
8.11.1
8.11.0
8.10.0
8.9.0
8.8.1
8.8.0
8.7.1
8.7.0
8.6.12
8.6.11
8.6.10
8.6.9
8.6.8
8.6.7
retired
8.6.6
8.6.5
8.6.4
8.6.3
8.6.2
8.6.1
8.6.0
8.5.3
8.5.2
8.5.1
8.5.0
8.4.0
8.3.0
8.2.3
8.2.2
8.2.1
8.2.0
8.1.0
8.0.1
8.0.0
7.4.3
7.4.2
7.4.1
7.4.0
7.3.0
7.2.0
7.1.4
7.1.3
7.1.2
7.1.1
7.1.0
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.8.8
6.8.7
6.8.6
6.8.5
6.8.4
6.8.3
6.8.2
6.8.1
6.8.0
6.7.1
6.7.0
6.6.1
6.6.0
6.5.1
6.5.0
6.4.0
6.3.0
6.2.0
6.1.1
6.1.0
6.0.1
6.0.0
5.4.1
5.4.0
5.3.0
5.2.8
5.2.7
5.2.6
5.2.5
5.2.4
5.2.3
5.2.2
5.2.1
5.2.0
5.1.2
5.1.1
5.1.0
5.0.2
5.0.1
5.0.0
4.0.1
4.0.0
3.0.0
2.1.0
2.0.0
1.0.1
1.0.0
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.1
0.19.0
0.18.3
0.18.2
0.18.1
0.18.0
0.17.7
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.2
0.16.1
0.16.0
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.9.0-rc.4
0.9.0-rc.3
0.9.0-rc.2
0.9.0-rc.1
0.9.0-rc.0
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.3
0.4.2
Elixir Z-Wave library
Current section
Files
Jump to
Current section
Files
lib/grizzly/zwave/command_classes/s0.ex
defmodule Grizzly.ZWave.CommandClasses.S0 do
@moduledoc """
S0 (Security) Command Class
"""
@behaviour Grizzly.ZWave.CommandClass
@impl Grizzly.ZWave.CommandClass
def byte(), do: 0x98
@impl Grizzly.ZWave.CommandClass
def name(), do: :s0
@authentication_vector <<0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
0x55, 0x55, 0x55, 0x55>>
@doc """
Derive the S0 authentication key (for calculating MACs) from the network key.
This is done by encrypting the S0 authentication vector (0x55 repeated 16
times) with the network key using AES-128-ECB.
"""
@spec authentication_key(<<_::128>>) :: <<_::128>>
def authentication_key(network_key) do
:crypto.crypto_one_time(:aes_128_ecb, network_key, @authentication_vector, encrypt: true)
end
@encryption_vector <<0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA>>
@doc """
Derive the S0 encryption key (for calculating MACs) from the network key.
This is done by encrypting the S0 encryption vector (0xAA repeated 16
times) with the network key using AES-128-ECB.
"""
@spec encryption_key(<<_::128>>) :: <<_::128>>
def encryption_key(network_key) do
:crypto.crypto_one_time(:aes_128_ecb, network_key, @encryption_vector, encrypt: true)
end
@mac_iv <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>
def encrypt(network_key, sender_nonce, receiver_nonce, payload) do
do_encrypt_decrypt(network_key, sender_nonce, receiver_nonce, payload, true)
end
def decrypt(network_key, sender_nonce, receiver_nonce, payload) do
do_encrypt_decrypt(network_key, sender_nonce, receiver_nonce, payload, false)
end
defp do_encrypt_decrypt(network_key, sender_nonce, receiver_nonce, payload, encrypt?) do
network_encryption_key = encryption_key(network_key)
iv = <<sender_nonce::binary-size(8), receiver_nonce::binary-size(8)>>
:crypto.crypto_one_time(:aes_128_ofb, network_encryption_key, iv, payload,
encrypt: encrypt?,
padding: :zero
)
end
@doc """
Calculates the MAC for an S0 Message Encapsulation command.
First, a block of authorization data is created by concatenating the following
values: the IV, the command byte (0x81 or 0xC1), the sender node ID, the receiver
node ID, the length of the encrypted payload, and the encrypted payload itself.
The IV is constructed by concatenating the sender nonce (which is included in
the command -- in the spec, this is the "initialization vector" field) with
the receiver nonce (which was obtained via a Nonce Get/Report exchange).
Because this is unclear in the docs, it is important to note that the sequencing
byte (which includes the second frame, sequenced, and sequence counter fields)
is part of the encrypted payload, which is why it isn't included in the auth
data block.
The auth data block is then padded to the block size (16 bytes for AES-128) and
then encrypted in 16-byte blocks using the network authentication key (see
`authentication_key/1`). The IV used for the first block is 16 bytes of 0x00,
and the IV for each subsequent block is the output from the previous block.
The MAC is the first 8 bytes of the final block.
"""
@spec calculate_mac(
network_key :: <<_::128>>,
command_byte :: 0x81 | 0xC1,
sender_node_id :: pos_integer(),
receiver_node_id :: pos_integer(),
sender_nonce :: <<_::64>>,
receiver_nonce :: <<_::64>>,
encrypted_payload :: binary()
) :: <<_::64>>
def calculate_mac(
network_key,
command_byte,
sender_node_id,
receiver_node_id,
sender_nonce,
receiver_nonce,
encrypted_payload
) do
network_auth_key = authentication_key(network_key)
iv = <<sender_nonce::binary-size(8), receiver_nonce::binary-size(8)>>
auth_data =
pad_to_block_size(
<<iv::binary-size(16), command_byte::8, sender_node_id::8, receiver_node_id::8,
byte_size(encrypted_payload)::8, encrypted_payload::binary>>
)
for <<block::binary-size(16) <- auth_data>>, reduce: @mac_iv do
iv ->
:crypto.crypto_one_time(:aes_128_cbc, network_auth_key, iv, block, encrypt: true)
end
|> binary_slice(0..7)
end
# We used a fixed block size here because S0 only uses AES-128.
defp pad_to_block_size(data) do
padding = 16 - rem(byte_size(data), 16)
<<data::binary-size(byte_size(data)), 0::padding*8>>
end
end