diff --git a/README.md b/README.md index 2eaa46f..f029caf 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,14 @@ Trust also offers the following subcommands for administration: generated certificates. The key repo may be used whereever a keysetname is required. + 'trust keyset pcr7data keysetName --passwdPolicy \ + --luksPolicy \ + --pcr7-tpm \ + --pcr7-prod \ + --pcr7-limited + Adds the specified data to the named keyset. This generates + the "pcr7data" directory in the keyset. + Exported functions in trust: doSudiCert(VMname, keysetname string) - Generates a unique uuid, diff --git a/cmd/trust/keyset.go b/cmd/trust/keyset.go index 251e537..09e5d9e 100644 --- a/cmd/trust/keyset.go +++ b/cmd/trust/keyset.go @@ -2,11 +2,13 @@ package main import ( "crypto/x509" + "crypto/rsa" "fmt" "os" "path/filepath" "strings" "time" + "encoding/json" "github.com/apex/log" "github.com/go-git/go-git/v5" @@ -239,6 +241,34 @@ var keysetCmd = cli.Command{ }, }, }, + cli.Command{ + Name: "pcr7data", + Action: doAddPCR7data, + Usage: "include the specified pcr7data into keyset", + ArgsUsage: "", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "pcr7-tpm", + Usage: "Pathname to the pcr7 tpm binary file", + }, + cli.StringFlag{ + Name: "pcr7-limited", + Usage: "Pathname to the pcr7 limited binary file", + }, + cli.StringFlag{ + Name: "pcr7-prod", + Usage: "Pathname to the pcr7 production binary file", + }, + cli.StringFlag{ + Name: "passwdPolicy", + Usage: "Pathname to the tpm passwd policy file (optional)", + }, + cli.StringFlag{ + Name: "luksPolicy", + Usage: "Pathname to the luks policy file (optional)", + }, + }, + }, }, } @@ -400,3 +430,194 @@ func doShowKeyset(ctx *cli.Context) error { return nil } + +type Pcr7Data struct { + limited string + tpm string + production string + passwdPolicy string + luksPolicy string +} + +func doAddPCR7data(ctx *cli.Context) error { + var p Pcr7Data + args := ctx.Args() + if len(args) != 1 { + return errors.New("Missing arguments") + } + + keysetName := args[0] + + p.passwdPolicy = ctx.String("passwdPolicy") + p.luksPolicy = ctx.String("luksPolicy") + p.limited = ctx.String("pcr7-limited") + p.tpm = ctx.String("pcr7-tpm") + p.production = ctx.String("pcr7-prod") + + if err := addPcr7data(keysetName, p); err != nil { + return err + } + return nil +} + +func addPcr7data(keysetName string, pdata Pcr7Data) error { + var err error + var notexist = true + var moskeypath, pcrIndex, newFile string + var tpmpolAdminpubkey, tpmpolLukspubkey *rsa.PublicKey + var jsonInfo []byte + type PCR7info struct { + Key string `json:"key"` + KeyType string `json:"key_type"` + Date string `json:"est_date"` + Comment string `json:"comment"` + } + + pcr7 := make(map[string]string) + + if keysetName == "" { + return errors.New("Please specify a keyset name") + } + + pcr7["limited"] = pdata.limited + pcr7["tpm"] = pdata.tpm + pcr7["production"] = pdata.production + + if pcr7["limited"] == "" || pcr7["tpm"] == "" || pcr7["production"] == "" { + return errors.New("Must specify all 3 pcr7 values: tpm, limited and production") + } + + // Make sure the pcr7 files exist + for _, path := range pcr7 { + if !PathExists(path) { + return fmt.Errorf("%s does not exist.", path) + } + } + + // For now, don't generate. Only accept the 2 policies. + if pdata.passwdPolicy == "" || !PathExists(pdata.passwdPolicy) { + return errors.New("The passwd policy file is missing.") + } + if pdata.luksPolicy == "" || !PathExists(pdata.luksPolicy) { + return errors.New("The luks policy file is missing.") + } + + moskeypath, err = getMosKeyPath() + if err != nil { + return err + } + keysetPath := filepath.Join(moskeypath, keysetName) + if !PathExists(keysetPath) { + return fmt.Errorf("The keyset, %s, does not exist.", keysetName) + } + + // Its ok if pcr7data dir already exists. We might be adding additional signdata + pcr7dataPath := filepath.Join(keysetPath, "pcr7data/policy2") + if !PathExists(pcr7dataPath) { + err = os.MkdirAll(keysetPath, 0750) + if err != nil { + return err + } + } else { + notexist = false + } + + defer func() { + if err != nil && notexist == true { + os.RemoveAll(filepath.Join(keysetPath, "pcr7data")) + } + }() + + // Check to see if public keys already exist for this keyset, if not + // then extract public keys and save them + pcr7dataPubkeys := filepath.Join(pcr7dataPath, "pubkeys") + if !PathExists(pcr7dataPubkeys) { + if err = trust.EnsureDir(pcr7dataPubkeys); err != nil { + return errors.New("Failed to create directory for public keys") + } + } + + tpmpolAdminpubkey, err = extractPubkey(filepath.Join(keysetPath, "tpmpol-admin/cert.pem")) + if err != nil { + return err + } + err = savePubkeytoFile(tpmpolAdminpubkey, filepath.Join(pcr7dataPubkeys, "tpmpass-snakeoil.pem")) + if err != nil { + return err + } + tpmpolLukspubkey, err = extractPubkey(filepath.Join(keysetPath, "tpmpol-luks/cert.pem")) + if err != nil { + return err + } + err = savePubkeytoFile(tpmpolLukspubkey, filepath.Join(pcr7dataPubkeys, "luks-snakeoil.pem")) + if err != nil { + return err + } + + // - Generate the index for pcr7-limited.bin. + // Add the binary pcr7 values into this index. + // - Generate the index for pcr7-production.bin. + // Add the signed tpm-luks policy into this index. + // - Generate the index for pcr7-tpm.bin. + // Add the signed tpm-passwd policy into this index. + for key, path := range pcr7 { + // create index used to name the sub-directories under policy-2 directory + pcrIndex, err = createPCR7Index(path) + if err != nil { + return err + } + indexdir := filepath.Join(pcr7dataPath, pcrIndex[0:2], pcrIndex[2:]) + if err = trust.EnsureDir(indexdir); err != nil { + return err + } + + // create info.json + jsonFile := filepath.Join(indexdir, "info.json") + + date := time.Now() + formatted := date.Format("2006-01-02") + timestamp := strings.ReplaceAll(formatted, "-", "") + info := &PCR7info{Key: keysetName, KeyType: key, Date: timestamp, Comment: "mos"+" "+keysetName} + jsonInfo, err = json.Marshal(info) + if err != nil { + return err + } + if err = os.WriteFile(jsonFile, jsonInfo, 0644); err != nil { + return err + } + + // write out info + switch key { + case "limited" : + newFile = filepath.Join(indexdir, "pcr_limited.bin") + if err = trust.CopyFile(pcr7["limited"], newFile); err != nil { + return err + } + newFile = filepath.Join(indexdir, "pcr_tpm.bin") + if err = trust.CopyFile(pcr7["tpm"], newFile); err != nil { + return err + } + newFile = filepath.Join(indexdir, "pcr_prod.bin") + if err = trust.CopyFile(pcr7["production"], newFile); err != nil { + return err + } + case "tpm" : + // Sign the policy + newFile = filepath.Join(indexdir, "tpm_passwd.policy.signed") + signingKey := filepath.Join(keysetPath, "tpmpol-admin/privkey.pem") + if err = trust.Sign(pdata.passwdPolicy, newFile, signingKey); err != nil { + return err + } + case "production" : + // Sign the policy + newFile = filepath.Join(indexdir, "tpm_luks.policy.signed") + signingKey := filepath.Join(keysetPath, "tpmpol-luks/privkey.pem") + if err = trust.Sign(pdata.luksPolicy, newFile, signingKey); err != nil { + return err + } + default : + return errors.New("Unrecognized uki key") + } + } + return nil +} diff --git a/cmd/trust/utils.go b/cmd/trust/utils.go index 23d798c..776cc0e 100644 --- a/cmd/trust/utils.go +++ b/cmd/trust/utils.go @@ -7,6 +7,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "encoding/hex" "fmt" "io" "math/big" @@ -72,6 +73,19 @@ func getMosKeyPath() (string, error) { return filepath.Join(dataDir, "machine", "trust", "keys"), nil } +func KeysetExists(keysetname string) bool { + mosKeyPath, err := getMosKeyPath() + if err != nil { + return false + } + keysetPath := filepath.Join(mosKeyPath, keysetname) + if PathExists(keysetPath) { + return true + } else { + return false + } +} + // SignCert creates a CA signed certificate and keypair in destdir func SignCert(template, CAcert *x509.Certificate, CAkey any, destdir string) error { // Check if credentials already exist @@ -405,3 +419,53 @@ func generateCreds(destdir string, doguid bool, template *x509.Certificate) erro return nil } + +func createPCR7Index(pcr7file string) (string, error) { + c, err := os.ReadFile(pcr7file) + if err != nil { + return "", err + } + for start := 0; start+1 < len(c); start += 2 { + tmp := c[start] + c[start] = c[start+1] + c[start+1] = tmp + } + encodedStr := hex.EncodeToString(c) + return encodedStr, nil +} + +func extractPubkey(certPath string) (*rsa.PublicKey, error) { + certPEM, err := os.ReadFile(certPath) + if err != nil { + return nil, err + } + block, _ := pem.Decode([]byte(certPEM)) + if block == nil { + return nil, fmt.Errorf("Failed to decode the certificate (%q)", certPath) + } + parsedCert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + return parsedCert.PublicKey.(*rsa.PublicKey), nil +} + +func savePubkeytoFile(pubkey *rsa.PublicKey, outPath string) error { + pubkeyPem, err := os.Create(outPath) + if err != nil { + return err + } + pkix, err := x509.MarshalPKIXPublicKey(pubkey) + if err != nil { + return err + } + err = pem.Encode(pubkeyPem, &pem.Block{Type: "PUBLIC KEY", Bytes: pkix}) + if err != nil { + return err + } + err = os.Chmod(outPath, 0644) + if err != nil { + return err + } + return nil +}