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 26f592678..888596138 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -70,7 +70,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 b473435b0..a72ba9164 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2594,6 +2594,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