diff --git a/backend/backend.go b/backend/backend.go index eaf94107..4618d05f 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -3,6 +3,7 @@ package backend import ( "crypto" "crypto/x509" + "crypto/x509/pkix" "encoding/json" "encoding/pem" "errors" @@ -10,6 +11,7 @@ import ( "io" "os" "path/filepath" + "strings" "github.com/foxboron/go-uefi/authenticode" "github.com/foxboron/go-uefi/efivar" @@ -33,7 +35,6 @@ type KeyBackend interface { Signer() crypto.Signer Certificate() *x509.Certificate Type() BackendType - Description() string } type KeyHierarchy struct { @@ -50,16 +51,19 @@ func (k *KeyHierarchy) GetConfig(keydir string) *config.Keys { Privkey: filepath.Join(keydir, "PK/PK.key"), Pubkey: filepath.Join(keydir, "PK/PK.pem"), Type: string(k.PK.Type()), + Subject: k.PK.Certificate().Subject.String(), }, KEK: &config.KeyConfig{ Privkey: filepath.Join(keydir, "KEK/KEK.key"), Pubkey: filepath.Join(keydir, "KEK/KEK.pem"), Type: string(k.KEK.Type()), + Subject: k.KEK.Certificate().Subject.String(), }, Db: &config.KeyConfig{ Privkey: filepath.Join(keydir, "db/db.key"), Pubkey: filepath.Join(keydir, "db/db.pem"), Type: string(k.Db.Type()), + Subject: k.Db.Certificate().Subject.String(), }, } } @@ -127,11 +131,11 @@ func (k *KeyHierarchy) RotateKeyWithBackend(hier hierarchy.Hierarchy, backend Ba var err error switch hier { case hierarchy.PK: - k.PK, err = createKey(k.state, string(backend), hier, k.PK.Description()) + k.PK, err = createKey(k.state, k.state.Config.Keys.PK, hier) case hierarchy.KEK: - k.KEK, err = createKey(k.state, string(backend), hier, k.KEK.Description()) + k.KEK, err = createKey(k.state, k.state.Config.Keys.KEK, hier) case hierarchy.Db: - k.Db, err = createKey(k.state, string(backend), hier, k.Db.Description()) + k.Db, err = createKey(k.state, k.state.Config.Keys.Db, hier) } return err } @@ -190,19 +194,18 @@ func (k *KeyHierarchy) SignFile(hier hierarchy.Hierarchy, peBinary *authenticode return peBinary.Bytes(), nil } -func createKey(state *config.State, backend string, hier hierarchy.Hierarchy, desc string) (KeyBackend, error) { - if desc == "" { - desc = hier.Description() - } - switch backend { +func createKey(state *config.State, key *config.KeyConfig, hier hierarchy.Hierarchy) (KeyBackend, error) { + subject := parseSubject(key.Subject, hier) + + switch key.Type { case "file", "": - return NewFileKey(hier, desc) + return NewFileKey(hier, subject) case "tpm": - return NewTPMKey(state.TPM, desc) + return NewTPMKey(state.TPM, subject) case "yubikey": - return NewYubikeyKey(state.Yubikey, hier) + return NewYubikeyKey(state.Yubikey, subject) default: - return NewFileKey(hier, desc) + return NewFileKey(hier, subject) } } @@ -211,17 +214,17 @@ func CreateKeys(state *config.State) (*KeyHierarchy, error) { var err error c := state.Config - hier.PK, err = createKey(state, c.Keys.PK.Type, hierarchy.PK, c.Keys.PK.Description) + hier.PK, err = createKey(state, c.Keys.PK, hierarchy.PK) if err != nil { return nil, err } - hier.KEK, err = createKey(state, c.Keys.KEK.Type, hierarchy.KEK, c.Keys.KEK.Description) + hier.KEK, err = createKey(state, c.Keys.KEK, hierarchy.KEK) if err != nil { return nil, err } - hier.Db, err = createKey(state, c.Keys.Db.Type, hierarchy.Db, c.Keys.Db.Description) + hier.Db, err = createKey(state, c.Keys.Db, hierarchy.Db) if err != nil { return nil, err } @@ -337,3 +340,55 @@ func InitBackendFromKeys(state *config.State, priv, pem []byte, hier hierarchy.H return nil, fmt.Errorf("unknown key backend: %s", t) } } + +func parseSubject(subj string, hier hierarchy.Hierarchy) pkix.Name { + var subject pkix.Name + + if subj != "" { + subject = pkix.Name{} + + fields := strings.SplitSeq(subj, "/") + for field := range fields { + if field == "" { + continue + } + kv := strings.SplitN(field, "=", 2) + if len(kv) != 2 { + continue + } + key := strings.ToUpper(strings.TrimSpace(kv[0])) + value := strings.TrimSpace(kv[1]) + + switch key { + case "C": + subject.Country = append(subject.Country, value) + case "O": + subject.Organization = append(subject.Organization, value) + case "OU": + subject.OrganizationalUnit = append(subject.OrganizationalUnit, value) + case "L": + subject.Locality = append(subject.Locality, value) + case "ST": + subject.Province = append(subject.Province, value) + case "CN": + subject.CommonName = value + case "SERIALNUMBER": + subject.SerialNumber = value + default: + } + } + + // Basic sanity: CN must be supplied + if subject.CommonName == "" { + panic("subject has no common name") + } + } else { + // return default + subject = pkix.Name{ + Country: []string{"WW"}, + CommonName: hier.Description(), + } + } + + return subject +} diff --git a/backend/file.go b/backend/file.go index 7319fd38..7bb58e65 100644 --- a/backend/file.go +++ b/backend/file.go @@ -27,7 +27,7 @@ type FileKey struct { privkey *rsa.PrivateKey } -func NewFileKey(_ hierarchy.Hierarchy, desc string) (*FileKey, error) { +func NewFileKey(_ hierarchy.Hierarchy, subject pkix.Name) (*FileKey, error) { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit) c := x509.Certificate{ @@ -36,9 +36,7 @@ func NewFileKey(_ hierarchy.Hierarchy, desc string) (*FileKey, error) { SignatureAlgorithm: x509.SHA256WithRSA, NotBefore: time.Now(), NotAfter: time.Now().AddDate(5, 0, 0), - Subject: pkix.Name{ - CommonName: desc, - }, + Subject: subject, } priv, err := rsa.GenerateKey(rand.Reader, RSAKeySize) if err != nil { @@ -116,7 +114,6 @@ func FileKeyFromBytes(keyb, pemb []byte) (*FileKey, error) { func (f *FileKey) Type() BackendType { return f.keytype } func (f *FileKey) Certificate() *x509.Certificate { return f.cert } func (f *FileKey) Signer() crypto.Signer { return f.privkey } -func (f *FileKey) Description() string { return f.Certificate().Subject.SerialNumber } func (f *FileKey) PrivateKeyBytes() []byte { privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(f.privkey) diff --git a/backend/tpm.go b/backend/tpm.go index e71e848a..e4bfbff7 100644 --- a/backend/tpm.go +++ b/backend/tpm.go @@ -27,10 +27,10 @@ type TPMKey struct { tpm func() transport.TPMCloser } -func NewTPMKey(tpmcb func() transport.TPMCloser, desc string) (*TPMKey, error) { +func NewTPMKey(tpmcb func() transport.TPMCloser, subject pkix.Name) (*TPMKey, error) { rwc := tpmcb() key, err := keyfile.NewLoadableKey(rwc, tpm2.TPMAlgRSA, 2048, []byte(nil), - keyfile.WithDescription(desc), + keyfile.WithDescription(subject.String()), ) if err != nil { return nil, err @@ -44,9 +44,7 @@ func NewTPMKey(tpmcb func() transport.TPMCloser, desc string) (*TPMKey, error) { SignatureAlgorithm: x509.SHA256WithRSA, NotBefore: time.Now(), NotAfter: time.Now().AddDate(5, 0, 0), - Subject: pkix.Name{ - CommonName: desc, - }, + Subject: subject, } pubkey, err := key.PublicKey() @@ -78,7 +76,6 @@ func NewTPMKey(tpmcb func() transport.TPMCloser, desc string) (*TPMKey, error) { func (t *TPMKey) Type() BackendType { return t.keytype } func (t *TPMKey) Certificate() *x509.Certificate { return t.cert } -func (t *TPMKey) Description() string { return t.TPMKey.Description } func (t *TPMKey) Signer() crypto.Signer { s, err := t.TPMKey.Signer(t.tpm(), []byte(nil), []byte(nil)) diff --git a/backend/yubikey.go b/backend/yubikey.go index 0c3eb13d..6b2840e8 100644 --- a/backend/yubikey.go +++ b/backend/yubikey.go @@ -18,7 +18,6 @@ import ( "time" "github.com/foxboron/sbctl/config" - "github.com/foxboron/sbctl/hierarchy" "github.com/foxboron/sbctl/logging" "github.com/go-piv/piv-go/v2/piv" @@ -41,7 +40,7 @@ type Yubikey struct { touchPolicy piv.TouchPolicy } -func NewYubikeyKey(yubikeyReader *config.YubikeyReader, hier hierarchy.Hierarchy) (*Yubikey, error) { +func NewYubikeyKey(yubikeyReader *config.YubikeyReader, subject pkix.Name) (*Yubikey, error) { cert, err := yubikeyReader.GetPIVKeyCert() if err != nil { if !errors.Is(err, piv.ErrNotFound) { @@ -107,16 +106,10 @@ func NewYubikeyKey(yubikeyReader *config.YubikeyReader, hier hierarchy.Hierarchy SignatureAlgorithm: x509.SHA256WithRSA, NotBefore: time.Now(), NotAfter: time.Now().AddDate(20, 0, 0), - Subject: pkix.Name{ - Country: []string{hier.Description()}, - CommonName: hier.Description(), - }, + Subject: subject, } - logging.Println(fmt.Sprintf("Creating %s (%s) key...\nPlease press Yubikey to confirm presence for RSA4096 MD5: %x", - hier.Description(), - hier.String(), - md5sum(ykCert.PublicKey))) + logging.Println(fmt.Sprintf("Please press Yubikey to confirm presence for RSA4096 MD5: %x", md5sum(ykCert.PublicKey))) derBytes, err := x509.CreateCertificate(rand.Reader, &c, &c, ykCert.PublicKey, priv) if err != nil { return nil, err @@ -183,8 +176,6 @@ func (f *Yubikey) Signer() crypto.Signer { return priv.(crypto.Signer) } -func (f *Yubikey) Description() string { return f.Certificate().Subject.SerialNumber } - // save YubiKey data to file func (f *Yubikey) PrivateKeyBytes() []byte { yubiData := YubikeyData{ diff --git a/cmd/sbctl/create-keys.go b/cmd/sbctl/create-keys.go index 59b9273e..a5f6e011 100644 --- a/cmd/sbctl/create-keys.go +++ b/cmd/sbctl/create-keys.go @@ -21,6 +21,9 @@ var ( KEKKeytype string DbKeytype string PKKeytype string + PKSubject string + KEKSubject string + DbSubject string OverwriteYubikey bool ) @@ -80,6 +83,15 @@ func RunCreateKeys(state *config.State) error { } } + if PKSubject != "" { + state.Config.Keys.PK.Subject = PKSubject + } + if KEKSubject != "" { + state.Config.Keys.KEK.Subject = KEKSubject + } + if DbSubject != "" { + state.Config.Keys.Db.Subject = DbSubject + } // if any keytype is yubikey close it appropriately at the end if Keytype == "yubikey" || PKKeytype == "yubikey" || KEKKeytype == "yubikey" || DbKeytype == "yubikey" { defer state.Yubikey.Close() @@ -119,6 +131,9 @@ func createKeysCmdFlags(cmd *cobra.Command) { f.StringVarP(&PKKeytype, "pk-keytype", "", "", "PK key type (default: file)") f.StringVarP(&KEKKeytype, "kek-keytype", "", "", "KEK key type (default: file)") f.StringVarP(&DbKeytype, "db-keytype", "", "", "db key type (default: file)") + f.StringVarP(&PKSubject, "pk-subj", "", "", "Distinguished name for Platform Key certificate (openssl style, e.g. /CN=Platform Key/C=WW/)") + f.StringVarP(&KEKSubject, "kek-subj", "", "", "Distinguished name for Key Exchange Key certificate (openssl style, e.g. /CN=Key Exchange Key/C=WW/)") + f.StringVarP(&DbSubject, "db-subj", "", "", "Distinguished name for Database Key certificate (openssl style, e.g. /CN=Database Key/C=WW/)") } func init() { diff --git a/config/config.go b/config/config.go index 1c83070c..99c4d0c5 100644 --- a/config/config.go +++ b/config/config.go @@ -29,10 +29,10 @@ type FileConfig struct { } type KeyConfig struct { - Privkey string `json:"privkey"` - Pubkey string `json:"pubkey"` - Type string `json:"type"` - Description string `json:"description,omitempty"` + Privkey string `json:"privkey"` + Pubkey string `json:"pubkey"` + Type string `json:"type"` + Subject string `json:"subject,omitempty"` } type Keys struct { @@ -88,16 +88,19 @@ func MkConfig(dir string) *Config { Privkey: path.Join(conf.Keydir, "PK", "PK.key"), Pubkey: path.Join(conf.Keydir, "PK", "PK.pem"), Type: "file", + Subject: "/CN=Platform Key/C=WW/", }, KEK: &KeyConfig{ Privkey: path.Join(conf.Keydir, "KEK", "KEK.key"), Pubkey: path.Join(conf.Keydir, "KEK", "KEK.pem"), Type: "file", + Subject: "/CN=Key Exchange Key/C=WW/", }, Db: &KeyConfig{ Privkey: path.Join(conf.Keydir, "db", "db.key"), Pubkey: path.Join(conf.Keydir, "db", "db.pem"), Type: "file", + Subject: "/CN=Database Key/C=WW/", }, } return conf diff --git a/docs/sbctl.conf.5.txt b/docs/sbctl.conf.5.txt index c001f001..f7080cef 100644 --- a/docs/sbctl.conf.5.txt +++ b/docs/sbctl.conf.5.txt @@ -111,6 +111,16 @@ Options + Default: file + *subject:* distinguished name ;; + Distinguished name to be used for the creation of certificates. + + + Needs to be given in openssl fashion. + + + Default: + * *pk:* /CN=Platform Key/C=WW/ + * *kek:* /CN=Key Exchange Key/C=WW/ + * *db*: /CN=Database Key/C=WW/ + Example ------- @@ -135,14 +145,17 @@ An example of a /etc/sbctl/sbctl.conf file with the default values. privkey: /var/lib/sbctl/keys/PK/PK.key pubkey: /var/lib/sbctl/keys/PK/PK.pem type: file + subject: /CN=Platform Key/C=WW/ kek: privkey: /var/lib/sbctl/keys/KEK/KEK.key pubkey: /var/lib/sbctl/keys/KEK/KEK.pem type: file + subject: /CN=Key Exchange Key/C=WW/ db: privkey: /var/lib/sbctl/keys/db/db.key pubkey: /var/lib/sbctl/keys/db/db.pem type: file + subject: /CN=Database Key/C=WW/ See Also --------