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  }