github.com/google/trillian-examples@v0.0.0-20240520080811-0d40d35cef0e/binary_transparency/firmware/internal/verify/bundle_test.go (about)

     1  // Copyright 2020 Google LLC. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.package bundle_test
    14  
    15  package verify_test
    16  
    17  import (
    18  	"bytes"
    19  	"crypto/sha512"
    20  	"encoding/base64"
    21  	"encoding/json"
    22  	"errors"
    23  	"testing"
    24  
    25  	"github.com/google/trillian-examples/binary_transparency/firmware/api"
    26  	"github.com/google/trillian-examples/binary_transparency/firmware/internal/crypto"
    27  	"github.com/google/trillian-examples/binary_transparency/firmware/internal/verify"
    28  	"github.com/transparency-dev/merkle/proof"
    29  	"golang.org/x/mod/sumdb/note"
    30  )
    31  
    32  const (
    33  	goldenProofBundle   = `{"ManifestStatement":"eyJUeXBlIjoxMDIsIlN0YXRlbWVudCI6ImV5SkVaWFpwWTJWSlJDSTZJbVIxYlcxNUlpd2lSbWx5YlhkaGNtVlNaWFpwYzJsdmJpSTZNU3dpUm1seWJYZGhjbVZKYldGblpWTklRVFV4TWlJNkltZ3ZTblpLTURVeE1GZE5Ua05hZG1wWFQwTXdVMUZxTDFKUFJHVXpLMGh6Uld0dE5HMUhUbnBWVEhSd1lXVlVhblkyU2xrcmQzSTBlVk51Vm5aNVJqVXdZa055TVhSd2NYZERiMEZvWm5CeFFscHNNbUpSUFQwaUxDSkZlSEJsWTNSbFpFWnBjbTEzWVhKbFRXVmhjM1Z5WlcxbGJuUWlPaUkyZEZWWGVYbHJlbmRtYjI5dVIzVm5ibVl4WkV3clkyZGtObUpGVjFWb2IyeEJUbUZFVERoS1dVdFhkR1J0VUdORlpWbDJaMDgyYmsxeUwwbE1aMWRRWTFWWloyZDJRVUZ5Y25SaFUwSnRZVzQxU0RSTFp6MDlJaXdpUW5WcGJHUlVhVzFsYzNSaGJYQWlPaUl5TURJd0xURXdMVEV3VkRFMU9qTXdPakl3TGpFd1dpSjkiLCJTaWduYXR1cmUiOiJUeUxVdFpCdHJHbyt3anRoSjI2Rk8wVE5QUHpTTDZhU0c1V0ZTanRCcjZLZ2x4a0RjR3dmZUxTTEpjbklmUnhZMnVJZHZLL09tMStXMndLNEkxSFRUYTdIUFZlSHo2MmF0V09hZm9TL1ZGc01OdEx1RkplaU5WNE5uY2Y0bllYMFBDdnN0MHRpYm5TVFRzNnEwMUZ1cEhaMnFwc2lyY2hFVXgwLzFjOFFOM2hGZVArSXcwVWxPNTVvZUhlWGtlRGRwL2w5SCsvZjYxYndFMmpHZVl1cFcvbld2bmN2NFgrS00weXgrYW1oVi9od0lCMDQ2aitNQzVndkd4LzJ2TkkySk5JeTBOQk13YWRIK1VONnp1MzRIZzM5NkY4MkxJU2NTOXU2MENBY3hzRlozR3d5NGpoR1JXR1lwSnhXdEJ6Zk5hZ1IvaVdmUzRJY2tJVmZ5Z2ZQUXc9PSJ9","Checkpoint":"RmlybXdhcmUgVHJhbnNwYXJlbmN5IExvZwo1Clg2VEY4QWNkSUh2OVp0UWwrU1NhZVZOYy9aNVJjNDJweDFpRlJLVGNDdHc9CjE2MDc0NTA3MzgxMTE1MDYwODgKCuKAlCBmdF9wZXJzb25hbGl0eSA2S0pDdlg1NTYrSGxNSnozMlhnQVhBYit1ODVOUEpSTkt2eHJ5enU1WTVibGkxWTh0YURsNDNjS2lmeVdsT1pYQWVnYndCaDUyUlNWc3ZJSUovTXY1K2Z0WHdjPQo=","InclusionProof":{"Value":null,"LeafIndex":4,"Proof":["KFh4IVeIwbsvbWyz2QHVCXXyjWTRDqusRa0ZEjS2fls="]}}`
    34  	goldenFirmwareImage = `Firmware image`
    35  	// goldenFirmwareHashB64 is a base64 encoded string for ExpectedMeasurement field inside ManifestStatement.
    36  	// For the dummy device, this is SHA512("dummy"||img), where img is the base64 decoded bytes from
    37  	// goldenUpdate.FirmwareImage
    38  	goldenFirmwareHashB64 = "6tUWyykzwfoonGugnf1dL+cgd6bEWUholANaDL8JYKWtdmPcEeYvgO6nMr/ILgWPcUYggvAArrtaSBman5H4Kg=="
    39  )
    40  
    41  func mustGetLogSigVerifier(t *testing.T) note.Verifier {
    42  	t.Helper()
    43  	v, err := note.NewVerifier(crypto.TestFTPersonalityPub)
    44  	if err != nil {
    45  		t.Fatalf("failed to create verifier: %q", err)
    46  	}
    47  	return v
    48  }
    49  
    50  // This test is useful for creating the Checkpoint field of the goldenProofBundle above.
    51  func TestGenerateGoldenCheckpoint(t *testing.T) {
    52  	cp := "RmlybXdhcmUgVHJhbnNwYXJlbmN5IExvZwo1Clg2VEY4QWNkSUh2OVp0UWwrU1NhZVZOYy9aNVJjNDJweDFpRlJLVGNDdHc9CjE2MDc0NTA3MzgxMTE1MDYwODgKCuKAlCBmdF9wZXJzb25hbGl0eSA2S0pDdlg1NTYrSGxNSnozMlhnQVhBYit1ODVOUEpSTkt2eHJ5enU1WTVibGkxWTh0YURsNDNjS2lmeVdsT1pYQWVnYndCaDUyUlNWc3ZJSUovTXY1K2Z0WHdjPQo="
    53  	nb, _ := base64.StdEncoding.DecodeString(cp)
    54  	s, err := note.NewSigner(crypto.TestFTPersonalityPriv)
    55  	if err != nil {
    56  		t.Fatalf("failed to create signer: %q", err)
    57  	}
    58  	n, err := note.Sign(&note.Note{Text: string(nb)}, s)
    59  	if err != nil {
    60  		t.Fatalf("failed to sign note: %q", err)
    61  	}
    62  	t.Log(string(n))
    63  	t.Log(base64.StdEncoding.EncodeToString(n))
    64  }
    65  
    66  func TestBundleForUpdate(t *testing.T) {
    67  	var dc api.LogCheckpoint
    68  	getProof := func(from, to uint64) ([][]byte, error) { return [][]byte{}, nil }
    69  
    70  	for _, test := range []struct {
    71  		desc    string
    72  		img     []byte
    73  		wantErr bool
    74  	}{
    75  		{
    76  			desc: "all good",
    77  			img:  []byte(goldenFirmwareImage),
    78  		}, {
    79  			desc:    "bad image hash",
    80  			img:     []byte("this is wrong"),
    81  			wantErr: true,
    82  		},
    83  	} {
    84  		t.Run(test.desc, func(t *testing.T) {
    85  			imgHash := sha512.Sum512(test.img)
    86  			_, _, err := verify.BundleForUpdate([]byte(goldenProofBundle), imgHash[:], dc, getProof, mustGetLogSigVerifier(t))
    87  			if (err != nil) != test.wantErr {
    88  				var lve proof.RootMismatchError
    89  				if errors.As(err, &lve) {
    90  					// Printing this out allows `goldenProofBundle` to be updated if needed
    91  					t.Errorf("calculated root %s", base64.StdEncoding.EncodeToString(lve.CalculatedRoot))
    92  				}
    93  				t.Fatalf("want err %v, got %q", test.wantErr, err)
    94  			}
    95  		})
    96  	}
    97  }
    98  
    99  func b64Decode(t *testing.T, b64 string) []byte {
   100  	t.Helper()
   101  	st, err := base64.StdEncoding.DecodeString(b64)
   102  	if err != nil {
   103  		t.Fatalf("b64 decoding failed: %v", err)
   104  	}
   105  	return st
   106  }
   107  
   108  func TestBundleForBoot(t *testing.T) {
   109  	for _, test := range []struct {
   110  		desc        string
   111  		measurement []byte
   112  		wantErr     bool
   113  	}{
   114  		{
   115  			desc:        "all good",
   116  			measurement: []byte(b64Decode(t, goldenFirmwareHashB64)),
   117  		}, {
   118  			desc:        "bad image hash",
   119  			measurement: []byte("this is wrong"),
   120  			wantErr:     true,
   121  		},
   122  	} {
   123  		t.Run(test.desc, func(t *testing.T) {
   124  			err := verify.BundleForBoot([]byte(goldenProofBundle), test.measurement, mustGetLogSigVerifier(t))
   125  			if (err != nil) != test.wantErr {
   126  				t.Fatalf("want err %v, got %q", test.wantErr, err)
   127  			}
   128  		})
   129  	}
   130  }
   131  
   132  // This test is really showing how the constants above are generated, and allows
   133  // them to be regenerated should any of the underlying formats change.
   134  func TestGoldenBundleGeneration(t *testing.T) {
   135  	h := sha512.Sum512([]byte(goldenFirmwareImage))
   136  	meta := api.FirmwareMetadata{
   137  		DeviceID:                    "dummy",
   138  		FirmwareRevision:            1,
   139  		FirmwareImageSHA512:         h[:],
   140  		ExpectedFirmwareMeasurement: b64Decode(t, goldenFirmwareHashB64),
   141  		BuildTimestamp:              "2020-10-10T15:30:20.10Z",
   142  	}
   143  
   144  	mbs, _ := json.Marshal(meta)
   145  	sig, err := crypto.Publisher.SignMessage(api.FirmwareMetadataType, mbs)
   146  	if err != nil {
   147  		t.Error(err)
   148  	}
   149  	ss := api.SignedStatement{
   150  		Type:      api.FirmwareMetadataType,
   151  		Statement: mbs,
   152  		Signature: sig,
   153  	}
   154  	var gpb api.ProofBundle
   155  	if err := json.Unmarshal([]byte(goldenProofBundle), &gpb); err != nil {
   156  		t.Error(err)
   157  	}
   158  	var gss api.SignedStatement
   159  	if err := json.Unmarshal(gpb.ManifestStatement, &gss); err != nil {
   160  		t.Error(err)
   161  	}
   162  	// Signature can't go into this check because they are non-deterministic.
   163  	if gss.Type != ss.Type || !bytes.Equal(gss.Statement, ss.Statement) {
   164  		gbs, _ := json.Marshal(gss)
   165  		sbs, _ := json.Marshal(ss)
   166  		// If this fails then the golden values in the test may need updating with `sbs`
   167  		t.Errorf("Golden != computed: %s, %s", gbs, sbs)
   168  	}
   169  
   170  }