mirror of https://github.com/ericonr/sbctl.git
240 lines
6.4 KiB
Go
240 lines
6.4 KiB
Go
package sbctl
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"math/big"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
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")
|
|
DBKey = filepath.Join(KeysPath, "db", "db.key")
|
|
DBCert = filepath.Join(KeysPath, "db", "db.pem")
|
|
)
|
|
|
|
func CreateKey(path, name string) []byte {
|
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
if err != nil {
|
|
log.Fatalf("Failed to generate serial number: %v", err)
|
|
}
|
|
c := x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
PublicKeyAlgorithm: x509.RSA,
|
|
SignatureAlgorithm: x509.SHA256WithRSA,
|
|
Subject: pkix.Name{
|
|
Country: []string{name},
|
|
},
|
|
}
|
|
priv, err := rsa.GenerateKey(rand.Reader, RSAKeySize)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
derBytes, err := x509.CreateCertificate(rand.Reader, &c, &c, &priv.PublicKey, priv)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create certificate: %v", err)
|
|
}
|
|
keyOut, err := os.OpenFile(fmt.Sprintf("%s.key", path), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
|
if err != nil {
|
|
log.Fatalf("Failed to open key.pem for writing: %v", err)
|
|
}
|
|
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
|
if err != nil {
|
|
log.Fatalf("Unable to marshal private key: %v", err)
|
|
}
|
|
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
|
|
log.Fatalf("Failed to write data to key.pem: %v", err)
|
|
}
|
|
if err := keyOut.Close(); err != nil {
|
|
log.Fatalf("Error closing key.pem: %v", err)
|
|
}
|
|
return derBytes
|
|
}
|
|
|
|
func SaveKey(k []byte, path string) {
|
|
err := ioutil.WriteFile(fmt.Sprintf("%s.der", path), k, 0644)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
certOut, err := os.Create(fmt.Sprintf("%s.pem", path))
|
|
if err != nil {
|
|
log.Fatalf("Failed to open cert.pem for writing: %v", err)
|
|
}
|
|
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: k}); err != nil {
|
|
log.Fatalf("Failed to write data to cert.pem: %v", err)
|
|
}
|
|
if err := certOut.Close(); err != nil {
|
|
log.Fatalf("Error closing cert.pem: %v", err)
|
|
}
|
|
|
|
}
|
|
|
|
func CreateUUID() []byte {
|
|
out, err := exec.Command("uuidgen").Output()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return bytes.TrimSuffix(out, []byte("\n"))
|
|
}
|
|
|
|
func KeyToSiglist(UUID []byte, input string) []byte {
|
|
msg.Printf("Create EFI signature list %s.esl...", input)
|
|
args := fmt.Sprintf("--owner %s --type x509 --output %s.esl %s", UUID, input, input)
|
|
fmt.Println(args)
|
|
out, err := exec.Command("/usr/bin/sbsiglist", strings.Split(args, " ")...).Output()
|
|
if err != nil {
|
|
log.Fatalf("Failed creating signature list: %s", err)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func SignEFIVariable(key, cert, varname, vardatafile, output string) []byte {
|
|
msg.Printf("Signing %s with %s...", vardatafile, key)
|
|
args := fmt.Sprintf("--key %s --cert %s --output %s %s %s", key, cert, output, varname, vardatafile)
|
|
fmt.Println(args)
|
|
out, err := exec.Command("/usr/bin/sbvarsign", strings.Split(args, " ")...).Output()
|
|
if err != nil {
|
|
log.Fatalf("Failed signing EFI variable: %s", err)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func SBKeySync(dir string) bool {
|
|
msg.Printf("Syncing %s to EFI variables...", dir)
|
|
args := fmt.Sprintf("--pk --verbose --keystore %s", dir)
|
|
cmd := exec.Command("sbkeysync", strings.Split(args, " ")...)
|
|
var out bytes.Buffer
|
|
cmd.Stdout = &out
|
|
cmd.Stderr = &out
|
|
if err := cmd.Run(); err != nil {
|
|
if exitError, ok := err.(*exec.ExitError); ok {
|
|
return exitError.ExitCode() == 0
|
|
}
|
|
}
|
|
stdout := string(out.Bytes())
|
|
for _, line := range strings.Split(stdout, "\n") {
|
|
if strings.Contains(line, "Operation not permitted") {
|
|
fmt.Println(stdout)
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func VerifyFile(cert, file string) bool {
|
|
args := fmt.Sprintf("--cert %s %s", cert, file)
|
|
cmd := exec.Command("sbverify", strings.Split(args, " ")...)
|
|
if err := cmd.Run(); err != nil {
|
|
if exitError, ok := err.(*exec.ExitError); ok {
|
|
return exitError.ExitCode() == 0
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func SignFile(key, cert, file, output string) []byte {
|
|
// Lets check if we have signed it already...
|
|
if VerifyFile(cert, file) {
|
|
msg.Printf("%s has been signed...", file)
|
|
return []byte{}
|
|
}
|
|
msg.Printf("Signing %s...", file)
|
|
args := fmt.Sprintf("--key %s --cert %s --output %s %s", key, cert, output, file)
|
|
out, err := exec.Command("sbsign", strings.Split(args, " ")...).Output()
|
|
if err != nil {
|
|
log.Fatalf("Failed signing file: %s", err)
|
|
}
|
|
return out
|
|
}
|
|
|
|
var SecureBootKeys = []struct {
|
|
Key string
|
|
Description string
|
|
// Path to the key we sign it with
|
|
SignedWith string
|
|
}{
|
|
{
|
|
Key: "PK",
|
|
Description: "Platform Key",
|
|
SignedWith: "PK",
|
|
},
|
|
{
|
|
Key: "KEK",
|
|
Description: "Key Exchange Key",
|
|
SignedWith: "PK",
|
|
},
|
|
{
|
|
Key: "db",
|
|
Description: "Database Key",
|
|
SignedWith: "KEK",
|
|
},
|
|
// Haven't used this yet so WIP
|
|
// {
|
|
// Key: "dbx",
|
|
// Description: "Forbidden Database Key",
|
|
// SignedWith: "KEK",
|
|
// },
|
|
}
|
|
|
|
func CheckIfKeysInitialized(output string) bool {
|
|
for _, key := range SecureBootKeys {
|
|
path := filepath.Join(output, key.Key)
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func InitializeSecureBootKeys(output string) {
|
|
os.MkdirAll(output, os.ModePerm)
|
|
|
|
var uuid []byte
|
|
guidPath := filepath.Join(output, "GUID")
|
|
if _, err := os.Stat(guidPath); os.IsNotExist(err) {
|
|
uuid = CreateUUID()
|
|
msg2.Printf("Created UUID %s...", uuid)
|
|
err := ioutil.WriteFile(guidPath, uuid, 0600)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
} else {
|
|
uuid, err = ioutil.ReadFile(guidPath)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
msg2.Printf("Using UUID %s...", uuid)
|
|
}
|
|
// Create the directories we need and keys
|
|
for _, key := range SecureBootKeys {
|
|
path := filepath.Join(output, "keys", key.Key)
|
|
os.MkdirAll(path, os.ModePerm)
|
|
keyPath := filepath.Join(path, key.Key)
|
|
pk := CreateKey(keyPath, key.Description)
|
|
SaveKey(pk, keyPath)
|
|
KeyToSiglist(uuid, fmt.Sprintf("%s.der", keyPath))
|
|
// Confusing code
|
|
// TODO: make it cleaner
|
|
signingkeyPath := filepath.Join(output, "keys", key.SignedWith, key.SignedWith)
|
|
signingKey := fmt.Sprintf("%s.key", signingkeyPath)
|
|
signingCertificate := fmt.Sprintf("%s.pem", signingkeyPath)
|
|
SignEFIVariable(signingKey, signingCertificate, key.Key, fmt.Sprintf("%s.der.esl", keyPath), fmt.Sprintf("%s.auth", keyPath))
|
|
}
|
|
}
|