From f60969558efca745d769859b9ec0d9e56e31b175 Mon Sep 17 00:00:00 2001 From: Slavek Kabrda Date: Mon, 26 Aug 2024 14:59:30 +0200 Subject: [PATCH] Address review Signed-off-by: Slavek Kabrda --- cmd/tuf/server/main.go | 6 +- go.mod | 2 + pkg/repo/repo.go | 7 +- pkg/repo/repo_test.go | 157 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 3 deletions(-) diff --git a/cmd/tuf/server/main.go b/cmd/tuf/server/main.go index e5e4b1990..2ea1972ad 100644 --- a/cmd/tuf/server/main.go +++ b/cmd/tuf/server/main.go @@ -52,7 +52,7 @@ var ( // Name of the "secret" where we create one entry per key JSON definition as generated by TUF, e.g. "root.json", "timestamp.json", ... keysSecretName = flag.String("keyssecret", "", "Name of the secret to create for generated keys (keys won't be stored unless this is provided)") noK8s = flag.Bool("no-k8s", false, "Run in a non-k8s environment") - metadataTargets = flag.Bool("metadata-targets", true, "Serve individual targets with custom Sigstore metadata") + metadataTargets = flag.Bool("metadata-targets", true, "Serve individual targets with custom Sigstore metadata. This will be deprecated and removed in the future.") trustedRoot = flag.Bool("trusted-root", true, "Generate and serve trusted_root.json") ) @@ -198,6 +198,10 @@ func main() { ctx := signals.NewContext() + if *metadataTargets { + logging.FromContext(ctx).Warnf("Serving individual TUF targets with custom Sigstore metadata will be deprecated and removed in the future.") + } + serve := false init := false overwrite := true diff --git a/go.mod b/go.mod index 494de4eb1..598799125 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/sigstore/sigstore v1.8.8 github.com/sigstore/sigstore-go v0.6.1-0.20240821212051-2198ac32dd94 github.com/sigstore/timestamp-authority v1.2.2 + github.com/stretchr/testify v1.9.0 github.com/theupdateframework/go-tuf v0.7.0 github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 go.uber.org/zap v1.27.0 @@ -224,6 +225,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect diff --git a/pkg/repo/repo.go b/pkg/repo/repo.go index 619274c20..871bd3216 100644 --- a/pkg/repo/repo.go +++ b/pkg/repo/repo.go @@ -20,6 +20,7 @@ import ( "context" "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rsa" "crypto/sha256" @@ -191,7 +192,7 @@ func CreateRepoWithOptions(ctx context.Context, files map[string][]byte, options // * CreateRepoOptions.AddMetadataTargets: true // * CreateRepoOptions.AddTrustedRoot: false func CreateRepo(ctx context.Context, files map[string][]byte) (tuf.LocalStore, string, error) { - return CreateRepoWithOptions(ctx, files, CreateRepoOptions{AddMetadataTargets: true, AddTrustedRoot: false}) + return CreateRepoWithOptions(ctx, files, CreateRepoOptions{AddMetadataTargets: true, AddTrustedRoot: true}) } func constructTrustedRoot(targets []TargetWithMetadata) (*TargetWithMetadata, error) { @@ -334,6 +335,8 @@ func getKeyWithDetails(key []byte) (crypto.PublicKey, crypto.Hash, error) { default: return 0, 0, fmt.Errorf("unsupported public modulus %d", v.Size()) } + case *ed25519.PublicKey: + hashFunc = crypto.SHA512 default: return 0, 0, errors.New("unknown public key type") } @@ -400,7 +403,7 @@ func concatCertChain(leaf []byte, intermediate [][]byte, root []byte) []byte { func getTargetUsage(name string) string { for _, knownTargetType := range []string{FulcioTarget, RekorTarget, CTFETarget, TSATarget} { - if strings.Contains(name, strings.ToLower(knownTargetType)) { + if strings.Contains(strings.ToLower(name), strings.ToLower(knownTargetType)) { return knownTargetType } } diff --git a/pkg/repo/repo_test.go b/pkg/repo/repo_test.go index 9a078d488..8385b063e 100644 --- a/pkg/repo/repo_test.go +++ b/pkg/repo/repo_test.go @@ -17,9 +17,14 @@ package repo import ( "bytes" "context" + "fmt" "os" "path/filepath" + "regexp" "testing" + + "github.com/sigstore/scaffolding/pkg/certs" + "github.com/stretchr/testify/require" ) const ( @@ -56,6 +61,125 @@ c70LfiFo//8/QsvyjLIUtEWHTkGeuf4PpbYXr5qpJ6tWhG2MARxdeg8CAwEAAQ== MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEF6j2sTItLcs0wKoOpMzI+9lJmCzf N6mY2prOeaBRV2dnsJzC94hOxkM5pSp9nbAK1TBOI45fOOPsH2rSR++HrA== -----END PUBLIC KEY-----` + + tsaCertChain = `-----BEGIN CERTIFICATE----- +MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMw +MjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRp +YXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMM +R2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9 +FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/ +BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0j +BBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9 +HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEA +g+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMw +ODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2 +aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMG +A1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKI +Mhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZN +ojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T +AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYD +VR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIx +AK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w9 +5QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJE +HdNmyA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB9DCCAXqgAwIBAgIUa/JAkdUjK4JUwsqtaiRJGWhqLSowCgYIKoZIzj0EAwMw +ODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2 +aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTMzMDQxMTAwMDAwMFowODEVMBMG +A1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBS +b290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf9jFAXxz4kx68AHRMOkFBhflDcMT +vzaXz4x/FCcXjJ/1qEKon/qPIGnaURskDtyNbNDOpeJTDDFqt48iMPrnzpx6IZwq +emfUJN4xBEZfza+pYt/iyod+9tZr20RRWSv/o0UwQzAOBgNVHQ8BAf8EBAMCAQYw +EgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQU9NYYlobnAG4c0/qjxyH/lq/w +z+QwCgYIKoZIzj0EAwMDaAAwZQIxALZLZ8BgRXzKxLMMN9VIlO+e4hrBnNBgF7tz +7Hnrowv2NetZErIACKFymBlvWDvtMAIwZO+ki6ssQ1bsZo98O8mEAf2NZ7iiCgDD +U0Vwjeco6zyeh0zBTs9/7gV6AHNQ53xD +-----END CERTIFICATE-----` + + trJSONValidForStart = "2024-08-26T12:03:30.241272895Z" + trJSON = `{ + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEF6j2sTItLcs0wKoOpMzI+9lJmCzfN6mY2prOeaBRV2dnsJzC94hOxkM5pSp9nbAK1TBOI45fOOPsH2rSR++HrA==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2024-08-26T12:03:30.241272895Z" + } + }, + "logId": { + "keyId": "xBzny6gmou42sCYrHOzNuGqi1s2cMxcCEq1wrKF9XDs=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "RedHat", + "commonName": "testcert" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIICNzCCAd2gAwIBAgITPLBoBQhl1hqFND9S+SGWbfzaRTAKBggqhkjOPQQDAjBoMQswCQYDVQQGEwJVSzESMBAGA1UECBMJV2lsdHNoaXJlMRMwEQYDVQQHEwpDaGlwcGVuaGFtMQ8wDQYDVQQKEwZSZWRIYXQxDDAKBgNVBAsTA0NUTzERMA8GA1UEAxMIdGVzdGNlcnQwHhcNMjEwMzEyMjMyNDQ5WhcNMzEwMjI4MjMyNDQ5WjBoMQswCQYDVQQGEwJVSzESMBAGA1UECBMJV2lsdHNoaXJlMRMwEQYDVQQHEwpDaGlwcGVuaGFtMQ8wDQYDVQQKEwZSZWRIYXQxDDAKBgNVBAsTA0NUTzERMA8GA1UEAxMIdGVzdGNlcnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQRn+Alyof6xP3GQClSwgV0NFuYYEwmKP/WLWr/LwB6LUYzt5v49RlqG83KuaJSpeOj7G7MVABdpIZYWwqAiZV3o2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUT8Jwm6JuVb0dsiuHUROiHOOVHVkwHwYDVR0jBBgwFoAUT8Jwm6JuVb0dsiuHUROiHOOVHVkwCgYIKoZIzj0EAwIDSAAwRQIhAJkNZmP6sKA+8EebRXFkBa9DPjacBpTcOljJotvKidRhAiAuNrIazKEw2G4dw8x1z6EYk9G+7fJP5m93bjm/JfMBtA==" + } + ] + }, + "validFor": { + "start": "2021-03-12T23:24:49Z", + "end": "2031-02-28T23:24:49Z" + } + } + ], + "ctlogs": [ + { + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAu1Ah4n2P8JGt92Qg86FdR8f1pou43yndggMuRCX0JB+bLn1rUFRAKQVd+xnnd4PXJLLdml8ZohCr0lhBuMxZ7zBzt0T98kblUCxBgABPNpWIkTgacyC8MlIYY/yBSuDWAJOA5IKi4Hh9nI+Mmb/FXgbOz5a5mZx8w7pMiTMu0+Rd9cPzRkUZDQfZsLONr6PwmyCAIL1oK80fevxKZPME0UV8bFPWnRxeVaFr5ddd/DOenV8H6SPyr4ODbSOItpl53y6Az0m3FTIUf8cSsyR7dfE4zpA3M4djjtoKDNFRsTjU2RWVQW9XMaxzznGVGhLEwkC+sYjR5NQvH5iiRvV18q+CGQqNX2+WWM3SPuty3nc86RBNR0FOgSQA0TL2OAs6bJNmfzcwZxAKYbj7/88tj6qrjLaQtFTbBm2a7+TAQfs3UTiQi00zEDYqeSj2WQvacNm1dWEAyx0QNLHiKGTn4TShGj8LUoGyjJ26Y6VPsotvCoj8jM0eaN8Pc9/AYywVI+QktjaPZa7KGH3XJHJkTIQQRcUxOtDstKpcriAefDs8jjL5ju9t5J3qEvgzmclNJKRnla4p3maM0vk+8cC7EXMV4P1zuCwr3akaHFJo5Y0aFhKsnHqTc70LfiFo//8/QsvyjLIUtEWHTkGeuf4PpbYXr5qpJ6tWhG2MARxdeg8CAwEAAQ==", + "keyDetails": "PKIX_RSA_PKCS1V15_4096_SHA256", + "validFor": { + "start": "2024-08-26T12:03:30.241272895Z" + } + }, + "logId": { + "keyId": "G3CTL21UG8/5ygV+/WVy/pvB8nUiZGOEnMVKIEDzPxY=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "GitHub, Inc.", + "commonName": "Internal Services Root" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEAg+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe" + }, + { + "rawBytes": "MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKIMhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZNojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYDVR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIxAK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w95QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJEHdNmyA==" + }, + { + "rawBytes": "MIIB9DCCAXqgAwIBAgIUa/JAkdUjK4JUwsqtaiRJGWhqLSowCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTMzMDQxMTAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf9jFAXxz4kx68AHRMOkFBhflDcMTvzaXz4x/FCcXjJ/1qEKon/qPIGnaURskDtyNbNDOpeJTDDFqt48iMPrnzpx6IZwqemfUJN4xBEZfza+pYt/iyod+9tZr20RRWSv/o0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaAAwZQIxALZLZ8BgRXzKxLMMN9VIlO+e4hrBnNBgF7tz7Hnrowv2NetZErIACKFymBlvWDvtMAIwZO+ki6ssQ1bsZo98O8mEAf2NZ7iiCgDDU0Vwjeco6zyeh0zBTs9/7gV6AHNQ53xD" + } + ] + }, + "validFor": { + "start": "2023-04-14T00:00:00Z", + "end": "2024-04-13T00:00:00Z" + } + } + ] +}` ) func TestCreateRepo(t *testing.T) { @@ -125,3 +249,36 @@ func TestCompressUncompressFS(t *testing.T) { t.Errorf("Roundtripped rekor differs:\n%s\n%s", rekorPublicKey, string(rtRekor)) } } + +func TestConstructTrustedRoot(t *testing.T) { + tsaCerts, err := certs.SplitCertChain([]byte(tsaCertChain), "tsa") + if err != nil { + t.Fatalf("Failed to split TSA cert chain: %v", err) + } + + targets := []TargetWithMetadata{ + {Name: "fulcio.crt.pem", Bytes: []byte(fulcioRootCert)}, + {Name: "ctfe.pub", Bytes: []byte(ctlogPublicKey)}, + {Name: "rekor.pub", Bytes: []byte(rekorPublicKey)}, + } + + for k, v := range tsaCerts { + targets = append(targets, TargetWithMetadata{Name: k, Bytes: v}) + } + + tr, err := constructTrustedRoot(targets) + if err != nil { + t.Fatalf("Failed to construct trusted root: %v", err) + } + if tr.Name != "trusted_root.json" { + t.Fatalf("Wrong name for trusted root target. Expected 'trusted_root.json', got '%s'", tr.Name) + } + actualTr := string(tr.Bytes) + + // the "validFor" for tlog/ctlog keys will be current time; let's replace them + // to be the time from the expected sample + re := regexp.MustCompile(`"validFor"\s*:\s*\{\s*"start"\s*:\s*"[^"]+"\s*\}`) + actualTr = re.ReplaceAllString(actualTr, fmt.Sprintf(`"validFor": {"start": "%s"}`, trJSONValidForStart)) + + require.JSONEq(t, trJSON, actualTr) +}