diff --git a/backend/backend.go b/backend/backend.go index eaf9410..1f77f9c 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -127,11 +127,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,17 +190,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) { +func CreateKey(state *config.State, key *config.KeyConfig, hier hierarchy.Hierarchy) (KeyBackend, error) { + desc := key.Description if desc == "" { desc = hier.Description() } - switch backend { + switch key.Type { case "file", "": return NewFileKey(hier, desc) case "tpm": return NewTPMKey(state.TPM, desc) case "yubikey": - return NewYubikeyKey(state.Yubikey, hier) + return NewYubikeyKey(state.Yubikey, hier, key) default: return NewFileKey(hier, desc) } @@ -211,17 +212,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 } @@ -229,7 +230,7 @@ func CreateKeys(state *config.State) (*KeyHierarchy, error) { return &hier, nil } -func readKey(state *config.State, keydir string, kc *config.KeyConfig, hier hierarchy.Hierarchy) (KeyBackend, error) { +func readKey(state *config.State, keydir string, hier hierarchy.Hierarchy) (KeyBackend, error) { path := filepath.Join(keydir, hier.String()) keyname := filepath.Join(path, fmt.Sprintf("%s.key", hier.String())) certname := filepath.Join(path, fmt.Sprintf("%s.pem", hier.String())) @@ -267,11 +268,11 @@ func GetKeyBackend(state *config.State, k hierarchy.Hierarchy) (KeyBackend, erro c := state.Config switch k { case hierarchy.PK: - return readKey(state, c.Keydir, c.Keys.PK, k) + return readKey(state, c.Keydir, k) case hierarchy.KEK: - return readKey(state, c.Keydir, c.Keys.KEK, k) + return readKey(state, c.Keydir, k) case hierarchy.Db: - return readKey(state, c.Keydir, c.Keys.Db, k) + return readKey(state, c.Keydir, k) } return nil, nil } diff --git a/backend/yubikey.go b/backend/yubikey.go index 0c3eb13..4d4fdd4 100644 --- a/backend/yubikey.go +++ b/backend/yubikey.go @@ -14,7 +14,8 @@ import ( "errors" "fmt" "math/big" - "os" + "strconv" + "strings" "time" "github.com/foxboron/sbctl/config" @@ -36,65 +37,149 @@ type Yubikey struct { keytype BackendType cert *x509.Certificate yubikeyReader *config.YubikeyReader + slot piv.Slot algorithm piv.Algorithm pinPolicy piv.PINPolicy touchPolicy piv.TouchPolicy } -func NewYubikeyKey(yubikeyReader *config.YubikeyReader, hier hierarchy.Hierarchy) (*Yubikey, error) { - cert, err := yubikeyReader.GetPIVKeyCert() - if err != nil { - if !errors.Is(err, piv.ErrNotFound) { - return nil, fmt.Errorf("failed finding yubikey: %v", err) +func NewYubikeyKey(yubikeyReader *config.YubikeyReader, hier hierarchy.Hierarchy, keyConfig *config.KeyConfig) (*Yubikey, error) { + var slot piv.Slot + var slotName string + var pivAlg piv.Algorithm + + logging.Println(fmt.Sprintf("\nCreating %s (%s) key...", hier.Description(), hier.String())) + + switch keyConfig.Slot { + case "9c": + slot = piv.SlotSignature + slotName = "Signature" + fmt.Printf("Using slot: %s (%s)\n", slot.String(), slotName) + case "9a": + slot = piv.SlotAuthentication + slotName = "Authentication" + fmt.Printf("Using slot: %s (%s)\n", slot.String(), slotName) + case "9e": + slot = piv.SlotCardAuthentication + slotName = "CardAuthentication" + fmt.Printf("Using slot: %s (%s)\n", slot.String(), slotName) + case "9d": + slot = piv.SlotKeyManagement + slotName = "KeyManagement" + fmt.Printf("Using slot: %s (%s)\n", slot.String(), slotName) + default: + // maybe one of retired slots + var found bool = false + slotHexVal, err := strconv.ParseUint(keyConfig.Slot, 16, 8) + if err == nil { + slot, found = piv.RetiredKeyManagementSlot(uint32(slotHexVal)) + } + if !found { + return nil, fmt.Errorf("yubikey: Invalid key slot %s", keyConfig.Slot) } + slotName = fmt.Sprintf("RetiredKeyManagementSlot:0x%s", keyConfig.Slot) } - if cert != nil { - // if there is a key and it is RSA4096 and overwrite is false, use it + // Try Key cert first then fallback to Attestation cert + // FIXME: Should it be other way around? + cert, err := yubikeyReader.GetPIVKeyCert(slot) + if err != nil && !errors.Is(err, piv.ErrNotFound) { + return nil, fmt.Errorf("yubikey: failed finding yubikey: %v", err) + } + + // Fallback to attestation cert + if cert == nil { + cert, err = yubikeyReader.GetPIVAttestationCert(slot) + if err != nil && !errors.Is(err, piv.ErrNotFound) { + return nil, fmt.Errorf("yubikey: failed finding yubikey: %v", err) + } + } + + // if there is a key and overwrite is false, use it + if cert != nil && !yubikeyReader.Overwrite { + var keyAlgName string + switch yubiPub := cert.PublicKey.(type) { case *rsa.PublicKey: - // RSA4096 Public Key - if yubiPub.N.BitLen() == 4096 && !yubikeyReader.Overwrite { - logging.Println(fmt.Sprintf("Using RSA4096 Key MD5: %x in Yubikey PIV Signature Slot", md5sum(cert.PublicKey))) - } else if !yubikeyReader.Overwrite { - return nil, fmt.Errorf("yubikey key creation failed; %s key present in signature slot", cert.PublicKeyAlgorithm.String()) + // RSA Public Key + bitlen := yubiPub.N.BitLen() + if bitlen < 2048 { + return nil, fmt.Errorf("yubikey: key creation failed; %s key present in %s slot is less than 2048 bits", cert.PublicKeyAlgorithm.String(), slotName) } - } - } - // if overwrite or there is no piv key create one - if yubikeyReader.Overwrite || cert == nil { - if yubikeyReader.Overwrite { - logging.Warn("Overwriting existing key %s in Signature slot", cert.PublicKeyAlgorithm.String()) - } + keyAlgName = fmt.Sprintf("RSA%d", bitlen) + logging.Println(fmt.Sprintf("Using existing %s Key MD5: %x in Yubikey PIV %s Slot", keyAlgName, md5sum(cert.PublicKey), slotName)) - // Generate a private key on the YubiKey. - key := piv.Key{ - Algorithm: piv.AlgorithmRSA4096, - PINPolicy: piv.PINPolicyAlways, - TouchPolicy: piv.TouchPolicyAlways, + default: + if yubikeyReader.Overwrite { + return nil, fmt.Errorf("yubikey: unsupported key type: %s", cert.PublicKey) + } } - logging.Println("Creating RSA4096 key...\nPlease press Yubikey to confirm presence") - newKey, err := yubikeyReader.GenerateKey(piv.DefaultManagementKey, piv.SlotSignature, key) - if err != nil { - return nil, err + switch keyAlgName { + case "RSA2048": + pivAlg = piv.AlgorithmRSA2048 + case "RSA3072": + pivAlg = piv.AlgorithmRSA3072 + case "RSA4096": + pivAlg = piv.AlgorithmRSA4096 + default: + if !yubikeyReader.Overwrite { + return nil, fmt.Errorf("yubikey: unsupported existing yubikey key algorithm: %s", keyAlgName) + } } - logging.Println(fmt.Sprintf("Created RSA4096 key MD5: %x", md5sum(newKey))) - // we overwrote the existing signing key, do not overwrite again if there are other - // key creation operations - yubikeyReader.Overwrite = false + return &Yubikey{ + keytype: YubikeyBackend, + cert: cert, + yubikeyReader: yubikeyReader, + slot: slot, + algorithm: pivAlg, + pinPolicy: piv.PINPolicyAlways, + touchPolicy: piv.TouchPolicyAlways, + }, nil + } + + // if overwrite and there is an existing piv key, print warning + if cert != nil && yubikeyReader.Overwrite { + logging.Warn("Overwriting existing key %s in Yubikey PIV %s Slot", cert.PublicKeyAlgorithm.String(), slotName) + } + + switch keyConfig.Algorithm { + case "RSA2048": + pivAlg = piv.AlgorithmRSA2048 + case "RSA3072": + pivAlg = piv.AlgorithmRSA3072 + case "RSA4096": + pivAlg = piv.AlgorithmRSA4096 + + default: + return nil, fmt.Errorf("yubikey: unsupported public key algorithm %s", keyConfig.Algorithm) + } + + // Get management key + mgmtKey, err := yubikeyReader.GetManagementKey() + if err != nil { + return nil, err } - ykCert, err := yubikeyReader.GetPIVKeyCert() + // Generate a private key on the YubiKey. + key := piv.Key{ + Algorithm: pivAlg, + PINPolicy: piv.PINPolicyAlways, + TouchPolicy: piv.TouchPolicyAlways, + } + logging.Println(fmt.Sprintf("Creating %s key in Yubikey PIV %s Slot...\nPlease press Yubikey to confirm presence", slotName, keyConfig.Algorithm)) + newKey, err := yubikeyReader.GenerateKey(mgmtKey, slot, key) if err != nil { return nil, err } + logging.Println(fmt.Sprintf("Created %s key in Yubikey PIV %s Slot MD5: %x", keyConfig.Algorithm, slotName, md5sum(newKey))) - auth := piv.KeyAuth{PIN: piv.DefaultPIN} - if pin, found := os.LookupEnv("SBCTL_YUBIKEY_PIN"); found { - auth = piv.KeyAuth{PIN: pin} + cert, err = yubikeyReader.GetPIVKeyCert(slot) + if err != nil { + return nil, err } - priv, err := yubikeyReader.PrivateKey(piv.SlotSignature, ykCert.PublicKey, auth) + + priv, err := yubikeyReader.PrivateKey(slot, cert.PublicKey) if err != nil { return nil, err } @@ -107,17 +192,11 @@ 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: parseSubject(keyConfig.Subject, hier), } - 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))) - derBytes, err := x509.CreateCertificate(rand.Reader, &c, &c, ykCert.PublicKey, priv) + logging.Println(fmt.Sprintf("Please press Yubikey to confirm presence for %s MD5: %x", keyConfig.Algorithm, md5sum(cert.PublicKey))) + derBytes, err := x509.CreateCertificate(rand.Reader, &c, &c, cert.PublicKey, priv) if err != nil { return nil, err } @@ -131,7 +210,8 @@ func NewYubikeyKey(yubikeyReader *config.YubikeyReader, hier hierarchy.Hierarchy keytype: YubikeyBackend, cert: cert, yubikeyReader: yubikeyReader, - algorithm: piv.AlgorithmRSA4096, + slot: slot, + algorithm: pivAlg, pinPolicy: piv.PINPolicyAlways, touchPolicy: piv.TouchPolicyAlways, }, nil @@ -139,25 +219,48 @@ func NewYubikeyKey(yubikeyReader *config.YubikeyReader, hier hierarchy.Hierarchy func YubikeyFromBytes(yubikeyReader *config.YubikeyReader, keyb, pemb []byte) (*Yubikey, error) { var yubiData YubikeyData + var slot piv.Slot err := json.Unmarshal(keyb, &yubiData) if err != nil { - return nil, fmt.Errorf("error unmarshalling yubikey: %v", err) + return nil, fmt.Errorf("yubikey: error unmarshalling yubikey: %v", err) + } + + switch strings.ToLower(yubiData.Slot) { + case "9c": + slot = piv.SlotSignature + case "9a": + slot = piv.SlotAuthentication + case "9e": + slot = piv.SlotCardAuthentication + case "9d": + slot = piv.SlotKeyManagement + default: + // maybe one of retired slots + var found bool = false + slotHexVal, err := strconv.ParseUint(strings.ToLower(yubiData.Slot), 16, 8) + if err == nil { + slot, found = piv.RetiredKeyManagementSlot(uint32(slotHexVal)) + } + if !found { + return nil, fmt.Errorf("yubikey: Invalid key slot %s", yubiData.Slot) + } } block, _ := pem.Decode(pemb) if block == nil { - return nil, fmt.Errorf("no pem block") + return nil, fmt.Errorf("yubikey: no pem block") } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { - return nil, fmt.Errorf("failed to parse cert: %w", err) + return nil, fmt.Errorf("yubikey: failed to parse cert: %w", err) } return &Yubikey{ keytype: YubikeyBackend, cert: cert, yubikeyReader: yubikeyReader, + slot: slot, algorithm: yubiData.Algorithm, pinPolicy: yubiData.PinPolicy, touchPolicy: yubiData.TouchPolicy, @@ -168,12 +271,7 @@ func (f *Yubikey) Type() BackendType { return f.keytype } func (f *Yubikey) Certificate() *x509.Certificate { return f.cert } func (f *Yubikey) Signer() crypto.Signer { - auth := piv.KeyAuth{PIN: piv.DefaultPIN} - if pin, found := os.LookupEnv("SBCTL_YUBIKEY_PIN"); found { - auth = piv.KeyAuth{PIN: pin} - } - - priv, err := f.yubikeyReader.PrivateKey(piv.SlotSignature, f.cert.PublicKey, auth) + priv, err := f.yubikeyReader.PrivateKey(f.slot, f.cert.PublicKey) if err != nil { panic(err) } @@ -187,12 +285,13 @@ func (f *Yubikey) Description() string { return f.Certificate().Subject.SerialNu // save YubiKey data to file func (f *Yubikey) PrivateKeyBytes() []byte { + pubKey, _ := x509.MarshalPKIXPublicKey(f.cert.PublicKey) yubiData := YubikeyData{ - Slot: piv.SlotSignature.String(), + Slot: f.slot.String(), Algorithm: f.algorithm, PinPolicy: f.pinPolicy, TouchPolicy: f.touchPolicy, - PublicKey: base64.StdEncoding.EncodeToString(x509.MarshalPKCS1PublicKey(f.cert.PublicKey.(*rsa.PublicKey))), + PublicKey: base64.StdEncoding.EncodeToString(pubKey), } b, err := json.Marshal(yubiData) @@ -205,13 +304,66 @@ func (f *Yubikey) PrivateKeyBytes() []byte { func (f *Yubikey) CertificateBytes() []byte { b := new(bytes.Buffer) if err := pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: f.cert.Raw}); err != nil { - panic("failed producing PEM encoded certificate") + panic("yubikey: failed producing PEM encoded certificate") } return b.Bytes() } func md5sum(key crypto.PublicKey) []byte { h := md5.New() - h.Write(x509.MarshalPKCS1PublicKey(key.(*rsa.PublicKey))) + pubKey, _ := x509.MarshalPKIXPublicKey(key) + h.Write(pubKey) return h.Sum(nil) } + +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("yubikey: subject missing common name") + } + } else { + // return default + subject = pkix.Name{ + Country: []string{"WW"}, + CommonName: hier.Description(), + } + } + + return subject +} diff --git a/cmd/sbctl/create-keys.go b/cmd/sbctl/create-keys.go index 59b9273..1b55ca5 100644 --- a/cmd/sbctl/create-keys.go +++ b/cmd/sbctl/create-keys.go @@ -4,6 +4,7 @@ import ( "fmt" "path" "path/filepath" + "strings" "github.com/foxboron/sbctl" "github.com/foxboron/sbctl/backend" @@ -21,6 +22,9 @@ var ( KEKKeytype string DbKeytype string PKKeytype string + PKSubject string + KEKSubject string + DbSubject string OverwriteYubikey bool ) @@ -63,25 +67,28 @@ func RunCreateKeys(state *config.State) error { return err } - // Should be own flag type - if Keytype != "" && (Keytype == "file" || Keytype == "tpm" || Keytype == "yubikey") { - state.Config.Keys.PK.Type = Keytype - state.Config.Keys.KEK.Type = Keytype - state.Config.Keys.Db.Type = Keytype + if Keytype != "" && (strings.HasPrefix(Keytype, "file") || strings.HasPrefix(Keytype, "tpm") || strings.HasPrefix(Keytype, "yubikey")) { + state.Config.Keys.PK.Type, state.Config.Keys.PK.Algorithm, state.Config.Keys.PK.Slot = splitKeyType(Keytype) + state.Config.Keys.KEK.Type, state.Config.Keys.KEK.Algorithm, state.Config.Keys.KEK.Slot = splitKeyType(Keytype) + state.Config.Keys.Db.Type, state.Config.Keys.Db.Algorithm, state.Config.Keys.Db.Slot = splitKeyType(Keytype) } else { - if PKKeytype != "" && (PKKeytype == "file" || PKKeytype == "tpm" || PKKeytype == "yubikey") { - state.Config.Keys.PK.Type = PKKeytype + if PKKeytype != "" && (strings.HasPrefix(PKKeytype, "file") || strings.HasPrefix(PKKeytype, "tpm") || strings.HasPrefix(PKKeytype, "yubikey")) { + state.Config.Keys.PK.Type, state.Config.Keys.PK.Algorithm, state.Config.Keys.PK.Slot = splitKeyType(PKKeytype) } - if KEKKeytype != "" && (KEKKeytype == "file" || KEKKeytype == "tpm" || KEKKeytype == "yubikey") { - state.Config.Keys.KEK.Type = KEKKeytype + if KEKKeytype != "" && (strings.HasPrefix(KEKKeytype, "file") || strings.HasPrefix(KEKKeytype, "tpm") || strings.HasPrefix(KEKKeytype, "yubikey")) { + state.Config.Keys.KEK.Type, state.Config.Keys.KEK.Algorithm, state.Config.Keys.KEK.Slot = splitKeyType(KEKKeytype) } - if DbKeytype != "" && (DbKeytype == "file" || DbKeytype == "tpm" || DbKeytype == "yubikey") { - state.Config.Keys.Db.Type = DbKeytype + if DbKeytype != "" && (strings.HasPrefix(DbKeytype, "file") || strings.HasPrefix(DbKeytype, "tpm") || strings.HasPrefix(DbKeytype, "yubikey")) { + state.Config.Keys.Db.Type, state.Config.Keys.Db.Algorithm, state.Config.Keys.Db.Slot = splitKeyType(DbKeytype) } } + state.Config.Keys.PK.Subject = PKSubject + state.Config.Keys.KEK.Subject = KEKSubject + 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" { + if strings.HasPrefix(Keytype, "yubikey") || strings.HasPrefix(PKKeytype, "yubikey") || strings.HasPrefix(KEKKeytype, "yubikey") || strings.HasPrefix(DbKeytype, "yubikey") { defer state.Yubikey.Close() } @@ -91,7 +98,6 @@ func RunCreateKeys(state *config.State) error { } logging.Print("Created Owner UUID %s\n", uuid) if !sbctl.CheckIfKeysInitialized(state.Fs, state.Config.Keydir) { - hier, err := backend.CreateKeys(state) if err != nil { logging.NotOk("") @@ -102,8 +108,7 @@ func RunCreateKeys(state *config.State) error { logging.NotOk("") return fmt.Errorf("couldn't initialize secure boot: %w", err) } - logging.Ok("") - logging.Println("Secure boot keys created!") + logging.Ok("Secure boot keys created!") } else { logging.Ok("Secure boot keys have already been created!") } @@ -112,13 +117,16 @@ func RunCreateKeys(state *config.State) error { func createKeysCmdFlags(cmd *cobra.Command) { f := cmd.Flags() - f.BoolVar(&OverwriteYubikey, "yk-overwrite", false, "overwrite existing key if it exists in the Yubikey Signature slot") + f.BoolVar(&OverwriteYubikey, "yk-overwrite", false, "overwrite existing key if it exists in the Yubikey slot") f.StringVarP(&exportPath, "export", "e", "", "export file path") f.StringVarP(&databasePath, "database-path", "d", "", "location to create GUID file") - f.StringVarP(&Keytype, "keytype", "", "", "key type for all keys") - 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(&Keytype, "keytype", "", "", "key type for all keys. Algorithm and slot for yubikey can also be specified: yubikey:RSA4096:9c (default: file)") + f.StringVarP(&PKKeytype, "pk-keytype", "", "", "PK key type Algorithm and slot for yubikey can also be specified: yubikey:RSA4096:9c (default: file)") + f.StringVarP(&KEKKeytype, "kek-keytype", "", "", "KEK key type Algorithm and slot for yubikey can also be specified: yubikey:RSA4096:9c (default: file)") + f.StringVarP(&DbKeytype, "db-keytype", "", "", "db key type Algorithm and slot for yubikey can also be specified: yubikey:RSA4096:9c (default: file)") + f.StringVarP(&PKSubject, "pk-subj", "", "", "Subject DN for Platform Key certificate (default: /CN=Platform Key/C=WW/)") + f.StringVarP(&KEKSubject, "kek-subj", "", "", "Subject DN for Key Exchange Key certificate (default: /CN=Key Exchange Key/C=WW/)") + f.StringVarP(&DbSubject, "db-subj", "", "", "Subject DN for Database Key certificate (default: /CN=Database Key/C=WW/)") } func init() { @@ -128,3 +136,18 @@ func init() { Cmd: createKeysCmd, }) } + +func splitKeyType(keyType string) (string, string, string) { + arr := strings.SplitN(keyType, ":", 3) + + if len(arr) == 1 { + return arr[0], "", "" + } + if len(arr) == 2 { + return arr[0], arr[1], "" + } + if len(arr) == 3 { + return arr[0], arr[1], arr[2] + } + return "", "", "" +} diff --git a/config/config.go b/config/config.go index 1c83070..3415317 100644 --- a/config/config.go +++ b/config/config.go @@ -32,6 +32,9 @@ type KeyConfig struct { Privkey string `json:"privkey"` Pubkey string `json:"pubkey"` Type string `json:"type"` + Algorithm string `json:"algorithm"` + Slot string `json:"slot,omitempty"` + Subject string `json:"subject,omitempty"` Description string `json:"description,omitempty"` } diff --git a/config/yubikeyreader.go b/config/yubikeyreader.go index b35c6b8..4e973a0 100644 --- a/config/yubikeyreader.go +++ b/config/yubikeyreader.go @@ -4,6 +4,7 @@ import ( "crypto" "crypto/x509" "fmt" + "os" "strings" "time" @@ -15,13 +16,46 @@ import ( type YubikeyReader struct { key *piv.YubiKey Overwrite bool + Pin string } -func (y *YubikeyReader) GetPIVKeyCert() (*x509.Certificate, error) { +// Fetches PIN protected management key. If it is not stored, default is returned +func (y *YubikeyReader) GetManagementKey() ([]byte, error) { + var err error + if y.Pin == "" { + if pin, found := os.LookupEnv("SBCTL_YUBIKEY_PIN"); found { + y.Pin = pin + } else { + y.Pin = piv.DefaultPIN + } + } + if err = y.connectToYubikey(); err != nil { + return nil, err + } + // FIXME: Should swallow error and return default key? + metadata, err := y.key.Metadata(y.Pin) + if err != nil { + return nil, err + } + if metadata.ManagementKey != nil { + return *metadata.ManagementKey, nil + } else { + return piv.DefaultManagementKey, nil + } +} + +func (y *YubikeyReader) GetPIVKeyCert(slot piv.Slot) (*x509.Certificate, error) { + if err := y.connectToYubikey(); err != nil { + return nil, err + } + return y.key.Certificate(slot) +} + +func (y *YubikeyReader) GetPIVAttestationCert(slot piv.Slot) (*x509.Certificate, error) { if err := y.connectToYubikey(); err != nil { return nil, err } - return y.key.Attest(piv.SlotSignature) + return y.key.Attest(slot) } func (y *YubikeyReader) GenerateKey(key []byte, slot piv.Slot, opts piv.Key) (crypto.PublicKey, error) { @@ -31,7 +65,15 @@ func (y *YubikeyReader) GenerateKey(key []byte, slot piv.Slot, opts piv.Key) (cr return y.key.GenerateKey(key, slot, opts) } -func (y *YubikeyReader) PrivateKey(slot piv.Slot, public crypto.PublicKey, auth piv.KeyAuth) (crypto.PrivateKey, error) { +func (y *YubikeyReader) PrivateKey(slot piv.Slot, public crypto.PublicKey) (crypto.PrivateKey, error) { + if y.Pin == "" { + if pin, found := os.LookupEnv("SBCTL_YUBIKEY_PIN"); found { + y.Pin = pin + } else { + y.Pin = piv.DefaultPIN + } + } + auth := piv.KeyAuth{PIN: y.Pin} if err := y.connectToYubikey(); err != nil { return nil, err } diff --git a/go.mod b/go.mod index 6b5227c..fd6d570 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.24.0 require ( github.com/fatih/color v1.17.0 github.com/foxboron/go-tpm-keyfiles v0.0.0-20240725205618-b7c5a84edf9d - github.com/foxboron/go-uefi v0.0.0-20251010190908-d29549a44f29 - github.com/go-piv/piv-go/v2 v2.3.0 + github.com/foxboron/go-uefi v0.0.0-20250207204325-69fb7dba244f + github.com/go-piv/piv-go/v2 v2.4.0 github.com/goccy/go-yaml v1.11.3 github.com/google/go-attestation v0.5.1 github.com/google/go-tpm v0.9.1 @@ -18,6 +18,7 @@ require ( github.com/spf13/cobra v1.8.1 golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 golang.org/x/sys v0.36.0 + golang.org/x/term v0.35.0 ) require ( diff --git a/go.sum b/go.sum index c8146e0..e2ba4b5 100644 --- a/go.sum +++ b/go.sum @@ -243,8 +243,8 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-piv/piv-go/v2 v2.3.0 h1:kKkrYlgLQTMPA6BiSL25A7/x4CEh2YCG7rtb/aTkx+g= -github.com/go-piv/piv-go/v2 v2.3.0/go.mod h1:ShZi74nnrWNQEdWzRUd/3cSig3uNOcEZp+EWl0oewnI= +github.com/go-piv/piv-go/v2 v2.4.0 h1:xamQ/fR4MJiw/Ndbk6yi7MVwhjrwlnDAPuaH9zcGb+I= +github.com/go-piv/piv-go/v2 v2.4.0/go.mod h1:ShZi74nnrWNQEdWzRUd/3cSig3uNOcEZp+EWl0oewnI= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= @@ -1077,6 +1077,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/keys.go b/keys.go index a857b5b..336b94c 100644 --- a/keys.go +++ b/keys.go @@ -139,14 +139,23 @@ var SecureBootKeys = []struct { // Check if we have already intialized keys in the given output directory func CheckIfKeysInitialized(vfs afero.Fs, output string) bool { for _, key := range SecureBootKeys { - path := filepath.Join(output, key.Key) - if _, err := vfs.Stat(path); errors.Is(err, os.ErrNotExist) { + found := CheckIfKeyFileExists(vfs, output, key.Key) + if !found { return false } } return true } +// Check if a specific key hierarchy key file exists +func CheckIfKeyFileExists(vfs afero.Fs, output string, key string) bool { + path := filepath.Join(output, key+".key") + if _, err := vfs.Stat(path); errors.Is(err, os.ErrNotExist) { + return false + } + return true +} + func GetEnrolledVendorCerts() []string { db, err := efi.Getdb() if err != nil {