github.com/nats-io/nats-server/v2@v2.11.0-preview.2/server/tpm/js_ek_tpm_windows.go (about) 1 // Copyright 2024 The NATS Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 //go:build windows 15 16 package tpm 17 18 import ( 19 "encoding/base64" 20 "encoding/json" 21 "fmt" 22 "io" 23 "os" 24 "path/filepath" 25 26 "github.com/google/go-tpm/legacy/tpm2" 27 "github.com/google/go-tpm/tpmutil" 28 "github.com/nats-io/nkeys" 29 ) 30 31 var ( 32 // Version of the NATS TPM JS implmentation 33 JsKeyTPMVersion = 1 34 ) 35 36 // How this works: 37 // Create a Storage Root Key (SRK) in the TPM. 38 // If existing JS Encryption keys do not exist on disk. 39 // - Create a JetStream encryption key (js key) and seal it to the SRK 40 // using a provided js encryption key password. 41 // - Save the public and private blobs to a file on disk. 42 // - Return the new js encryption key (the private portion of the nkey) 43 // Otherwise (keys exist on disk) 44 // - Read the public and private blobs from disk 45 // - Load them into the TPM 46 // - Unseal the js key using the TPM, and the provided js encryption keys password. 47 // 48 // Note: a SRK password for the SRK is supported but not tested here. 49 50 // Gets/Regenerates the Storage Root Key (SRK) from the TPM. Caller MUST flush this handle when done. 51 func regenerateSRK(rwc io.ReadWriteCloser, srkPassword string) (tpmutil.Handle, error) { 52 // Default EK template defined in: 53 // https://trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf 54 // Shared SRK template based off of EK template and specified in: 55 // https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf 56 srkTemplate := tpm2.Public{ 57 Type: tpm2.AlgRSA, 58 NameAlg: tpm2.AlgSHA256, 59 Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth | tpm2.FlagRestricted | tpm2.FlagDecrypt | tpm2.FlagNoDA, 60 AuthPolicy: nil, 61 // We must use RSA 2048 for the intel TSS2 stack 62 RSAParameters: &tpm2.RSAParams{ 63 Symmetric: &tpm2.SymScheme{ 64 Alg: tpm2.AlgAES, 65 KeyBits: 128, 66 Mode: tpm2.AlgCFB, 67 }, 68 KeyBits: 2048, 69 ModulusRaw: make([]byte, 256), 70 }, 71 } 72 // Create the parent key against which to seal the data 73 srkHandle, _, err := tpm2.CreatePrimary(rwc, tpm2.HandleOwner, tpm2.PCRSelection{}, "", srkPassword, srkTemplate) 74 return srkHandle, err 75 } 76 77 type natsTPMPersistedKeys struct { 78 Version int `json:"version"` 79 PrivateKey []byte `json:"private_key"` 80 PublicKey []byte `json:"public_key"` 81 } 82 83 // Writes the private and public blobs to disk in a single file. If the directory does 84 // not exist, it will be created. If the file already exists it will be overwritten. 85 func writeTPMKeysToFile(filename string, privateBlob []byte, publicBlob []byte) error { 86 keyDir := filepath.Dir(filename) 87 if err := os.MkdirAll(keyDir, 0750); err != nil { 88 return fmt.Errorf("unable to create/access directory %q: %v", keyDir, err) 89 } 90 91 // Create a new set of persisted keys. Note that the private key doesn't necessarily 92 // need to be protected as the TPM password is required to use unseal, although it's 93 // a good idea to put this in a secure location accessible to the server. 94 tpmKeys := natsTPMPersistedKeys{ 95 Version: JsKeyTPMVersion, 96 PrivateKey: make([]byte, base64.StdEncoding.EncodedLen(len(privateBlob))), 97 PublicKey: make([]byte, base64.StdEncoding.EncodedLen(len(publicBlob))), 98 } 99 base64.StdEncoding.Encode(tpmKeys.PrivateKey, privateBlob) 100 base64.StdEncoding.Encode(tpmKeys.PublicKey, publicBlob) 101 // Convert to JSON 102 keysJSON, err := json.Marshal(tpmKeys) 103 if err != nil { 104 return fmt.Errorf("unable to marshal keys to JSON: %v", err) 105 } 106 // Write the JSON to a file 107 if err := os.WriteFile(filename, keysJSON, 0640); err != nil { 108 return fmt.Errorf("unable to write keys file to %q: %v", filename, err) 109 } 110 return nil 111 } 112 113 // Reads the private and public blobs from a single file. If the file does not exist, 114 // or the file cannot be read and the keys decoded, an error is returned. 115 func readTPMKeysFromFile(filename string) ([]byte, []byte, error) { 116 keysJSON, err := os.ReadFile(filename) 117 if err != nil { 118 return nil, nil, err 119 } 120 121 var tpmKeys natsTPMPersistedKeys 122 if err := json.Unmarshal(keysJSON, &tpmKeys); err != nil { 123 return nil, nil, fmt.Errorf("unable to unmarshal TPM file keys JSON from %s: %v", filename, err) 124 } 125 126 // Placeholder for future-proofing. Here is where we would 127 // check the current version against tpmKeys.Version and 128 // handle any changes. 129 130 // Base64 decode the private and public blobs. 131 privateBlob := make([]byte, base64.StdEncoding.DecodedLen(len(tpmKeys.PrivateKey))) 132 publicBlob := make([]byte, base64.StdEncoding.DecodedLen(len(tpmKeys.PublicKey))) 133 prn, err := base64.StdEncoding.Decode(privateBlob, tpmKeys.PrivateKey) 134 if err != nil { 135 return nil, nil, fmt.Errorf("unable to decode privateBlob from base64: %v", err) 136 } 137 pun, err := base64.StdEncoding.Decode(publicBlob, tpmKeys.PublicKey) 138 if err != nil { 139 return nil, nil, fmt.Errorf("unable to decode publicBlob from base64: %v", err) 140 } 141 return publicBlob[:pun], privateBlob[:prn], nil 142 } 143 144 // Creates a new JetStream encryption key, seals it to the TPM, and saves the public and 145 // private blobs to disk in a JSON encoded file. The key is returned as a string. 146 func createAndSealJsEncryptionKey(rwc io.ReadWriteCloser, srkHandle tpmutil.Handle, srkPassword, jsKeyFile, jsKeyPassword string, pcr int) (string, error) { 147 // Get the authorization policy that will protect the data to be sealed 148 sessHandle, policy, err := policyPCRPasswordSession(rwc, pcr) 149 if err != nil { 150 return "", fmt.Errorf("unable to get policy: %v", err) 151 } 152 if err := tpm2.FlushContext(rwc, sessHandle); err != nil { 153 return "", fmt.Errorf("unable to flush session: %v", err) 154 } 155 // Seal the data to the parent key and the policy 156 user, err := nkeys.CreateUser() 157 if err != nil { 158 return "", fmt.Errorf("unable to create seed: %v", err) 159 } 160 // We'll use the seed to represent the encryption key. 161 jsStoreKey, err := user.Seed() 162 if err != nil { 163 return "", fmt.Errorf("unable to get seed: %v", err) 164 } 165 privateArea, publicArea, err := tpm2.Seal(rwc, srkHandle, srkPassword, jsKeyPassword, policy, jsStoreKey) 166 if err != nil { 167 return "", fmt.Errorf("unable to seal data: %v", err) 168 } 169 err = writeTPMKeysToFile(jsKeyFile, privateArea, publicArea) 170 if err != nil { 171 return "", fmt.Errorf("unable to write key file: %v", err) 172 } 173 return string(jsStoreKey), nil 174 } 175 176 // Unseals the JetStream encryption key from the TPM with the provided keys. 177 // The key is returned as a string. 178 func unsealJsEncrpytionKey(rwc io.ReadWriteCloser, pcr int, srkHandle tpmutil.Handle, srkPassword, objectPassword string, publicBlob, privateBlob []byte) (string, error) { 179 // Load the public/private blobs into the TPM for decryption. 180 objectHandle, _, err := tpm2.Load(rwc, srkHandle, srkPassword, publicBlob, privateBlob) 181 if err != nil { 182 return "", fmt.Errorf("unable to load data: %v", err) 183 } 184 defer tpm2.FlushContext(rwc, objectHandle) 185 186 // Create the authorization session with TPM. 187 sessHandle, _, err := policyPCRPasswordSession(rwc, pcr) 188 if err != nil { 189 return "", fmt.Errorf("unable to get auth session: %v", err) 190 } 191 defer func() { 192 tpm2.FlushContext(rwc, sessHandle) 193 }() 194 // Unseal the data we've loaded into the TPM with the object (js key) password. 195 unsealedData, err := tpm2.UnsealWithSession(rwc, sessHandle, objectHandle, objectPassword) 196 if err != nil { 197 return "", fmt.Errorf("unable to unseal data: %v", err) 198 } 199 return string(unsealedData), nil 200 } 201 202 // Returns session handle and policy digest. 203 func policyPCRPasswordSession(rwc io.ReadWriteCloser, pcr int) (sessHandle tpmutil.Handle, policy []byte, retErr error) { 204 sessHandle, _, err := tpm2.StartAuthSession( 205 rwc, 206 tpm2.HandleNull, /*tpmKey*/ 207 tpm2.HandleNull, /*bindKey*/ 208 make([]byte, 16), /*nonceCaller*/ 209 nil, /*secret*/ 210 tpm2.SessionPolicy, 211 tpm2.AlgNull, 212 tpm2.AlgSHA256) 213 if err != nil { 214 return tpm2.HandleNull, nil, fmt.Errorf("unable to start session: %v", err) 215 } 216 defer func() { 217 if sessHandle != tpm2.HandleNull && err != nil { 218 if err := tpm2.FlushContext(rwc, sessHandle); err != nil { 219 retErr = fmt.Errorf("%v\nunable to flush session: %v", retErr, err) 220 } 221 } 222 }() 223 224 pcrSelection := tpm2.PCRSelection{ 225 Hash: tpm2.AlgSHA256, 226 PCRs: []int{pcr}, 227 } 228 if err := tpm2.PolicyPCR(rwc, sessHandle, nil, pcrSelection); err != nil { 229 return sessHandle, nil, fmt.Errorf("unable to bind PCRs to auth policy: %v", err) 230 } 231 if err := tpm2.PolicyPassword(rwc, sessHandle); err != nil { 232 return sessHandle, nil, fmt.Errorf("unable to require password for auth policy: %v", err) 233 } 234 policy, err = tpm2.PolicyGetDigest(rwc, sessHandle) 235 if err != nil { 236 return sessHandle, nil, fmt.Errorf("unable to get policy digest: %v", err) 237 } 238 return sessHandle, policy, nil 239 } 240 241 // LoadJetStreamEncryptionKeyFromTPM loads the JetStream encryption key from the TPM. 242 // If the keyfile does not exist, a key will be created and sealed. Public and private blobs 243 // used to decrypt the key in future sessions will be saved to disk in the file provided. 244 // The key will be unsealed and returned only with the correct password and PCR value. 245 func LoadJetStreamEncryptionKeyFromTPM(srkPassword, jsKeyFile, jsKeyPassword string, pcr int) (string, error) { 246 rwc, err := tpm2.OpenTPM() 247 if err != nil { 248 return "", fmt.Errorf("could not open the TPM: %v", err) 249 } 250 defer rwc.Close() 251 252 // Load the key from the TPM 253 srkHandle, err := regenerateSRK(rwc, srkPassword) 254 defer func() { 255 tpm2.FlushContext(rwc, srkHandle) 256 }() 257 if err != nil { 258 return "", fmt.Errorf("unable to regenerate SRK from the TPM: %v", err) 259 } 260 // Read the keys from the key file. If the filed doesn't exist it means we need to create 261 // a new js encrytpion key. 262 publicBlob, privateBlob, err := readTPMKeysFromFile(jsKeyFile) 263 if err != nil { 264 if os.IsNotExist(err) { 265 jsek, err := createAndSealJsEncryptionKey(rwc, srkHandle, srkPassword, jsKeyFile, jsKeyPassword, pcr) 266 if err != nil { 267 return "", fmt.Errorf("unable to generate new key from the TPM: %v", err) 268 } 269 // we've created and sealed the JS Encryption key, now we just return it. 270 return jsek, nil 271 } 272 return "", fmt.Errorf("unable to load key from TPM: %v", err) 273 } 274 275 // Unseal the JetStream encryption key using the TPM. 276 jsek, err := unsealJsEncrpytionKey(rwc, pcr, srkHandle, srkPassword, jsKeyPassword, publicBlob, privateBlob) 277 if err != nil { 278 return "", fmt.Errorf("unable to unseal key from the TPM: %v", err) 279 } 280 return jsek, nil 281 }