2020-05-03 12:41:09 -05:00
|
|
|
package sbctl
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509/pkix"
|
|
|
|
"encoding/pem"
|
2021-05-18 17:56:08 -05:00
|
|
|
"errors"
|
2020-05-03 12:41:09 -05:00
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2021-04-16 15:13:55 -05:00
|
|
|
"time"
|
2020-06-06 17:00:45 -05:00
|
|
|
|
2021-05-31 17:52:50 -05:00
|
|
|
"github.com/foxboron/go-uefi/efi"
|
2021-06-02 14:38:23 -05:00
|
|
|
"github.com/foxboron/go-uefi/efi/pecoff"
|
|
|
|
"github.com/foxboron/go-uefi/efi/pkcs7"
|
2021-05-31 17:52:50 -05:00
|
|
|
"github.com/foxboron/go-uefi/efi/signature"
|
|
|
|
"github.com/foxboron/go-uefi/efi/util"
|
2020-10-28 18:00:50 -05:00
|
|
|
"golang.org/x/sys/unix"
|
2020-05-03 12:41:09 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
var RSAKeySize = 4096
|
|
|
|
|
|
|
|
var (
|
|
|
|
DatabasePath = "/usr/share/secureboot/"
|
|
|
|
KeysPath = filepath.Join(DatabasePath, "keys")
|
|
|
|
PKKey = filepath.Join(KeysPath, "PK", "PK.key")
|
|
|
|
PKCert = filepath.Join(KeysPath, "PK", "PK.pem")
|
2020-06-11 17:13:38 -05:00
|
|
|
KEKKey = filepath.Join(KeysPath, "KEK", "KEK.key")
|
|
|
|
KEKCert = filepath.Join(KeysPath, "KEK", "KEK.pem")
|
2020-05-03 12:41:09 -05:00
|
|
|
DBKey = filepath.Join(KeysPath, "db", "db.key")
|
|
|
|
DBCert = filepath.Join(KeysPath, "db", "db.pem")
|
2020-10-28 18:00:50 -05:00
|
|
|
|
|
|
|
DBPath = filepath.Join(DatabasePath, "files.db")
|
2021-05-18 12:58:02 -05:00
|
|
|
|
|
|
|
GUIDPath = filepath.Join(DatabasePath, "GUID")
|
2020-05-03 12:41:09 -05:00
|
|
|
)
|
|
|
|
|
2021-05-19 16:56:58 -05:00
|
|
|
// Check if we can access the db certificate to verify files
|
|
|
|
func CanVerifyFiles() error {
|
|
|
|
if err := unix.Access(DBCert, unix.R_OK); err != nil {
|
|
|
|
return fmt.Errorf("couldn't access %s: %w", DBCert, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-05-31 17:52:50 -05:00
|
|
|
func CreateKey(name string) ([]byte, []byte, error) {
|
2020-05-03 12:41:09 -05:00
|
|
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
2021-05-19 18:08:45 -05:00
|
|
|
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
|
2020-05-03 12:41:09 -05:00
|
|
|
c := x509.Certificate{
|
|
|
|
SerialNumber: serialNumber,
|
|
|
|
PublicKeyAlgorithm: x509.RSA,
|
|
|
|
SignatureAlgorithm: x509.SHA256WithRSA,
|
2021-04-16 15:13:55 -05:00
|
|
|
NotBefore: time.Now(),
|
|
|
|
NotAfter: time.Now().AddDate(5, 0, 0),
|
|
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
2020-05-03 12:41:09 -05:00
|
|
|
Subject: pkix.Name{
|
2021-04-16 15:13:55 -05:00
|
|
|
Country: []string{name},
|
|
|
|
CommonName: name,
|
2020-05-03 12:41:09 -05:00
|
|
|
},
|
|
|
|
}
|
|
|
|
priv, err := rsa.GenerateKey(rand.Reader, RSAKeySize)
|
|
|
|
if err != nil {
|
2021-05-31 17:52:50 -05:00
|
|
|
return nil, nil, err
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
2021-05-31 17:52:50 -05:00
|
|
|
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
2020-05-03 12:41:09 -05:00
|
|
|
if err != nil {
|
2021-05-31 17:52:50 -05:00
|
|
|
return nil, nil, fmt.Errorf("unable to marshal private key: %v", err)
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
2021-05-31 17:52:50 -05:00
|
|
|
keyOut := new(bytes.Buffer)
|
|
|
|
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyBytes}); err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("failed to write data to key: %v", err)
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
2021-05-31 17:52:50 -05:00
|
|
|
derBytes, err := x509.CreateCertificate(rand.Reader, &c, &c, &priv.PublicKey, priv)
|
2020-05-03 12:41:09 -05:00
|
|
|
if err != nil {
|
2021-05-31 17:52:50 -05:00
|
|
|
return nil, nil, err
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
2021-05-31 17:52:50 -05:00
|
|
|
certOut := new(bytes.Buffer)
|
|
|
|
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("failed to write data to certificate: %v", err)
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
2021-05-31 17:52:50 -05:00
|
|
|
return keyOut.Bytes(), certOut.Bytes(), nil
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
|
|
|
|
2021-05-31 17:52:50 -05:00
|
|
|
func SaveKey(k []byte, file string) error {
|
|
|
|
os.MkdirAll(filepath.Dir(file), os.ModePerm)
|
2021-07-14 16:30:38 -05:00
|
|
|
err := os.WriteFile(file, k, 0400)
|
2020-05-03 12:41:09 -05:00
|
|
|
if err != nil {
|
2021-05-19 18:08:45 -05:00
|
|
|
return err
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
2021-05-19 18:08:45 -05:00
|
|
|
return nil
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
|
|
|
|
2021-05-31 18:00:23 -05:00
|
|
|
func Enroll(uuid util.EFIGUID, cert, signerKey, signerPem []byte, efivar string) error {
|
|
|
|
c := signature.NewSignatureList(signature.CERT_X509_GUID)
|
|
|
|
c.AppendBytes(uuid, cert)
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
signature.WriteSignatureList(buf, *c)
|
2021-06-11 18:45:54 -05:00
|
|
|
key, err := util.ReadKey(signerKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
crt, err := util.ReadCert(signerPem)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
signedBuf, err := efi.SignEFIVariable(key, crt, efivar, buf.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-31 18:00:23 -05:00
|
|
|
return efi.WriteEFIVariable(efivar, signedBuf)
|
|
|
|
}
|
|
|
|
|
|
|
|
func KeySync(guid util.EFIGUID, keydir string) error {
|
|
|
|
PKKey, _ := os.ReadFile(filepath.Join(keydir, "PK", "PK.key"))
|
|
|
|
PKPem, _ := os.ReadFile(filepath.Join(keydir, "PK", "PK.pem"))
|
|
|
|
KEKKey, _ := os.ReadFile(filepath.Join(keydir, "KEK", "KEK.key"))
|
|
|
|
KEKPem, _ := os.ReadFile(filepath.Join(keydir, "KEK", "KEK.pem"))
|
|
|
|
dbPem, _ := os.ReadFile(filepath.Join(keydir, "db", "db.pem"))
|
|
|
|
if err := Enroll(guid, dbPem, KEKKey, KEKPem, "db"); err != nil {
|
|
|
|
return err
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
2021-05-31 18:00:23 -05:00
|
|
|
if err := Enroll(guid, KEKPem, PKKey, PKPem, "KEK"); err != nil {
|
|
|
|
return err
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
2021-05-31 18:00:23 -05:00
|
|
|
if err := Enroll(guid, PKPem, PKKey, PKPem, "PK"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
|
|
|
|
2021-05-17 18:47:29 -05:00
|
|
|
func VerifyFile(cert, file string) (bool, error) {
|
|
|
|
if err := unix.Access(cert, unix.R_OK); err != nil {
|
|
|
|
return false, fmt.Errorf("couldn't access %s: %w", cert, err)
|
|
|
|
}
|
|
|
|
|
2021-06-02 14:38:23 -05:00
|
|
|
peFile, err := os.ReadFile(file)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2021-06-11 18:45:54 -05:00
|
|
|
x509Cert, err := util.ReadCertFromFile(cert)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2021-06-02 14:38:23 -05:00
|
|
|
sigs, err := pecoff.GetSignatures(peFile)
|
|
|
|
if err != nil {
|
2021-09-05 07:42:31 -05:00
|
|
|
return false, fmt.Errorf("%s: %w", file, err)
|
2021-06-02 14:38:23 -05:00
|
|
|
}
|
|
|
|
if len(sigs) == 0 {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
for _, signature := range sigs {
|
|
|
|
ok, err := pkcs7.VerifySignature(x509Cert, signature.Certificate)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
if ok {
|
|
|
|
return true, nil
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
|
|
|
}
|
2021-06-02 14:38:23 -05:00
|
|
|
// If we come this far we haven't found a signature that matches the cert
|
|
|
|
return false, nil
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
|
|
|
|
2021-05-18 17:56:08 -05:00
|
|
|
var ErrAlreadySigned = errors.New("already signed file")
|
|
|
|
|
2020-07-09 22:38:43 -05:00
|
|
|
func SignFile(key, cert, file, output, checksum string) error {
|
2020-07-09 09:19:05 -05:00
|
|
|
|
|
|
|
// Check file exists before we do anything
|
2021-05-18 17:56:08 -05:00
|
|
|
if _, err := os.Stat(file); errors.Is(err, os.ErrNotExist) {
|
2021-05-18 16:34:59 -05:00
|
|
|
return fmt.Errorf("%s does not exist", file)
|
2020-07-09 09:19:05 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 21:33:03 -05:00
|
|
|
// Let's check if we have signed it already AND the original file hasn't changed
|
2021-05-17 18:47:29 -05:00
|
|
|
ok, err := VerifyFile(cert, output)
|
2021-06-28 05:34:19 -05:00
|
|
|
if errors.Is(err, os.ErrNotExist) && (file != output) {
|
|
|
|
// if the file does not exist and file is not the same as output
|
|
|
|
// then we just catch the error and continue. This is expected
|
|
|
|
// behaviour
|
|
|
|
} else if err != nil {
|
2021-05-17 18:47:29 -05:00
|
|
|
return err
|
|
|
|
}
|
2021-06-28 05:34:19 -05:00
|
|
|
|
2021-05-19 18:08:45 -05:00
|
|
|
chk, err := ChecksumFile(file)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if ok && chk == checksum {
|
2021-05-18 17:56:08 -05:00
|
|
|
return ErrAlreadySigned
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
2020-07-09 09:07:53 -05:00
|
|
|
|
2020-10-28 18:00:50 -05:00
|
|
|
// Let's also check if we can access the key
|
|
|
|
if err := unix.Access(key, unix.R_OK); err != nil {
|
2021-05-18 17:56:08 -05:00
|
|
|
return fmt.Errorf("couldn't access %s: %w", key, err)
|
2020-10-28 18:00:50 -05:00
|
|
|
}
|
|
|
|
|
2021-06-02 14:38:23 -05:00
|
|
|
// We want to write the file back with correct permissions
|
|
|
|
si, err := os.Stat(file)
|
2020-05-03 12:41:09 -05:00
|
|
|
if err != nil {
|
2021-05-18 16:34:59 -05:00
|
|
|
return fmt.Errorf("failed signing file: %w", err)
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
2021-06-02 14:38:23 -05:00
|
|
|
|
|
|
|
peFile, err := os.ReadFile(file)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-06-11 18:45:54 -05:00
|
|
|
Cert, err := util.ReadCertFromFile(cert)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
Key, err := util.ReadKeyFromFile(key)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-06-02 14:38:23 -05:00
|
|
|
|
|
|
|
ctx := pecoff.PECOFFChecksum(peFile)
|
|
|
|
|
2021-06-11 18:45:54 -05:00
|
|
|
sig, err := pecoff.CreateSignature(ctx, Cert, Key)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-06-02 14:38:23 -05:00
|
|
|
|
2021-06-11 18:45:54 -05:00
|
|
|
b, err := pecoff.AppendToBinary(ctx, sig)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-06-28 05:34:19 -05:00
|
|
|
if err = os.WriteFile(output, b, si.Mode()); err != nil {
|
2021-06-02 14:38:23 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-09 22:38:43 -05:00
|
|
|
return nil
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
|
|
|
|
2021-05-31 17:52:50 -05:00
|
|
|
// Map up our default keys in a struct
|
2020-05-03 12:41:09 -05:00
|
|
|
var SecureBootKeys = []struct {
|
|
|
|
Key string
|
|
|
|
Description string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
Key: "PK",
|
|
|
|
Description: "Platform Key",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: "KEK",
|
|
|
|
Description: "Key Exchange Key",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: "db",
|
|
|
|
Description: "Database Key",
|
|
|
|
},
|
|
|
|
// Haven't used this yet so WIP
|
|
|
|
// {
|
|
|
|
// Key: "dbx",
|
|
|
|
// Description: "Forbidden Database Key",
|
|
|
|
// SignedWith: "KEK",
|
|
|
|
// },
|
|
|
|
}
|
|
|
|
|
2021-05-29 18:15:24 -05:00
|
|
|
// Check if we have already intialized keys in the given output directory
|
2020-05-03 12:41:09 -05:00
|
|
|
func CheckIfKeysInitialized(output string) bool {
|
|
|
|
for _, key := range SecureBootKeys {
|
|
|
|
path := filepath.Join(output, key.Key)
|
2021-05-31 17:52:50 -05:00
|
|
|
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
|
2020-05-03 12:41:09 -05:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-05-29 18:15:24 -05:00
|
|
|
// Initialize the secure boot keys needed to setup secure boot.
|
|
|
|
// It creates the following keys:
|
|
|
|
// * Platform Key (PK)
|
|
|
|
// * Key Exchange Key (KEK)
|
|
|
|
// * db (database)
|
2021-05-16 18:47:19 -05:00
|
|
|
func InitializeSecureBootKeys(output string) error {
|
2021-05-29 18:15:24 -05:00
|
|
|
if CheckIfKeysInitialized(output) {
|
|
|
|
return nil
|
2021-05-16 18:47:19 -05:00
|
|
|
}
|
2020-05-03 12:41:09 -05:00
|
|
|
for _, key := range SecureBootKeys {
|
2021-05-31 17:52:50 -05:00
|
|
|
keyfile, cert, err := CreateKey(key.Description)
|
2021-05-19 18:08:45 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-31 17:52:50 -05:00
|
|
|
path := filepath.Join(output, key.Key)
|
|
|
|
SaveKey(keyfile, filepath.Join(path, fmt.Sprintf("%s.key", key.Key)))
|
|
|
|
SaveKey(cert, filepath.Join(path, fmt.Sprintf("%s.pem", key.Key)))
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|
2021-05-16 18:47:19 -05:00
|
|
|
return nil
|
2020-05-03 12:41:09 -05:00
|
|
|
}
|