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 := ¬e.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 }