From 9a19619ff1995a39845abac9d7c8daa4ee896e3f Mon Sep 17 00:00:00 2001 From: Pirmin Vogel Date: Sun, 13 Oct 2024 00:36:24 +0200 Subject: [PATCH] [aes] Add GHASH module Signed-off-by: Pirmin Vogel --- hw/ip/aes/aes.core | 2 + hw/ip/aes/rtl/aes_ghash.sv | 364 +++++++++++++++++++++++++++++++++++++ hw/ip/aes/rtl/aes_pkg.sv | 100 ++++++++++ 3 files changed, 466 insertions(+) create mode 100644 hw/ip/aes/rtl/aes_ghash.sv diff --git a/hw/ip/aes/aes.core b/hw/ip/aes/aes.core index f20c3d42979484..aad7700452f3c4 100644 --- a/hw/ip/aes/aes.core +++ b/hw/ip/aes/aes.core @@ -8,6 +8,7 @@ filesets: files_rtl: depend: - lowrisc:prim:all + - lowrisc:prim:gf_mult - lowrisc:prim:lc_sync - lowrisc:prim:lfsr - lowrisc:prim:sparse_fsm @@ -53,6 +54,7 @@ filesets: - rtl/aes_key_expand.sv - rtl/aes_prng_clearing.sv - rtl/aes_prng_masking.sv + - rtl/aes_ghash.sv - rtl/aes.sv file_type: systemVerilogSource diff --git a/hw/ip/aes/rtl/aes_ghash.sv b/hw/ip/aes/rtl/aes_ghash.sv new file mode 100644 index 00000000000000..c22a3b0988c938 --- /dev/null +++ b/hw/ip/aes/rtl/aes_ghash.sv @@ -0,0 +1,364 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// AES GHASH implementation for AES-GCM +// +// This module implements the GHASH core including hash state and hash key register required for +// AES-GCM. + +`include "prim_assert.sv" + +module aes_ghash import aes_pkg::*; +#( + parameter bit SecMasking = 0, + parameter sbox_impl_e SecSBoxImpl = SBoxImplLut, + + localparam int NumShares = SecMasking ? 2 : 1 // derived parameter +) ( + input logic clk_i, + input logic rst_ni, + + // Input handshake signals + input sp2v_e in_valid_i, + output sp2v_e in_ready_o, + + // Output handshake signals + output sp2v_e out_valid_o, + input sp2v_e out_ready_i, + + // Control signals + input aes_op_e op_i, + input gcm_phase_e gcm_phase_i, + input logic [4:0] num_valid_bytes_i, + input sp2v_e load_hash_subkey_i, + input logic alert_fatal_i, + output logic alert_o, + + // Pseudo-random data for register clearing + input logic [GCMDegree-1:0] prd_clearing_i [NumShares], + + // I/O data signals + input logic [GCMDegree-1:0] cipher_state_init_i [NumShares], // Masked cipher core input + // For GCM_RESTORE + input logic [GCMDegree-1:0] data_in_prev_i, // Ciphertext for decryption, AAD + input logic [GCMDegree-1:0] data_out_i, // Ciphertext for encryption + input logic [GCMDegree-1:0] cipher_state_done_i [NumShares], // Masked cipher core output + output logic [GCMDegree-1:0] ghash_state_done_o [NumShares] +); + + // Parameters + // The number of cycles must be a power of two and ideally matches the minimum latency of the + // cipher core which is 56 clock cycles (masked) or 12 clock cycles (unmasked) for AES-128. + localparam int unsigned GFMultCycles = SecMasking ? 32 : 8; + + // Signals + logic [GCMDegree-1:0] s_d [NumShares]; + logic [GCMDegree-1:0] s_q [NumShares]; + sp2v_e s_we; + s_sel_e s_sel; + logic [15:0][7:0] ghash_in; + logic [15:0][7:0] ghash_in_valid; + ghash_in_sel_e ghash_in_sel; + logic [GCMDegree-1:0] ghash_state_d [NumShares]; + logic [GCMDegree-1:0] ghash_state_q [NumShares]; + logic [GCMDegree-1:0] ghash_state_zero [NumShares]; + logic [GCMDegree-1:0] ghash_state_load [NumShares]; + logic [GCMDegree-1:0] ghash_state_add [NumShares]; + sp2v_e ghash_state_we; + ghash_state_sel_e ghash_state_sel; + logic [GCMDegree-1:0] ghash_state_mult [NumShares]; + logic [GCMDegree-1:0] hash_subkey_d [NumShares]; + logic [GCMDegree-1:0] hash_subkey_q [NumShares]; + sp2v_e hash_subkey_we; + hash_subkey_sel_e hash_subkey_sel; + logic gf_mult_req; + logic gf_mult_ack; + aes_ghash_e aes_ghash_ns, aes_ghash_cs; + + //////////////////// + // S = AES_K(J_0) // + //////////////////// + // The initial counter block J_0 encrypted using the encryption key K. For the unmasked + // implementation this is only used at the very end. For the masked implementaion, it is used + // multiple times and in various forms throughout the computation of the authentication tag. + always_comb begin : s_mux + unique case (s_sel) + S_LOAD: s_d = cipher_state_done_i; + S_CLEAR: s_d = prd_clearing_state_i; + default: s_d = prd_clearing_state_i; + endcase + end + + always_ff @(posedge clk_i or negedge rst_ni) begin : s_reg + if (!rst_ni) begin + s_q <= '{default: '0}; + end else if (s_we == SP2V_HIGH) begin + s_q <= s_d; + end + end + + ///////////////// + // GHASH Input // + ///////////////// + // Select the cipher text for encryptio + always_comb begin : ghash_in_mux + unique case (ghash_in_sel) + GHASH_IN_DATA_IN_PREV: ghash_in = data_in_prev_i; + GHASH_IN_DATA_OUT: ghash_in = data_out_i; + GHASH_IN_S: ghash_in = s_q; + default: ghash_in = data_in_prev_i; + endcase + end + + // Mask invalid bytes. + always_comb begin + for (int i = 0; i < 16; i++) begin + ghash_in_valid[i] = num_valid_bytes_i > i ? ghash_in[i] : 8'b0; + end + end + + ///////////////// + // GHASH State // + ///////////////// + if (!SecMasking) begin : gen_ghash_state_zero_unmasked + assign ghash_state_zero[0] = '0; + end else begin : gen_ghash_state_zero_unmasked + assign ghash_state_zero[0] = prd_clearing_i[1]; + assign ghash_state_zero[1] = prd_clearing_i[1]; + end + + // Add the GHASH input to the current state. + assign ghash_state_add[0] = ghash_state_q[0] ^ ghash_in_valid; + if (SecMasking) begin : gen_ghash_state_share_1 + assign ghash_state_add[1] = ghash_state_q[1]; + assign ghash_state_mult[1] = ghash_state_q[1]; + end + + always_comb begin : ghash_state_mux + unique case (ghash_state_sel) + GHASH_STATE_RESTORE: ghash_state_d = cipher_state_init_i; + GHASH_STATE_ZERO: ghash_state_d = ghash_state_zero; + GHASH_STATE_ADD: ghash_state_d = ghash_state_add; + GHASH_STATE_MULT: ghash_state_d = ghash_state_mult; + GHASH_STATE_CLEAR: ghash_state_d = prd_clearing_i; + default: ghash_state_d = prd_clearing_i; + endcase + end + + always_ff @(posedge clk_i or negedge rst_ni) begin : ghash_state_reg + if (!rst_ni) begin + ghash_state_q <= '{default: '0}; + end else if (ghash_state_we == SP2V_HIGH) begin + ghash_state_q <= ghash_state_d; + end + end + + ///////////////// + // Hash Subkey // + ///////////////// + always_comb begin : hash_subkey_mux + unique case (hash_subkey_sel) + HASH_SUBKEY_LOAD: hash_subkey_d = cipher_state_done_i; + HASH_SUBKEY_CLEAR: hash_subkey_d = prd_clearing_i; + default: hash_subkey_d = prd_clearing_i; + endcase + end + + always_ff @(posedge clk_i or negedge rst_ni) begin : hash_subkey_reg + if (!rst_ni) begin + hash_subkey_q <= '{default: '0}; + end else if (hash_subkey_we == SP2V_HIGH) begin + hash_subkey_q <= hash_subkey_d; + end + end + + ////////////////////////// + // GF(2^128) Multiplier // + ////////////////////////// + + prim_gf_mult #( + .Width (GCMDegree), + .StagesPerCycle(GCMDegree / 8), + .IPoly (GCMIPoly), + ) u_gf_mult ( + .clk_i (clk_i), + .rst_ni(rst_ni), + + .req_i(gf_mult_req), + .ack_o(gf_mult_ack), + + .operand_a_i(ghash_state_q[0]), // The A input is scanned. + .operand_b_i(hash_subkey_q[0]), // The B input is not scanned. + + .prod_o(ghash_state_mult[0]) + ); + + ///////////////// + // Control FSM // + ///////////////// + + always_comb begin : aes_ghash_fsm + + // Handshake signals + in_ready_o = SP2V_LOW; + out_valid_o = SP2V_LOW; + + // Data path + s_sel = S_CLEAR; + s_we = SP2V_LOW; + + ghash_in_sel = GHASH_IN_DATA_IN_PREV; + + ghash_state_sel = GHASH_STATE_CLEAR; + ghash_state_we = SP2V_LOW; + + hash_subkey_sel = HASH_SUBKEY_CLEAR; + hash_subkey_we = SP2V_LOW; + + gf_mult_req = 1'b0; + + // FSM + aes_ghash_ns = aes_ghash_cs; + + // Alert + alert_o = 1'b0; + + unique case (aes_ghash_cs) + + // load key: input handshake only + // - wait for GHASH ready in main FSM state after CTRL_PRNG_UPDATE + // load s: input handshake only + // - wait for GHASH ready in main FSM state after CTRL_PRNG_UPDATE + // aad: add + mult, handshake between main FSM and GHASH only, no cipher op + // - wait for GHASH ready in main FSM state after CTRL_PRNG_UPDATE + // - perform handshake similar as for decrypt + // encrypt: add + mult, handshake between main FSM, cipher and GHASH + // - don't wait for GHASH ready in main FSM IDLE + // - wait for GHASH ready in main FSM FINISH, use finish signal + // ghash_ready -> ghash_valid, can wait in cycle before FINISH + // by adding a new state after CTRL_PRNG_UPDATE, before CTRL_FINISH + // we know that GHASH will be ready to consume the cipher output in the next cycle + // decrypt: add + mult, handshake between main FSM, cipher and GHASH + // - wait for GHASH ready in main FSM FINISH, use finish signal + // ghash_ready -> ghash_valid, can wait in cycle before FINISH + // by adding a new state after CTRL_PRNG_UPDATE, perform before CTRL_FINISH + // save & tag: wait for GHASH ready after CTRL_PRNG_UPDATE, clear internal state with output handshake + // - output handshake in CTRL FINISH + // clear: input handshake only + // - wait for GHASH ready in main FSM state after CTRL_PRNG_UPDATE + + GHASH_IDLE: begin + in_ready_o = SP2V_HIGH; + if (in_valid_i == SP2V_HIGH) begin + if (clear_i) begin + // Clearing has highest priority. + s_we = SP2V_HIGH; + ghash_state_we = SP2V_HIGH; + hash_subkey_sel = SP2V_HIGH; + + end else if (gcm_phase_i == GCM_INIT) begin + if (load_hash_subkey_i == SP2V_HIGH) begin + // Load the hash subkey and zero the state. + hash_subkey_sel = HASH_SUBKEY_LOAD; + hash_subkey_we = SP2V_HIGH; + ghash_state_sel = GHASH_STATE_ZERO; + ghash_state_we = SP2V_HIGH; + end else begin + // Load S. + s_sel = S_LOAD; + s_we = SP2V_HIGH; + end + + end else if (gcm_phase_i == GCM_RESTORE) begin + // Restore a previously loaded GHASH state. + ghash_state_sel = GHASH_STATE_RESTORE; + ghash_state_we = SP2V_HIGH; + + end else if (gcm_phase_i == GCM_AAD || + gcm_phase_i == GCM_TEXT || + gcm_phase_i == GCM_LEN) begin + // Select the proper input for the addition. + ghash_in_sel = + (gcm_phase_i == GCM_AAD) ? GHASH_IN_DATA_IN_PREV : + (gcm_phase_i == GCM_TEXT && op_i == AES_DEC) ? GHASH_IN_DATA_IN_PREV : + (gcm_phase_i == GCM_TEXT && op_i == AES_ENC) ? GHASH_IN_DATA_OUT : + (gcm_phase_i == GCM_LEN) ? GHASH_IN_DATA_OUT : + GHASH_IN_DATA_IN_PREV; + + // Add the current input to the GHASH state to start the multiplication in the next + // clock cycle. + ghash_state_sel = GHASH_STATE_ADD; + ghash_state_we = SP2V_HIGH; + + aes_ghash_ns = GHASH_MULT; + + end else if (gcm_phase_i == GCM_SAVE) begin + // Get ready to output the current GHASH state. + aes_ghash_ns = GHASH_OUT; + + end else if (gcm_phase_i == GCM_TAG) begin + // Add S to the GHASH state and then get ready to output the final tag. + ghash_in_sel = GHASH_IN_S; + ghash_state_sel = GHASH_STATE_ADD; + ghash_state_we = SP2V_HIGH; + + aes_ghash_ns = GHASH_OUT; + end else begin + // Handshake without a valid command. We should never get here. If we do (e.g. via a + // malicious glitch), error out immediately. + aes_ghash_ns = GHASH_ERROR; + end + end + end + + GHASH_MULT: begin + // Perform the multiplication and update the state. + gf_mult_req = 1'b1; + if (gf_mult_ack) begin + ghash_state_sel = GHASH_STATE_MULT; + ghash_state_we = SP2V_HIGH; + aes_ghash_ns = GHASH_IDLE; + end + end + + GHASH_OUT: begin + // Perform output handshake and clear all internal state with pseudo-random data. + out_valid_o = SP2V_HIGH; + if (out_ready_i == SP2V_HIGH) begin + s_we = SP2V_HIGH; + ghash_state_we = SP2V_HIGH; + hash_subkey_sel = SP2V_HIGH; + aes_ghash_ns = GHASH_IDLE; + end + end + + GHASH_ERROR: begin + // Terminal error state + alert_o = 1'b1; + end + + // We should never get here. If we do (e.g. via a malicious glitch), error out immediately. + default: begin + aes_ghash_ns = GHASH_ERROR; + alert_o = 1'b1; + end + endcase + + // Unconditionally jump into the terminal error state if a fatal alert has been triggered. + if (alert_fatal_i) begin + aes_ghash_ns = GHASH_ERROR; + end + end + + // SEC_CM: GHASH.FSM.SPARSE + `PRIM_FLOP_SPARSE_FSM(u_state_regs, aes_ghash_ns, + aes_ghash_cs, aes_ghash_e, GHASH_IDLE) + + ///////////// + // Outputs // + ///////////// + + assign ghash_state_done_o = ghash_state_q; + +endmodule diff --git a/hw/ip/aes/rtl/aes_pkg.sv b/hw/ip/aes/rtl/aes_pkg.sv index d0112eaafbc7dd..2b2d08a1655b56 100644 --- a/hw/ip/aes/rtl/aes_pkg.sv +++ b/hw/ip/aes/rtl/aes_pkg.sv @@ -83,6 +83,19 @@ typedef enum integer { // see aes_sbox_canright_dom.sv } sbox_impl_e; +// GF(2^128) irreducible, field-generating polynomial for AES-GCM +// See Section "6.3 Multiplication Operation on Blocks" of +// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf on Page 11: +// "Let R be the bit string 11100001 || 0^120." +// And further on Page 12: +// "The reduction modulus is the polynomial of degree 128 that corresponds to R || 1" +// Or in other words: x^128 + x^7 + x^2 + x + 1 +// The MSB gets clipped off below. +parameter int unsigned GCMDegree = 128; +parameter bit [GCMDegree-1:0] GCMIPoly = GCMDegree'(1'b1) << 7 | + GCMDegree'(1'b1) << 2 | + GCMDegree'(1'b1) << 1 | + GCMDegree'(1'b1) << 0; // Parameters used for controlgroups in the coverage parameter int AES_OP_WIDTH = 2; @@ -242,6 +255,32 @@ typedef struct packed { CTRL_ERROR = 6'b010111 } aes_ctrl_e; +// Encoding generated with: +// $ ./util/design/sparse-fsm-encode.py -d 3 -m 4 -n 5 \ +// -s 31468618 --language=sv +// +// Hamming distance histogram: +// +// 0: -- +// 1: -- +// 2: -- +// 3: |||||||||||||||||||| (66.67%) +// 4: |||||||||| (33.33%) +// 5: -- +// +// Minimum Hamming distance: 3 +// Maximum Hamming distance: 4 +// Minimum Hamming weight: 1 +// Maximum Hamming weight: 4 +// +localparam int GhashStateWidth = 5; +typedef enum logic [GhashStateWidth-1:0] { + GHASH_IDLE = 5'b11000, + GHASH_MULT = 5'b00100, + GHASH_OUT_ = 5'b01011, + GHASH_ERROR = 5'b10111 +} aes_ghash_e; + // Generic, sparse mux selector encodings // Encoding generated with: @@ -313,6 +352,35 @@ typedef enum logic [Mux4SelWidth-1:0] { MUX4_SEL_3 = 5'b10111 } mux4_sel_e; +// Encoding generated with: +// $ ./util/design/sparse-fsm-encode.py -d 3 -m 5 -n 6 \ +// -s 31468618 --language=sv +// +// Hamming distance histogram: +// +// 0: -- +// 1: -- +// 2: -- +// 3: |||||||||||||||||||| (50.00%) +// 4: |||||||||||||||| (40.00%) +// 5: |||| (10.00%) +// 6: -- +// +// Minimum Hamming distance: 3 +// Maximum Hamming distance: 5 +// Minimum Hamming weight: 1 +// Maximum Hamming weight: 5 +// +localparam int Mux5SelWidth = 6; +typedef enum logic [Mux5SelWidth-1:0] { + MUX5_SEL_0 = 6'b110000, + MUX5_SEL_1 = 6'b001000, + MUX5_SEL_2 = 6'b000011, + MUX5_SEL_3 = 6'b011101, + MUX5_SEL_4 = 6'b111110 +} mux5_sel_e; + + // $ ./sparse-fsm-encode.py -d 3 -m 6 -n 6 \ // -s 31468618 --language=sv // @@ -437,6 +505,38 @@ typedef enum logic [AddSOSelWidth-1:0] { ADD_SO_DIP = MUX3_SEL_2 } add_so_sel_e; +parameter int SSelNum = 2; +parameter int SSelWidth = Mux2SelWidth; +typedef enum logic [SSelWidth-1:0] { + S_LOAD = MUX2_SEL_0, + S_CLEAR = MUX2_SEL_1 +} s_sel_e; + +parameter int GHashInSelNum = 3; +parameter int GHashInSelWidth = Mux3SelWidth; +typedef enum logic [GHashInSelWidth-1:0] { + GHASH_IN_DATA_IN_PREV = MUX3_SEL_0, + GHASH_IN_DATA_OUT = MUX3_SEL_1, + GHASH_IN_S = MUX3_SEL_2 +} ghash_in_sel_e; + +parameter int GHashStateSelNum = 5; +parameter int GHashStateSelWidth = Mux5SelWidth; +typedef enum logic [GHashStateSelWidth-1:0] { + GHASH_STATE_RESTORE = MUX5_SEL_0, + GHASH_STATE_ZERO = MUX5_SEL_1, + GHASH_STATE_ADD = MUX5_SEL_2, + GHASH_STATE_MULT = MUX5_SEL_3, + GHASH_STATE_CLEAR = MUX5_SEL_4 +} ghash_state_sel_e; + +parameter int HashSubkeySelNum = 2; +parameter int HashSubkeySelWidth = Mux2SelWidth; +typedef enum logic [HashSubkeySelWidth-1:0] { + HASH_SUBKEY_LOAD = MUX2_SEL_0, + HASH_SUBKEY_CLEAR = MUX2_SEL_1 +} hash_subkey_sel_e; + // Sparse two-value signal type sp2v_e parameter int Sp2VNum = 2; parameter int Sp2VWidth = Mux2SelWidth;