Files
hsm/signer_tr31_test.go
zeekay 8232dc56c4 standards: KMIP 2.1, TR-31/X9.143, FIPS provider gate, compliance matrix
Add KMIP 2.1 signer (build tag `kmip`) using gemalto/kmip-go for
Sign/SignatureVerify/Activate/Revoke/Destroy over TLS 1.3 mTLS.
Wire-format complete; live KMS integration tests deferred.

Add TR-31/ANSI X9.143 banking key block (Version "D" — AES-256 KBPK
Variant Binding Method) for PCI-PIN interop. NIST SP 800-108 KDF +
SP 800-38B AES-CMAC + AES-CBC. Authenticator-bound header prevents
relabeling attacks. Tests cover roundtrip, header tamper, key tamper,
wrong-KBPK, and NIST CAVP AES-CMAC vectors §D.1.

Add RequireFIPSProvider startup gate that rejects local / mldsa /
nitrokey / zymbit / personal hardware wallets in FIPS-mandated
deployments. Approves aws / gcp / azure / pkcs11 / kmip / yubihsm.

Add STANDARDS.md compliance matrix tracking 50+ standards across API
(PKCS#11 v2.40+v3.0, KMIP 2.1), auth (TLS 1.3 mTLS, FIDO2), crypto
(NIST SP 800-90A/B/C/108/131A, FIPS 197/180-4/198-1/202/203/204/205/186-5),
hardware certs (FIPS 140-2/3 + CC EAL4-7+), banking (TR-31, TR-34,
ISO 9564, PCI-DSS, PCI-PIN), and crypto-currency (CCSS L1-3, NIST IR
8214A). Honest claims only — entries cite test fixtures or are flagged
as in-flight or missing with a remediation plan.
2025-12-27 16:20:00 -08:00

222 lines
6.3 KiB
Go

// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package hsm
import (
"context"
"encoding/hex"
"strings"
"testing"
)
// TestTR31RoundTrip wraps and unwraps a PIN-encryption key and checks
// that every header field and the plaintext bytes round-trip.
func TestTR31RoundTrip(t *testing.T) {
signer, _, err := NewTR31SignerWithRandomKBPK()
if err != nil {
t.Fatalf("NewTR31Signer: %v", err)
}
defer signer.Wipe()
pek := make([]byte, 16) // AES-128 PEK
for i := range pek {
pek[i] = byte(i + 0x10)
}
block := TR31KeyBlock{
Usage: TR31UsagePINEncryption,
Algorithm: TR31AlgAES,
ModeOfUse: TR31ModeBoth,
KeyVersion: "00",
Exportability: TR31ExportNonExportable,
PlaintextKey: pek,
}
wrapped, err := signer.Wrap(block)
if err != nil {
t.Fatalf("Wrap: %v", err)
}
if wrapped[0] != 'D' {
t.Errorf("version byte = %q, want D", wrapped[:1])
}
if !strings.HasPrefix(string(wrapped[5:7]), "P0") {
t.Errorf("usage = %q, want P0", wrapped[5:7])
}
got, err := signer.Unwrap(wrapped)
if err != nil {
t.Fatalf("Unwrap: %v", err)
}
if got.Usage != TR31UsagePINEncryption {
t.Errorf("Usage = %q, want %q", got.Usage, TR31UsagePINEncryption)
}
if got.Algorithm != TR31AlgAES {
t.Errorf("Algorithm = %q, want A", got.Algorithm)
}
if got.ModeOfUse != TR31ModeBoth {
t.Errorf("ModeOfUse = %q, want B", got.ModeOfUse)
}
if got.Exportability != TR31ExportNonExportable {
t.Errorf("Exportability = %q, want N", got.Exportability)
}
if hex.EncodeToString(got.PlaintextKey) != hex.EncodeToString(pek) {
t.Errorf("plaintext mismatch: got %x want %x", got.PlaintextKey, pek)
}
}
// TestTR31TamperHeader confirms that flipping any header byte breaks
// authentication. This is the key security property of X9.143 — the
// MAC is computed over the header so attackers cannot relabel a "M0"
// MAC key as a "P0" PIN-encryption key.
func TestTR31TamperHeader(t *testing.T) {
signer, _, err := NewTR31SignerWithRandomKBPK()
if err != nil {
t.Fatalf("NewTR31Signer: %v", err)
}
defer signer.Wipe()
wrapped, err := signer.Wrap(TR31KeyBlock{
Usage: TR31UsageMAC,
Algorithm: TR31AlgAES,
ModeOfUse: TR31ModeSign,
KeyVersion: "00",
Exportability: TR31ExportNonExportable,
PlaintextKey: make([]byte, 16),
})
if err != nil {
t.Fatalf("Wrap: %v", err)
}
// Flip M0 to P0 (relabel as PIN key) — must fail authentication.
tampered := append([]byte{}, wrapped...)
tampered[5] = 'P'
if _, err := signer.Unwrap(tampered); err == nil {
t.Error("Unwrap of header-tampered block should have failed")
}
}
// TestTR31TamperKey confirms that flipping any ciphertext byte breaks
// authentication.
func TestTR31TamperKey(t *testing.T) {
signer, _, err := NewTR31SignerWithRandomKBPK()
if err != nil {
t.Fatalf("NewTR31Signer: %v", err)
}
defer signer.Wipe()
wrapped, err := signer.Wrap(TR31KeyBlock{
Usage: TR31UsageDataEncryption,
Algorithm: TR31AlgAES,
ModeOfUse: TR31ModeBoth,
KeyVersion: "00",
Exportability: TR31ExportTrusted,
PlaintextKey: make([]byte, 32),
})
if err != nil {
t.Fatalf("Wrap: %v", err)
}
// Flip a ciphertext byte (in the body, before the 32-char authenticator).
tampered := append([]byte{}, wrapped...)
idx := len(tampered) - 33
tampered[idx] ^= 0x01
if _, err := signer.Unwrap(tampered); err == nil {
t.Error("Unwrap of ciphertext-tampered block should have failed")
}
}
// TestTR31WrongKBPK confirms the MAC fails when a different KBPK is used
// to unwrap.
func TestTR31WrongKBPK(t *testing.T) {
signer1, _, _ := NewTR31SignerWithRandomKBPK()
defer signer1.Wipe()
signer2, _, _ := NewTR31SignerWithRandomKBPK()
defer signer2.Wipe()
wrapped, err := signer1.Wrap(TR31KeyBlock{
Usage: TR31UsageKeyEncryption,
Algorithm: TR31AlgAES,
ModeOfUse: TR31ModeBoth,
KeyVersion: "00",
Exportability: TR31ExportSensitive,
PlaintextKey: make([]byte, 32),
})
if err != nil {
t.Fatalf("Wrap: %v", err)
}
if _, err := signer2.Unwrap(wrapped); err == nil {
t.Error("Unwrap with wrong KBPK should have failed")
}
}
// TestTR31SignerInterface exercises Register/Sign/Verify so the
// Signer interface adapter is correct.
func TestTR31SignerInterface(t *testing.T) {
signer, _, _ := NewTR31SignerWithRandomKBPK()
defer signer.Wipe()
pek := make([]byte, 16)
for i := range pek {
pek[i] = byte(i + 1)
}
block := TR31KeyBlock{
Usage: TR31UsagePINEncryption,
Algorithm: TR31AlgAES,
ModeOfUse: TR31ModeBoth,
KeyVersion: "00",
Exportability: TR31ExportNonExportable,
PlaintextKey: pek,
}
if err := signer.Register("pek-1", block); err != nil {
t.Fatalf("Register: %v", err)
}
if signer.Provider() != "tr31" {
t.Errorf("Provider = %q, want tr31", signer.Provider())
}
wrapped, err := signer.Sign(context.Background(), "pek-1", nil)
if err != nil {
t.Fatalf("Sign: %v", err)
}
ok, err := signer.Verify(context.Background(), "pek-1", nil, wrapped)
if err != nil {
t.Fatalf("Verify: %v", err)
}
if !ok {
t.Error("Verify(valid wrap) = false")
}
}
// TestTR31InvalidKBPK confirms the constructor rejects wrong-size KBPKs.
func TestTR31InvalidKBPK(t *testing.T) {
for _, n := range []int{0, 16, 24, 33, 64} {
if _, err := NewTR31Signer(make([]byte, n)); err == nil {
t.Errorf("NewTR31Signer(%d-byte KBPK) should have failed", n)
}
}
}
// TestTR31AESCMACVector uses the canonical NIST SP 800-38B test vector
// to confirm the AES-CMAC implementation matches the standard.
func TestTR31AESCMACVector(t *testing.T) {
// NIST SP 800-38B §D.1 — AES-128 example 1 (empty message).
key, _ := hex.DecodeString("2b7e151628aed2a6abf7158809cf4f3c")
want, _ := hex.DecodeString("bb1d6929e95937287fa37d129b756746")
got, err := aesCMAC(key, nil)
if err != nil {
t.Fatalf("aesCMAC: %v", err)
}
if hex.EncodeToString(got) != hex.EncodeToString(want) {
t.Errorf("AES-CMAC empty: got %x want %x", got, want)
}
// Example 2: 16-byte message.
msg, _ := hex.DecodeString("6bc1bee22e409f96e93d7e117393172a")
want2, _ := hex.DecodeString("070a16b46b4d4144f79bdd9dd04a287c")
got2, err := aesCMAC(key, msg)
if err != nil {
t.Fatalf("aesCMAC: %v", err)
}
if hex.EncodeToString(got2) != hex.EncodeToString(want2) {
t.Errorf("AES-CMAC 16B: got %x want %x", got2, want2)
}
}