From 56d392793a4969e80f71491f82f18c4702014edd Mon Sep 17 00:00:00 2001 From: Phil Porada Date: Mon, 7 Oct 2024 14:56:14 -0400 Subject: [PATCH] Allow block-a-key to process private key files (#7737) The CAB/F Debian weak keys (https://github.com/cabforum/Debian-weak-keys) repository contains a bunch of DER encoded private keys that we should ensure are blocked. I hacked up the block-a-key tool to output a base64 encoded SPKI hash from an arbitrary PEM formatted private key file. --- test/block-a-key/main.go | 33 +++++++++++++++++++++++++-------- test/block-a-key/main_test.go | 25 +++++++++++++++++++------ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/test/block-a-key/main.go b/test/block-a-key/main.go index 0d027712a..da12fcf06 100644 --- a/test/block-a-key/main.go +++ b/test/block-a-key/main.go @@ -10,6 +10,7 @@ import ( "os" "github.com/letsencrypt/boulder/core" + "github.com/letsencrypt/boulder/privatekey" "github.com/letsencrypt/boulder/web" ) @@ -29,8 +30,9 @@ installation: go install github.com/letsencrypt/boulder/test/block-a-key/... usage: - block-a-key -cert + block-a-key -cert block-a-key -jwk + block-a-key -privateKey output format: # @@ -41,10 +43,21 @@ examples: ./test/block-a-key/test/test.ecdsa.jwk.json cuwGhNNI6nfob5aqY90e7BleU6l7rfxku4X3UTJ3Z7M= $> block-a-key -cert ./test/block-a-key/test/test.rsa.cert.pem ./test/block-a-key/test/test.rsa.cert.pem Qebc1V3SkX3izkYRGNJilm9Bcuvf0oox4U2Rn+b4JOE= + $> block-a-key -privateKey private.key ` -// keyFromCert returns the public key from a PEM encoded certificate located in -// pemFile or returns an error. +// keyFromPrivateKeyFile returns the public key from a PEM formatted private key +// located in pemFile or returns an error. +func keyFromPrivateKeyFile(pemFile string) (crypto.PublicKey, error) { + _, pubKey, err := privatekey.Load(pemFile) + if err != nil { + return nil, err + } + return pubKey, nil +} + +// keyFromCert returns the public key from a PEM formatted certificate located +// in pemFile or returns an error. func keyFromCert(pemFile string) (crypto.PublicKey, error) { c, err := core.LoadCert(pemFile) if err != nil { @@ -64,8 +77,9 @@ func keyFromJWK(jsonFile string) (crypto.PublicKey, error) { } func main() { - certFileArg := flag.String("cert", "", "path to a PEM encoded X509 certificate file") + certFileArg := flag.String("cert", "", "path to a PEM formatted X509 certificate file") jwkFileArg := flag.String("jwk", "", "path to a JSON encoded JWK file") + privKeyFileArg := flag.String("privateKey", "", "path to a PEM formatted private key file") flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n\n", usageHelp) @@ -75,12 +89,12 @@ func main() { flag.Parse() - if *certFileArg == "" && *jwkFileArg == "" { - log.Fatalf("error: a -cert or -jwk argument must be provided") + if *certFileArg == "" && *jwkFileArg == "" && *privKeyFileArg == "" { + log.Fatalf("error: a -cert, -jwk, or -privateKey argument must be provided") } - if *certFileArg != "" && *jwkFileArg != "" { - log.Fatalf("error: -cert and -jwk arguments are mutually exclusive") + if *certFileArg != "" && *jwkFileArg != "" && *privKeyFileArg != "" { + log.Fatalf("error: -cert, -jwk, and -privateKey arguments are mutually exclusive") } var file string @@ -93,6 +107,9 @@ func main() { } else if *jwkFileArg != "" { file = *jwkFileArg key, err = keyFromJWK(file) + } else if *privKeyFileArg != "" { + file = *privKeyFileArg + key, err = keyFromPrivateKeyFile(file) } else { err = errors.New("unexpected command line state") } diff --git a/test/block-a-key/main_test.go b/test/block-a-key/main_test.go index 6dbe265e0..7160bfda6 100644 --- a/test/block-a-key/main_test.go +++ b/test/block-a-key/main_test.go @@ -10,10 +10,11 @@ import ( func TestKeyBlocking(t *testing.T) { testCases := []struct { - name string - certPath string - jwkPath string - expected string + name string + certPath string + jwkPath string + privKeyPath string + expected string }{ // NOTE(@cpu): The JWKs and certificates were generated with the same // keypair within an algorithm/parameter family. E.g. the RSA JWK public key @@ -39,16 +40,28 @@ func TestKeyBlocking(t *testing.T) { certPath: "test/test.rsa.cert.pem", expected: "Qebc1V3SkX3izkYRGNJilm9Bcuvf0oox4U2Rn+b4JOE=", }, + { + name: "P-256 ECDSA Private Key", + privKeyPath: "../hierarchy/ee-e1.key.pem", + expected: "ysCgov5oH7fsFs+ry0ODIx7runcINcS8V/0a0NWNQSY=", + }, + { + name: "2048 RSA Private Key", + privKeyPath: "../hierarchy/ee-r4.key.pem", + expected: "ClG5+g8ypi7kMF6mxMT+gszQbwLjPsIv9mHNVjOv4FU=", + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var key crypto.PublicKey var err error - if tc.jwkPath != "" { + if tc.certPath != "" { + key, err = keyFromCert(tc.certPath) + } else if tc.jwkPath != "" { key, err = keyFromJWK(tc.jwkPath) } else { - key, err = keyFromCert(tc.certPath) + key, err = keyFromPrivateKeyFile(tc.privKeyPath) } test.AssertNotError(t, err, "error getting key from input file") spkiHash, err := core.KeyDigestB64(key)