sbctl/keys.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))
}
}