diff --git a/.github/workflows/fulcio-rekor-kind.yaml b/.github/workflows/fulcio-rekor-kind.yaml index 57add244..ecb1dd72 100644 --- a/.github/workflows/fulcio-rekor-kind.yaml +++ b/.github/workflows/fulcio-rekor-kind.yaml @@ -92,7 +92,7 @@ jobs: - name: Setup Knative uses: chainguard-dev/actions/setup-knative@main with: - version: "latest" + version: "1.7.x" serving-features: > { "kubernetes.podspec-fieldref": "enabled" diff --git a/.github/workflows/test-release.yaml b/.github/workflows/test-release.yaml index d00ca463..48468d12 100644 --- a/.github/workflows/test-release.yaml +++ b/.github/workflows/test-release.yaml @@ -62,7 +62,7 @@ jobs: - name: Setup Knative uses: chainguard-dev/actions/setup-knative@main with: - version: "latest" + version: "1.7.x" serving-features: > { "kubernetes.podspec-fieldref": "enabled" diff --git a/cmd/ctlog/createctconfig/main.go b/cmd/ctlog/createctconfig/main.go index 9e11eb6c..e7f8baf2 100644 --- a/cmd/ctlog/createctconfig/main.go +++ b/cmd/ctlog/createctconfig/main.go @@ -21,6 +21,7 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" + "errors" "flag" "fmt" "log" @@ -37,6 +38,7 @@ import ( "google.golang.org/protobuf/types/known/anypb" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/rest" "knative.dev/pkg/logging" "knative.dev/pkg/signals" @@ -55,6 +57,7 @@ const ( var ( cmname = flag.String("configmap", "ctlog-config", "Name of the configmap where the treeID lives") configInSecret = flag.Bool("config-in-secret", false, "If set to true, create the ctlog configuration proto into a secret specified in ctlog-secrets under key 'config'") + 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.") @@ -150,7 +153,16 @@ func main() { if existingSecret.Data[privateKey] == nil || existingSecret.Data[publicKey] == nil || (existingSecret.Data[configKey] == nil && existingCMConfig == nil) { - ctlogConfig, err := createConfigWithKeys(ctx, *keyType) + var ctlogConfig *ctlog.CTLogConfig + 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) } @@ -248,3 +260,23 @@ func createConfigWithKeys(ctx context.Context, keytype string) (*ctlog.CTLogConf PubKey: signer.Public(), }, nil } + +// create +func createConfigFromExistingSecret(ctx context.Context, nsSecret v1.SecretInterface, secretName string) (*ctlog.CTLogConfig, error) { + existingSecret, err := nsSecret.Get(ctx, secretName, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("getting an existing private key secret: %w", err) + } + private := existingSecret.Data[privateKey] + if private == nil || len(private) == 0 { + return nil, errors.New("secret missing private key entry") + } + priv, pub, err := ctlog.DecryptExistingPrivateKey(private, *keyPassword) + if err != nil { + return nil, fmt.Errorf("decrypting existing private key secret: %w", err) + } + return &ctlog.CTLogConfig{ + PrivKey: priv, + PubKey: pub, + }, nil +} diff --git a/pkg/ctlog/config.go b/pkg/ctlog/config.go index 96e1b873..f5055508 100644 --- a/pkg/ctlog/config.go +++ b/pkg/ctlog/config.go @@ -26,6 +26,7 @@ import ( "crypto/rand" "crypto/x509" "encoding/pem" + "errors" "fmt" "strings" @@ -216,24 +217,12 @@ func Unmarshal(ctx context.Context, in map[string][]byte) (*CTLogConfig, error) return nil, fmt.Errorf("Not a valid PEMKeyFile in proto") } - privPEM, _ := pem.Decode(private) - if privPEM == nil { - return nil, fmt.Errorf("did not find valid private PEM data") - } ret.PrivKeyPassword = pb.Password - privatePEMBlock, err := x509.DecryptPEMBlock(privPEM, []byte(pb.Password)) + ret.PrivKey, _, err = DecryptExistingPrivateKey(private, ret.PrivKeyPassword) if err != nil { - return nil, fmt.Errorf("failed to decrypt private PEMKeyFile: %w", err) - } - - if ret.PrivKey, err = x509.ParsePKCS8PrivateKey(privatePEMBlock); err != nil { - // Try it as RSA - if ret.PrivKey, err = x509.ParsePKCS1PrivateKey(privatePEMBlock); err != nil { - return nil, fmt.Errorf("failed to parse private key PEM: %w", err) - } + return nil, fmt.Errorf("decrypting existing private key: %w", err) } - // If there's legacy rootCA entry, check it first. if legacyRoot, ok := in[LegacyRootCAKey]; ok && len(legacyRoot) > 0 { ret.FulcioCerts = append(ret.FulcioCerts, legacyRoot) @@ -375,3 +364,33 @@ func mustMarshalAny(pb proto.Message) *anypb.Any { } return ret } + +// 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) { + 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 { + // Try it as RSA + if priv, err = x509.ParsePKCS1PrivateKey(privatePEMBlock); err != nil { + if priv, err = x509.ParseECPrivateKey(privatePEMBlock); err != nil { + return nil, nil, fmt.Errorf("failed to parse private key PEM: %w", err) + } + } + } + var ok bool + var signer crypto.Signer + if signer, ok = priv.(crypto.Signer); !ok { + return nil, nil, errors.New("failed to convert private key to Signer") + } + + return priv, signer.Public(), nil +} diff --git a/pkg/ctlog/config_test.go b/pkg/ctlog/config_test.go index 9003a85b..362949a2 100644 --- a/pkg/ctlog/config_test.go +++ b/pkg/ctlog/config_test.go @@ -90,6 +90,23 @@ KTkomoSY/OxE/5doBCACehThH+96joWfgC0rXi9qAwZ6hwIMJAKy privateKeyEncodedRSA = "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpQcm9jLVR5cGU6IDQsRU5DUllQVEVECkRFSy1JbmZvOiBBRVMtMjU2LUNCQyw3NWUxNTkxNzQ0NTc4MjMwNGUzYjY1NGQ5NjhjY2M4MAoKV1pPQ1QrQXlaUmlaaFpDdXMveGxuR2dFbzNwTk1GRSsra0YvWVdBZUxMQjhmclNuL2NlL3VjbURuOURGQ01VZApORlNhSks1YzNvWEJCckt0Uk1sQ0I2S2RGblJucHNpVHUzbU1sVzVPdzRNTVh0L3JJaEFXbDFDaUFYUkdqL0NWClg4clRvQldpOFN4dXh3aWgrOHlrY0VpaVg3Ti9aWkNYOUppbjFQeTc0QUczWHBPT28rbFhwKzRTN1BwQmlZbzAKU0pzaUZ4Mlk0LzF4RXBWMEVWdmZobmN1R0k1R0ROcm0wUnBBNnNraGRSbU5iMW1HYkR5ZXdnMndPTTJTRHRGQwpSWEE5aFAxV1czUWx0VGhXRml2VTU0SngrYktMc3Fnem9JMzNZRmRFdnRPNmNxWCtoOVprN1pORmxaMDNaREk4Ck5RdzEyT3Z3VnpEeE5XdmFYVFhIMEpJc2tUSTE5cjFCTnB6aW1xdWg4ZWRYSTFuT2ppbUM5VjlRQTF0TVNmWmkKVmM2RW9VSG55N0xNVXkydG1yN3R2M2pLRWJHT09nclNRcXhJejAxcjFtV0dpREU2YkNDeFFueUhOUHExQmlIRQp1WTR3K25iU2V5UDhVc3h6YjlVNkRSd2IxVzZkMjlmbGNsdFp1TFlqdEhRL1JwRUdxbWRNc1RmRU1wRUVTNU9jClJPVmtsQlpQM0NHN3I4NGN0aVBMUGpvZnk0aG4rai9SeTBtT2tzcFcyVjNlQ2FvdGQwU0lQZFhxT3h6K2p3U1kKaDRBelg1VHdMSlg2UDlSaVdVZ2xQUWZKNjhCclpOT1Ywc3IwaEIwc1NXY25mSWorWWxSSzMvUXJTZGdhellRRQo0ZHBrK0hDUUE4bkdwN1M1Uks4ZGdxek1QYS96Z1AvR1dnN0t5K0dVWFB3cXRhalBFd1ZVWFJPNGViWUJCQ1RwClFHYnRSSmdRRjFzSmtqN1F0d0J4NzVoM25ZSjlWdEhiMWR2d2FKL09mWklhSklKQkRROVlyRGtqMjVmdDdtWlEKZVlGN1c5NlhCU0xHc2ZhdzlDMXhNRXZVY081UGtkS3ArR3pvMFhUaXhNb1U1Q0h6Yk0rQnFqMFZycGpNV29XbQphbHZpYVc4RlNYQkZQZUNoNFIrOXhwN1Q3ZWl6OU9uRFpKRVdnR1B1YXZyN29XL0t6blE1RS9SVlJtRllaZVY2CkluRXlmUVlRVE5QMnVBWjdibFRCeEc0VlhWdjA4ZUhWVHJ4YkVBcmE4VXJrZkQ1Nm02U3M3YWsrYU1mdG0vSnkKZHBxbTJ5YWlpSDd1SmRiZ1hyNTBnNEFDUThtZlE1QjNpbk1Ea0NFZ2RyQTRTQXg1YXNaQjJ0V2l1VC9SZFVSLworMUpXbjNKdXBEL2dhWU5CTVBTRzhjL0hKa0xmeE5UdzZVaHBBTlg5TkErTlE1UVdCUTVaaWNhbUNLQWJUczEvCjhUUlJlbnBLdUdhZXVsazhneVNOTm5xa0plZUNlZ1c5RGR1d3BZcUpjVkJ3L3lrY3BDc0hleVVZSTFOZkd0dCsKcTJ0Z2h0WGhaSGpFV1ZhcWVIb0JOTHlxZ0NET0l6U1QyTnFSeC9yYXhXckl1K0JwMTJTazNpQm5pc1Y0cE02NQprMnFaTDVhY2FDb3lIWTlSWStKSThYdHBzcHVjclViZnp6K0F3ZVZpdkcwN0hkOWRnV0dMRHRwMDJ4VGFMb09pCnp1NnV1dU9heUtZaUI4N2RBYlJlZUY0RVNrTlZOM3k4c1hIS3lnRlFvN2pqRExWVnBwRVVYWC8vN081VU9aZ0wKMWtWcVJ4K2hLeTQvTnVqQUVReWJubnMzRlpIMHBDMDQvcnAwS2xBeHlmRzBRNWJvTWdBeUR0VGlyUFBzK2lwTQpveDh1aWdlQlFaTmZyWW41TVA2UWVUSWY0QWx4NWNzSktxb1Nzb2dZclljbWhoSkhkc1Q4QUpidlpXSUo4L01JCjRFKzJ6UEZSNUlOYzNGbjVoVFpnRzNMQjh4N0ErTHlCbEdNR2owdW9melVzdnZMNnpxeEtqZ3F1Qm5DbTNmTHYKSjFnaDFYbkUyeENVekZhSlpQOVVNU1N2bmVmci92TzBFMjFxL0NlSGRUNWZsaUl4UjBZQ0t5MENvd3ZIeUdyYwpmc2JWWS92dGhIcUxLYmx0Vkh0bndPOExFTmhWZmVweGhFUy9sQUZrWmgrbmZFYjVsUnRZb2hZSW9RUkFOR1A0CmhCS1BhWldua0kwbFl5TmJNU1h3d3U0R2lScFdUUjhUYW84WDlXSWlJdmgyc3hHd0NleTBPSGZCVGtoYnR3Y2sKQzlaT0pERW9SNXBlOGZXSitzWXBia1laYjd3TzhSVEMyNlBGZTRQdEtKRFNGWXlOMzM2T1ZVdzM2RkZmVzR0QQpvcGtBdkRVbDdXVEZ1TlB4RVZ3SXZQSnN2ZDdnaG9Kdm1MYm4xQldQTS8wY0lobkQ0YkdrbjBsVURTTUFjUXIvCkV2R0h4Z2xpeU4wdktnOWU5SE9VNkVOYVdMaTRzemhwdE05RzF6UnBic01CV05zRW5TTEEwL3BaS01TOXdGdk8KL1N2VEVFc3dlM2xKWjV3WFc2R3lUdURFMzQ4Z011UFk4RmpCajZjQVo3RUJLTmYrWG1TY3VQTHYxVzd3Nm52cApKTGtQRS8wQmswdEZWRndlZUlERHJOTEg4Z0dseTY3MHk5cUxQSi8rMUhwdXpwR2tqc3RwWEs2QkRqWXUzeEFlCkhsd3E2RDNmRTMrZ0VkcW5RUmhZeHRacWxqaGIydFIxYUErZndhcWVBT2dNOG43RkNaY0gvK0ZBakdhRis3YjEKQ0RIdjA0cktKdVFGZjZTKzNzQktaVW9aVllJakxidE9VWko4c2QvZEZaQ01mNGhnN3RiaXNQeVFxMjQ2MUI3Wgp1SnFidlozdHhiT0lpd3k0cklCT2VtTnJaR3ArYmMzT3FuOHZQaEtpM3c2aDd4M2lvUzBxS250bStMbG11MXBqCnZOZnQwNmFZYklGcUhkY3ZqQ1AxajZNemY1Rm9TMGhmVnlpRmltOVFUOVpGeDl4bDNGeHBkK2VsYkxYY09pM0YKU2dISWE5SUdYQXNsSmo5dE5zdC9GaHBxeFdQbmt5c3dNTjRCQkJ2SDJNZU5odWpVUGdWblp5bEVodU1jQTBrcgpzdWMrNmliMEdRYUhRSW1pOHpmQ1FyQUVXMzZ2WWRxK1M0ZjBOeEZVNFZkclFzd2tpYlJhSytBTkZGY1ZKUzFJClcxWFdoU0FKV2VPUjJONmxJVFNqZVNDbXc5bnlXb1prZXBvSEkrcTlDZ0cvV09qRy9ZUkdjZUJNSFZQbk1zNDYKanA1NitvQkdXSUVpK3dvRU51UFV0aDNlZnZNT2dGTlBGZWh1QUFUUHBOeGtaMlBheHpRVmp6NXJGR09tNmJtZgoyWExIQVZxcTFjYVhEY1RidGxoSWh0Q3A5cmlGcXIzc2R6YlFxWThCWUsyQjdyQ0JHbXFjZld0akgvWUZadkNrCnFWNWpoOHQ2MFp2Z2F1bU15Y2h2NGNVaFRWMFJzZ1BteE9GMzdUenY1T0d3OVBKeS9sdFphNncveFFZQWVMaHQKRnVWN0I0WFJvdERyYklvZkNNM1ZObXdXTnN4R29LNWY1LzV6bVBEQ1JQNjZDNkkwbWVLNjZXb3prY0N2NTRMcQpJZDJaZTN5aUY2bjE0K05xZUZMWGVsdnRvay9RSWdiTEd3ME9XVEQyaFJtZGVYMjhUMEVMMW5kZ0ZUYU0xV3NlCkVJdXQxWXNLWXk1Vml2bDg1V0JiZEsvKzZuMjVIa2l3SGV5bHRsOWZ1cFEwSlcyM01yc1I2RWwybU1qQ0FFTEwKQ0l4TjdrOGFRTk92SndmV25LWjQ0U3BIalFPUXdtTTJySlVpZzBhZURUMWNMck9sVDNSVndUeG5DK00rN2V6SwpTZElza0ZZR0ZXdW12NlBZSVZBMy9MOE16T3dWeGs1WWwzcnpJaVh4UGlrdU1FeEtqNlRsNU8rQjBXQ2c0UVVUCjFGdk1zZksxNUwrRjdaeExuVi96WTVmQ2VBUEY2dXZDYjJ4VFBBeGZwN0VxK0tsSEdybzBWb1UwSGRSNFJLR2YKZlg0TytkZ3NNUHB1K1lQWTBWVGZTVjdVN2dWdklPcHhzc2lQbXQwdmRLSjJLK04xWUV5TmdKVlBCNUtyVXZveQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=" publicKeyEncodedRSA = "LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUNDZ0tDQWdFQXVhTkpwYmdWT2VSQ2g4TmFGaEVBYytTZGplKzhPV1NLdGxPZnFFd1ExWVlGQytVTVRjNHkKdHZDQWxRRjNvRURTdDBhR0tvRkVyamY5MldzeXZITis0WG5ZRnZZSkVnM0ttd29OR3BmbXN1ckErcnhscFhDSQpsNG8zLy9IT2RoemZDc2l3TGR6VWUvbCtFQ1NXdkRnZzRoN0MwdlBIYVE0dGpyYUVMZ1VYSmVTaEQ4ek9qVlZXCk9SaEVKYWVEVHlKNnArZGtkS0ZFVEt6bmVuSnNFTmRBbmwzd1pORlNYbDg1L2FjL05DRDNFMXpQNmNkc0F5bnAKWTlzQk9ER1pmZTh5ajBWVFNQOW4rTXRKdWJtRG9xYlFBV09IUDNNRCs0ZnBTTnA1QS9Oa1hicDdneHkzWEtZRwpQUlBrNjlQZUVhZFdYMnRRaThDTi9QcG51TUJtUzdRdFljMVQ3RzZLZ1V4ZEJBQ3Y0Vm5VbHc5aUlRTzZPWFlsCkRJUHhHYXc2QzNUOGhsR3l6UEM3TWU2cjRFUURuQzNkYXFTSU1sbFFWSkFTakVSNWRNMXZjRFVmZWYxMnh4YXcKQkRFTmRFTVpOeVFRMHVFZGtFVVZLZTZwaTh6dE9uV2c1QkRVWWxuOEJNT3pKczZCVWQxR1VFWHhhenBhdHB0dAphaFhYVjhYZTUwSHloOXNWNEVDUENZUFpsb0JGU1IybHJKTExabnFDaFROM2s0SHV4R3Uydmxsc2xCbzZZT1R5CkRQQ2dJMys2TDFyUU9uM2pBZ3RWVy9ZdDAxS2RDK0tSaE5tU2w1eFVmL0ZZc091c3J4bU1oL2JnR2sxZldtZmMKbC9KNFBhRlNiS3VkWXZIeFp6MVdqMm5zZitQNEg5c2JZdUJya2RmSFhnQVVOTk5YNjYxVjFkOENBd0VBQVE9PQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCg==" + + // 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" + existingEncryptedPrivateKey = ` +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,3C33CA88DF439D434ABDB2DD03491BEC + +A9UPVwTxy82/vDcG9q/e5SDKYokAGYvMyS5KD9rfyS5RGGQDdpkQPK0q6v9AFJbn +VCphFSJvnjFAR90XgF2EK+fVpX2GQjFEPhODVzAmqjawZHfTeGeMU5cJ+nNW+O6A +71ay3pGMAEQAvrzEErTLzCsBf2HZV1ioeFZVwHysvAA= +-----END EC PRIVATE KEY-----` ) // testConfig wraps the private,public, and config into a single struct @@ -138,7 +155,11 @@ func TestRoundTrip(t *testing.T) { if err != nil { t.Fatalf("Failed to generate Private Key: %v", err) } - for k, v := range map[string]crypto.PrivateKey{"rsa": privateKeyRSA, "ecdsa": privateKeyECDSA} { + privateKeyEC, _, err := DecryptExistingPrivateKey([]byte(existingEncryptedPrivateKey), existingEncryptedPrivateKeyPassword) + if err != nil { + t.Fatalf("Failed to parse encrypted Private Key: %v", err) + } + for k, v := range map[string]crypto.PrivateKey{"rsa": privateKeyRSA, "ecdsa": privateKeyECDSA, "ec": privateKeyEC} { t.Logf("testing with %s", k) var ok bool var signer crypto.Signer @@ -318,3 +339,16 @@ func validateFulcioEntries(ctx context.Context, config map[string][]byte, fulcio } } } + +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") + } +}