github.com/linuxboot/fiano@v1.2.0/pkg/amd/psb/psbbinary_test.go (about)

     1  // Copyright 2023 the LinuxBoot Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package psb
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/rsa"
    10  	"crypto/sha256"
    11  	"encoding/hex"
    12  	"errors"
    13  	"math/big"
    14  	"testing"
    15  
    16  	"github.com/klauspost/compress/zstd"
    17  
    18  	amd_manifest "github.com/linuxboot/fiano/pkg/amd/manifest"
    19  	"github.com/stretchr/testify/require"
    20  	"github.com/stretchr/testify/suite"
    21  )
    22  
    23  // SMU off chip firmware signing key information
    24  var N = "971472917905694235859527690907502923402301948031921241171273698806712501341143764872164817936205323093673268936716169271014956780829744939605805336378355604317689589643879933296619769853977997537504409513857548495835554760316169706145871580241299859391727532123062316131763291333497897666085493912196453830838835409116482525805019707393297818644968410993413227674593522445365251216146843534545538446317137254095278019564256619548518070446087126473960778214502148894932879929259282158773739433517309889480032232186219446391610435919379779378252560032397509979809202392043110002380553502817271070140095904800150060054898411959116413989750574319580379588063907374731560190664369153626299598002608484164204182073967364520170387618360414494928968064669008506200599321614764379343663974785424808230448954042498800415697272404985468748776993396194504501173460207698442122815473122167399338113883863561341937240713815386295760840924504030444583243381824402019007306020777513381800531855947355754646526659806863711949295870720607069380951550984974668335178048036250641301510976999844724811954772642716092471925323531085871838421575832526959241303117218657087055880924069551198635498158413029865582648473844773084423426422930595846516126856911"
    25  var expectedSmuOffChipFirmwareHash = [32]byte{
    26  	0xd8, 0xdc, 0x03, 0xff, 0x18, 0x1a, 0xcc, 0x9d, 0x09, 0xac, 0x5a, 0xe7, 0x59, 0x67, 0xdc, 0x96,
    27  	0x60, 0xe7, 0xbb, 0x08, 0xd0, 0x3f, 0xa3, 0xb1, 0xbf, 0x64, 0x17, 0x0e, 0x43, 0xdc, 0xb2, 0xf2,
    28  }
    29  var expectedZeroSmuOffChipFirmwareHash = [32]byte{
    30  	0x10, 0xe2, 0x10, 0x3e, 0xe7, 0x39, 0x21, 0x93, 0x1a, 0x78, 0x28, 0xeb, 0xdf, 0x32, 0x5d, 0x3a,
    31  	0x3a, 0x64, 0xc7, 0xa9, 0x0c, 0xc1, 0xda, 0x5c, 0x0c, 0xa6, 0xfe, 0x17, 0xb1, 0xe3, 0xdd, 0x78,
    32  }
    33  
    34  const FirmwareLen = 16777216
    35  
    36  type PsbBinarySuite struct {
    37  	suite.Suite
    38  
    39  	firmwareImage []byte
    40  }
    41  
    42  func (suite *PsbBinarySuite) SetupTest() {
    43  	suite.firmwareImage = make([]byte, 0)
    44  	reader, err := zstd.NewReader(nil)
    45  	if err != nil {
    46  		panic("could not create zstd reader")
    47  	}
    48  	suite.firmwareImage, err = reader.DecodeAll(firmwareImageCompressed, nil)
    49  	if err != nil {
    50  		panic("could not decompress zstd firmware")
    51  	}
    52  }
    53  
    54  func (suite *PsbBinarySuite) TestPSBBinaryIsParsedCorrectly() {
    55  	psbBinary, err := newPSPBinary(smuOffChipFirmware)
    56  	require.NoError(suite.T(), err)
    57  
    58  	hdr := psbBinary.Header()
    59  	require.Equal(suite.T(), uint32(0x31535024), hdr.Version())
    60  }
    61  
    62  func (suite *PsbBinarySuite) TestPSBBinarySignedData() {
    63  	// we are using SMU off-chip firmware for testing PSB binary control
    64  	// paths and we need the corresponding signing key, which is contained
    65  	// in the key database. We could just extract the single key from the
    66  	// database, but it's easier to parse the database as a whole
    67  	keySet := NewKeySet()
    68  	err := parseKeyDatabase(keyDB, keySet)
    69  	require.NoError(suite.T(), err)
    70  
    71  	psbBinary, err := newPSPBinary(smuOffChipFirmware)
    72  	require.NoError(suite.T(), err)
    73  
    74  	blob, err := psbBinary.getSignedBlob(keySet)
    75  
    76  	require.NoError(suite.T(), err)
    77  
    78  	// verify that the signed data matches the content of the blob, excluding the final signature
    79  	require.Equal(suite.T(), smuOffChipFirmware[:len(smuOffChipFirmware)-512], blob.SignedData())
    80  
    81  	sig := blob.Signature()
    82  	require.NotNil(suite.T(), sig)
    83  	key := sig.SigningKey()
    84  	require.NotNil(suite.T(), key)
    85  	require.Equal(suite.T(), hex.EncodeToString(smuSigningKeyID[:]), key.data.KeyID.String())
    86  
    87  	// obtain the RSA key from the generic Key object
    88  	pubKey, err := key.Get()
    89  	require.NoError(suite.T(), err)
    90  	rsaKey := pubKey.(*rsa.PublicKey)
    91  	require.NotNil(suite.T(), rsaKey)
    92  
    93  	n := big.Int{}
    94  	n.SetString(N, 10)
    95  	require.Equal(suite.T(), n, *rsaKey.N)
    96  	require.Equal(suite.T(), int(65537), rsaKey.E)
    97  }
    98  
    99  func (suite *PsbBinarySuite) TestPSBBinaryPSPDirectoryLevel2Entry() {
   100  	// Test full extraction from firmware of entry from PSP Directory
   101  	require.Equal(suite.T(), FirmwareLen, len(suite.firmwareImage))
   102  
   103  	amdFw, err := ParseAMDFirmware(suite.firmwareImage)
   104  	require.NoError(suite.T(), err)
   105  
   106  	smuOffChipFirmwareType := amd_manifest.PSPDirectoryTableEntryType(0x12)
   107  
   108  	data, err := ExtractPSPEntry(amdFw, 2, smuOffChipFirmwareType)
   109  	require.NoError(suite.T(), err)
   110  	shaSmuOffChipFirmwareHash := sha256.Sum256(data)
   111  
   112  	require.Equal(suite.T(), expectedSmuOffChipFirmwareHash, shaSmuOffChipFirmwareHash)
   113  
   114  	// if we dump the SMU off-chip firmware level 1 entry, we expect to find a region of all zeros
   115  	data, err = ExtractPSPEntry(amdFw, 1, smuOffChipFirmwareType)
   116  	require.NoError(suite.T(), err)
   117  	shaSmuOffChipFirmwareHash = sha256.Sum256(data)
   118  
   119  	require.Equal(suite.T(), expectedZeroSmuOffChipFirmwareHash, shaSmuOffChipFirmwareHash)
   120  }
   121  
   122  func (suite *PsbBinarySuite) TestPSBBinaryPSPDirectoryLevel2EntryValidation() {
   123  	// Test positive validation of PSP Directory entry
   124  	require.Equal(suite.T(), FirmwareLen, len(suite.firmwareImage))
   125  
   126  	amdFw, err := ParseAMDFirmware(suite.firmwareImage)
   127  	require.NoError(suite.T(), err)
   128  
   129  	keyDB, err := GetKeys(amdFw, 2)
   130  	require.NoError(suite.T(), err)
   131  
   132  	signatureValidation, err := ValidatePSPEntries(amdFw, keyDB, PSPDirectoryLevel2, []uint32{0x12})
   133  
   134  	require.NoError(suite.T(), err)
   135  	require.Equal(suite.T(), 1, len(signatureValidation))
   136  	require.NoError(suite.T(), signatureValidation[0].err)
   137  	require.NotNil(suite.T(), signatureValidation[0].signingKey)
   138  
   139  	signingKey := signatureValidation[0].signingKey
   140  
   141  	require.Equal(suite.T(), hex.EncodeToString(smuSigningKeyID[:]), signingKey.data.KeyID.String())
   142  }
   143  
   144  func (suite *PsbBinarySuite) TestPSBBinaryPSPDirectoryLevel2EntryWrongSignature() {
   145  	// Test negative validation of PSP Directory entry after corruption
   146  	require.Equal(suite.T(), FirmwareLen, len(suite.firmwareImage))
   147  
   148  	amdFw, err := ParseAMDFirmware(suite.firmwareImage)
   149  	require.NoError(suite.T(), err)
   150  
   151  	smuOffChipFirmwareType := 0x12
   152  
   153  	// obtain the ranges of entry 0x12 within PSP Directory Level 2 (SMU off-chip firmware)
   154  	// and corrupt the very beginning of the blob
   155  	pspFirmware := amdFw.PSPFirmware()
   156  	for _, entry := range pspFirmware.PSPDirectoryLevel2.Entries {
   157  		if entry.Type == amd_manifest.PSPDirectoryTableEntryType(smuOffChipFirmwareType) {
   158  			amdFw.Firmware().ImageBytes()[entry.LocationOrValue] = 0x0
   159  		}
   160  	}
   161  
   162  	keyDB, err := GetKeys(amdFw, 2)
   163  	require.NoError(suite.T(), err)
   164  
   165  	// ValidatePSPEntries will succeed, but the signature validation object returned will hold a signature check error
   166  	signatureValidation, err := ValidatePSPEntries(amdFw, keyDB, PSPDirectoryLevel2, []uint32{uint32(smuOffChipFirmwareType)})
   167  	require.NoError(suite.T(), err)
   168  
   169  	require.Equal(suite.T(), 1, len(signatureValidation))
   170  	require.Error(suite.T(), signatureValidation[0].err)
   171  	var sigErr *SignatureCheckError
   172  	require.True(suite.T(), errors.As(signatureValidation[0].err, &sigErr))
   173  
   174  	require.NotNil(suite.T(), signatureValidation[0].signingKey)
   175  	signingKey := signatureValidation[0].signingKey
   176  	require.Equal(suite.T(), hex.EncodeToString(smuSigningKeyID[:]), signingKey.data.KeyID.String())
   177  }
   178  
   179  func (suite *PsbBinarySuite) TestPSBBinaryPSPDirectoryLevel2EntryWrongKeys() {
   180  	// Test negative validation of PSP Directory entry after corruption
   181  	require.Equal(suite.T(), FirmwareLen, len(suite.firmwareImage))
   182  
   183  	amdFw, err := ParseAMDFirmware(suite.firmwareImage)
   184  	require.NoError(suite.T(), err)
   185  
   186  	smuOffChipFirmwareType := 0x12
   187  
   188  	// signatureParameters indicates the id of the signing key and is placed at 56 bytes offset
   189  	// from the beginning of the blob
   190  	signatureParametersOffset := uint64(56)
   191  
   192  	// obtain the ranges of entry 0x12 within PSP Directory Level 2 (SMU off-chip firmware)
   193  	// and modify the fingerprint of the signing key for the blob so that the key becomes
   194  	// effectively unknown
   195  	pspFirmware := amdFw.PSPFirmware()
   196  	for _, entry := range pspFirmware.PSPDirectoryLevel2.Entries {
   197  		if entry.Type == amd_manifest.PSPDirectoryTableEntryType(smuOffChipFirmwareType) {
   198  			amdFw.Firmware().ImageBytes()[entry.LocationOrValue+signatureParametersOffset] = 0x99
   199  		}
   200  	}
   201  
   202  	keyDB, err := GetKeys(amdFw, 2)
   203  	require.NoError(suite.T(), err)
   204  
   205  	// ValidatePSPEntries will succeed, but the signature validation object returned will hold a signature check error
   206  	signatureValidation, err := ValidatePSPEntries(amdFw, keyDB, PSPDirectoryLevel2, []uint32{uint32(smuOffChipFirmwareType)})
   207  	require.NoError(suite.T(), err)
   208  
   209  	require.Equal(suite.T(), 1, len(signatureValidation))
   210  	require.Error(suite.T(), signatureValidation[0].err)
   211  
   212  	var unknownSigningKeyErr *UnknownSigningKeyError
   213  	require.True(suite.T(), errors.As(signatureValidation[0].err, &unknownSigningKeyErr))
   214  }
   215  
   216  func (suite *PsbBinarySuite) TestPSBBinaryDumpEntry() {
   217  	require.Equal(suite.T(), FirmwareLen, len(suite.firmwareImage))
   218  
   219  	amdFw, err := ParseAMDFirmware(suite.firmwareImage)
   220  	require.NoError(suite.T(), err)
   221  
   222  	var buff bytes.Buffer
   223  
   224  	// dump SMU off-chip firmware
   225  	smuOffChipFirmwareType := amd_manifest.PSPDirectoryTableEntryType(0x12)
   226  	n, err := DumpPSPEntry(amdFw, 2, smuOffChipFirmwareType, &buff)
   227  
   228  	require.NoError(suite.T(), err)
   229  	require.Equal(suite.T(), n, len(smuOffChipFirmware))
   230  
   231  	shaSmuOffChipFirmwareHash := sha256.Sum256(buff.Bytes())
   232  	expectedSmuOffChipFirmwareHash := [32]byte{
   233  		0xd8, 0xdc, 0x03, 0xff, 0x18, 0x1a, 0xcc, 0x9d, 0x09, 0xac, 0x5a, 0xe7, 0x59, 0x67, 0xdc, 0x96,
   234  		0x60, 0xe7, 0xbb, 0x08, 0xd0, 0x3f, 0xa3, 0xb1, 0xbf, 0x64, 0x17, 0x0e, 0x43, 0xdc, 0xb2, 0xf2,
   235  	}
   236  	require.Equal(suite.T(), expectedSmuOffChipFirmwareHash, shaSmuOffChipFirmwareHash)
   237  
   238  	// dump Key database
   239  	keyDatabaseLen := 4992
   240  
   241  	buff.Reset()
   242  	n, err = DumpPSPEntry(amdFw, 2, KeyDatabaseEntry, &buff)
   243  
   244  	require.NoError(suite.T(), err)
   245  	require.Equal(suite.T(), n, keyDatabaseLen)
   246  
   247  	keyDBHash := sha256.Sum256(buff.Bytes())
   248  
   249  	expectedKeyDBHash := [32]byte{
   250  		0xec, 0x16, 0x0f, 0xfa, 0x63, 0xae, 0xcd, 0xc9, 0x23, 0xb0, 0x34, 0x16, 0x70, 0x85, 0x50, 0xe7,
   251  		0x49, 0x48, 0xba, 0x6c, 0xf7, 0x7f, 0x01, 0x49, 0x53, 0x1b, 0x2a, 0x6a, 0x66, 0x28, 0x2a, 0x2c,
   252  	}
   253  
   254  	require.Equal(suite.T(), expectedKeyDBHash, keyDBHash)
   255  }
   256  
   257  func (suite *PsbBinarySuite) TestPSBBinaryPatchEntry() {
   258  	require.Equal(suite.T(), FirmwareLen, len(suite.firmwareImage))
   259  
   260  	amdFw, err := ParseAMDFirmware(suite.firmwareImage)
   261  	require.NoError(suite.T(), err)
   262  
   263  	smuOffChipFirmwareType := amd_manifest.PSPDirectoryTableEntryType(0x12)
   264  	patchedEntry := make([]byte, len(smuOffChipFirmware))
   265  	buff := bytes.NewBuffer(patchedEntry)
   266  
   267  	firmwareImageCopy := make([]byte, 0, len(suite.firmwareImage))
   268  	buffImage := bytes.NewBuffer(firmwareImageCopy)
   269  
   270  	n, err := PatchPSPEntry(amdFw, 2, smuOffChipFirmwareType, buff, buffImage)
   271  
   272  	require.NoError(suite.T(), err)
   273  	require.Equal(suite.T(), len(suite.firmwareImage), n)
   274  
   275  	start := uint64(0)
   276  	end := uint64(0)
   277  	pspFirmware := amdFw.PSPFirmware()
   278  	for _, entry := range pspFirmware.PSPDirectoryLevel2.Entries {
   279  		if entry.Type == amd_manifest.PSPDirectoryTableEntryType(smuOffChipFirmwareType) {
   280  			start = entry.LocationOrValue
   281  			end = entry.LocationOrValue + uint64(entry.Size)
   282  		}
   283  	}
   284  
   285  	require.NotEqual(suite.T(), 0, start)
   286  	require.NotEqual(suite.T(), 0, end)
   287  
   288  	require.Equal(suite.T(), sha256.Sum256(firmwareImageCopy[:start]), sha256.Sum256(suite.firmwareImage[:start]))
   289  
   290  	require.Equal(suite.T(), expectedSmuOffChipFirmwareHash, sha256.Sum256(suite.firmwareImage[start:end]))
   291  	require.Equal(suite.T(), expectedZeroSmuOffChipFirmwareHash, sha256.Sum256(buffImage.Bytes()[start:end]))
   292  
   293  	require.Equal(suite.T(), sha256.Sum256(buffImage.Bytes()[end:]), sha256.Sum256(suite.firmwareImage[end:]))
   294  }
   295  
   296  func TestPsbBinarySuite(t *testing.T) {
   297  	suite.Run(t, new(PsbBinarySuite))
   298  }