Skip to content

Commit a88c7b5

Browse files
committed
Support >16 byte serial numbers
This makes use of the flags field to indicate that the rest of the OTP area should be used for the serial number. This lets you have up to 48 byte serial numbers which certainly seems like way more than anyone would ever need. This change also beefs up the OTP section tests to ensure that the code is backwards compatible since it's assumed that most users will continue to have <= 16 byte serial numbers.
1 parent ab5af9f commit a88c7b5

File tree

3 files changed

+101
-26
lines changed

3 files changed

+101
-26
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ device's X.509 certificate is signed, so cloud servers can trust the
208208
manufacturer serial number.
209209

210210
At this point, you're the manufacturer. Decide how you'd like your serial
211-
numbers to look. Whatever you pick, it must fit in 16-bytes. Representing the
211+
numbers to look. Whatever you pick, it must fit in 48-bytes. Representing the
212212
serial number is ASCII is commonly done. If you don't want to deal with this, do
213213
what we do (Base32-encode the ATECC508A/608A's globally unique identifier):
214214

@@ -388,7 +388,8 @@ following layout:
388388
Bytes | Name | Contents
389389
------ | ----------------- | -------------------------
390390
0-3 | Magic | 4e 72 76 73
391-
4-5 | Flags | TBD. Set to 0
391+
4 | Flags_MSB | 0
392+
5 | Flags_LSB | 0 = 16 byte serial number, 1 = 32 byte serial number
392393
6-15 | Board name | 10 byte name for the board in ASCII (set unused bytes to 0)
393-
16-31 | Mfg serial number | 16 byte manufacturer-assigned serial number in ASCII (set unused bytes to 0)
394-
32-63 | User | These are unassigned
394+
16-31 | Mfg serial number | Serial number in ASCII (set unused bytes to 0)
395+
32-63 | Serial# or User | If Flags == 1, then the rest of the serial number

lib/nerves_key/otp.ex

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,32 @@ defmodule NervesKey.OTP do
2121
user: <<_::256>>
2222
}
2323

24+
@type raw() :: <<_::512>>
25+
2426
@doc """
2527
Create a NervesKey OTP data struct
2628
"""
27-
@spec new(String.t(), String.t(), binary()) :: t()
28-
def new(board_name, manufacturer_sn, user \\ <<0::size(256)>>) do
29-
%__MODULE__{flags: 0, board_name: board_name, manufacturer_sn: manufacturer_sn, user: user}
29+
@spec new(String.t(), String.t(), binary() | nil) :: t()
30+
def new(board_name, manufacturer_sn, user \\ nil)
31+
32+
def new(board_name, manufacturer_sn, nil)
33+
when byte_size(manufacturer_sn) > 16 and byte_size(manufacturer_sn) <= 32 do
34+
%__MODULE__{flags: 1, board_name: board_name, manufacturer_sn: manufacturer_sn, user: <<>>}
3035
end
3136

37+
def new(board_name, manufacturer_sn, user)
38+
when byte_size(manufacturer_sn) <= 16 do
39+
%__MODULE__{
40+
flags: 0,
41+
board_name: board_name,
42+
manufacturer_sn: manufacturer_sn,
43+
user: check_user(user)
44+
}
45+
end
46+
47+
defp check_user(nil), do: <<0::256>>
48+
defp check_user(user) when byte_size(user) <= 32, do: user
49+
3250
@doc """
3351
Read NervesKey information from the OTP data.
3452
"""
@@ -42,15 +60,27 @@ defmodule NervesKey.OTP do
4260
@doc """
4361
Write NervesKey information to the OTP zone.
4462
"""
45-
@spec write(Transport.t(), <<_::512>>) :: :ok | {:error, atom()}
63+
@spec write(Transport.t(), raw()) :: :ok | {:error, atom()}
4664
defdelegate write(transport, data), to: OTPZone
4765

4866
@doc """
4967
Convert a raw configuration to a nice map.
5068
"""
51-
@spec from_raw(<<_::512>>) :: {:ok, t()} | {:error, atom()}
69+
@spec from_raw(raw()) :: {:ok, t()} | {:error, atom()}
70+
def from_raw(<<@magic::binary(), flags::16, board_name::10-bytes, manufacturer_sn::48-bytes>>)
71+
when flags == 1 do
72+
# Long serial number flag
73+
{:ok,
74+
%__MODULE__{
75+
flags: flags,
76+
board_name: Util.trim_zeros(board_name),
77+
manufacturer_sn: Util.trim_zeros(manufacturer_sn),
78+
user: <<>>
79+
}}
80+
end
81+
5282
def from_raw(
53-
<<@magic::binary(), flags::size(16), board_name::10-bytes, manufacturer_sn::16-bytes,
83+
<<@magic::binary(), flags::16, board_name::10-bytes, manufacturer_sn::16-bytes,
5484
user::32-bytes>>
5585
) do
5686
{:ok,
@@ -67,7 +97,7 @@ defmodule NervesKey.OTP do
6797
@doc """
6898
Convert a raw configuration to a nice map. Raise if there is an error.
6999
"""
70-
@spec from_raw!(<<_::512>>) :: t() | no_return()
100+
@spec from_raw!(raw()) :: t()
71101
def from_raw!(raw) do
72102
{:ok, raw} = from_raw(raw)
73103
raw
@@ -76,8 +106,15 @@ defmodule NervesKey.OTP do
76106
@doc """
77107
Convert a nice config map back to a raw configuration
78108
"""
79-
@spec to_raw(t()) :: <<_::512>>
80-
def to_raw(info) do
109+
@spec to_raw(t()) :: raw()
110+
def to_raw(%__MODULE__{flags: 1} = info) do
111+
board_name = Util.pad_zeros(info.board_name, 10)
112+
manufacturer_sn = Util.pad_zeros(info.manufacturer_sn, 48)
113+
114+
<<@magic::binary(), info.flags::size(16), board_name::binary(), manufacturer_sn::binary()>>
115+
end
116+
117+
def to_raw(%__MODULE__{} = info) do
81118
board_name = Util.pad_zeros(info.board_name, 10)
82119
manufacturer_sn = Util.pad_zeros(info.manufacturer_sn, 16)
83120
user = Util.pad_zeros(info.user, 32)

test/nerves_key/otp_test.exs

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,59 @@ defmodule NervesKey.OTPTest do
33

44
alias NervesKey.OTP
55

6-
@test_data %OTP{
7-
flags: 1234,
8-
manufacturer_sn: "1234578",
9-
board_name: "my_board",
10-
user:
11-
<<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
12-
25, 26, 27, 28, 29, 30, 31>>
13-
}
14-
15-
test "to raw and back" do
16-
raw_and_back = @test_data |> OTP.to_raw() |> OTP.from_raw!()
17-
assert raw_and_back == @test_data
6+
defp assert_raw_and_back(otp) do
7+
raw_and_back = otp |> OTP.to_raw() |> OTP.from_raw!()
8+
9+
assert otp == raw_and_back
1810
end
1911

2012
test "magic is right" do
21-
<<magic::4-bytes, _rest::binary()>> = OTP.to_raw(@test_data)
13+
<<magic::4-bytes, _rest::binary()>> = OTP.to_raw(OTP.new("", ""))
2214
assert magic == <<0x4E, 0x72, 0x76, 0x73>>
2315
end
16+
17+
test "encode normal length serial numbers" do
18+
otp =
19+
OTP.new(
20+
"my_board",
21+
"1234578",
22+
<<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
23+
24, 25, 26, 27, 28, 29, 30, 31>>
24+
)
25+
26+
assert OTP.to_raw(otp) ==
27+
<<0x4E, 0x72, 0x76, 0x73, 0x00, 0x00, "my_board", 0, 0, "1234578", 0, 0, 0, 0, 0, 0,
28+
0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
29+
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31>>
30+
31+
assert_raw_and_back(otp)
32+
end
33+
34+
test "encode 16 byte serial" do
35+
otp = OTP.new("my_board", "1234567890123456")
36+
37+
assert OTP.to_raw(otp) ==
38+
<<0x4E, 0x72, 0x76, 0x73, 0x00, 0x00, "my_board", 0, 0, "1234567890123456", 0::256>>
39+
40+
assert_raw_and_back(otp)
41+
end
42+
43+
test "encode 17 byte serial" do
44+
otp = OTP.new("my_board", "12345678901234567")
45+
46+
assert OTP.to_raw(otp) ==
47+
<<0x4E, 0x72, 0x76, 0x73, 0x00, 0x01, "my_board", 0, 0, "12345678901234567", 0::248>>
48+
49+
assert_raw_and_back(otp)
50+
end
51+
52+
test "encode 32 byte serial" do
53+
otp = OTP.new("my_board", "12345678901234567890123456789012")
54+
55+
assert OTP.to_raw(otp) ==
56+
<<0x4E, 0x72, 0x76, 0x73, 0x00, 0x01, "my_board", 0, 0,
57+
"12345678901234567890123456789012", 0::128>>
58+
59+
assert_raw_and_back(otp)
60+
end
2461
end

0 commit comments

Comments
 (0)