diff --git a/.github/scripts/init-temp-keys.sh b/.github/scripts/init-temp-keys.sh index 69cdda833..014a57dd7 100755 --- a/.github/scripts/init-temp-keys.sh +++ b/.github/scripts/init-temp-keys.sh @@ -64,9 +64,10 @@ if [ "$opt_hsm" = true ]; then pkcs11-tool --module "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH}" --login --show-info --list-objects --pin "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN}" fi -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 if [ "$opt_hsm" = true ]; then pkcs11-tool --module "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH}" --login --pin "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN}" --write-object kas-private.pem --type privkey --label "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_KEYS_RSA_LABEL}" diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 68074a98a..06dfc16c6 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -208,7 +208,7 @@ jobs: echo "log.level = DEBUG" >> softhsm2.conf echo "SOFTHSM2_CONF=$(pwd)/softhsm2.conf" >> "$GITHUB_ENV" - if: matrix.directory == 'service' - run: .github/scripts/init-temp-keys.sh --hsm + 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 d6bc2122f..72ab24739 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2436,6 +2436,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..c8dfb5e24 --- /dev/null +++ b/service/cmd/keys.go @@ -0,0 +1,126 @@ +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) { + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) //nolint:mnd // random 16 byte serial number + 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 719c1db22..72e55dcc6 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 softhsm opensc openssl -RUN /scripts/hsm-init-temporary-keys.sh +RUN /app/opentdf keys init RUN make test