I have kept digging into this for the ATECC chip as well with just the goal of being able to get a secret for full disk encryption that could only be provided from the secure element and with reasonable protection on the bus. This thread seems to have an answer. You use the HKDF mechanism (well the part I think) and it can take something in a private/protected slot on the chip, take an input and produce an output.
I have a very rough hacking session going on this for ATECC508A.Request:
@atecc508a_op_kdf 0x56
def kdf(transport, input, a \\ 0x00) do
Transport.transaction(transport, fn request ->
# See Table 11-33 - Mode Encoding
nonce_mode = <<0::size(2), 0::size(1), 0::size(3), 3::size(2)>>
nonce_in =
<<a, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
0x0F, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
0x0D, 0x0E, 0x0F>>
request.(
<<@atecc508a_op_nonce, nonce_mode::binary, 0::size(16), nonce_in::binary>>
|> IO.inspect(as: :binary, base: :hex),
29,
1
)
|> interpret_result()
|> case do
{{:ok, value}, _retry} ->
Logger.info("nonce: #{inspect(value)}")
# input max 32 byte
# mode = 0x40 + 0x02 + 0x10
mode = 0x40 + 0x02 + 0x10
log_binary(mode, "mode")
# 0x40 (KDF_MODE_ALG_HKDF)
# 0x02 (KDF_MODE_SOURCE_SLOT) # we want to use a slotted private key
# 0x10 (KDF_MODE_TARGET_OUTPUT) output to output
# 0x14 KDF_MODE_TARGET_OUTPUT_ENC encrypted output
# slot 0
# key_id = <<0x00, 0x00>>
# source slot 8
key_id = <<0x00, 0x08>>
log_binary(key_id, "key_id")
details = <<byte_size(input)::8, 0x00::16, 0x02::8>>
log_binary(details, "details")
input_length = byte_size(input)
rem = (32 - input_length) * 8
input = input <> <<0::size(rem)>>
log_binary(input, "input")
payload =
<<@atecc508a_op_kdf, mode, :binary.decode_unsigned(key_id),
:binary.decode_unsigned(details), :binary.decode_unsigned(input)>>
log_binary(payload, "payload")
# output_size = <<32,0,0,0>> |> :binary.decode_unsigned()
request.(payload, 128, 32)
# request.(payload, 128, 64)
{error, _retry} ->
error
end
end)
|> interpret_result()
end
defp log_binary(bin, label) when is_integer(bin) do
log_binary(<<bin>>, label)
end
defp log_binary(bin, label) do
Logger.info(
"#{label}| hex: #{inspect(bin, as: :binary, base: :hex)} bin: #{inspect(bin, as: :binary, base: :binary)} [#{inspect(byte_size(bin))}]"
)
rescue
_ ->
Logger.error("Could not print: #{inspect(bin)}")
end
So the nonce call input is not particularly important now. I could make that random or based on input. The current setup, the nonce input ends up in the TempKey area. If I set 0x00 instead of 0x02 I can have that be my salt for the KDF. Not what I want. Ideally I’d like to use output 0x14 for encrypted output instead of 0x10 also don’t have that working.
Made some progress this evening where I realized the ATECC is very particular about ECC private keys, with good reason. So it won’t let me use those for this. Presumably that would let me do some kind of mathematical attack to figure out what they are. So there are limits.
Using the wide-open settings slots that NervesKey configures I could actually make the slot work, assuming I put some data in there and that became my salt. If I can put a secret in a writable slot and lock it to be private with some non-ECC just-very-private data in it that should be good for what I want done.
And I think I saw something indicating that the encrypted output is only available for a locked slot. Not sure, that might be speculation on my part but I will attempt to figure it out. I think the encrypted output is the “IO protection” and I hope that doesn’t require the Secure Boot approach they have. I plan to f around and find out.


















