blob: c2c6048f400d1cc43d1d21032c6c8c3be01a8cb0 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Manage all sideload keys
`include "prim_assert.sv"
module keymgr_sideload_key_ctrl import keymgr_pkg::*;(
input clk_i,
input rst_ni,
input init_i,
input keymgr_sideload_clr_e clr_key_i, // clear key just deletes the key
input wipe_key_i, // wipe key deletes and renders sideloads useless until reboot
input [Shares-1:0][RandWidth-1:0] entropy_i,
input keymgr_key_dest_e dest_sel_i,
input prim_mubi_pkg::mubi4_t hw_key_sel_i,
input data_en_i,
input data_valid_i,
input hw_key_req_t key_i,
input [Shares-1:0][kmac_pkg::AppDigestW-1:0] data_i,
output logic prng_en_o,
output hw_key_req_t aes_key_o,
output hw_key_req_t kmac_key_o,
output otbn_key_req_t otbn_key_o,
output logic sideload_sel_err_o,
output logic fsm_err_o
);
// Encoding generated with:
// $ ./util/design/sparse-fsm-encode.py -d 5 -m 4 -n 10 \
// -s 1700801647 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: --
// 4: --
// 5: |||||||||||||||||||| (33.33%)
// 6: |||||||||| (16.67%)
// 7: |||||||||||||||||||| (33.33%)
// 8: |||||||||| (16.67%)
// 9: --
// 10: --
//
// Minimum Hamming distance: 5
// Maximum Hamming distance: 8
// Minimum Hamming weight: 3
// Maximum Hamming weight: 7
//
localparam int StateWidth = 10;
typedef enum logic [StateWidth-1:0] {
StSideloadReset = 10'b0011111011,
StSideloadIdle = 10'b0101000101,
StSideloadWipe = 10'b1110110010,
StSideloadStop = 10'b1000001010
} keymgr_sideload_e;
keymgr_sideload_e state_q, state_d;
// This primitive is used to place a size-only constraint on the
// flops in order to prevent FSM state encoding optimizations.
`PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, keymgr_sideload_e, StSideloadReset)
logic keys_en;
logic [Shares-1:0][KeyWidth-1:0] data_truncated;
for(genvar i = 0; i < Shares; i++) begin : gen_truncate_data
assign data_truncated[i] = data_i[i][KeyWidth-1:0];
end
// clear all keys when selected by software, or when
// wipe command is received
logic clr_all_keys;
logic [LastIdx-1:0] slot_clr;
assign clr_all_keys = wipe_key_i |
!(clr_key_i inside {SideLoadClrIdle,
SideLoadClrAes,
SideLoadClrKmac,
SideLoadClrOtbn});
assign slot_clr[AesIdx] = clr_all_keys | (clr_key_i == SideLoadClrAes);
assign slot_clr[KmacIdx] = clr_all_keys | (clr_key_i == SideLoadClrKmac);
assign slot_clr[OtbnIdx] = clr_all_keys | (clr_key_i == SideLoadClrOtbn);
logic clr;
assign clr = |slot_clr;
always_comb begin
keys_en = 1'b0;
state_d = state_q;
fsm_err_o = 1'b0;
unique case (state_q)
StSideloadReset: begin
if (init_i) begin
state_d = StSideloadIdle;
end
end
// when clear is received, delete the selected key
// when wipe is received, delete the key and disable sideload until reboot.
StSideloadIdle: begin
keys_en = 1'b1;
if (wipe_key_i) begin
state_d = StSideloadWipe;
end
end
StSideloadWipe: begin
keys_en = 1'b0;
if (!wipe_key_i) begin
state_d = StSideloadStop;
end
end
// intentional terminal state
StSideloadStop: begin
keys_en = 1'b0;
end
default: begin
fsm_err_o = 1'b1;
end
endcase // unique case (state_q)
end
import prim_mubi_pkg::mubi4_test_true_strict;
prim_mubi_pkg::mubi4_t [LastIdx-1:0] hw_key_sel;
prim_mubi4_sync #(
.NumCopies(int'(LastIdx)),
.AsyncOn(0) // clock/reset below is only used for SVAs.
) u_mubi_buf (
.clk_i,
.rst_ni,
.mubi_i(hw_key_sel_i),
.mubi_o(hw_key_sel)
);
logic [LastIdx-1:0] slot_sel;
assign slot_sel[AesIdx] = (dest_sel_i == Aes) & mubi4_test_true_strict(hw_key_sel[AesIdx]);
assign slot_sel[KmacIdx] = (dest_sel_i == Kmac) & mubi4_test_true_strict(hw_key_sel[KmacIdx]);
assign slot_sel[OtbnIdx] = (dest_sel_i == Otbn) & mubi4_test_true_strict(hw_key_sel[OtbnIdx]);
keymgr_sideload_key u_aes_key (
.clk_i,
.rst_ni,
.en_i(keys_en),
.set_en_i(data_en_i),
.set_i(data_valid_i & slot_sel[AesIdx]),
.clr_i(slot_clr[AesIdx]),
.entropy_i(entropy_i),
.key_i(data_truncated),
.valid_o(aes_key_o.valid),
.key_o(aes_key_o.key)
);
keymgr_sideload_key #(
.Width(OtbnKeyWidth)
) u_otbn_key (
.clk_i,
.rst_ni,
.en_i(keys_en),
.set_en_i(data_en_i),
.set_i(data_valid_i & slot_sel[OtbnIdx]),
.clr_i(slot_clr[OtbnIdx]),
.entropy_i(entropy_i),
.key_i(data_i),
.valid_o(otbn_key_o.valid),
.key_o(otbn_key_o.key)
);
hw_key_req_t kmac_sideload_key;
keymgr_sideload_key u_kmac_key (
.clk_i,
.rst_ni,
.en_i(keys_en),
.set_en_i(data_en_i),
.set_i(data_valid_i & slot_sel[KmacIdx]),
.clr_i(slot_clr[KmacIdx]),
.entropy_i(entropy_i),
.key_i(data_truncated),
.valid_o(kmac_sideload_key.valid),
.key_o(kmac_sideload_key.key)
);
logic [LastIdx-1:0] valid_tracking_q;
for (genvar i = int'(AesIdx); i < LastIdx; i++) begin : gen_tracking_valid
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
valid_tracking_q[i] <= '0;
end else if (slot_clr[i]) begin
valid_tracking_q[i] <= '0;
end else if (slot_sel[i])begin
valid_tracking_q[i] <= 1'b1;
end
end
end
// SEC_CM: SIDE_LOAD_SEL.CTRL.CONSISTENCY
logic [LastIdx-1:0] valids;
assign valids[AesIdx] = aes_key_o.valid;
assign valids[KmacIdx] = kmac_sideload_key.valid;
assign valids[OtbnIdx] = otbn_key_o.valid;
// If valid tracking claims a valid should be 0 but 1 is observed, it is
// an error.
// Note the sideload error is not a direct constant comparision. Instead
// it provides hint when valids is allowed to be valid. If valid becomes
// 1 outside that window, then an error is triggered.
assign sideload_sel_err_o = |(~valid_tracking_q & valids);
// when directed by keymgr_ctrl, switch over to internal key and feed to kmac
assign kmac_key_o = key_i.valid ? key_i : kmac_sideload_key;
// when clearing, request prng
assign prng_en_o = clr;
/////////////////////////////////////
// Assertions
/////////////////////////////////////
// When updating a sideload key, the secret key state must always be used as the source
`ASSERT(KmacKeySource_a, data_valid_i |-> key_i.valid)
endmodule // keymgr_sideload_key_ctrl