From edb6df640171865cad7b76f24c2f61e1b84e5d6d Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Tue, 18 Nov 2025 16:07:20 -0800 Subject: [PATCH] Replace ct_server with TesseraCT in setup In the setup scaffolding workflow, update Fulcio to use the Static CT log TesseraCT instead of the Trillian-based ct_server. The createtree job is obsolete, so it is removed. TesseraCT does not use a config file, all parameters are passed in via command line, so remove the CT config map and the config field from the secret. TesseraCT does not support encrypting the private key, so remove support for supplying a password. Signed-off-by: Colleen Murphy --- .github/workflows/add-remove-new-fulcio.yaml | 6 +- cmd/ctlog/createctconfig/main.go | 134 +++-------- cmd/ctlog/managectroots/main.go | 47 +--- cmd/ctlog/verifyfulcio/main.go | 3 +- config/ctlog/certs/300-createconfig.yaml | 1 - config/ctlog/createtree/100-namespace.yaml | 5 - config/ctlog/createtree/101-binding.yaml | 25 -- config/ctlog/createtree/101-configmap.yaml | 13 - .../ctlog/createtree/101-service-account.yaml | 6 - config/ctlog/createtree/300-createtree.yaml | 27 --- config/ctlog/createtree/placeholder.go | 15 -- config/ctlog/ctlog/300-ctlog.yaml | 14 +- config/fulcio/fulcio/300-fulcio.yaml | 4 +- pkg/ctlog/config.go | 227 +++++------------- pkg/ctlog/config_test.go | 167 ++----------- .../config/add-new-fulcio/300-add-fulcio.yaml | 2 - .../config/new-fulcio/fulcio/300-fulcio.yaml | 2 +- .../remove-old-fulcio/300-remove-fulcio.yaml | 2 - 18 files changed, 131 insertions(+), 569 deletions(-) delete mode 100644 config/ctlog/createtree/100-namespace.yaml delete mode 100644 config/ctlog/createtree/101-binding.yaml delete mode 100644 config/ctlog/createtree/101-configmap.yaml delete mode 100644 config/ctlog/createtree/101-service-account.yaml delete mode 100644 config/ctlog/createtree/300-createtree.yaml delete mode 100644 config/ctlog/createtree/placeholder.go diff --git a/.github/workflows/add-remove-new-fulcio.yaml b/.github/workflows/add-remove-new-fulcio.yaml index 963908d86..32a5e2bc5 100644 --- a/.github/workflows/add-remove-new-fulcio.yaml +++ b/.github/workflows/add-remove-new-fulcio.yaml @@ -197,7 +197,7 @@ jobs: - name: Dump the trusted certs run: | - curl ${CTLOG_URL}/sigstorescaffolding/ct/v1/get-roots | jq .certificates + curl ${CTLOG_URL}/ct/v1/get-roots | jq .certificates env: CTLOG_URL: ${{ env.CTLOG_URL }} @@ -205,7 +205,6 @@ jobs: run: | go run ./cmd/ctlog/verifyfulcio/main.go \ --ctlog-url ${CTLOG_URL} \ - --log-prefix sigstorescaffolding \ --fulcio ${FULCIO_URL} \ --fulcio ${NEW_FULCIO_URL} env: @@ -226,7 +225,7 @@ jobs: - name: Dump the trusted certs run: | - curl ${CTLOG_URL}/sigstorescaffolding/ct/v1/get-roots | jq .certificates + curl ${CTLOG_URL}/ct/v1/get-roots | jq .certificates env: CTLOG_URL: ${{ env.CTLOG_URL }} @@ -234,7 +233,6 @@ jobs: run: | go run ./cmd/ctlog/verifyfulcio/main.go \ --ctlog-url ${CTLOG_URL} \ - --log-prefix sigstorescaffolding \ --fulcio ${NEW_FULCIO_URL} env: CTLOG_URL: ${{ env.CTLOG_URL }} diff --git a/cmd/ctlog/createctconfig/main.go b/cmd/ctlog/createctconfig/main.go index 0b04379ce..2e8c116af 100644 --- a/cmd/ctlog/createctconfig/main.go +++ b/cmd/ctlog/createctconfig/main.go @@ -24,10 +24,8 @@ import ( "errors" "flag" "fmt" - "log" "net/url" "os" - "strconv" fulcioclient "github.com/sigstore/fulcio/pkg/api" "github.com/sigstore/scaffolding/pkg/ctlog" @@ -44,26 +42,18 @@ import ( ) const ( - // Key in the configmap holding the value of the tree. - treeKey = "treeID" - configKey = "config" privateKey = "private" publicKey = "public" bitSize = 4096 ) var ( - cmname = flag.String("configmap", "ctlog-config", "Name of the configmap where the treeID lives") - privateKeySecret = flag.String("private-secret", "", "If there's an existing private key that should be used, read it from this secret, decrypt with the key-password and use it instead of creating a new one.") - secretName = flag.String("secret", "ctlog-secrets", "Name of the secret to create for the keyfiles") - pubKeySecretName = flag.String("pubkeysecret", "ctlog-public-key", "Name of the secret to create containing only the public key") - ctlogPrefix = flag.String("log-prefix", "sigstorescaffolding", "Prefix to append to the url. This is basically the name of the log.") - fulcioURL = flag.String("fulcio-url", "http://fulcio.fulcio-system.svc", "Where to fetch the fulcio Root CA from") - trillianServerAddr = flag.String("trillian-server", "log-server.trillian-system.svc:80", "Address of the gRPC Trillian Admin Server (host:port)") - // TODO: Support ed25519 - keyType = flag.String("keytype", "ecdsa", "Which private key to generate [rsa,ecdsa]") - curveType = flag.String("curvetype", "p256", "Curve type to use [p256, p384,p521]") - keyPassword = flag.String("key-password", "test", "Password for encrypting the PEM key") + privateKeySecret = flag.String("private-secret", "", "If there's an existing private key that should be used, read it from this secret.") + secretName = flag.String("secret", "ctlog-secrets", "Name of the secret to create for the keyfiles") + pubKeySecretName = flag.String("pubkeysecret", "ctlog-public-key", "Name of the secret to create containing only the public key") + fulcioURL = flag.String("fulcio-url", "http://fulcio.fulcio-system.svc", "Where to fetch the fulcio Root CA from") + keyType = flag.String("keytype", "ecdsa", "Which private key to generate [rsa,ecdsa]") + curveType = flag.String("curvetype", "p256", "Curve type to use [p256, p384,p521]") // Supported elliptic curve functions. supportedCurves = map[string]elliptic.Curve{ @@ -100,25 +90,6 @@ func main() { if err != nil { logging.FromContext(ctx).Panicf("Failed to get clientset: %v", err) } - cm, err := clientset.CoreV1().ConfigMaps(ns).Get(ctx, *cmname, metav1.GetOptions{}) - if err != nil { - logging.FromContext(ctx).Panicf("Failed to get the configmap %s/%s: %v", ns, *cmname, err) - } - - if cm.Data == nil { - cm.Data = make(map[string]string) - } - treeID, ok := cm.Data[treeKey] - if !ok { - logging.FromContext(ctx).Errorf("No treeid yet, bailing") - os.Exit(-1) - } - - logging.FromContext(ctx).Infof("Found treeid: %s", treeID) - treeIDInt, err := strconv.ParseInt(treeID, 10, 64) - if err != nil { - logging.FromContext(ctx).Panicf("Invalid TreeID %s : %v", treeID, err) - } // Fetch the fulcio Root CA u, err := url.Parse(*fulcioURL) @@ -131,13 +102,6 @@ func main() { logging.FromContext(ctx).Panicf("Failed to fetch fulcio Root cert: %w", err) } - // See if there's an existing configuration already in the ConfigMap - var existingCMConfig []byte - if cm.BinaryData != nil && cm.BinaryData[configKey] != nil { - logging.FromContext(ctx).Infof("Found existing ctlog config in ConfigMap") - existingCMConfig = cm.BinaryData[configKey] - } - // See if there's existing secret with the keys we want nsSecret := clientset.CoreV1().Secrets(ns) existingSecret, err := nsSecret.Get(ctx, *secretName, metav1.GetOptions{}) @@ -145,83 +109,43 @@ func main() { logging.FromContext(ctx).Fatalf("Failed to get secret %s/%s: %v", ns, *secretName, err) } - // If any of the private, public or config either from secret or configmap - // is not there, create a new configuration. - if existingSecret.Data[privateKey] == nil || - existingSecret.Data[publicKey] == nil || - (existingSecret.Data[configKey] == nil && existingCMConfig == nil) { - var ctlogConfig *ctlog.Config - var err error - if *privateKeySecret != "" { - // We have an existing private key, use it instead of creating - // a new one. - ctlogConfig, err = createConfigFromExistingSecret(ctx, nsSecret, *privateKeySecret) - } else { - // Create a fresh private key. - ctlogConfig, err = createConfigWithKeys(ctx, *keyType) - } - if err != nil { - logging.FromContext(ctx).Fatalf("Failed to generate keys: %v", err) - } - ctlogConfig.PrivKeyPassword = *keyPassword - ctlogConfig.LogID = treeIDInt - ctlogConfig.LogPrefix = *ctlogPrefix - ctlogConfig.TrillianServerAddr = *trillianServerAddr - if err = ctlogConfig.AddFulcioRoot(ctx, root.ChainPEM); err != nil { - logging.FromContext(ctx).Infof("Failed to add fulcio root: %v", err) - } - configMap, err := ctlogConfig.MarshalConfig(ctx) - if err != nil { - logging.FromContext(ctx).Fatalf("Failed to marshal ctlog config: %v", err) - } - - if err := secret.ReconcileSecret(ctx, *secretName, ns, configMap, nsSecret); err != nil { - logging.FromContext(ctx).Fatalf("Failed to reconcile secret: %v", err) - } - - pubData := map[string][]byte{publicKey: configMap[publicKey]} - if err := secret.ReconcileSecret(ctx, *pubKeySecretName, ns, pubData, nsSecret); err != nil { - logging.FromContext(ctx).Panicf("Failed to reconcile public key secret %s/%s: %v", ns, *secretName, err) - } - - logging.FromContext(ctx).Infof("Created CTLog configuration") + // If either the private or public key from secret is not there, create a new configuration. + if existingSecret.Data[privateKey] != nil && + existingSecret.Data[publicKey] != nil { + logging.FromContext(ctx).Infof("Public and private key already exist") os.Exit(0) } - // Prefer the secret config if it exists, but if it doesn't use - // configmap for backwards compatibility / migration. - if existingSecret.Data[configKey] != nil { - logging.FromContext(ctx).Infof("Found existing config in the secret, using that %s/%s", ns, *secretName) + var ctlogConfig *ctlog.Config + if *privateKeySecret != "" { + // We have an existing private key, use it instead of creating + // a new one. + ctlogConfig, err = createConfigFromExistingSecret(ctx, nsSecret, *privateKeySecret) } else { - existingSecret.Data[configKey] = existingCMConfig + // Create a fresh private key. + ctlogConfig, err = createConfigWithKeys(ctx, *keyType) } - - existingConfig, err := ctlog.Unmarshal(ctx, existingSecret.Data) if err != nil { - log.Fatalf("Failed to unmarshal existing configuration: %v", err) + logging.FromContext(ctx).Fatalf("Failed to generate keys: %v", err) } - - // Finally add Fulcio to it, marshal and write out. - if err = existingConfig.AddFulcioRoot(ctx, root.ChainPEM); err != nil { - log.Printf("Failed to add fulcio root: %v", err) + if err = ctlogConfig.AddFulcioRoot(ctx, root.ChainPEM); err != nil { + logging.FromContext(ctx).Infof("Failed to add fulcio root: %v", err) } - marshaled, err := existingConfig.MarshalConfig(ctx) + marshaled, err := ctlogConfig.MarshalConfig() if err != nil { - log.Fatalf("Failed to marshal new configuration: %v", err) + logging.FromContext(ctx).Fatalf("Failed to marshal ctlog config: %v", err) } - // Take out the public / private key from the secret since we didn't mess - // with those. ReconcileSecret will not touch fields that are not here, so - // just remove them from the map. - delete(marshaled, privateKey) - delete(marshaled, publicKey) + if err := secret.ReconcileSecret(ctx, *secretName, ns, marshaled, nsSecret); err != nil { - logging.FromContext(ctx).Panicf("Failed to reconcile secret %s/%s: %v", ns, *secretName, err) + logging.FromContext(ctx).Fatalf("Failed to reconcile secret: %v", err) } - pubData := map[string][]byte{publicKey: existingSecret.Data[publicKey]} + pubData := map[string][]byte{publicKey: marshaled[publicKey]} if err := secret.ReconcileSecret(ctx, *pubKeySecretName, ns, pubData, nsSecret); err != nil { - logging.FromContext(ctx).Panicf("Failed to reconcile secret %s/%s: %v", ns, *secretName, err) + logging.FromContext(ctx).Panicf("Failed to reconcile public key secret %s/%s: %v", ns, *secretName, err) } + + logging.FromContext(ctx).Infof("Created CTLog configuration") } // createConfigWithKeys creates otherwise empty CTLogCOnfig but fills @@ -263,7 +187,7 @@ func createConfigFromExistingSecret(ctx context.Context, nsSecret v1.SecretInter if len(private) == 0 { return nil, errors.New("secret missing private key entry") } - priv, pub, err := ctlog.DecryptExistingPrivateKey(private, *keyPassword) + priv, pub, err := ctlog.ParseExistingPrivateKey(private) if err != nil { return nil, fmt.Errorf("decrypting existing private key secret: %w", err) } diff --git a/cmd/ctlog/managectroots/main.go b/cmd/ctlog/managectroots/main.go index c69282460..871aebeca 100644 --- a/cmd/ctlog/managectroots/main.go +++ b/cmd/ctlog/managectroots/main.go @@ -18,11 +18,9 @@ import ( "flag" "net/url" "os" - "strings" fulcioclient "github.com/sigstore/fulcio/pkg/api" "github.com/sigstore/scaffolding/pkg/ctlog" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -31,19 +29,10 @@ import ( "sigs.k8s.io/release-utils/version" ) -const ( - // Key in the configmap holding the value of the tree. - treeKey = "treeID" - configKey = "config" - bitSize = 4096 -) - var ( - cmname = flag.String("configmap", "ctlog-config", "Name of the configmap where the treeID lives. if configInSecret is false, ctlog config gets added here also.") - configInSecret = flag.Bool("config-in-secret", false, "If set to true, fetch / update the ctlog configuration proto into a secret specified in ctlog-secrets under key 'config'.") - secretName = flag.String("secret", "ctlog-secrets", "Name of the secret to fetch private key for CTLog.") - fulcioURL = flag.String("fulcio-url", "http://fulcio.fulcio-system.svc", "Where to fetch the fulcio Root CA from.") - operation = flag.String("operation", "", "Operation to perform for the specified fulcio [add,remove]") + secretName = flag.String("secret", "ctlog-secrets", "Name of the secret to fetch private key for CTLog.") + fulcioURL = flag.String("fulcio-url", "http://fulcio.fulcio-system.svc", "Where to fetch the fulcio Root CA from.") + operation = flag.String("operation", "", "Operation to perform for the specified fulcio [add,remove]") ) type ctRootOp string @@ -107,27 +96,7 @@ func main() { current["private"] = secrets.Data["private"] current["public"] = secrets.Data["public"] current["rootca"] = secrets.Data["rootca"] - for k, v := range secrets.Data { - if strings.HasPrefix(k, "fulcio-") { - current[k] = v - } - } - // If the config is stored in the secret, we don't need to deal with the - // configmap. - var cm *corev1.ConfigMap - if !*configInSecret { - var err error - cm, err = clientset.CoreV1().ConfigMaps(ns).Get(ctx, *cmname, metav1.GetOptions{}) - if err != nil { - logging.FromContext(ctx).Panicf("Failed to get the configmap %s/%s: %v", ns, *cmname, err) - } - if cm.BinaryData == nil || cm.BinaryData[configKey] == nil { - logging.FromContext(ctx).Fatalf("Configmap does not hold existing configmap %s/%s: %v", ns, *cmname, err) - } - current[configKey] = cm.BinaryData[configKey] - } else { - current[configKey] = secrets.Data[configKey] - } + current["fulcio"] = secrets.Data["fulcio"] conf, err := ctlog.Unmarshal(ctx, current) if err != nil { @@ -144,16 +113,10 @@ func main() { } // Marshal it and update configuration - newConfig, err := conf.MarshalConfig(ctx) + newConfig, err := conf.MarshalConfig() if err != nil { logging.FromContext(ctx).Fatalf("Failed to marshal config: %v", err) } - if !*configInSecret { - cm.BinaryData[configKey] = newConfig[configKey] - if _, err = clientset.CoreV1().ConfigMaps(ns).Update(ctx, cm, metav1.UpdateOptions{}); err != nil { - logging.FromContext(ctx).Fatalf("Failed to update configmap %s/%s: %v", ns, *cmname, err) - } - } // Update the secret with the information secrets.Data = newConfig diff --git a/cmd/ctlog/verifyfulcio/main.go b/cmd/ctlog/verifyfulcio/main.go index 5c552ccb5..22d084b02 100644 --- a/cmd/ctlog/verifyfulcio/main.go +++ b/cmd/ctlog/verifyfulcio/main.go @@ -51,7 +51,6 @@ type CertResponse struct { func main() { flag.Var(&fulcioList, "fulcio", "List of fulcios which must be in the list") var ctlogURL = flag.String("ctlog-url", "ctlog.ctlog-system.svc", "CTLog to check Fulcios against.") - var ctlogPrefix = flag.String("log-prefix", "sigstorescaffolding", "Prefix to append to the gtlogURL url. This is basically the name of the log.") flag.Parse() var strictMatch = flag.Bool("strict", true, "If set to true ctlog must only contain the Fulcios in the list, no more, no less") ctx := signals.NewContext() @@ -66,7 +65,7 @@ func main() { fmt.Printf("GOT: %s\n", fulcioList.String()) // First grab the certs that CTLog has. - ctlog := fmt.Sprintf("%s/%s/ct/v1/get-roots", *ctlogURL, *ctlogPrefix) + ctlog := fmt.Sprintf("%s/ct/v1/get-roots", *ctlogURL) /* #nosec G107 */ ctlogResponse, err := http.Get(ctlog) if err != nil { diff --git a/config/ctlog/certs/300-createconfig.yaml b/config/ctlog/certs/300-createconfig.yaml index 6ce478186..aa8dabf76 100644 --- a/config/ctlog/certs/300-createconfig.yaml +++ b/config/ctlog/certs/300-createconfig.yaml @@ -21,7 +21,6 @@ spec: - name: createctconfig image: ko://github.com/sigstore/scaffolding/cmd/ctlog/createctconfig args: [ - "--configmap=ctlog-config", "--secret=ctlog-secret" ] env: diff --git a/config/ctlog/createtree/100-namespace.yaml b/config/ctlog/createtree/100-namespace.yaml deleted file mode 100644 index a4bcdbfde..000000000 --- a/config/ctlog/createtree/100-namespace.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -kind: Namespace -apiVersion: v1 -metadata: - name: ctlog-system diff --git a/config/ctlog/createtree/101-binding.yaml b/config/ctlog/createtree/101-binding.yaml deleted file mode 100644 index b2d63a6c4..000000000 --- a/config/ctlog/createtree/101-binding.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - namespace: ctlog-system - name: cm-operator -rules: -- apiGroups: [""] # "" indicates the core API group - resources: ["configmaps"] - resourceNames: ["ctlog-config"] - verbs: ["get", "update"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: role-cm-updater - namespace: ctlog-system -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: cm-operator -subjects: -- kind: ServiceAccount - name: createtree - namespace: ctlog-system diff --git a/config/ctlog/createtree/101-configmap.yaml b/config/ctlog/createtree/101-configmap.yaml deleted file mode 100644 index 95b51d632..000000000 --- a/config/ctlog/createtree/101-configmap.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: ctlog-config - namespace: ctlog-system -data: - __placeholder: | - ################################################################### - # Just a placeholder so that reapplying this won't overwrite treeID - # if it already exists. This caused grief, do not remove. - ################################################################### - diff --git a/config/ctlog/createtree/101-service-account.yaml b/config/ctlog/createtree/101-service-account.yaml deleted file mode 100644 index 6dbd96553..000000000 --- a/config/ctlog/createtree/101-service-account.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: createtree - namespace: ctlog-system diff --git a/config/ctlog/createtree/300-createtree.yaml b/config/ctlog/createtree/300-createtree.yaml deleted file mode 100644 index 5e51d2d4b..000000000 --- a/config/ctlog/createtree/300-createtree.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: createtree - namespace: ctlog-system -spec: - # This number looks crazy, but on k8s 1.23 there does not seem to be - # exponential backoff, so just keep on trying. For any other version - # won't run this far by any chance. Also with activeDeadlineSeconds we're - # capping this to 5 minutes. - backoffLimit: 90 - activeDeadlineSeconds: 300 - ttlSecondsAfterFinished: 600 - template: - spec: - serviceAccountName: createtree - restartPolicy: Never - automountServiceAccountToken: true - containers: - - name: createtree - image: ko://github.com/sigstore/scaffolding/cmd/trillian/createtree - args: [ - "--namespace=ctlog-system", - "--configmap=ctlog-config", - "--display_name=ctlogtree" - ] diff --git a/config/ctlog/createtree/placeholder.go b/config/ctlog/createtree/placeholder.go deleted file mode 100644 index 528ee0dd2..000000000 --- a/config/ctlog/createtree/placeholder.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 The Sigstore Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package createtree diff --git a/config/ctlog/ctlog/300-ctlog.yaml b/config/ctlog/ctlog/300-ctlog.yaml index 37fbab77f..ac440b31d 100644 --- a/config/ctlog/ctlog/300-ctlog.yaml +++ b/config/ctlog/ctlog/300-ctlog.yaml @@ -19,19 +19,27 @@ spec: serviceAccountName: ctlog containers: - name: ctfe - image: ko://github.com/google/certificate-transparency-go/trillian/ctfe/ct_server + image: ko://github.com/transparency-dev/tesseract/cmd/tesseract/posix args: [ "--http_endpoint=0.0.0.0:6962", - "--log_config=/ctfe-keys/config", - "--alsologtostderr" + "--storage_dir=/ctfe", + "--origin=ctlog.ctlog-system.svc", + "--ext_key_usages=CodeSigning", + "--v=1", + "--private_key=/ctfe-keys/private", + "--roots_pem_file=/ctfe-keys/fulcio", ] volumeMounts: - name: keys mountPath: "/ctfe-keys" readOnly: true + - name: storage + mountPath: "/ctfe" ports: - containerPort: 6962 volumes: - name: keys secret: secretName: ctlog-secret + - name: storage + emptyDir: {} diff --git a/config/fulcio/fulcio/300-fulcio.yaml b/config/fulcio/fulcio/300-fulcio.yaml index 0006826dc..3377b2da1 100644 --- a/config/fulcio/fulcio/300-fulcio.yaml +++ b/config/fulcio/fulcio/300-fulcio.yaml @@ -34,7 +34,7 @@ spec: - "/var/run/fulcio-secrets/cert.pem" - "--fileca-key-passwd" - "$(PASSWORD)" - - "--ct-log-url=http://ctlog.ctlog-system.svc/sigstorescaffolding" + - "--ct-log-url=http://ctlog.ctlog-system.svc" env: - name: PASSWORD valueFrom: @@ -104,7 +104,7 @@ spec: - "/var/run/fulcio-secrets/cert.pem" - "--fileca-key-passwd" - "$(PASSWORD)" - - "--ct-log-url=http://ctlog.ctlog-system.svc/sigstorescaffolding" + - "--ct-log-url=http://ctlog.ctlog-system.svc" env: - name: PASSWORD valueFrom: diff --git a/pkg/ctlog/config.go b/pkg/ctlog/config.go index dc26d28b0..ced31f6d1 100644 --- a/pkg/ctlog/config.go +++ b/pkg/ctlog/config.go @@ -23,40 +23,28 @@ import ( "bytes" "context" "crypto" - "crypto/rand" + "crypto/ecdsa" + "crypto/rsa" "crypto/x509" "encoding/pem" "errors" "fmt" "strings" - "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" - "github.com/google/trillian/crypto/keyspb" "github.com/sigstore/sigstore/pkg/cryptoutils" - "google.golang.org/protobuf/encoding/prototext" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" "knative.dev/pkg/logging" ) const ( - // ConfigKey is the key in the map holding the marshalled CTLog config. - ConfigKey = "config" // PrivateKey is the key in the map holding the encrypted PEM private key // for CTLog. PrivateKey = "private" // PublicKey is the key in the map holding the PEM public key for CTLog. PublicKey = "public" - // LegacyRootCAKey is the key for when we only supported a single entry - // in the config. - LegacyRootCAKey = "rootca" - bitSize = 4096 - - // This is hardcoded since this is where we mount the certs in the - // container. - rootsPemFileDir = "/ctfe-keys/" - // This file contains the private key for the CTLog - privateKeyFile = "/ctfe-keys/private" + // FulcioKey is the key in the map holding the list of Fulcio certificates + // for CTLog. + FulcioKey = "fulcio" + bitSize = 4096 ) // Config abstracts the proto munging to/from bytes suitable for working @@ -64,14 +52,8 @@ const ( // technically they are not part of the config, however because we create a // secret/CM that we then mount, they need to be synced. type Config struct { - PrivKey crypto.PrivateKey - PrivKeyPassword string - PubKey crypto.PublicKey - LogID int64 - LogPrefix string - - // Address of the gRPC Trillian Admin Server (host:port) - TrillianServerAddr string + PrivKey crypto.PrivateKey + PubKey crypto.PublicKey // FulcioCerts contains one or more Root certificates for Fulcio. // It may contain more than one if Fulcio key is rotated for example, so @@ -131,10 +113,6 @@ func (c *Config) RemoveFulcioRoot(ctx context.Context, fulcioRoot []byte) error func (c *Config) String() string { var sb strings.Builder - sb.WriteString(fmt.Sprintf("PrivateKeyPassword: %s\n", c.PrivKeyPassword)) - sb.WriteString(fmt.Sprintf("LogID: %d\n", c.LogID)) - sb.WriteString(fmt.Sprintf("LogPrefix: %s\n", c.LogPrefix)) - sb.WriteString(fmt.Sprintf("TrillianServerAddr: %s\n", c.TrillianServerAddr)) for _, fulcioCert := range c.FulcioCerts { sb.WriteString(fmt.Sprintf("fulciocert:\n%s\n", string(fulcioCert))) } @@ -160,178 +138,86 @@ func (c *Config) String() string { // Note however that because we do not update public/private keys once set // we do not roundtrip these into their original forms. func Unmarshal(_ context.Context, in map[string][]byte) (*Config, error) { - var config, private, public []byte + var private, public []byte var ok bool - if config, ok = in[ConfigKey]; !ok { - return nil, fmt.Errorf("missing entry for %s", ConfigKey) - } if private, ok = in[PrivateKey]; !ok { return nil, fmt.Errorf("missing entry for %s", PrivateKey) } if public, ok = in[PublicKey]; !ok { return nil, fmt.Errorf("missing entry for %s", PublicKey) } - multiConfig := configpb.LogMultiConfig{} - if err := prototext.Unmarshal(config, &multiConfig); err != nil { - return nil, fmt.Errorf("failed to unmarshal: %w", err) - } - // We only have one backend specified for us, so even though multiconfig - // can have many, we'll have only one. - if multiConfig.LogConfigs == nil { - return nil, fmt.Errorf("missing multiConfig or nil LogConfigs") - } - if len(multiConfig.LogConfigs.Config) != 1 { - return nil, fmt.Errorf("unexpected number of LogConfig, want 1 got %d", len(multiConfig.LogConfigs.Config)) - } - ret := Config{} - logConfig := multiConfig.GetLogConfigs().Config[0] - ret.LogID = logConfig.LogId - ret.LogPrefix = logConfig.Prefix - - if multiConfig.Backends == nil { - return nil, fmt.Errorf("missing backends") - } - if len(multiConfig.Backends.GetBackend()) != 1 { - return nil, fmt.Errorf("unexpected number of Backends, want 1 got %d", len(multiConfig.Backends.Backend)) - } - ret.TrillianServerAddr = multiConfig.Backends.GetBackend()[0].GetBackendSpec() - // Then we need to decode public key var err error + ret := Config{} ret.PubKey, err = cryptoutils.UnmarshalPEMToPublicKey(public) if err != nil { return nil, fmt.Errorf("failed to unmarshal public key: %w", err) } - privProto, err := logConfig.PrivateKey.UnmarshalNew() + ret.PrivKey, _, err = parsePrivateKey(private) if err != nil { - return nil, fmt.Errorf("invalid private key: %w", err) - } - pb, ok := privProto.(*keyspb.PEMKeyFile) - if !ok { - return nil, fmt.Errorf("not a valid PEMKeyFile in proto") + return nil, fmt.Errorf("failed to unmarshal private key: %w", err) } - ret.PrivKeyPassword = pb.Password - - ret.PrivKey, _, err = DecryptExistingPrivateKey(private, ret.PrivKeyPassword) - if err != nil { - return nil, fmt.Errorf("decrypting existing private key: %w", err) - } - // Make sure to dedupe along the way just to make sure we do not have - // duplicate entries. - uniqueFulcioCerts := map[string][]byte{} - - // If there's legacy rootCA entry, check it first. This will get converted - // to fulcio-0 when marshaling, but we just want to make sure it's there - // when we're converting from ConfigMap based configuration into secret - // based one. - if legacyRoot, ok := in[LegacyRootCAKey]; ok && len(legacyRoot) > 0 { - uniqueFulcioCerts[string(legacyRoot)] = legacyRoot - } - - for k, v := range in { - if strings.HasPrefix(k, "fulcio-") { - uniqueFulcioCerts[string(v)] = v + if roots, ok := in[FulcioKey]; ok { + rest := roots + for len(rest) != 0 { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + return nil, fmt.Errorf("invalid fulcio roots: %w", err) + } + ret.FulcioCerts = append(ret.FulcioCerts, pem.EncodeToMemory(block)) } } - - // Then loop through Fulcio roots that have been deduped above - for _, v := range uniqueFulcioCerts { - ret.FulcioCerts = append(ret.FulcioCerts, v) - } return &ret, nil } // MarshalConfig marshals the CTLogConfig into a format that can be handed -// to the CTLog in form of a secret or configmap. Returns a map with the +// to the CTLog in form of a secret. Returns a map with the // following keys: -// config - CTLog configuration // private - CTLog private key, PEM encoded and encrypted with the password // public - CTLog public key, PEM encoded // fulcio-%d - For each fulcioCerts, contains one entry so we can support // multiple. -func (c *Config) MarshalConfig(ctx context.Context) (map[string][]byte, error) { - // Since we can have multiple Fulcio secrets, we need to construct a set - // of files containing them for the RootsPemFile. Names don't matter - // so we just call them fulcio-% - // What matters however is to ensure that the filenames match the keys - // in the configmap / secret that we construct so they get properly mounted. - rootPems := make([]string, 0, len(c.FulcioCerts)) - for i := range c.FulcioCerts { - rootPems = append(rootPems, fmt.Sprintf("%sfulcio-%d", rootsPemFileDir, i)) - } - - var pubkey crypto.Signer - var ok bool - // Note this goofy cast to crypto.Signer since the any interface has no - // methods so cast here so that we get the Public method which all core - // keys support. - if pubkey, ok = c.PrivKey.(crypto.Signer); !ok { - logging.FromContext(ctx).Fatalf("Failed to convert private key to crypto.Signer") - } - keyDER, err := x509.MarshalPKIXPublicKey(pubkey.Public()) - if err != nil { - logging.FromContext(ctx).Panicf("Failed to marshal the public key: %v", err) - } - proto := configpb.LogConfig{ - LogId: c.LogID, - Prefix: c.LogPrefix, - RootsPemFile: rootPems, - PrivateKey: mustMarshalAny(&keyspb.PEMKeyFile{ - Path: privateKeyFile, - Password: c.PrivKeyPassword}), - PublicKey: &keyspb.PublicKey{Der: keyDER}, - LogBackendName: "trillian", - ExtKeyUsages: []string{"CodeSigning"}, - } - - multiConfig := configpb.LogMultiConfig{ - LogConfigs: &configpb.LogConfigSet{ - Config: []*configpb.LogConfig{&proto}, - }, - Backends: &configpb.LogBackendSet{ - Backend: []*configpb.LogBackend{{ - Name: "trillian", - BackendSpec: c.TrillianServerAddr, - }}, - }, - } - marshalledConfig, err := prototext.Marshal(&multiConfig) - if err != nil { - return nil, err - } +func (c *Config) MarshalConfig() (map[string][]byte, error) { secrets, err := c.marshalSecrets() if err != nil { return nil, err } - secrets[ConfigKey] = marshalledConfig return secrets, nil } // MarshalSecrets returns a map suitable for creating a secret out of // containing the following keys: -// private - CTLog private key, PEM encoded and encrypted with the password +// private - CTLog private key, PEM encoded // public - CTLog public key, PEM encoded // fulcio-%d - For each fulcioCerts, contains one entry so we can support // multiple. func (c *Config) marshalSecrets() (map[string][]byte, error) { - // Encode private key to PKCS #8 ASN.1 PEM. - marshalledPrivKey, err := x509.MarshalPKCS8PrivateKey(c.PrivKey) + var marshalledPrivKey []byte + var err error + var blockType string + switch k := c.PrivKey.(type) { + case *rsa.PrivateKey: + blockType = "PRIVATE KEY" + // Encode private key to PKCS #8 ASN.1 PEM. + marshalledPrivKey, err = x509.MarshalPKCS8PrivateKey(k) + case *ecdsa.PrivateKey: + blockType = "EC PRIVATE KEY" + marshalledPrivKey, err = x509.MarshalECPrivateKey(k) + default: + return nil, fmt.Errorf("unrecognized private key type %T", k) + } if err != nil { return nil, fmt.Errorf("failed to marshal private key: %w", err) } block := &pem.Block{ - Type: "PRIVATE KEY", + Type: blockType, Bytes: marshalledPrivKey, } - // Encrypt the pem - encryptedBlock, err := x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(c.PrivKeyPassword), x509.PEMCipherAES256) // nolint - if err != nil { - return nil, fmt.Errorf("failed to encrypt private key: %w", err) - } - privPEM := pem.EncodeToMemory(encryptedBlock) + privPEM := pem.EncodeToMemory(block) if privPEM == nil { return nil, fmt.Errorf("failed to encode encrypted private key") } @@ -360,38 +246,34 @@ func (c *Config) marshalSecrets() (map[string][]byte, error) { PrivateKey: privPEM, PublicKey: pubPEM, } - for i, cert := range c.FulcioCerts { - fulcioKey := fmt.Sprintf("fulcio-%d", i) - data[fulcioKey] = cert + for _, cert := range c.FulcioCerts { + data[FulcioKey] = append(data[FulcioKey], cert...) } return data, nil } -func mustMarshalAny(pb proto.Message) *anypb.Any { - ret, err := anypb.New(pb) +// ParseExistingPrivateKey reads in a private key bytes and returns private, public keys for it. +func ParseExistingPrivateKey(privateKey []byte) (crypto.PrivateKey, crypto.PublicKey, error) { + priv, signer, err := parsePrivateKey(privateKey) if err != nil { - panic(fmt.Sprintf("MarshalAny failed: %v", err)) + return nil, nil, err } - return ret + + return priv, signer.Public(), nil } -// DecryptExistingPrivateKey reads in an encrypted private key, decrypts with -// the given password, and returns private, public keys for it. -func DecryptExistingPrivateKey(privateKey []byte, password string) (crypto.PrivateKey, crypto.PublicKey, error) { +func parsePrivateKey(privateKey []byte) (crypto.PrivateKey, crypto.Signer, error) { privPEM, _ := pem.Decode(privateKey) if privPEM == nil { return nil, nil, fmt.Errorf("did not find valid private PEM data") } - privatePEMBlock, err := x509.DecryptPEMBlock(privPEM, []byte(password)) - if err != nil { - return nil, nil, fmt.Errorf("failed to decrypt private PEMKeyFile: %w", err) - } var priv crypto.PrivateKey - if priv, err = x509.ParsePKCS8PrivateKey(privatePEMBlock); err != nil { + var err error + if priv, err = x509.ParsePKCS8PrivateKey(privPEM.Bytes); err != nil { // Try it as RSA - if priv, err = x509.ParsePKCS1PrivateKey(privatePEMBlock); err != nil { - if priv, err = x509.ParseECPrivateKey(privatePEMBlock); err != nil { + if priv, err = x509.ParsePKCS1PrivateKey(privPEM.Bytes); err != nil { + if priv, err = x509.ParseECPrivateKey(privPEM.Bytes); err != nil { return nil, nil, fmt.Errorf("failed to parse private key PEM: %w", err) } } @@ -401,6 +283,5 @@ func DecryptExistingPrivateKey(privateKey []byte, password string) (crypto.Priva if signer, ok = priv.(crypto.Signer); !ok { return nil, nil, errors.New("failed to convert private key to Signer") } - - return priv, signer.Public(), nil + return priv, signer, nil } diff --git a/pkg/ctlog/config_test.go b/pkg/ctlog/config_test.go index 5f61d9bf3..466601307 100644 --- a/pkg/ctlog/config_test.go +++ b/pkg/ctlog/config_test.go @@ -27,14 +27,11 @@ import ( "encoding/pem" "fmt" "reflect" - "strings" "testing" - "github.com/google/certificate-transparency-go/trillian/ctfe/configpb" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/sigstore/rekor/pkg/pki/x509/testutils" - "google.golang.org/protobuf/encoding/prototext" ) // Just a test Root Cert from a Fulcio instance spun up using Scaffolding. @@ -74,39 +71,26 @@ KTkomoSY/OxE/5doBCACehThH+96joWfgC0rXi9qAwZ6hwIMJAKy -----END CERTIFICATE----- ` - // testConfigECDSA contains above cert in it as well as privateKeyEncoded and - // publicKeyEncoded. - testConfigECDSA = "YmFja2VuZHM6e2JhY2tlbmQ6e25hbWU6InRyaWxsaWFuIn19ICBsb2dfY29uZmlnczp7Y29uZmlnOntsb2dfaWQ6MjAyMiAgcHJlZml4OiIyMDIyLWN0bG9nIiAgcm9vdHNfcGVtX2ZpbGU6Ii9jdGZlLWtleXMvZnVsY2lvLTAiICBwcml2YXRlX2tleTp7W3R5cGUuZ29vZ2xlYXBpcy5jb20va2V5c3BiLlBFTUtleUZpbGVdOntwYXRoOiIvY3RmZS1rZXlzL3ByaXZrZXkucGVtIiAgcGFzc3dvcmQ6Im15dGVzdHBhc3N3b3JkIn19ICBwdWJsaWNfa2V5OntkZXI6IjBZMFx4MTNceDA2XHgwNypceDg2SFx4Y2U9XHgwMlx4MDFceDA2XHgwOCpceDg2SFx4Y2U9XHgwM1x4MDFceDA3XHgwM0JceDAwXHgwNNWwXHhlM1x4YTZYXHhjZS9ceGE1XHg5NFx4ZjZceGM2Plx4ODJceGJje1x4ZGVceGYwfG0rXHhkMVx4Y2U7XHg4NVx4YmZceGYyXHhmOFx4OTRceGYwfVx4ZDlceDFkPlx4N2ZKKFx4YzY+cVx4OGZceGM4XHgwZVx4YTJdXHgxNFx4ODhceGM4XHhkNX7Du2ZzXHhlZVx4OTlceDFicVx4MGVgR1x4ZWZceGUyQlx4ZjQifSAgZXh0X2tleV91c2FnZXM6IkNvZGVTaWduaW5nIiAgbG9nX2JhY2tlbmRfbmFtZToidHJpbGxpYW4ifX0=" - // ECDSA private key - privateKeyEncodedECDSA = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tClByb2MtVHlwZTogNCxFTkNSWVBURUQKREVLLUluZm86IEFFUy0yNTYtQ0JDLDJiNDU2MGUyY2RlMGE3ZWM0NjZlMzkzYWRmYmE0Y2I0CiAgICAgICAKVUk4d2lUbXhNajhKWXVHSUFEMnpKVjRmQjZHUE9wUGhxSldYdlR3RWFucHBzTXN3UUFCaVZ5NWdkSi9BNThQVAo0ZTFFSDM4Y3Z3YTBMQjQ2SHBoZW9vWCtJM2RHdHlzRUpFR0d3QXMwYUhkU25aeVV3TnRpalRUQkZJcWxzd3pKCnI2WmJ4dmlxZVRmRm80ZUtEMGorRjlja2R3d2dGT2YzRHdaUUMrNEN1cVNqczdaZkFKZEF6Lys0c2JRd1ZzQUIKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=" + privateKeyEncodedECDSA = "LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSURSVVYwbHhqMGE5eXdXUUdURUlCT2FDdVo5amNCYmJFS3puL09zaldFKzBvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFdUp5eU0wL3BPUm1rRVVUTzdwMlNnQ0VrV3M2WWo2VHNRb0Y3eDM3QWtpSXEvQ3llaFNveQpOSjFaZy9YQkduaXpNNHZhSk12MXZDdGFDR0x2RGdGd1lRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=" // ECDSA public key - publicKeyEncodedECDSA = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFT3Y1bzVXV0tZaVVSODdzNGZpMEpKbU1EUVV2cQpSck1mNGRlQnpzV3BCWVdVK1Y4TXVDMkh6aTFOTHI4czRlQ0J5dWVDZmFQWFN4STgzUkowamEwbnd3PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==" - - // This is for RSA, since previously deployed CTLog used RSA. - testConfigRSA = "YmFja2VuZHM6e2JhY2tlbmQ6e25hbWU6InRyaWxsaWFuIiBiYWNrZW5kX3NwZWM6ImxvZy1zZXJ2ZXIudHJpbGxpYW4tc3lzdGVtLnN2Yzo4MCJ9fSBsb2dfY29uZmlnczp7Y29uZmlnOntsb2dfaWQ6ODMxMzUyNzQxMDgyOTkwNTY3OSBwcmVmaXg6InNpZ3N0b3Jlc2NhZmZvbGRpbmciIHJvb3RzX3BlbV9maWxlOiIvY3RmZS1rZXlzL3Jvb3RzLnBlbSIgcHJpdmF0ZV9rZXk6e1t0eXBlLmdvb2dsZWFwaXMuY29tL2tleXNwYi5QRU1LZXlGaWxlXTp7cGF0aDoiL2N0ZmUta2V5cy9wcml2a2V5LnBlbSIgcGFzc3dvcmQ6InRlc3QifX0gcHVibGljX2tleTp7ZGVyOiIwXHg4Mlx4MDJcIjBcclx4MDZcdCpceDg2SFx4ODZceGY3XHJceDAxXHgwMVx4MDFceDA1XHgwMFx4MDNceDgyXHgwMlx4MGZceDAwMFx4ODJceDAyXG5ceDAyXHg4Mlx4MDJceDAxXHgwMFx4YjlceGEzSVx4YTVceGI4XHgxNTlceGU0Qlx4ODdceGMzWlx4MTZceDExXHgwMHPknY1ceGVmXHhiYzlkXHg4YVx4YjZTXHg5Zlx4YThMXHgxMNWGXHgwNVx4MGJceGU1XHgwY01ceGNlMlx4YjZceGYwXHg4MFx4OTVceDAxd1x4YTBA0rdGXHg4NipceDgxRFx4YWU3XHhmZFx4ZDlrMlx4YmNzflx4ZTF5XHhkOFx4MTZceGY2XHRceDEyXHLKm1xuXHJceDFhXHg5N1x4ZTZceGIyXHhlYVx4YzBceGZhXHhiY2VceGE1cFx4ODhceDk3XHg4YTdceGZmXHhmMVx4Y2V2XHgxY1x4ZGZcbsiwLVx4ZGNceGQ0e1x4Zjl+XHgxMCRceDk2XHhiYzggXHhlMlx4MWVceGMyXHhkMlx4ZjNceGM3aVx4MGUtXHg4ZVx4YjZceDg0Llx4MDVceDE3JVx4ZTRceGExXHgwZlx4Y2POjVVWOVx4MThEJVx4YTdceDgzT1wielx4YTdceGU3ZHRceGExRExceGFjXHhlN3pybFx4MTBceGQ3QFx4OWVdXHhmMGRceGQxUl5fOVx4ZmRceGE3PzQgXHhmN1x4MTNcXFx4Y2ZceGU5XHhjN2xceDAzKVx4ZTljXHhkYlx4MDE4MVx4OTl9XHhlZjJceDhmRVNIXHhmZmdceGY4XHhjYklceGI5XHhiOVx4ODNceGEyXHhhNlx4ZDBceDAxY1x4ODc/c1x4MDNceGZiXHg4N1x4ZTlIXHhkYXlceDAzXHhmM2RdXHhiYXtceDgzXHgxY1x4YjdcXFx4YTZceDA2PVx4MTNceGU0XHhlYlx4ZDNceGRlXHgxMVx4YTdWX2tQXHg4Ylx4YzBceDhkXHhmY1x4ZmFnXHhiOFx4YzBmS1x4YjQtYVx4Y2RTXHhlY25ceDhhXHg4MUxdXHgwNFx4MDBceGFmXHhlMVnUl1x4MGZiIVx4MDNceGJhOXYlXHgwY1x4ODNceGYxXHgxOVx4YWM6XHgwYnRceGZjXHg4NlFceGIyXHhjY1x4ZjBceGJiMVx4ZWVceGFiXHhlMERceDAzXHg5Yy1ceGRkalx4YTRceDg4MllQVFx4OTBceDEyXHg4Y0R5dFx4Y2RvcDVceDFmeVx4ZmR2XHhjN1x4MTZceGIwXHgwNDFccnRDXHgxOTckXHgxMFx4ZDJceGUxXHgxZFx4OTBFXHgxNSnuqYtceGNjXHhlZDp1XHhhMFx4ZTRceDEwXHhkNGJZXHhmY1x4MDTDsybOgVFceGRkRlBFXHhmMWs6Wlx4YjZceDlibWpceDE1XHhkN1dceGM1XHhkZVx4ZTdBXHhmMlx4ODdceGRiXHgxNVx4ZTBAXHg4Zlx0XHg4M9mWXHg4MEVJXHgxZFx4YTVceGFjXHg5Mlx4Y2Jmelx4ODJceDg1M3dceDkzXHg4MVx4ZWVceGM0a1x4YjZceGJlWWxceDk0XHgxYTpgXHhlNFx4ZjJceDBjXHhmMFx4YTAjXHg3Zlx4YmEvWlx4ZDA6fVx4ZTNceDAyXHgwYlVbXHhmNi1ceGQzUlx4OWRceDBi4pGE2ZJceDk3XHg5Y1RceDdmXHhmMVhceGIw66yvXHgxOVx4OGNceDg3XHhmNlx4ZTBceDFhTV9aZ9yXXHhmMng9XHhhMVJsXHhhYlx4OWRiXHhmMVx4ZjFnPVZceDhmaVx4ZWNceDdmXHhlM1x4ZjhceDFmXHhkYlx4MWJiXHhlMGtceDkxXHhkN1x4YzdeXHgwMFx4MTQ0XHhkM1dceGViXHhhZFVceGQ1XHhkZlx4MDJceDAzXHgwMVx4MDBceDAxIn0gZXh0X2tleV91c2FnZXM6IkNvZGVTaWduaW5nIiBsb2dfYmFja2VuZF9uYW1lOiJ0cmlsbGlhbiJ9fQ==" + publicKeyEncodedECDSA = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFdUp5eU0wL3BPUm1rRVVUTzdwMlNnQ0VrV3M2WQpqNlRzUW9GN3gzN0FraUlxL0N5ZWhTb3lOSjFaZy9YQkduaXpNNHZhSk12MXZDdGFDR0x2RGdGd1lRPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==" - privateKeyEncodedRSA = "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpQcm9jLVR5cGU6IDQsRU5DUllQVEVECkRFSy1JbmZvOiBBRVMtMjU2LUNCQyw3NWUxNTkxNzQ0NTc4MjMwNGUzYjY1NGQ5NjhjY2M4MAoKV1pPQ1QrQXlaUmlaaFpDdXMveGxuR2dFbzNwTk1GRSsra0YvWVdBZUxMQjhmclNuL2NlL3VjbURuOURGQ01VZApORlNhSks1YzNvWEJCckt0Uk1sQ0I2S2RGblJucHNpVHUzbU1sVzVPdzRNTVh0L3JJaEFXbDFDaUFYUkdqL0NWClg4clRvQldpOFN4dXh3aWgrOHlrY0VpaVg3Ti9aWkNYOUppbjFQeTc0QUczWHBPT28rbFhwKzRTN1BwQmlZbzAKU0pzaUZ4Mlk0LzF4RXBWMEVWdmZobmN1R0k1R0ROcm0wUnBBNnNraGRSbU5iMW1HYkR5ZXdnMndPTTJTRHRGQwpSWEE5aFAxV1czUWx0VGhXRml2VTU0SngrYktMc3Fnem9JMzNZRmRFdnRPNmNxWCtoOVprN1pORmxaMDNaREk4Ck5RdzEyT3Z3VnpEeE5XdmFYVFhIMEpJc2tUSTE5cjFCTnB6aW1xdWg4ZWRYSTFuT2ppbUM5VjlRQTF0TVNmWmkKVmM2RW9VSG55N0xNVXkydG1yN3R2M2pLRWJHT09nclNRcXhJejAxcjFtV0dpREU2YkNDeFFueUhOUHExQmlIRQp1WTR3K25iU2V5UDhVc3h6YjlVNkRSd2IxVzZkMjlmbGNsdFp1TFlqdEhRL1JwRUdxbWRNc1RmRU1wRUVTNU9jClJPVmtsQlpQM0NHN3I4NGN0aVBMUGpvZnk0aG4rai9SeTBtT2tzcFcyVjNlQ2FvdGQwU0lQZFhxT3h6K2p3U1kKaDRBelg1VHdMSlg2UDlSaVdVZ2xQUWZKNjhCclpOT1Ywc3IwaEIwc1NXY25mSWorWWxSSzMvUXJTZGdhellRRQo0ZHBrK0hDUUE4bkdwN1M1Uks4ZGdxek1QYS96Z1AvR1dnN0t5K0dVWFB3cXRhalBFd1ZVWFJPNGViWUJCQ1RwClFHYnRSSmdRRjFzSmtqN1F0d0J4NzVoM25ZSjlWdEhiMWR2d2FKL09mWklhSklKQkRROVlyRGtqMjVmdDdtWlEKZVlGN1c5NlhCU0xHc2ZhdzlDMXhNRXZVY081UGtkS3ArR3pvMFhUaXhNb1U1Q0h6Yk0rQnFqMFZycGpNV29XbQphbHZpYVc4RlNYQkZQZUNoNFIrOXhwN1Q3ZWl6OU9uRFpKRVdnR1B1YXZyN29XL0t6blE1RS9SVlJtRllaZVY2CkluRXlmUVlRVE5QMnVBWjdibFRCeEc0VlhWdjA4ZUhWVHJ4YkVBcmE4VXJrZkQ1Nm02U3M3YWsrYU1mdG0vSnkKZHBxbTJ5YWlpSDd1SmRiZ1hyNTBnNEFDUThtZlE1QjNpbk1Ea0NFZ2RyQTRTQXg1YXNaQjJ0V2l1VC9SZFVSLworMUpXbjNKdXBEL2dhWU5CTVBTRzhjL0hKa0xmeE5UdzZVaHBBTlg5TkErTlE1UVdCUTVaaWNhbUNLQWJUczEvCjhUUlJlbnBLdUdhZXVsazhneVNOTm5xa0plZUNlZ1c5RGR1d3BZcUpjVkJ3L3lrY3BDc0hleVVZSTFOZkd0dCsKcTJ0Z2h0WGhaSGpFV1ZhcWVIb0JOTHlxZ0NET0l6U1QyTnFSeC9yYXhXckl1K0JwMTJTazNpQm5pc1Y0cE02NQprMnFaTDVhY2FDb3lIWTlSWStKSThYdHBzcHVjclViZnp6K0F3ZVZpdkcwN0hkOWRnV0dMRHRwMDJ4VGFMb09pCnp1NnV1dU9heUtZaUI4N2RBYlJlZUY0RVNrTlZOM3k4c1hIS3lnRlFvN2pqRExWVnBwRVVYWC8vN081VU9aZ0wKMWtWcVJ4K2hLeTQvTnVqQUVReWJubnMzRlpIMHBDMDQvcnAwS2xBeHlmRzBRNWJvTWdBeUR0VGlyUFBzK2lwTQpveDh1aWdlQlFaTmZyWW41TVA2UWVUSWY0QWx4NWNzSktxb1Nzb2dZclljbWhoSkhkc1Q4QUpidlpXSUo4L01JCjRFKzJ6UEZSNUlOYzNGbjVoVFpnRzNMQjh4N0ErTHlCbEdNR2owdW9melVzdnZMNnpxeEtqZ3F1Qm5DbTNmTHYKSjFnaDFYbkUyeENVekZhSlpQOVVNU1N2bmVmci92TzBFMjFxL0NlSGRUNWZsaUl4UjBZQ0t5MENvd3ZIeUdyYwpmc2JWWS92dGhIcUxLYmx0Vkh0bndPOExFTmhWZmVweGhFUy9sQUZrWmgrbmZFYjVsUnRZb2hZSW9RUkFOR1A0CmhCS1BhWldua0kwbFl5TmJNU1h3d3U0R2lScFdUUjhUYW84WDlXSWlJdmgyc3hHd0NleTBPSGZCVGtoYnR3Y2sKQzlaT0pERW9SNXBlOGZXSitzWXBia1laYjd3TzhSVEMyNlBGZTRQdEtKRFNGWXlOMzM2T1ZVdzM2RkZmVzR0QQpvcGtBdkRVbDdXVEZ1TlB4RVZ3SXZQSnN2ZDdnaG9Kdm1MYm4xQldQTS8wY0lobkQ0YkdrbjBsVURTTUFjUXIvCkV2R0h4Z2xpeU4wdktnOWU5SE9VNkVOYVdMaTRzemhwdE05RzF6UnBic01CV05zRW5TTEEwL3BaS01TOXdGdk8KL1N2VEVFc3dlM2xKWjV3WFc2R3lUdURFMzQ4Z011UFk4RmpCajZjQVo3RUJLTmYrWG1TY3VQTHYxVzd3Nm52cApKTGtQRS8wQmswdEZWRndlZUlERHJOTEg4Z0dseTY3MHk5cUxQSi8rMUhwdXpwR2tqc3RwWEs2QkRqWXUzeEFlCkhsd3E2RDNmRTMrZ0VkcW5RUmhZeHRacWxqaGIydFIxYUErZndhcWVBT2dNOG43RkNaY0gvK0ZBakdhRis3YjEKQ0RIdjA0cktKdVFGZjZTKzNzQktaVW9aVllJakxidE9VWko4c2QvZEZaQ01mNGhnN3RiaXNQeVFxMjQ2MUI3Wgp1SnFidlozdHhiT0lpd3k0cklCT2VtTnJaR3ArYmMzT3FuOHZQaEtpM3c2aDd4M2lvUzBxS250bStMbG11MXBqCnZOZnQwNmFZYklGcUhkY3ZqQ1AxajZNemY1Rm9TMGhmVnlpRmltOVFUOVpGeDl4bDNGeHBkK2VsYkxYY09pM0YKU2dISWE5SUdYQXNsSmo5dE5zdC9GaHBxeFdQbmt5c3dNTjRCQkJ2SDJNZU5odWpVUGdWblp5bEVodU1jQTBrcgpzdWMrNmliMEdRYUhRSW1pOHpmQ1FyQUVXMzZ2WWRxK1M0ZjBOeEZVNFZkclFzd2tpYlJhSytBTkZGY1ZKUzFJClcxWFdoU0FKV2VPUjJONmxJVFNqZVNDbXc5bnlXb1prZXBvSEkrcTlDZ0cvV09qRy9ZUkdjZUJNSFZQbk1zNDYKanA1NitvQkdXSUVpK3dvRU51UFV0aDNlZnZNT2dGTlBGZWh1QUFUUHBOeGtaMlBheHpRVmp6NXJGR09tNmJtZgoyWExIQVZxcTFjYVhEY1RidGxoSWh0Q3A5cmlGcXIzc2R6YlFxWThCWUsyQjdyQ0JHbXFjZld0akgvWUZadkNrCnFWNWpoOHQ2MFp2Z2F1bU15Y2h2NGNVaFRWMFJzZ1BteE9GMzdUenY1T0d3OVBKeS9sdFphNncveFFZQWVMaHQKRnVWN0I0WFJvdERyYklvZkNNM1ZObXdXTnN4R29LNWY1LzV6bVBEQ1JQNjZDNkkwbWVLNjZXb3prY0N2NTRMcQpJZDJaZTN5aUY2bjE0K05xZUZMWGVsdnRvay9RSWdiTEd3ME9XVEQyaFJtZGVYMjhUMEVMMW5kZ0ZUYU0xV3NlCkVJdXQxWXNLWXk1Vml2bDg1V0JiZEsvKzZuMjVIa2l3SGV5bHRsOWZ1cFEwSlcyM01yc1I2RWwybU1qQ0FFTEwKQ0l4TjdrOGFRTk92SndmV25LWjQ0U3BIalFPUXdtTTJySlVpZzBhZURUMWNMck9sVDNSVndUeG5DK00rN2V6SwpTZElza0ZZR0ZXdW12NlBZSVZBMy9MOE16T3dWeGs1WWwzcnpJaVh4UGlrdU1FeEtqNlRsNU8rQjBXQ2c0UVVUCjFGdk1zZksxNUwrRjdaeExuVi96WTVmQ2VBUEY2dXZDYjJ4VFBBeGZwN0VxK0tsSEdybzBWb1UwSGRSNFJLR2YKZlg0TytkZ3NNUHB1K1lQWTBWVGZTVjdVN2dWdklPcHhzc2lQbXQwdmRLSjJLK04xWUV5TmdKVlBCNUtyVXZveQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=" + privateKeyEncodedRSA = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRZ0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1N3d2dna29BZ0VBQW9JQ0FRRFNQM1c2ekFUdDZuWTUKbVM3dU5Qeks3MHRHUWJaWlRzWllkSEUxUGpLcGdtT1JuZFdtc1pNN2RuYkNGWFllN29weFFpL2hyclJ3UngwbwoyWjhOM3E2L1o3NGliT1ZDa2JkU2xpUW9ROVhKYUlRMy8zOHJKandTWVQ1NG5ReU8ydStMYUwyTWc4RFIrUmhHCjRoclJCSzB3ZEc4N0I3RkVrbUordDNJY0tURmJweHRTOE0yam5NNGM4eHhiWDMvUGNKY2tPc1ZXdlNIRCs5N2UKVWJZYURsbmc1anJJeTdHK29CVGVTMnkyWE9NYURyd1lHNHV3T3pLV0hzYTZxUGlMWEdoOFF4Y25UMFo3QWdMcAptOTBtQWp0L2hyeGVqb3JrdXp2N2tXTWhpZ0VGSjlXd0sreXJzM3JYNFYyTnlCUXRpeTBmU0gzVjFWV1ZkSDZpCmZvcHVKRGppUXB0WmF1Mk4vT3B5cURWdlZmVU8rWlVjWmNZSXJtaVF3N1JFNjUyZ0pwMUlucTQrRU9Ob2tvc2IKRTIwVFEycmhZRUlWK2pYMUMvOXMvMisxUlF0dktPeHVCZCtpWXZZaFRONm4zTFZKRi9nZUxGbjVhUTNEdmk5OQo0ZHkrU2RXSnU2ekU5aHlYS0RMRCtoZENtUk4rSWpTcGw4bFJicFdjQVRoeDhtbmxpSWRqWFU5cDkvd2QyY1J3Ck1YN211VHFDSU0wc1Q2SXQ0R3VZdWNVOGIycHNPcUp4azU2SExueUtvdlZoQmlTT3lIemk5b1RPQnFCdlRyV2MKMCtMS1lub2diUFRpR2MxMmQvcU85VGFvcngzZDdSMkIzWmpzTTRaRlJld2tSUko4Vmw2WStrZ3ZYcEdyczR1Wgp1TkF0Ykd6a1B4UG5xZEJqT2U4aU5RaVdrMGprZVFJREFRQUJBb0lDQUFPZmZGUGZ1Q1lHdmFBaklrcVFqSVZOCllsVmFSSWpTeHJBM2hzdml2OVMrbm5YMVFQMUNYM0I0TmlFRXV2MndJVXFhV0F1TTRIeDBKKzNRUjR3TVRrNlcKRWJ5emRscVRVRDY5YWtUQ0JuNXJWL3Z2RElOSWdXTXFYSDA2UWtFajhnVExwVU0xUjFsVzhETFRLcUIzY3RTUgpsNzV0LzRGWVZHelg2Y0RQejVORGFldktvb0NJbWQ5VEs5Q1hSZ2lPYVhjQ1hFR0dZMzA3d0l6QmlRc2g1dGZ0ClNtTmVGSTRJWVg1WWU3aHVHVlpxOHBWOUdWeDJ2Z1JxNWdSMEY1OEVmMDNFMzRmdk54NWZobi85WTVqZGFQdTYKN1ZGajBNT1M4UkdyWVFpWmx0MlQ0TkYzQmFPMUpXVEZuc0JzQzR6NEI2c0dWaG9kTndCVnBaT3A3VHRyVWJUcwphN0lMMHlMUy9MbWlyQWhRUjJRb0x3b3lad1lpU0xIRkJreGVzeDFETlppYjArY2kzd2d1N2NWemRQYkVicWdyCjNKYzBpbUhzaTViaWFjaENRc3A1Tk5xL1VoZ1JaciszWmllQmFOQ1VlR09BTWZMR2ZseUFoT1lRM21VUWpFTWEKQ3pENW9xWWVqYU14eDd1R1VFUDh1dzJpSC9vZlA3OGtESUN5M2xtSit5eFptM255MEFFc1g0TEQyRW9PaW1oYwp5MGp6UXVsSnBiSXh4aGtNSEhHdDBoMEZwbk5rbHc5NVlKOEpEYjlWYzNwdjRSNkpyV3FlZkV1OXpoQzVvWHFYCmE2aUpLZE5mS21iT3d5cTlYTWJ6cGszQUpvOWVXMFZhSGdpMU1TL1owdjc4bTlxV1d1Q3V4bUxnNnBHYTBnWTMKRnQwYVRHSnFxQ004c1B1NHI2dEJBb0lCQVFEMlFYV1UvWU94Ti9YcTZ6VEx0Y0NTUC9zcU9HQThWanpwUnZ2TApiUDZFVnpPb29HRWFvSkFLTTFURitmRzVFK2dsQlljTUNxU2t5Y3EyZXV0aHM3MW1hUFgrdzJCVVBYOGZ5VmxFClFGUHQzQXlGV3d3bzFOaFZkelBDYi9SNVMwWVFUS1VNZytrSHhOM2M4Q0pmU2lZT0xRaHVrQ09JRHp6U0hqV2MKUjM4c3prS0IzaDlCTXEzMFF3TmhqS1dTRlVMaWloZkFrT1krYmlvQWxqTWgyRzVCb2RwWEVNSmxlNldCSTZmSgpaNG1wcjNDcmQvN0xEZUhIUzdUVG04UUsraGU0TW5PaHBFWEVjS2kyeHozMDV4Q3pRNnZFbmJHSWNKc2U0eDBHCk11c3VHVzMzaGMrb3NodjFtZHcxL3FFUkhoZitLMU5CQzZQZEh1Q0ZHMVQvbWFZVkFvSUJBUURha1Q3cmJtSEUKWlBqTHVNY0ROaVBpT2JER0Faa0pkWE9BVjlBQWtrTzJ5R1BUZTRLUkgyVThWc2dqekJjLzk2RlA2eW9HNVQxTgplbDlXWi9FcmZlbXM1NFdXeVpUOGpYWXFpeURBNG9Yekx4M3Bkb3ZVRUZnckRBZUg1WXl5Ni9mallBdDVqS1ZrCkw5aFh1cUNFZlBXZmxOaUJ0OE5pVXNCQUcrRXpsNVBzQ0xIQnlvM2x5OXUyTTFibEEvVkF1eHNuckhoVGpwWVcKNHQ3NXpDcjByTW5Ha3lPSDdvMWp6T1VJYU1CcDVid292bHdTb1pIWkRUSkdFcWhiSlRSYUJlQ2JLYzhBUFZHNQpMbm9VUnk0b3BISGFEcmEyWVRHeUN0bVhtK0h4WHkzeGhmZHFzUWFuQ0pVY0p5b3NwMFJvL2lxYWJpQ3dYWVUvCk9Cbm55clQxSlNIVkFvSUJBR3VlWmVXTCtWNmNwek5ZUVVWNWs4UVdoQXlLZ0x3OXIvYisxNUdxZTN5WW8zSGgKVFM2VzF2d3VQTEVjcjJBRDdDTXB6RUFkOHFBMXRBcVZvNEthUzM2VEJsYWxTZGJtM1VTbCtRWVQydG9MbmNrMQo1aFYrRjJFYWJCdGdWQVlpT0dkdEo0QlZzYVI4aTcwL2tMWDJNTFZuUnRVUzF3UmlMR0ZqWkdoODhuNUJVZDF4CmxsVW04ZERhN0lKWU5nK21qUWwxOGpWczNjS1E0SGhMSytOeHM1V3BSME5maHFWVktScEwyOHJ3SGNCemRKanIKSXdYWWRrQmp2STN4OS9ZWUgvK1d4T1B5WjY4VzBSUzM5RUt3TEtNN1FyajFkWjI4SUg2YUlKZ1I3cWZCNDBZVwpTNDljNzAwaFJaU3ZSL0swSlNZbUJ3ZFpMKzYxek1jL0Q2RjRvNVVDZ2dFQWJjUDk5bHlVQ3Y2dW1Ca3ZFU1RTCmRwMkVjcHlBeitoRlhsSTdhdDRKMWJUanRXVFUySzhNdDNYWncyaU8wSmc3VWhpSEhibG94UTFNN2ViN2psMEkKeXNYbktDZ0tnNTlEbGZBVFBldEZYRER3Yzd3T1V5ejJLb0E3RS91clludnhIU2F4L0pRdXg1Ympyb05TYzljUgp2OWdQdDIyaldUQzN6anB5S2VmWTZQUWcyWE14T2hQY1BxK2YxeG5heEd4ekljU1RGVnVKY3VyekVqNS80Q3NhCmxuaDBvcUtpTFZuTU9DSHJhQU54TUlFUldtWDhDaVovZGdPT3UxOSs0Q3NOZHI5VGJ3cGNqWVNTMkxZNnJ6eU8KMVBVSXU2VXFRUUVEOEFqZ09za1RHTFd2NE13UXpEZ2FNbTVVMXVJV0VDaDlHdHR0M1VUS1UwcUljQWswUWQwcApGUUtDQVFFQWhiMlphM2kyY2NlUVBzc2MvYVBqR2NVMjErVWZvbE04Vndvck5kNE93eFZSTEpUNXZwN1lzQ1pKCjdUSmJvUXRpNlZVRmZXUGNsb2JiWnB0b3lURktDcHRwNm9OejMweTFKK2VEaVhzR2lNZEo1Y3ZDVDFXWWhLSjQKWE1lMXhTZnZYVTlMcEZTVURscmczcm0xYVZXV01TU1Z0Q1RIODRBdDdQNUhBN0o3ZGVaM2ZjOVJxeUg3NkNYNQpiZVBHWC9SWEluVFN6T20zOHZUaXpYYnVuMjlxMjV0bW93WEczdEdOTHE3K0xNUlBBVCtQYWhyVnVyakRwb3d1ClpqSFRYMFM4N0VTa2QwYXRSMHd3YUM4R2IvUkgyUmhkNGphQzQ0WUlBNzJBS0xQdEdHaE0yemIwY3hRM2VpK0YKZXFiWGJEVEsybHE5LzVCL2RLRmNDNnJ1MGo3R3V3PT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=" - publicKeyEncodedRSA = "LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUNDZ0tDQWdFQXVhTkpwYmdWT2VSQ2g4TmFGaEVBYytTZGplKzhPV1NLdGxPZnFFd1ExWVlGQytVTVRjNHkKdHZDQWxRRjNvRURTdDBhR0tvRkVyamY5MldzeXZITis0WG5ZRnZZSkVnM0ttd29OR3BmbXN1ckErcnhscFhDSQpsNG8zLy9IT2RoemZDc2l3TGR6VWUvbCtFQ1NXdkRnZzRoN0MwdlBIYVE0dGpyYUVMZ1VYSmVTaEQ4ek9qVlZXCk9SaEVKYWVEVHlKNnArZGtkS0ZFVEt6bmVuSnNFTmRBbmwzd1pORlNYbDg1L2FjL05DRDNFMXpQNmNkc0F5bnAKWTlzQk9ER1pmZTh5ajBWVFNQOW4rTXRKdWJtRG9xYlFBV09IUDNNRCs0ZnBTTnA1QS9Oa1hicDdneHkzWEtZRwpQUlBrNjlQZUVhZFdYMnRRaThDTi9QcG51TUJtUzdRdFljMVQ3RzZLZ1V4ZEJBQ3Y0Vm5VbHc5aUlRTzZPWFlsCkRJUHhHYXc2QzNUOGhsR3l6UEM3TWU2cjRFUURuQzNkYXFTSU1sbFFWSkFTakVSNWRNMXZjRFVmZWYxMnh4YXcKQkRFTmRFTVpOeVFRMHVFZGtFVVZLZTZwaTh6dE9uV2c1QkRVWWxuOEJNT3pKczZCVWQxR1VFWHhhenBhdHB0dAphaFhYVjhYZTUwSHloOXNWNEVDUENZUFpsb0JGU1IybHJKTExabnFDaFROM2s0SHV4R3Uydmxsc2xCbzZZT1R5CkRQQ2dJMys2TDFyUU9uM2pBZ3RWVy9ZdDAxS2RDK0tSaE5tU2w1eFVmL0ZZc091c3J4bU1oL2JnR2sxZldtZmMKbC9KNFBhRlNiS3VkWXZIeFp6MVdqMm5zZitQNEg5c2JZdUJya2RmSFhnQVVOTk5YNjYxVjFkOENBd0VBQVE9PQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCg==" + publicKeyEncodedRSA = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUEwajkxdXN3RTdlcDJPWmt1N2pUOAp5dTlMUmtHMldVN0dXSFJ4TlQ0eXFZSmprWjNWcHJHVE8zWjJ3aFYySHU2S2NVSXY0YTYwY0VjZEtObWZEZDZ1CnYyZStJbXpsUXBHM1VwWWtLRVBWeVdpRU4vOS9LeVk4RW1FK2VKME1qdHJ2aTJpOWpJUEEwZmtZUnVJYTBRU3QKTUhSdk93ZXhSSkppZnJkeUhDa3hXNmNiVXZETm81ek9IUE1jVzE5L3ozQ1hKRHJGVnIwaHcvdmUzbEcyR2c1Wgo0T1k2eU11eHZxQVUza3RzdGx6akdnNjhHQnVMc0RzeWxoN0d1cWo0aTF4b2ZFTVhKMDlHZXdJQzZadmRKZ0k3CmY0YThYbzZLNUxzNys1RmpJWW9CQlNmVnNDdnNxN042MStGZGpjZ1VMWXN0SDBoOTFkVlZsWFIrb242S2JpUTQKNGtLYldXcnRqZnpxY3FnMWIxWDFEdm1WSEdYR0NLNW9rTU8wUk91ZG9DYWRTSjZ1UGhEamFKS0xHeE50RTBOcQo0V0JDRmZvMTlRdi9iUDl2dFVVTGJ5anNiZ1hmb21MMklVemVwOXkxU1JmNEhpeForV2tOdzc0dmZlSGN2a25WCmlidXN4UFljbHlneXcvb1hRcGtUZmlJMHFaZkpVVzZWbkFFNGNmSnA1WWlIWTExUGFmZjhIZG5FY0RGKzVyazYKZ2lETkxFK2lMZUJybUxuRlBHOXFiRHFpY1pPZWh5NThpcUwxWVFZa2pzaDg0dmFFemdhZ2IwNjFuTlBpeW1KNgpJR3owNGhuTmRuZjZqdlUycUs4ZDNlMGRnZDJZN0RPR1JVWHNKRVVTZkZaZW1QcElMMTZScTdPTG1ialFMV3hzCjVEOFQ1Nm5RWXpudklqVUlscE5JNUhrQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=" // Testing importing an existing key that's been added to TUF already. // Generated with: // openssl ecparam -name prime256v1 -genkey -noout -out privkey.pem // openssl ec -in privkey.pem -pubout -out pubkey.pem - // openssl ec -in privkey.pem -out privatekey_encrypted.pem -aes256 - // And encrypted with this supersecretpassword - existingEncryptedPrivateKeyPassword = "supersecretpassword" //nolint: gosec existingEncryptedPrivateKey = ` -----BEGIN EC PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: AES-256-CBC,3C33CA88DF439D434ABDB2DD03491BEC - -A9UPVwTxy82/vDcG9q/e5SDKYokAGYvMyS5KD9rfyS5RGGQDdpkQPK0q6v9AFJbn -VCphFSJvnjFAR90XgF2EK+fVpX2GQjFEPhODVzAmqjawZHfTeGeMU5cJ+nNW+O6A -71ay3pGMAEQAvrzEErTLzCsBf2HZV1ioeFZVwHysvAA= +MHcCAQEEIM6pOLxVCBLPNcwsA7BOOb9k4c0q//YjX2eSzGeLBru6oAoGCCqGSM49 +AwEHoUQDQgAEIvSnDm70zQ5+ezI0jetTGrPIOhetyv0ENmll0DjhsRKFWAX8zT38 +cXaAnFOJsC5M011+x6v+IMNkY/1jrWaHfw== -----END EC PRIVATE KEY-----` ) @@ -115,18 +99,17 @@ VCphFSJvnjFAR90XgF2EK+fVpX2GQjFEPhODVzAmqjawZHfTeGeMU5cJ+nNW+O6A type testConfig struct { private string public string - config string } var testConfigs = map[string]testConfig{ "rsa": { private: privateKeyEncodedRSA, public: publicKeyEncodedRSA, - config: testConfigRSA}, + }, "ecdsa": { private: privateKeyEncodedECDSA, public: publicKeyEncodedECDSA, - config: testConfigECDSA}, + }, } func TestUnmarshal(t *testing.T) { @@ -156,7 +139,7 @@ func TestRoundTrip(t *testing.T) { if err != nil { t.Fatalf("Failed to generate Private Key: %v", err) } - privateKeyEC, _, err := DecryptExistingPrivateKey([]byte(existingEncryptedPrivateKey), existingEncryptedPrivateKeyPassword) + privateKeyEC, _, err := ParseExistingPrivateKey([]byte(existingEncryptedPrivateKey)) if err != nil { t.Fatalf("Failed to parse encrypted Private Key: %v", err) } @@ -168,17 +151,14 @@ func TestRoundTrip(t *testing.T) { t.Errorf("failed to convert to Signer") } configIn := &Config{ - PrivKey: v, - PrivKeyPassword: "mytestpassword", - PubKey: signer.Public(), - LogID: 2022, - LogPrefix: "2022-ctlog", + PrivKey: v, + PubKey: signer.Public(), } if err := configIn.AddFulcioRoot(context.Background(), []byte(existingRootCert)); err != nil { t.Logf("Failed to add fulcio root: %v", err) } - marshaledConfig, err := configIn.MarshalConfig(context.Background()) + marshaledConfig, err := configIn.MarshalConfig() if err != nil { t.Fatalf("Failed to marshal: %v", err) } @@ -212,13 +192,13 @@ func TestAddNewFulcioAndRemoveOld(t *testing.T) { if err := config.AddFulcioRoot(ctx, newFulcioCert); err != nil { t.Fatalf("Failed to add fulcio root: %v", err) } - marshaled, err := config.MarshalConfig(context.Background()) + marshaled, err := config.MarshalConfig() if err != nil { t.Fatalf("Failed to MarshalConfig: %v", err) } // Now test that we have configuration that trusts both Fulcio roots - // simulating while one is being spun down. + // simultaneously while one is being spun down. expected := [][]byte{} expected = append(expected, []byte(existingRootCert), newFulcioCert) validateFulcioEntries(ctx, marshaled, expected, t) @@ -236,7 +216,7 @@ func TestAddNewFulcioAndRemoveOld(t *testing.T) { if err := newConfig.RemoveFulcioRoot(ctx, []byte(existingRootCert)); err != nil { t.Fatalf("Failed to remove fulcio root: %v", err) } - marshaledNew, err := newConfig.MarshalConfig(context.Background()) + marshaledNew, err := newConfig.MarshalConfig() if err != nil { t.Fatalf("Failed to marshal new configuration after removal: %v", err) } @@ -251,10 +231,6 @@ func TestAddNewFulcioAndRemoveOld(t *testing.T) { func createBaseConfig(t *testing.T, tc testConfig) (map[string][]byte, error) { t.Helper() - c, err := b64.StdEncoding.DecodeString(tc.config) - if err != nil { - return nil, fmt.Errorf("Failed to decode testConfig: %w", err) - } private, err := b64.StdEncoding.DecodeString(tc.private) if err != nil { return nil, fmt.Errorf("Failed to decode privateKeyEncoded: %w", err) @@ -264,10 +240,9 @@ func createBaseConfig(t *testing.T, tc testConfig) (map[string][]byte, error) { return nil, fmt.Errorf("Failed to decode publicKeyEncoded: %w", err) } return map[string][]byte{ - "config": c, "private": private, "public": public, - "rootca": []byte(existingRootCert), + "fulcio": []byte(existingRootCert), }, nil } @@ -291,110 +266,20 @@ func createTestCert(_ *testing.T) ([]byte, error) { // validateFulcioEntries will take in a marshalled config and validate // that it has only the fulcioCerts specified as well as matching number -// of fulcio-%d entries in both the CTLog configuration as well as in the +// of fulcio entries in both the CTLog configuration as well as in the // passed in map (that gets mounted as secret). func validateFulcioEntries(_ context.Context, config map[string][]byte, fulcioCerts [][]byte, t *testing.T) { t.Helper() - // This keeps track of if we've seen a file entry in the CTLog config - // for fulcio-%d entry. There should be one for each fulcioCerts - foundFile := make(map[string]bool, len(fulcioCerts)) - for i := range fulcioCerts { - foundFile[fmt.Sprintf("%sfulcio-%d", rootsPemFileDir, i)] = false - } - foundPEM := make([]bool, len(fulcioCerts)) - PEMEntriesFound := 0 - // First make sure we have all the PEMs that we expect in the map. - for k, v := range config { - if strings.HasPrefix(k, "fulcio-") { - for i, fulcioCert := range fulcioCerts { - if bytes.Equal(v, fulcioCert) { - foundPEM[i] = true - } - } - PEMEntriesFound++ - } + foundRoots, ok := config["fulcio"] + if !ok { + t.Errorf("Failed to find a PEM for entry") } - - if PEMEntriesFound != len(fulcioCerts) { - t.Errorf("Unexpected number of PEM entries, want: %d got %d", len(fulcioCerts), PEMEntriesFound) - } - for i, found := range foundPEM { - if !found { - t.Errorf("Failed to find a PEM for entry %d", i) - } + expectedCerts := make([]byte, 0) + for _, f := range fulcioCerts { + expectedCerts = append(expectedCerts, f...) } - - // Then validate that for each of those there's an entry in the CTLog - // config file. - // Then check the log config that it tells CTLog to trust these two certs - // above - multiConfig := configpb.LogMultiConfig{} - if err := prototext.Unmarshal(config[ConfigKey], &multiConfig); err != nil { - t.Fatalf("failed to unmarshal ctlog proto: %v", err) - } - - trustedCerts := multiConfig.GetLogConfigs().Config[0].RootsPemFile - if len(trustedCerts) != len(fulcioCerts) { - t.Fatalf("Unexpected number of file entries, want: %d got %d", len(fulcioCerts), len(trustedCerts)) - } - for _, fileName := range trustedCerts { - if strings.HasPrefix(fileName, rootsPemFileDir) { - foundFile[fileName] = true - } - } - for fileName, found := range foundFile { - if !found { - t.Errorf("Failed to find a PEM for entry %s", fileName) - } - } -} - -func TestDecrypteExistingPrivateKey(t *testing.T) { - priv, pub, err := DecryptExistingPrivateKey([]byte(existingEncryptedPrivateKey), existingEncryptedPrivateKeyPassword) - if err != nil { - t.Fatalf("Failed to decrypt existing private key %v", err) - } - if priv == nil { - t.Fatalf("got back a nil private key") - } - if pub == nil { - t.Fatalf("got back a nil public key") - } -} - -func TestDedupeUnmarshaling(t *testing.T) { - for k, v := range testConfigs { - t.Logf("testing with: %s", k) - cm, err := createBaseConfig(t, v) - if err != nil { - t.Fatalf("Failed to create base config: %v", err) - } - // Override the legacy rootca entry with our own for ease of testing. It - // doesn't really matter what it is for this test. - cm["fulcio-0"] = []byte("this is a test cert") - cm["fulcio-1"] = []byte("this is a test cert") - cm["fulcio-99"] = []byte("this is a different test cert") - config, err := Unmarshal(context.Background(), cm) - if err != nil { - t.Fatalf("failed to Unmarshal: %v", err) - } - // We should have original root cert, 0&1 deduped into one and fulcio-99 - if len(config.FulcioCerts) != 3 { - t.Errorf("wanted 3 fulcio certs, got: %d", len(config.FulcioCerts)) - } - checkContains(t, config.FulcioCerts, []byte("this is a test cert")) - checkContains(t, config.FulcioCerts, []byte("this is a different test cert")) - checkContains(t, config.FulcioCerts, cm["rootca"]) - } -} - -func checkContains(t *testing.T, fulcioCerts [][]byte, cert []byte) { - t.Helper() - for i := range fulcioCerts { - if bytes.Equal(fulcioCerts[i], cert) { - return - } + if !bytes.Equal(foundRoots, expectedCerts) { + t.Errorf("mismatched PEM entries") } - t.Errorf("did not find %s in fulcioCerts", string(cert)) } diff --git a/testdata/config/add-new-fulcio/300-add-fulcio.yaml b/testdata/config/add-new-fulcio/300-add-fulcio.yaml index 3f1199925..7694c4800 100644 --- a/testdata/config/add-new-fulcio/300-add-fulcio.yaml +++ b/testdata/config/add-new-fulcio/300-add-fulcio.yaml @@ -21,11 +21,9 @@ spec: - name: managectroots image: ko://github.com/sigstore/scaffolding/cmd/ctlog/managectroots args: [ - "--configmap=ctlog-config", "--secret=ctlog-secret", "--fulcio-url=http://fulcio-new.fulcio-system.svc", "--operation=add", - "--config-in-secret=true" ] env: - name: NAMESPACE diff --git a/testdata/config/new-fulcio/fulcio/300-fulcio.yaml b/testdata/config/new-fulcio/fulcio/300-fulcio.yaml index 4b65d2f07..f0becbbf4 100644 --- a/testdata/config/new-fulcio/fulcio/300-fulcio.yaml +++ b/testdata/config/new-fulcio/fulcio/300-fulcio.yaml @@ -27,7 +27,7 @@ spec: - "/var/run/fulcio-secrets/cert.pem" - "--fileca-key-passwd" - "$(PASSWORD)" - - "--ct-log-url=http://ctlog.ctlog-system.svc/sigstorescaffolding" + - "--ct-log-url=http://ctlog.ctlog-system.svc" env: - name: PASSWORD valueFrom: diff --git a/testdata/config/remove-old-fulcio/300-remove-fulcio.yaml b/testdata/config/remove-old-fulcio/300-remove-fulcio.yaml index 9a148531b..98f08f7eb 100644 --- a/testdata/config/remove-old-fulcio/300-remove-fulcio.yaml +++ b/testdata/config/remove-old-fulcio/300-remove-fulcio.yaml @@ -21,11 +21,9 @@ spec: - name: managectroots image: ko://github.com/sigstore/scaffolding/cmd/ctlog/managectroots args: [ - "--configmap=ctlog-config", "--secret=ctlog-secret", "--fulcio-url=http://fulcio.fulcio-system.svc", "--operation=remove", - "--config-in-secret=true" ] env: - name: NAMESPACE