From ee9317a4e97f8445f9a031f4588b9f1e46cbe991 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Mon, 5 Aug 2024 13:25:22 -0400 Subject: [PATCH] chore(core): Adds go version of init-keys This is allows using the service with one fewer script For now, it only implements creation of the two KAS keys, not the keycloak cert, which is more complex and requires running keytool or something similar as the go standard library doesn't seem to include the necessary formats --- .github/scripts/init-temp-keys.sh | 7 +- .github/workflows/checks.yaml | 2 +- go.work.sum | 1 + service/cmd/keys.go | 127 ++++++++++++++++++++++++++++++ ubuntu.Dockerfile | 3 +- 5 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 service/cmd/keys.go diff --git a/.github/scripts/init-temp-keys.sh b/.github/scripts/init-temp-keys.sh index 80a8c82a5..c11709394 100755 --- a/.github/scripts/init-temp-keys.sh +++ b/.github/scripts/init-temp-keys.sh @@ -41,9 +41,10 @@ fi mkdir -p "$opt_output" -openssl req -x509 -nodes -newkey RSA:2048 -subj "/CN=kas" -keyout "$opt_output/kas-private.pem" -out "$opt_output/kas-cert.pem" -days 365 -openssl ecparam -name prime256v1 >ecparams.tmp -openssl req -x509 -nodes -newkey ec:ecparams.tmp -subj "/CN=kas" -keyout "$opt_output/kas-ec-private.pem" -out "$opt_output/kas-ec-cert.pem" -days 365 +if ! go run github.com/opentdf/platform/service keys init -o="$opt_output" $( [ "$opt_verbose" == true ] && printf %s '-v' ); then + echo "[ERROR] keys init failed" + exit 1 +fi mkdir -p keys openssl req -x509 -nodes -newkey RSA:2048 -subj "/CN=ca" -keyout keys/keycloak-ca-private.pem -out keys/keycloak-ca.pem -days 365 diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 9cc721b7b..a9041866c 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -84,7 +84,7 @@ jobs: skip-cache: true args: --out-format=colored-line-number - if: matrix.directory == 'service' - run: .github/scripts/init-temp-keys.sh + run: go run ./service keys init - run: go test ./... -short working-directory: ${{ matrix.directory }} - if: matrix.directory == 'service' diff --git a/go.work.sum b/go.work.sum index da83fd090..e27af5666 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2593,6 +2593,7 @@ oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= oras.land/oras-go/v2 v2.3.1 h1:lUC6q8RkeRReANEERLfH86iwGn55lbSWP20egdFHVec= oras.land/oras-go/v2 v2.3.1/go.mod h1:5AQXVEu1X/FKp1F9DMOb5ZItZBOa0y5dha0yCm4NR9c= +rbazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= diff --git a/service/cmd/keys.go b/service/cmd/keys.go new file mode 100644 index 000000000..c1211ebfa --- /dev/null +++ b/service/cmd/keys.go @@ -0,0 +1,127 @@ +package cmd + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "os" + "time" + + "github.com/spf13/cobra" +) + +var ( + verbose bool + output string +) + +func init() { + keysCmd := cobra.Command{ + Use: "keys", + Short: "Initialize and manage KAS public keys", + } + + initCmd := &cobra.Command{ + Use: "init", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + return keysInit() + }, + } + initCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "verbose logging") + initCmd.Flags().StringVarP(&output, "output", "o", ".", "directory to store new keys to") + keysCmd.AddCommand(initCmd) + + rootCmd.AddCommand(&keysCmd) +} + +func CertTemplate() (*x509.Certificate, error) { + // generate a random serial number (a real cert authority would have some logic behind this) + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) //nolint:mnd // 128 bit uid is sufficiently unique + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, fmt.Errorf("failed to generate serial number [%w]", err) + } + + tmpl := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{CommonName: "kas"}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 30 * 365), //nolint:mnd // About a year to expire + BasicConstraintsValid: true, + } + return &tmpl, nil +} + +func storeKeyPair(priv, pub any, privateFile, publicFile string) error { + privateBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return fmt.Errorf("unable to marshal private key [%w]", err) + } + keyPEM := pem.EncodeToMemory( + &pem.Block{ + Type: "PRIVATE KEY", + Bytes: privateBytes, + }, + ) + if err := os.WriteFile(privateFile, keyPEM, 0o400); err != nil { + return fmt.Errorf("unable to store key [%w]", err) + } + + certTemplate, err := CertTemplate() + if err != nil { + return fmt.Errorf("unable to create cert template [%w]", err) + } + + pubBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, pub, priv) + if err != nil { + return fmt.Errorf("unable to create cert [%w]", err) + } + _, err = x509.ParseCertificate(pubBytes) + if err != nil { + return fmt.Errorf("unable to parse cert [%w]", err) + } + // Encode public key to PKCS#1 ASN.1 PEM. + pubPEM := pem.EncodeToMemory( + &pem.Block{ + Type: "CERTIFICATE", + Bytes: pubBytes, + }, + ) + + if err := os.WriteFile(publicFile, pubPEM, 0o400); err != nil { + return fmt.Errorf("unable to store rsa public key [%w]", err) + } + return nil +} + +func keysInit() error { + // openssl req -x509 -nodes -newkey RSA:2048 + // -subj "/CN=kas" -keyout "$opt_output/kas-private.pem" -out "$opt_output/kas-cert.pem" -days 365 + // Generate RSA key. + keyRSA, err := rsa.GenerateKey(rand.Reader, 2048) //nolint:mnd // 256 byte key + if err != nil { + return fmt.Errorf("unable to generate rsa key [%w]", err) + } + if err := storeKeyPair(keyRSA, keyRSA.Public(), output+"/kas-private.pem", output+"/kas-cert.pem"); err != nil { + return err + } + + // openssl ecparam -name prime256v1 >ecparams.tmp + // openssl req -x509 -nodes -newkey ec:ecparams.tmp -subj "/CN=kas" -keyout "$opt_output/kas-ec-private.pem" -out "$opt_output/kas-ec-cert.pem" -days 365 + keyEC, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return fmt.Errorf("failed to generate ECDSA private key [%w]", err) + } + if err := storeKeyPair(keyEC, keyEC.Public(), output+"/kas-ec-private.pem", output+"/kas-ec-cert.pem"); err != nil { + return err + } + + return nil +} diff --git a/ubuntu.Dockerfile b/ubuntu.Dockerfile index ca59793d9..369a9ea82 100644 --- a/ubuntu.Dockerfile +++ b/ubuntu.Dockerfile @@ -27,8 +27,7 @@ RUN make opentdf FROM builder as tester -RUN apt-get update -y && apt-get install -y opensc openssl -RUN /scripts/init-temp-keys.sh +RUN /app/opentdf keys init RUN make test