github.com/transparency-dev/armored-witness-applet@v0.1.1/trusted_applet/key.go (about)

     1  // Copyright 2023 The Armored Witness Applet authors. 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.
    14  
    15  package main
    16  
    17  import (
    18  	"crypto/aes"
    19  	"crypto/sha256"
    20  	"encoding/binary"
    21  	"fmt"
    22  	"io"
    23  	"log"
    24  
    25  	"github.com/goombaio/namegenerator"
    26  	"github.com/transparency-dev/armored-witness-os/api"
    27  	"github.com/usbarmory/GoTEE/syscall"
    28  	"golang.org/x/crypto/hkdf"
    29  	"golang.org/x/mod/sumdb/note"
    30  )
    31  
    32  var (
    33  	// attestPublicKey can be used to verify that a given witnessPublicKey
    34  	// was derived on a known device.
    35  	attestPublicKey             string
    36  	witnessPublicKey            string
    37  	witnessSigningKey           string
    38  	witnessPublicKeyAttestation string
    39  )
    40  
    41  // deriveIdentityKeys creates this witness' signing and attestation identities.
    42  //
    43  // Keys are derived using the OS' DeriveKey RPC, which in turn uses the hardware
    44  // secret along with several diversification parameters including one passed in
    45  // from this function.
    46  //
    47  // The witness signing ID diversifier includes the value of securely stored counter
    48  // which will be incremented each time a new identity is required.
    49  //
    50  // The device attestation identity uses a static diversifier, and so is intended
    51  // to remain stable throughout the lifetime of the (fused) device.
    52  //
    53  // TODO(al): The derived key should change if the device is wiped.
    54  //
    55  // Since we never store these derived keys anywhere, for any given device (and,
    56  // in the case of the witness ID, counter) this function MUST reproduce the
    57  // same key on each boot.
    58  func deriveIdentityKeys() {
    59  	var status api.Status
    60  	if err := syscall.Call("RPC.Status", nil, &status); err != nil {
    61  		log.Fatalf("Failed to fetch Status: %v", err)
    62  	}
    63  
    64  	// Add an obvious prefix to key names when we're running without secure boot
    65  	prefix := ""
    66  	if !status.HAB {
    67  		prefix = "DEV:"
    68  	}
    69  
    70  	witnessSigningKey, witnessPublicKey = deriveNoteSigner(
    71  		fmt.Sprintf("%sWitnessKey-id:%d", prefix, status.IdentityCounter),
    72  		status.Serial,
    73  		status.HAB,
    74  		func(rnd io.Reader) string {
    75  			return fmt.Sprintf("%sArmoredWitness-%s", prefix, randomName(rnd))
    76  		})
    77  
    78  	attestPublicKey, witnessPublicKeyAttestation = attestID(&status, witnessPublicKey)
    79  
    80  }
    81  
    82  // attestID creates a signer which is forever static for a fused device, and uses
    83  // that to sign a note which binds the passed in witness ID to this device's
    84  // serial number and current identity counter.
    85  //
    86  // The attestation note contents is formatted like so:
    87  //
    88  //	"ArmoredWitness ID attestation v1"
    89  //	<Device serial string>
    90  //	<Witness identity counter in decimal>
    91  //	<Witness identity note verifier string>
    92  //
    93  // Returns the note verifier string which can be used to open the note, and the note containing the witness ID attestation.
    94  func attestID(status *api.Status, pubkey string) (string, string) {
    95  	// Add an obvious prefix to key names when we're running without secure boot
    96  	prefix := ""
    97  	if !status.HAB {
    98  		prefix = "DEV:"
    99  	}
   100  
   101  	// The diversifier or key names in here MUST NOT be changed, or we'll
   102  	// break the invariant that this key is static for the lifetime of the
   103  	// (fused) device!
   104  	attestSigner, attestPublicKey := deriveNoteSigner(
   105  		fmt.Sprintf("%sID-Attestation", prefix),
   106  		status.Serial,
   107  		status.HAB,
   108  		func(_ io.Reader) string {
   109  			return fmt.Sprintf("%sAW-ID-Attestation-%s", prefix, status.Serial)
   110  		})
   111  
   112  	aN := &note.Note{
   113  		Text: fmt.Sprintf("ArmoredWitness ID attestation v1\n%s\n%d\n%s\n", status.Serial, status.IdentityCounter, witnessPublicKey),
   114  	}
   115  	aSigner, err := note.NewSigner(attestSigner)
   116  	if err != nil {
   117  		panic(fmt.Errorf("failed to create attestation signer: %v", err))
   118  	}
   119  	attestation, err := note.Sign(aN, aSigner)
   120  	if err != nil {
   121  		panic(fmt.Errorf("failed to sign witness ID attestation: %v", err))
   122  	}
   123  	return attestPublicKey, string(attestation)
   124  }
   125  
   126  // deriveNoteSigner uses the h/w secret to derive a new note.Signer.
   127  //
   128  // diversifier should uniquely specify the key's intended usage, uniqueID should be the
   129  // device's h/w unique identifier, hab should reflect the device's secure boot status, and keyName
   130  // should be a function which will return the name for the key - it may use the provided Reader as
   131  // a source of entropy while generating the name if needed.
   132  func deriveNoteSigner(diversifier string, uniqueID string, hab bool, keyName func(io.Reader) string) (string, string) {
   133  	// We'll use the provided RPC call to do the derivation in h/w, but since this is based on
   134  	// AES it expects the diversifier to be 16 bytes long.
   135  	// We'll hash our diversifier text and truncate to 16 bytes, and use that:
   136  	diversifierHash := sha256.Sum256([]byte(diversifier))
   137  	var aesKey [sha256.Size]byte
   138  	if err := syscall.Call("RPC.DeriveKey", ([aes.BlockSize]byte)(diversifierHash[:aes.BlockSize]), &aesKey); err != nil {
   139  		log.Fatalf("Failed to derive h/w key, %v", err)
   140  	}
   141  
   142  	r := hkdf.New(sha256.New, aesKey[:], []byte(uniqueID), nil)
   143  
   144  	// And finally generate our note keypair
   145  	sec, pub, err := note.GenerateKey(r, keyName(r))
   146  	if err != nil {
   147  		log.Fatalf("Failed to generate derived note key: %v", err)
   148  	}
   149  	return sec, pub
   150  }
   151  
   152  // randomName generates a random human-friendly name.
   153  func randomName(rnd io.Reader) string {
   154  	// Figure out our name
   155  	nSeed := make([]byte, 8)
   156  	if _, err := rnd.Read(nSeed); err != nil {
   157  		log.Fatalf("Failed to read name entropy: %v", err)
   158  	}
   159  
   160  	ng := namegenerator.NewNameGenerator(int64(binary.LittleEndian.Uint64(nSeed)))
   161  	return ng.Generate()
   162  }