總結一下過去數周做了什麼事,實作了 FIPS 205 的簽章演算法 (沒有全部都正確實作),這個過程中知道了簽章演算法用到了什麼資料結構,包括 ADRS、compressed ADRS、FORS signature、WOTS+ signature、HT signature;知道了NIST 800-90A/90B/90C、approved RBG、熵、TRNG 和亂數彼此的關係,知道他們如何用來產生種子,如何合併 sk.seed、sk.prf、pk.seed 和 pk.root 來產生 private key 和 public key。
完成了在 GitHub Actions 上整合 Renode 到 CI 的流程中,並能在 CI 的流程裡進行基礎的測試與檢查。
實驗了在 Renode 上模擬 nRF52840 和 nRF5340 的 bare-metal 執行環境,在 Renode 觀察 UART 的輸出;也經歷了針對 nRF52840 和 nRF5340 的 bare-metal 開發,實驗如何不依賴 OS 的 library。
實驗了如何將演算法從硬體抽離,讓演算法的同一個實作能在不同的硬體上執行,也就是說,在 ci.yml 裡讓同一個實作 build 出兩個 elf file,分別在 Renode 模擬 nRF52840 和 nRF5340 的執行結果。
實驗了連結到 Oberon 用他的 library 去算 HMAC-SHA-256 Day 18。
實驗了連結到 MbedTLS 用他的 library 去計算 SHA-256,不過失敗了。
實驗了依照 PSA Crypto API 的 function declaration 去改寫程式,為了相容 Platform Security Architecture (PSA) 做準備。這個過程中知道了如何用 PSA Crypto API 產生亂數、產生 key、計算 SHA-256 和計算 HMAC-SHA-256,但是結果經過 KAT 的比對,失敗了。
在用 psa_key_id_t 改寫程式的時候也意識到了 把 sk.seed 和 sk.prf 當參數直接在 function 之間傳遞並不安全,所以 PSA Crypto API 傳遞的是 psa_key_id_t 而不是 sk.seed 和 sk.prf 的 byte array。
實驗了用 NIST 提供的 PQCgenKAT_sign.c 加上我自己的實作 能否產出和 SPHINCS+ 官方一致的 PQCsignKAT_64.rsp,很可惜並沒有成功,PQCsignKAT_64.rsp 最終還是不一樣。
實驗了將 elf 轉成 hex 並寫到 nRF5340 DK,然後觀察 nRF5340 DK 的 UART 的輸出,很可惜又沒有成功,在 Serial Terminal 還是什麼都沒看到。
另外,UART 的初始化並沒有從硬體抽象化出來,uarte0_init 的設定如果換了硬體就不能使用了,這個在 KAT 比對的時候有發現。由於 KAT 依賴 OpenSSL,所以執行環境是 ubuntu on x86,一旦真輸出到 UART 就直接 Segmentation fault (core dumped)。
雖然幾個重要的階段都失敗了,但我覺得這是一個好的開始,因為方向是正確的,只要總結失敗的經驗,30天鐵人賽結束之後能以此為基礎繼續開發。
但有一件事,我希望在 Day 30 能辦到,就是將 SHA256 的計算 offload 到 CC312,今天將以這個目標作為 ending。
首先就將呼叫 sha256 的地方都換成 psa_hash_compute
psa_status_t status = psa_hash_compute(PSA_ALG_SHA_256,
combined,
sizeof(combined),
out32,
sizeof(out32),
&olen);
然後加上 #ifndef HARD,只有在純軟的模擬環境才用自己寫的 psa_mac_compute
, psa_hash_compute
, ...
#ifndef HARD
psa_status_t psa_crypto_init(void)
{
....
}
...
#endif
然後修改 create_sk_prf
,加上 #ifndef HARD,在純軟的模擬環境就直接產生一個亂數,如果是實體的開發版,就呼叫 psa_generate_key
psa_status_t create_sk_prf(psa_key_id_t *sk_prf_key_id, uint8_t desired_key_id) {
#ifndef HARD
// if computed by software, use random and ignore p_sk_prf_key_id
psa_generate_random(sk_prf, SPX_N);
return PSA_SUCCESS;
#endif
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_type(&attr, PSA_KEY_TYPE_HMAC);
psa_set_key_bits(&attr, (psa_key_bits_t)(8 * SPX_N));
psa_set_key_algorithm(&attr, PSA_ALG_HMAC(PSA_ALG_SHA_256));
psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_PERSISTENT);
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_MESSAGE);
psa_set_key_id(&attr, desired_key_id);
return psa_generate_key(&attr, sk_prf_key_id);
}
Makefile 也要修改,加上這段
...
else ifeq ($(TARGET),nrf5340dk_hard)
...
ARCH_DIR := cortex-m33
FLOAT_DIR := hard-float
ELF := sign_nrf5340dk_hard.elf
NRF_CC_BACKEND := nrf_cc312_mbedcrypto
# CC312/Platform(PSA)路徑
NRFX := third_party/nrfxlib/crypto
VER := 0.9.19
LIBDIR_CC312 := $(NRFX)/nrf_cc312_mbedcrypto/lib/cortex-m33/hard-float/no-interrupts
LIBDIR_PLAT := $(NRFX)/nrf_cc3xx_platform/lib/cortex-m33/hard-float/no-interrupts
LIBS := \
$(LIBDIR_CC312)/libnrf_cc312_psa_crypto_$(VER).a \
$(LIBDIR_CC312)/libnrf_cc312_core_$(VER).a \
$(LIBDIR_PLAT)/libnrf_cc3xx_platform_$(VER).a
endif
ci.yml 也要修改,加上這段
- name: Make HEX from sign_nrf5340dk_hard.elf
shell: bash
run: |
set -euo pipefail
arm-none-eabi-objcopy -O ihex sign_nrf5340dk_hard.elf sign_nrf5340dk_hard.hex
ls -l sign_nrf5340dk_hard.elf sign_nrf5340dk_hard.hex
其餘的修改屬於勞力密集,就不一一列舉,這個方向應該是對的,不過最終還是沒成功。
額外提醒一下 nrf_cc312_mbedcrypto/lib 這裡的靜態連結檔 (*.a) 是有版權的,如果要使用請參閱 Nordic 的 LICENSE。
由於我對 Nordic 的 LICENSE 在散佈靜態連結檔 (*.a) 的條文沒有足夠的把握,所以我在 GitHub 上就不把靜態連結檔 (*.a) 放進 elf/hex 裡,有興趣的同好請自行參考 nrf_cc312_mbedcrypto/lib。
總結這 30 天的經驗,我覺得我更加了解 SLH-DSA 簽章演算法是如何 通過一個個 signature 和公鑰的產生來組成最終的簽章,學到了 FORS signature、WOTS+ signature 和 HT signature 各自產生的順序以及各自是如何建立的。在實作 SLH-DSA 的演算法中也學到了有哪些細節要注意,從 Makefile 到 ci.yml、從 function declaration 到 library 的路徑指定、學到了產生亂數的順序如何影響 NIST KAT 比對結果。雖然一路都是失敗居多,但每一次的失敗都是一個經驗,都能讓後續的延伸開發避免犯同樣的錯誤。