Skip to content

Commit

Permalink
1633 port genkey command (#98)
Browse files Browse the repository at this point in the history
* Port genkey command from enki

as part of kairos-io/kairos#1633

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Port genkey e2e test and don't use viper

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Run the genkey tests in CI

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Allow certs with negative serial numbers

Not sure why the asus certs have a negative serial number but they do.
If the asus box had that others might too, so we should better support
it. The alternative would be to generate certs with positive serial
number for the tests.

https://github.com/golang/go/blob/master/src/crypto/x509/parser.go#L1014-L1018
microsoft/mssql-docker#895 (comment)

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Remove focus

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

---------

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
  • Loading branch information
jimmykarily authored Nov 12, 2024
1 parent 2a1a58c commit cb118b3
Show file tree
Hide file tree
Showing 6 changed files with 1,584 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
go-version-file: go.mod
- name: Run e2e tests
run: |
sudo go run github.com/onsi/ginkgo/v2/ginkgo -r -p --fail-fast --timeout=2h --label-filter "build-uki" ./e2e
sudo go run github.com/onsi/ginkgo/v2/ginkgo -r -p --fail-fast --timeout=2h --label-filter "build-uki || genkey" ./e2e
test-bootable:
runs-on: ubuntu-latest
Expand Down
208 changes: 208 additions & 0 deletions e2e/genkey_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package e2e_test

import (
"bytes"
"crypto/x509"
"os"
"os/exec"
"path/filepath"
"strings"
"time"

"github.com/foxboron/go-uefi/efi/signature"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("genkey", Label("genkey", "e2e"), func() {
var resultDir string
var err error
var auroraboot *Auroraboot
var asusKeysDir string

BeforeEach(func() {
resultDir, err = os.MkdirTemp("", "auroraboot-genkey-test-")
Expect(err).ToNot(HaveOccurred())

currentDir, err := os.Getwd()
Expect(err).ToNot(HaveOccurred())
asusKeysDir = filepath.Join(currentDir, "assets", "asus-PN64-vendor-keys")

auroraboot = NewAuroraboot("quay.io/kairos/osbuilder-tools", resultDir, asusKeysDir)
})

AfterEach(func() {
os.RemoveAll(resultDir)
auroraboot.Cleanup()
})

When("expiration-in-days is not specified", func() {
It("builds certificates with expiration in 365 days", func() {
out, err := auroraboot.Run("genkey", "-o", resultDir, "mykey")
Expect(err).ToNot(HaveOccurred(), out)

expectExpirationIn(365, resultDir)
})
})

When("expiration-in-days is specified", func() {
It("builds certificates that expire after the specified days", func() {
out, err := auroraboot.Run("genkey", "-o", resultDir, "-e", "1000", "mykey")
Expect(err).ToNot(HaveOccurred(), out)

expectExpirationIn(1000, resultDir)
})
})

When("skip-microsoft-certs-I-KNOW-WHAT-IM-DOING is set", func() {
It("doesn't bake-in the microsoft certificates", func() {
out, err := auroraboot.Run("genkey",
"--skip-microsoft-certs-I-KNOW-WHAT-IM-DOING",
"-o", resultDir, "mykey")
Expect(err).ToNot(HaveOccurred(), out)

expectAuthToNotContainSigner("Microsoft", filepath.Join(resultDir, "db.auth"))
})
})

When("skip-microsoft-certs-I-KNOW-WHAT-IM-DOING is not set", func() {
It("bakes-in the microsoft certificates", func() {
out, err := auroraboot.Run("genkey",
"-o", resultDir, "mykey")
Expect(err).ToNot(HaveOccurred(), out)

expectAuthToContainSigner("Microsoft", filepath.Join(resultDir, "db.auth"))
})
})

When("custom-cert-dir is used", func() {
It("embeds the custom certs to the .auth files", func() {
out, err := auroraboot.Run("genkey",
"-o", resultDir,
"--custom-cert-dir", asusKeysDir, "mykey")
Expect(err).ToNot(HaveOccurred(), out)

expectAuthToContainSigner("Microsoft", filepath.Join(resultDir, "db.auth"))
expectAuthToContainSigner("ASUS", filepath.Join(resultDir, "db.auth"))
expectAuthToContainSigner("mykey", filepath.Join(resultDir, "db.auth"))

expectAuthToContainSigner("Microsoft", filepath.Join(resultDir, "KEK.auth"))
expectAuthToContainSigner("ASUS", filepath.Join(resultDir, "KEK.auth"))
expectAuthToContainSigner("mykey", filepath.Join(resultDir, "KEK.auth"))
})

It("embeds the custom certs to the .der files", func() {
out, err := auroraboot.Run("genkey",
"-o", resultDir,
"--custom-cert-dir", asusKeysDir, "mykey")
Expect(err).ToNot(HaveOccurred(), out)

issuers := getIssuersFromDER(filepath.Join(resultDir, "db.der"))
Expect(issuers).To(ContainElement(MatchRegexp("Microsoft")))
Expect(issuers).To(ContainElement(MatchRegexp("mykey")))
Expect(issuers).To(ContainElement(MatchRegexp("ASUS")))

issuers = getIssuersFromDER(filepath.Join(resultDir, "KEK.der"))
Expect(issuers).To(ContainElement(MatchRegexp("Microsoft")))
Expect(issuers).To(ContainElement(MatchRegexp("mykey")))
Expect(issuers).To(ContainElement(MatchRegexp("ASUS")))
})

When("the directory does not contain the db file", func() {
It("returns an error", func() {
out, err := auroraboot.Run("genkey",
"-o", resultDir, // random directory without the db file
"--custom-cert-dir", "/tmp", "mykey")
Expect(err).To(HaveOccurred())
Expect(out).To(ContainSubstring("reading custom cert file db: open /tmp/db: no such file or directory"))
})
})
})
})

func expectAuthToContainSigner(owner, authFile string) {
Expect(authIssuers(authFile)).To(ContainElement(MatchRegexp(owner)), "Expected %s to be in %s", owner, authFile)
}

func expectAuthToNotContainSigner(owner, authFile string) {
Expect(authIssuers(authFile)).ToNot(ContainElement(MatchRegexp(owner)), "Expected %s to not be in %s", owner, authFile)
}

func authIssuers(authFile string) []string {
b, err := os.ReadFile(authFile)
Expect(err).ToNot(HaveOccurred())

f := bytes.NewReader(b)
_, err = signature.ReadEFIVariableAuthencation2(f)
Expect(err).ToNot(HaveOccurred())
siglist, err := signature.ReadSignatureDatabase(f)
Expect(err).ToNot(HaveOccurred())

issuers := []string{}
for _, sig := range siglist {
for _, sigEntry := range sig.Signatures {
if sig.SignatureType == signature.CERT_X509_GUID {
cert, _ := x509.ParseCertificate(sigEntry.Data)
if cert != nil {
issuers = append(issuers, cert.Issuer.String())
}
}
}
}

return issuers
}

func expectDerToContainIssuer(issuer, derFile string) {
cmd := exec.Command("openssl", "x509", "-in", derFile, "-noout", "-inform", "der", "-text")

out, err := cmd.CombinedOutput()
Expect(err).ToNot(HaveOccurred(), string(out))

Expect(string(out)).To(ContainSubstring(issuer))
}

// getDateFromString accepts a date in the form: "Feb 6 15:53:30 2025 GMT"
// and returns the day, month and year as integers
func getDateFromString(dateString string) (int, int, int) {
// Define the layout matching the format of the string
layout := "Jan 2 15:04:05 2006 MST"
dateTime, err := time.Parse(layout, dateString)
Expect(err).ToNot(HaveOccurred())

return dateTime.Day(), int(dateTime.Month()), dateTime.Year()
}

func expectExpirationIn(n int, resultDir string) {
By("checking the expiration")
cmd := exec.Command("openssl", "x509", "-enddate", "-noout",
"-in", filepath.Join(resultDir, "db.pem"))
o, err := cmd.CombinedOutput()
Expect(err).ToNot(HaveOccurred(), o)

dateStr := strings.TrimSpace(strings.TrimPrefix(string(o), "notAfter="))
certDay, certMonth, certYear := getDateFromString(dateStr)

expectedTime := time.Now().Add(time.Duration(n) * 24 * time.Hour)
Expect(certDay).To(Equal(expectedTime.Day()))
Expect(certMonth).To(Equal(int(expectedTime.Month())))
Expect(certYear).To(Equal(expectedTime.Year()))
}

func getIssuersFromDER(filePath string) []string {
// Open the DER file
fileData, err := os.ReadFile(filePath)
Expect(err).ToNot(HaveOccurred())

var issuers []string

certs, err := x509.ParseCertificates(fileData)
Expect(err).ToNot(HaveOccurred())

for _, cert := range certs {
// Append the issuer to the list
issuers = append(issuers, cert.Issuer.String())
}

return issuers
}
11 changes: 9 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
module github.com/kairos-io/AuroraBoot

go 1.23.1
go 1.23.3

toolchain go1.23.3
// https://github.com/golang/go/blob/583d750fa119d504686c737be6a898994b674b69/src/crypto/x509/parser.go#L1014-L1018
// For keys with negative serial number:
godebug x509negativeserial=1

require (
github.com/cavaliergopher/grab/v3 v3.0.1
github.com/distribution/reference v0.6.0
github.com/foxboron/go-uefi v0.0.0-20241017190036-fab4fdf2f2f3
github.com/foxboron/sbctl v0.0.0-20240526163235-64e649b31c8e
github.com/google/uuid v1.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/kairos-io/enki v0.2.2
Expand Down Expand Up @@ -73,6 +76,7 @@ require (
github.com/edsrzf/mmap-go v1.2.0 // indirect
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.14.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect
Expand All @@ -88,9 +92,12 @@ require (
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/certificate-transparency-go v1.1.2 // indirect
github.com/google/go-attestation v0.5.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-containerregistry v0.20.2 // indirect
github.com/google/go-tpm v0.9.1 // indirect
github.com/google/go-tspi v0.3.0 // indirect
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gookit/color v1.5.4 // indirect
Expand Down
Loading

0 comments on commit cb118b3

Please sign in to comment.