func TestSignRootOldKeyCertMissing(t *testing.T) { gun := "docker/test-sign-root" referenceTime := time.Now() cs := cryptoservice.NewCryptoService(trustmanager.NewKeyMemoryStore( passphrase.ConstantRetriever("password"))) rootPublicKey, err := cs.Create(data.CanonicalRootRole, gun, data.ECDSAKey) require.NoError(t, err) rootPrivateKey, _, err := cs.GetPrivateKey(rootPublicKey.ID()) require.NoError(t, err) oldRootCert, err := cryptoservice.GenerateCertificate(rootPrivateKey, gun, referenceTime.AddDate(-9, 0, 0), referenceTime.AddDate(1, 0, 0)) require.NoError(t, err) oldRootCertKey := trustmanager.CertToKey(oldRootCert) repo := initRepoWithRoot(t, cs, oldRootCertKey) // Create a first signature, using the old key. signedRoot, err := repo.SignRoot(data.DefaultExpires(data.CanonicalRootRole)) require.NoError(t, err) verifySignatureList(t, signedRoot, oldRootCertKey) err = verifyRootSignatureAgainstKey(t, signedRoot, oldRootCertKey) require.NoError(t, err) // Create a new certificate newRootCert, err := cryptoservice.GenerateCertificate(rootPrivateKey, gun, referenceTime, referenceTime.AddDate(10, 0, 0)) require.NoError(t, err) newRootCertKey := trustmanager.CertToKey(newRootCert) require.NotEqual(t, oldRootCertKey.ID(), newRootCertKey.ID()) // Only trust the new certificate err = repo.ReplaceBaseKeys(data.CanonicalRootRole, newRootCertKey) require.NoError(t, err) updatedRootRole, err := repo.GetBaseRole(data.CanonicalRootRole) require.NoError(t, err) updatedRootKeyIDs := updatedRootRole.ListKeyIDs() require.Equal(t, 1, len(updatedRootKeyIDs)) require.Equal(t, newRootCertKey.ID(), updatedRootKeyIDs[0]) // Now forget all about the old certificate: drop it from the Root carried keys delete(repo.Root.Signed.Keys, oldRootCertKey.ID()) repo2 := NewRepo(cs) repo2.Root = repo.Root repo2.originalRootRole = updatedRootRole // Create a second signature signedRoot, err = repo2.SignRoot(data.DefaultExpires(data.CanonicalRootRole)) require.NoError(t, err) verifySignatureList(t, signedRoot, newRootCertKey) // Without oldRootCertKey // Verify that the signature can be verified when trusting the new certificate err = verifyRootSignatureAgainstKey(t, signedRoot, newRootCertKey) require.NoError(t, err) err = verifyRootSignatureAgainstKey(t, signedRoot, oldRootCertKey) require.Error(t, err) }
// CreateKey creates a new key inside the cryptoservice for the given role and gun, // returning the public key. If the role is a root role, create an x509 key. func CreateKey(cs signed.CryptoService, gun, role, keyAlgorithm string) (data.PublicKey, error) { key, err := cs.Create(role, gun, keyAlgorithm) if err != nil { return nil, err } if role == data.CanonicalRootRole { start := time.Now().AddDate(0, 0, -1) privKey, _, err := cs.GetPrivateKey(key.ID()) if err != nil { return nil, err } cert, err := cryptoservice.GenerateCertificate( privKey, gun, start, start.AddDate(1, 0, 0), ) if err != nil { return nil, err } // Keep the x509 key type consistent with the key's algorithm switch keyAlgorithm { case data.RSAKey: key = data.NewRSAx509PublicKey(trustmanager.CertToPEM(cert)) case data.ECDSAKey: key = data.NewECDSAx509PublicKey(trustmanager.CertToPEM(cert)) default: // This should be impossible because of the Create() call above, but just in case return nil, fmt.Errorf("invalid key algorithm type") } } return key, nil }
func generateCertificate(t *testing.T, gun string, expireInHours int64) *x509.Certificate { ecdsaPrivKey, err := trustmanager.GenerateECDSAKey(rand.Reader) assert.NoError(t, err) startTime := time.Now() endTime := startTime.Add(time.Hour * time.Duration(expireInHours)) cert, err := cryptoservice.GenerateCertificate(ecdsaPrivKey, gun, startTime, endTime) assert.NoError(t, err) return cert }
func generateValidTestCert() (*x509.Certificate, string, error) { privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) if err != nil { return nil, "", err } keyID := privKey.ID() startTime := time.Now() endTime := startTime.AddDate(10, 0, 0) cert, err := cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime) if err != nil { return nil, "", err } return cert, keyID, nil }
func generateShortRSAKeyTestCert() (*x509.Certificate, string, error) { // 1024 bits is too short privKey, err := trustmanager.GenerateRSAKey(rand.Reader, 1024) if err != nil { return nil, "", err } keyID := privKey.ID() startTime := time.Now() endTime := startTime.AddDate(10, 0, 0) cert, err := cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime) if err != nil { return nil, "", err } return cert, keyID, nil }
func generateExpiredTestCert() (*x509.Certificate, string, error) { privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) if err != nil { return nil, "", err } keyID := privKey.ID() // Set to Unix time 0 start time, valid for one more day startTime := time.Unix(0, 0) endTime := startTime.AddDate(0, 0, 1) cert, err := cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime) if err != nil { return nil, "", err } return cert, keyID, nil }
func rootCertKey(gun string, privKey data.PrivateKey) (data.PublicKey, error) { // Hard-coded policy: the generated certificate expires in 10 years. startTime := time.Now() cert, err := cryptoservice.GenerateCertificate( privKey, gun, startTime, startTime.Add(notary.Year*10)) if err != nil { return nil, err } x509PublicKey := trustmanager.CertToKey(cert) if x509PublicKey == nil { return nil, fmt.Errorf( "cannot use regenerated certificate: format %s", cert.PublicKeyAlgorithm) } return x509PublicKey, nil }
func createKey(cs signed.CryptoService, gun, role string) (data.PublicKey, error) { key, err := cs.Create(role, data.ECDSAKey) if err != nil { return nil, err } if role == data.CanonicalRootRole { start := time.Now().AddDate(0, 0, -1) privKey, _, err := cs.GetPrivateKey(key.ID()) if err != nil { return nil, err } cert, err := cryptoservice.GenerateCertificate( privKey, gun, start, start.AddDate(1, 0, 0), ) if err != nil { return nil, err } key = data.NewECDSAx509PublicKey(trustmanager.CertToPEM(cert)) } return key, nil }
// Generates a KeyStoreManager in a temporary directory and returns the // manager and certificates for two keys which have been added to the keystore. // Also returns the temporary directory so it can be cleaned up. func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) ( string, *KeyStoreManager, []*x509.Certificate) { tempBaseDir, err := ioutil.TempDir("", "notary-test-") assert.NoError(t, err, "failed to create a temporary directory: %s", err) // Create a FileStoreManager keyStoreManager, err := NewKeyStoreManager(tempBaseDir, passphraseRetriever) assert.NoError(t, err) certs := make([]*x509.Certificate, 2) for i := 0; i < 2; i++ { keyID, err := keyStoreManager.GenRootKey(keyAlg) assert.NoError(t, err) key, _, err := keyStoreManager.KeyStore.GetKey(keyID) assert.NoError(t, err) cert, err := cryptoservice.GenerateCertificate(key, gun) assert.NoError(t, err) certs[i] = cert } return tempBaseDir, keyStoreManager, certs }
// Initialize creates a new repository by using rootKey as the root Key for the // TUF repository. func (r *NotaryRepository) Initialize(rootKeyID string) error { privKey, _, err := r.CryptoService.GetPrivateKey(rootKeyID) if err != nil { return err } rootCert, err := cryptoservice.GenerateCertificate(privKey, r.gun) if err != nil { return err } r.KeyStoreManager.AddTrustedCert(rootCert) // The root key gets stored in the TUF metadata X509 encoded, linking // the tuf root.json to our X509 PKI. // If the key is RSA, we store it as type RSAx509, if it is ECDSA we store it // as ECDSAx509 to allow the gotuf verifiers to correctly decode the // key on verification of signatures. var rootKey data.PublicKey switch privKey.Algorithm() { case data.RSAKey: rootKey = data.NewRSAx509PublicKey(trustmanager.CertToPEM(rootCert)) case data.ECDSAKey: rootKey = data.NewECDSAx509PublicKey(trustmanager.CertToPEM(rootCert)) default: return fmt.Errorf("invalid format for root key: %s", privKey.Algorithm()) } // All the timestamp keys are generated by the remote server. remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip) if err != nil { return err } rawTSKey, err := remote.GetKey("timestamp") if err != nil { return err } timestampKey, err := data.UnmarshalPublicKey(rawTSKey) if err != nil { return err } logrus.Debugf("got remote %s timestamp key with keyID: %s", timestampKey.Algorithm(), timestampKey.ID()) // This is currently hardcoding the targets and snapshots keys to ECDSA // Targets and snapshot keys are always generated locally. targetsKey, err := r.CryptoService.Create("targets", data.ECDSAKey) if err != nil { return err } snapshotKey, err := r.CryptoService.Create("snapshot", data.ECDSAKey) if err != nil { return err } kdb := keys.NewDB() kdb.AddKey(rootKey) kdb.AddKey(targetsKey) kdb.AddKey(snapshotKey) kdb.AddKey(timestampKey) err = initRoles(kdb, rootKey, targetsKey, snapshotKey, timestampKey) if err != nil { return err } r.tufRepo = tuf.NewRepo(kdb, r.CryptoService) err = r.tufRepo.InitRoot(false) if err != nil { logrus.Debug("Error on InitRoot: ", err.Error()) switch err.(type) { case tuferrors.ErrInsufficientSignatures, trustmanager.ErrPasswordInvalid: default: return err } } err = r.tufRepo.InitTargets() if err != nil { logrus.Debug("Error on InitTargets: ", err.Error()) return err } err = r.tufRepo.InitSnapshot() if err != nil { logrus.Debug("Error on InitSnapshot: ", err.Error()) return err } return r.saveMetadata() }
// Initialize creates a new repository by using rootKey as the root Key for the // TUF repository. func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...string) error { privKey, _, err := r.CryptoService.GetPrivateKey(rootKeyID) if err != nil { return err } // currently we only support server managing timestamps and snapshots, and // nothing else - timestamps are always managed by the server, and implicit // (do not have to be passed in as part of `serverManagedRoles`, so that // the API of Initialize doens't change). var serverManagesSnapshot bool locallyManagedKeys := []string{ data.CanonicalTargetsRole, data.CanonicalSnapshotRole, // root is also locally managed, but that should have been created // already } remotelyManagedKeys := []string{data.CanonicalTimestampRole} for _, role := range serverManagedRoles { switch role { case data.CanonicalTimestampRole: continue // timestamp is already in the right place case data.CanonicalSnapshotRole: // because we put Snapshot last locallyManagedKeys = []string{data.CanonicalTargetsRole} remotelyManagedKeys = append( remotelyManagedKeys, data.CanonicalSnapshotRole) serverManagesSnapshot = true default: return ErrInvalidRemoteRole{Role: role} } } // Hard-coded policy: the generated certificate expires in 10 years. startTime := time.Now() rootCert, err := cryptoservice.GenerateCertificate( privKey, r.gun, startTime, startTime.AddDate(10, 0, 0)) if err != nil { return err } r.CertManager.AddTrustedCert(rootCert) // The root key gets stored in the TUF metadata X509 encoded, linking // the tuf root.json to our X509 PKI. // If the key is RSA, we store it as type RSAx509, if it is ECDSA we store it // as ECDSAx509 to allow the gotuf verifiers to correctly decode the // key on verification of signatures. var rootKey data.PublicKey switch privKey.Algorithm() { case data.RSAKey: rootKey = data.NewRSAx509PublicKey(trustmanager.CertToPEM(rootCert)) case data.ECDSAKey: rootKey = data.NewECDSAx509PublicKey(trustmanager.CertToPEM(rootCert)) default: return fmt.Errorf("invalid format for root key: %s", privKey.Algorithm()) } kdb := keys.NewDB() err = addKeyForRole(kdb, data.CanonicalRootRole, rootKey) if err != nil { return err } // we want to create all the local keys first so we don't have to // make unnecessary network calls for _, role := range locallyManagedKeys { // This is currently hardcoding the keys to ECDSA. key, err := r.CryptoService.Create(role, data.ECDSAKey) if err != nil { return err } if err := addKeyForRole(kdb, role, key); err != nil { return err } } for _, role := range remotelyManagedKeys { // This key is generated by the remote server. key, err := getRemoteKey(r.baseURL, r.gun, role, r.roundTrip) if err != nil { return err } logrus.Debugf("got remote %s %s key with keyID: %s", role, key.Algorithm(), key.ID()) if err := addKeyForRole(kdb, role, key); err != nil { return err } } r.tufRepo = tuf.NewRepo(kdb, r.CryptoService) err = r.tufRepo.InitRoot(false) if err != nil { logrus.Debug("Error on InitRoot: ", err.Error()) return err } _, err = r.tufRepo.InitTargets(data.CanonicalTargetsRole) if err != nil { logrus.Debug("Error on InitTargets: ", err.Error()) return err } err = r.tufRepo.InitSnapshot() if err != nil { logrus.Debug("Error on InitSnapshot: ", err.Error()) return err } return r.saveMetadata(serverManagesSnapshot) }
func generateExpiredTestingCertificate(rootKey data.PrivateKey, gun string) (*x509.Certificate, error) { startTime := time.Now().AddDate(-10, 0, 0) return cryptoservice.GenerateCertificate(rootKey, gun, startTime, startTime.AddDate(1, 0, 0)) }
func TestValidateRootWithPinnedCA(t *testing.T) { var testSignedRoot data.Signed var signedRootBytes bytes.Buffer // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) // Unmarshal our signedRoot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) typedSignedRoot, err := data.RootFromSigned(&testSignedRoot) require.NoError(t, err) // This call to trustpinning.ValidateRoot will fail because we have an invalid path for the CA _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}}) require.Error(t, err) // This call to trustpinning.ValidateRoot will fail because we have no valid GUNs to use, and TOFUS is disabled _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: true}) require.Error(t, err) // This call to trustpinning.ValidateRoot will succeed because we have no valid GUNs to use and we fall back to enabled TOFUS validatedRoot, err := trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: false}) require.NoError(t, err) generateRootKeyIDs(typedSignedRoot) require.Equal(t, typedSignedRoot, validatedRoot) // Write an invalid CA cert (not even a PEM) to the tempDir and ensure validation fails when using it invalidCAFilepath := filepath.Join(tempBaseDir, "invalid.ca") require.NoError(t, ioutil.WriteFile(invalidCAFilepath, []byte("ABSOLUTELY NOT A PEM"), 0644)) // Using this invalid CA cert should fail on trustpinning.ValidateRoot _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"docker.com/notary": invalidCAFilepath}, DisableTOFU: true}) require.Error(t, err) validCAFilepath := "../fixtures/root-ca.crt" // If we pass an invalid Certs entry in addition to this valid CA entry, since Certs has priority for pinning we will fail _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"invalidID"}}, CA: map[string]string{"docker.com/notary": validCAFilepath}, DisableTOFU: true}) require.Error(t, err) // Now construct a new root with a valid cert chain, such that signatures are correct over the 'notary-signer' GUN. Pin the root-ca and validate leafCert, err := trustmanager.LoadCertFromFile("../fixtures/notary-signer.crt") require.NoError(t, err) intermediateCert, err := trustmanager.LoadCertFromFile("../fixtures/intermediate-ca.crt") require.NoError(t, err) pemChainBytes, err := trustmanager.CertChainToPEM([]*x509.Certificate{leafCert, intermediateCert}) require.NoError(t, err) newRootKey := data.NewPublicKey(data.RSAx509Key, pemChainBytes) rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{newRootKey.ID()}, nil) require.NoError(t, err) testRoot, err := data.NewRoot( map[string]data.PublicKey{newRootKey.ID(): newRootKey}, map[string]*data.RootRole{ data.CanonicalRootRole: &rootRole.RootRole, data.CanonicalTimestampRole: &rootRole.RootRole, data.CanonicalTargetsRole: &rootRole.RootRole, data.CanonicalSnapshotRole: &rootRole.RootRole}, false, ) testRoot.Signed.Version = 1 require.NoError(t, err, "Failed to create new root") keyReader, err := os.Open("../fixtures/notary-signer.key") require.NoError(t, err, "could not open key file") pemBytes, err := ioutil.ReadAll(keyReader) require.NoError(t, err, "could not read key file") privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "") require.NoError(t, err) store, err := trustmanager.NewKeyFileStore(tempBaseDir, passphraseRetriever) require.NoError(t, err) cs := cryptoservice.NewCryptoService(store) err = store.AddKey(trustmanager.KeyInfo{Role: data.CanonicalRootRole, Gun: "notary-signer"}, privKey) require.NoError(t, err) newTestSignedRoot, err := testRoot.ToSigned() require.NoError(t, err) err = signed.Sign(cs, newTestSignedRoot, []data.PublicKey{newRootKey}, 1, nil) require.NoError(t, err) newTypedSignedRoot, err := data.RootFromSigned(newTestSignedRoot) require.NoError(t, err) // Check that we validate correctly against a pinned CA and provided bundle validatedRoot, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": validCAFilepath}, DisableTOFU: true}) require.NoError(t, err) generateRootKeyIDs(newTypedSignedRoot) require.Equal(t, newTypedSignedRoot, validatedRoot) // Add an expired CA for the same gun to our previous pinned bundle, ensure that we still validate correctly goodRootCABundle, err := trustmanager.LoadCertBundleFromFile(validCAFilepath) require.NoError(t, err) memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) cryptoService := cryptoservice.NewCryptoService(memKeyStore) testPubKey, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) require.NoError(t, err) testPrivKey, _, err := memKeyStore.GetKey(testPubKey.ID()) require.NoError(t, err) expiredCert, err := generateExpiredTestingCertificate(testPrivKey, "notary-signer") require.NoError(t, err) bundleWithExpiredCert, err := trustmanager.CertChainToPEM(append(goodRootCABundle, expiredCert)) require.NoError(t, err) bundleWithExpiredCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem") require.NoError(t, ioutil.WriteFile(bundleWithExpiredCertPath, bundleWithExpiredCert, 0644)) // Check that we validate correctly against a pinned CA and provided bundle validatedRoot, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true}) require.NoError(t, err) require.Equal(t, newTypedSignedRoot, validatedRoot) testPubKey2, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) require.NoError(t, err) testPrivKey2, _, err := memKeyStore.GetKey(testPubKey2.ID()) require.NoError(t, err) expiredCert2, err := generateExpiredTestingCertificate(testPrivKey2, "notary-signer") require.NoError(t, err) allExpiredCertBundle, err := trustmanager.CertChainToPEM([]*x509.Certificate{expiredCert, expiredCert2}) require.NoError(t, err) allExpiredCertPath := filepath.Join(tempBaseDir, "all_expired_cert.pem") require.NoError(t, ioutil.WriteFile(allExpiredCertPath, allExpiredCertBundle, 0644)) // Now only use expired certs in the bundle, we should fail _, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": allExpiredCertPath}, DisableTOFU: true}) require.Error(t, err) // Add a CA cert for a that won't validate against the root leaf certificate testPubKey3, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) require.NoError(t, err) testPrivKey3, _, err := memKeyStore.GetKey(testPubKey3.ID()) require.NoError(t, err) validCert, err := cryptoservice.GenerateCertificate(testPrivKey3, "notary-signer", time.Now(), time.Now().AddDate(1, 0, 0)) require.NoError(t, err) bundleWithWrongCert, err := trustmanager.CertChainToPEM([]*x509.Certificate{validCert}) require.NoError(t, err) bundleWithWrongCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem") require.NoError(t, ioutil.WriteFile(bundleWithWrongCertPath, bundleWithWrongCert, 0644)) _, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithWrongCertPath}, DisableTOFU: true}) require.Error(t, err) }
// SignRoot signs with all old roles with valid keys, and also optionally any old // signatures we have keys for even if they aren't in an old root. It ignores any // root role whose version is higher than the current version. If signing fails, // it reverts back. func TestSignRootOldRootRolesAndOldSigs(t *testing.T) { gun := "docker/test-sign-root" referenceTime := time.Now() cs := cryptoservice.NewCryptoService(trustmanager.NewKeyMemoryStore( passphrase.ConstantRetriever("password"))) rootCertKeys := make([]data.PublicKey, 9) rootPrivKeys := make([]data.PrivateKey, cap(rootCertKeys)) for i := 0; i < cap(rootCertKeys); i++ { rootPublicKey, err := cs.Create(data.CanonicalRootRole, gun, data.ECDSAKey) require.NoError(t, err) rootPrivateKey, _, err := cs.GetPrivateKey(rootPublicKey.ID()) require.NoError(t, err) rootCert, err := cryptoservice.GenerateCertificate(rootPrivateKey, gun, referenceTime.AddDate(-9, 0, 0), referenceTime.AddDate(1, 0, 0)) require.NoError(t, err) rootCertKeys[i] = trustmanager.CertToKey(rootCert) rootPrivKeys[i] = rootPrivateKey } repo := initRepoWithRoot(t, cs, rootCertKeys[6]) // sign with key 0, which represents the key for the a version of the root we // no longer have a record of signedObj, err := repo.Root.ToSigned() require.NoError(t, err) signedObj, err = repo.sign(signedObj, nil, []data.PublicKey{rootCertKeys[0]}) require.NoError(t, err) // should be signed with key 0 verifySignatureList(t, signedObj, rootCertKeys[0]) repo.Root.Signatures = signedObj.Signatures // bump root version and also add the above keys and extra roles to root repo.Root.Signed.Version = 6 oldExpiry := repo.Root.Signed.Expires // add every key to the root's key list except 1 for i, key := range rootCertKeys { if i != 1 { repo.Root.Signed.Keys[key.ID()] = key } } // invalid root role because key not included in the key map - valid root version name repo.Root.Signed.Roles["root.1"] = &data.RootRole{KeyIDs: []string{rootCertKeys[1].ID()}, Threshold: 1} // invalid root versions names, but valid roles repo.Root.Signed.Roles["2.root"] = &data.RootRole{KeyIDs: []string{rootCertKeys[2].ID()}, Threshold: 1} repo.Root.Signed.Roles["root3"] = &data.RootRole{KeyIDs: []string{rootCertKeys[3].ID()}, Threshold: 1} repo.Root.Signed.Roles["root.4a"] = &data.RootRole{KeyIDs: []string{rootCertKeys[4].ID()}, Threshold: 1} // valid old root role and version repo.Root.Signed.Roles["root.5"] = &data.RootRole{KeyIDs: []string{rootCertKeys[5].ID()}, Threshold: 1} // greater or equal to the current root version, so invalid name, but valid root role repo.Root.Signed.Roles["root.6"] = &data.RootRole{KeyIDs: []string{rootCertKeys[7].ID()}, Threshold: 1} lenRootRoles := len(repo.Root.Signed.Roles) // rotate the current key to key 8 require.NoError(t, repo.ReplaceBaseKeys(data.CanonicalRootRole, rootCertKeys[8])) requiredKeys := []data.PrivateKey{ rootPrivKeys[5], // we need an old valid root role - this was specified in root5 rootPrivKeys[6], // we need the previous valid key prior to root rotation rootPrivKeys[8], // we need the new root key we've rotated to } for _, privKey := range requiredKeys { // if we can't sign with a previous root, we fail require.NoError(t, cs.RemoveKey(privKey.ID())) _, err = repo.SignRoot(data.DefaultExpires(data.CanonicalRootRole)) require.Error(t, err) require.IsType(t, signed.ErrInsufficientSignatures{}, err) require.Contains(t, err.Error(), "signing keys not available") // add back for next test require.NoError(t, cs.AddKey(data.CanonicalRootRole, gun, privKey)) } // we haven't saved any unsaved roles because there was an error signing, // nor have we bumped the version or altered the expiry require.Equal(t, 6, repo.Root.Signed.Version) require.Equal(t, oldExpiry, repo.Root.Signed.Expires) require.Len(t, repo.Root.Signed.Roles, lenRootRoles) // remove all the keys we don't need and demonstrate we can still sign for _, index := range []int{1, 2, 3, 4, 7} { require.NoError(t, cs.RemoveKey(rootPrivKeys[index].ID())) } // SignRoot will sign with all the old keys based on old root roles as well // as any old signatures signedObj, err = repo.SignRoot(data.DefaultExpires(data.CanonicalRootRole)) require.NoError(t, err) expectedSigningKeys := []data.PublicKey{ rootCertKeys[0], // old signature key, not in any role rootCertKeys[5], // root.5 key which is valid rootCertKeys[6], // previous key before rotation, rootCertKeys[8], // newly rotated key } verifySignatureList(t, signedObj, expectedSigningKeys...) // verify that we saved the previous root (which overwrote an invalid saved root), // since it wasn't in the list of old valid roots, and we didn't save the newest // role require.NotNil(t, repo.Root.Signed.Roles["root.6"]) require.Equal(t, data.RootRole{KeyIDs: []string{rootCertKeys[6].ID()}, Threshold: 1}, *repo.Root.Signed.Roles["root.6"]) require.Nil(t, repo.Root.Signed.Roles["root.7"]) // bumped version, 1 new roles, but one overwrote the previous root.6, so actually no // additional roles require.Equal(t, 7, repo.Root.Signed.Version) require.Len(t, repo.Root.Signed.Roles, lenRootRoles) require.True(t, oldExpiry.Before(repo.Root.Signed.Expires)) lenRootRoles = len(repo.Root.Signed.Roles) // remove the optional key require.NoError(t, cs.RemoveKey(rootPrivKeys[0].ID())) // SignRoot will still succeed even if the key that wasn't in a root isn't // available oldExpiry = repo.Root.Signed.Expires signedObj, err = repo.SignRoot(data.DefaultExpires(data.CanonicalRootRole)) require.NoError(t, err) verifySignatureList(t, signedObj, expectedSigningKeys[1:]...) // no additional roles were added require.Len(t, repo.Root.Signed.Roles, lenRootRoles) require.Equal(t, 8, repo.Root.Signed.Version) // bumped version require.True(t, oldExpiry.Before(repo.Root.Signed.Expires)) // expiry updated // now rotate a non-root key newTargetsKey, err := cs.Create(data.CanonicalTargetsRole, gun, data.ECDSAKey) require.NoError(t, err) require.NoError(t, repo.ReplaceBaseKeys(data.CanonicalTargetsRole, newTargetsKey)) // we still sign with all old roles no additional roles were added oldExpiry = repo.Root.Signed.Expires signedObj, err = repo.SignRoot(data.DefaultExpires(data.CanonicalRootRole)) require.NoError(t, err) verifySignatureList(t, signedObj, expectedSigningKeys[1:]...) require.Len(t, repo.Root.Signed.Roles, lenRootRoles) require.Equal(t, 9, repo.Root.Signed.Version) // bumped version require.True(t, oldExpiry.Before(repo.Root.Signed.Expires)) // expiry updated // rotating a targets key again, if we are missing the previous root's keys, signing will fail newTargetsKey, err = cs.Create(data.CanonicalTargetsRole, gun, data.ECDSAKey) require.NoError(t, err) require.NoError(t, repo.ReplaceBaseKeys(data.CanonicalTargetsRole, newTargetsKey)) require.NoError(t, cs.RemoveKey(rootPrivKeys[6].ID())) oldExpiry = repo.Root.Signed.Expires _, err = repo.SignRoot(data.DefaultExpires(data.CanonicalRootRole)) require.Error(t, err) require.IsType(t, signed.ErrInsufficientSignatures{}, err) require.Contains(t, err.Error(), "signing keys not available") // no additional roles were saved, version has not changed require.Len(t, repo.Root.Signed.Roles, lenRootRoles) require.Equal(t, 9, repo.Root.Signed.Version) // version has not changed require.Equal(t, oldExpiry, repo.Root.Signed.Expires) }
// Initialize repo and test publishing targets with delegation roles func TestClientDelegationsPublishing(t *testing.T) { setUp(t) tempDir := tempDirWithConfig(t, "{}") defer os.RemoveAll(tempDir) server := setupServer() defer server.Close() // Setup certificate for delegation role tempFile, err := ioutil.TempFile("", "pemfile") assert.NoError(t, err) privKey, err := trustmanager.GenerateRSAKey(rand.Reader, 2048) assert.NoError(t, err) privKeyBytesNoRole, err := trustmanager.KeyToPEM(privKey, "") assert.NoError(t, err) privKeyBytesWithRole, err := trustmanager.KeyToPEM(privKey, "user") assert.NoError(t, err) startTime := time.Now() endTime := startTime.AddDate(10, 0, 0) cert, err := cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime) assert.NoError(t, err) _, err = tempFile.Write(trustmanager.CertToPEM(cert)) assert.NoError(t, err) tempFile.Close() defer os.Remove(tempFile.Name()) rawPubBytes, _ := ioutil.ReadFile(tempFile.Name()) parsedPubKey, _ := trustmanager.ParsePEMPublicKey(rawPubBytes) canonicalKeyID, err := utils.CanonicalKeyID(parsedPubKey) assert.NoError(t, err) // Set up targets for publishing tempTargetFile, err := ioutil.TempFile("", "targetfile") assert.NoError(t, err) tempTargetFile.Close() defer os.Remove(tempTargetFile.Name()) var target = "sdgkadga" var output string // init repo _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - none yet output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "No delegations present in this repository.") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // validate that we have all keys, including snapshot assertNumKeys(t, tempDir, 1, 2, true) // rotate the snapshot key to server output, err = runCommand(t, tempDir, "-s", server.URL, "key", "rotate", "gun", "-r", "--key-type", "snapshot") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // validate that we lost the snapshot signing key _, signingKeyIDs := assertNumKeys(t, tempDir, 1, 1, true) targetKeyID := signingKeyIDs[0] // add new valid delegation with single new cert output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/releases", tempFile.Name(), "--paths", "\"\"") assert.NoError(t, err) assert.Contains(t, output, "Addition of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see our one delegation output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.NotContains(t, output, "No delegations present in this repository.") // remove the targets key to demonstrate that delegates don't need this key keyDir := filepath.Join(tempDir, "private", "tuf_keys") assert.NoError(t, os.Remove(filepath.Join(keyDir, "gun", targetKeyID+".key"))) // Note that we need to use the canonical key ID, followed by the base of the role here err = ioutil.WriteFile(filepath.Join(keyDir, canonicalKeyID+"_releases.key"), privKeyBytesNoRole, 0700) assert.NoError(t, err) // add a target using the delegation -- will only add to targets/releases _, err = runCommand(t, tempDir, "add", "gun", target, tempTargetFile.Name(), "--roles", "targets/releases") assert.NoError(t, err) // list targets for targets/releases - we should see no targets until we publish output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "No targets") output, err = runCommand(t, tempDir, "-s", server.URL, "status", "gun") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list targets for targets/releases - we should see our target! output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "targets/releases") // remove the target for this role only _, err = runCommand(t, tempDir, "remove", "gun", target, "--roles", "targets/releases") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list targets for targets/releases - we should see no targets output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "No targets present") // Try adding a target with a different key style - private/tuf_keys/canonicalKeyID.key with "user" set as the "role" PEM header // First remove the old key and add the new style assert.NoError(t, os.Remove(filepath.Join(keyDir, canonicalKeyID+"_releases.key"))) err = ioutil.WriteFile(filepath.Join(keyDir, canonicalKeyID+".key"), privKeyBytesWithRole, 0700) assert.NoError(t, err) // add a target using the delegation -- will only add to targets/releases _, err = runCommand(t, tempDir, "add", "gun", target, tempTargetFile.Name(), "--roles", "targets/releases") assert.NoError(t, err) // list targets for targets/releases - we should see no targets until we publish output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "No targets") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list targets for targets/releases - we should see our target! output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "targets/releases") // remove the target for this role only _, err = runCommand(t, tempDir, "remove", "gun", target, "--roles", "targets/releases") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // Now remove this key, and make a new file to import the delegation's key from assert.NoError(t, os.Remove(filepath.Join(keyDir, canonicalKeyID+".key"))) tempPrivFile, err := ioutil.TempFile("/tmp", "privfile") assert.NoError(t, err) defer os.Remove(tempPrivFile.Name()) // Write the private key to a file so we can import it _, err = tempPrivFile.Write(privKeyBytesNoRole) assert.NoError(t, err) tempPrivFile.Close() // Import the private key, associating it with our delegation role _, err = runCommand(t, tempDir, "key", "import", tempPrivFile.Name(), "--role", "targets/releases") assert.NoError(t, err) // add a target using the delegation -- will only add to targets/releases _, err = runCommand(t, tempDir, "add", "gun", target, tempTargetFile.Name(), "--roles", "targets/releases") assert.NoError(t, err) // list targets for targets/releases - we should see no targets until we publish output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "No targets") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list targets for targets/releases - we should see our target! output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") assert.NoError(t, err) assert.Contains(t, output, "targets/releases") }
// Initialize repo and test delegations commands by adding, listing, and removing delegations func TestClientDelegationsInteraction(t *testing.T) { setUp(t) tempDir := tempDirWithConfig(t, "{}") defer os.RemoveAll(tempDir) server := setupServer() defer server.Close() // Setup certificate tempFile, err := ioutil.TempFile("", "pemfile") assert.NoError(t, err) privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) startTime := time.Now() endTime := startTime.AddDate(10, 0, 0) cert, err := cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime) assert.NoError(t, err) _, err = tempFile.Write(trustmanager.CertToPEM(cert)) assert.NoError(t, err) tempFile.Close() defer os.Remove(tempFile.Name()) rawPubBytes, _ := ioutil.ReadFile(tempFile.Name()) parsedPubKey, _ := trustmanager.ParsePEMPublicKey(rawPubBytes) keyID, err := utils.CanonicalKeyID(parsedPubKey) assert.NoError(t, err) var output string // -- tests -- // init repo _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - none yet output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "No delegations present in this repository.") // add new valid delegation with single new cert, and no path output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", tempFile.Name()) assert.NoError(t, err) assert.Contains(t, output, "Addition of delegation role") assert.NotContains(t, output, "path") // check status - see delegation output, err = runCommand(t, tempDir, "status", "gun") assert.NoError(t, err) assert.Contains(t, output, "Unpublished changes for gun") // list delegations - none yet because still unpublished output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "No delegations present in this repository.") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // check status - no changelist output, err = runCommand(t, tempDir, "status", "gun") assert.NoError(t, err) assert.Contains(t, output, "No unpublished changes for gun") // list delegations - we should see our added delegation, with no paths output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "targets/delegation") assert.Contains(t, output, keyID) assert.NotContains(t, output, "\"\"") // add all paths to this delegation output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--all-paths") assert.NoError(t, err) assert.Contains(t, output, "Addition of delegation role") assert.Contains(t, output, "\"\"") assert.Contains(t, output, "<all paths>") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see our added delegation, with no paths output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "targets/delegation") assert.Contains(t, output, "\"\"") assert.Contains(t, output, "<all paths>") // Setup another certificate tempFile2, err := ioutil.TempFile("", "pemfile2") assert.NoError(t, err) privKey, err = trustmanager.GenerateECDSAKey(rand.Reader) startTime = time.Now() endTime = startTime.AddDate(10, 0, 0) cert, err = cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime) assert.NoError(t, err) _, err = tempFile2.Write(trustmanager.CertToPEM(cert)) assert.NoError(t, err) assert.NoError(t, err) tempFile2.Close() defer os.Remove(tempFile2.Name()) rawPubBytes2, _ := ioutil.ReadFile(tempFile2.Name()) parsedPubKey2, _ := trustmanager.ParsePEMPublicKey(rawPubBytes2) keyID2, err := utils.CanonicalKeyID(parsedPubKey2) assert.NoError(t, err) // add to the delegation by specifying the same role, this time add a scoped path output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", tempFile2.Name(), "--paths", "path") assert.NoError(t, err) assert.Contains(t, output, "Addition of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see two keys output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, ",") assert.Contains(t, output, "path") assert.Contains(t, output, keyID) assert.Contains(t, output, keyID2) // remove the delegation's first key output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", keyID) assert.NoError(t, err) assert.Contains(t, output, "Removal of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see the delegation but with only the second key output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.NotContains(t, output, keyID) assert.Contains(t, output, keyID2) // remove the delegation's second key output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", keyID2) assert.NoError(t, err) assert.Contains(t, output, "Removal of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see no delegations output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "No delegations present in this repository.") // add delegation with multiple certs and multiple paths output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", tempFile.Name(), tempFile2.Name(), "--paths", "path1,path2") assert.NoError(t, err) assert.Contains(t, output, "Addition of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see two keys output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, ",") assert.Contains(t, output, "path1,path2") assert.Contains(t, output, keyID) assert.Contains(t, output, keyID2) // add delegation with multiple certs and multiple paths output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--paths", "path3") assert.NoError(t, err) assert.Contains(t, output, "Addition of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see two keys output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, ",") assert.Contains(t, output, "path1,path2,path3") // just remove two paths from this delegation output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", "--paths", "path2,path3") assert.NoError(t, err) assert.Contains(t, output, "Removal of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see the same two keys, and only path1 output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, ",") assert.Contains(t, output, "path1") assert.NotContains(t, output, "path2") assert.NotContains(t, output, "path3") assert.Contains(t, output, keyID) assert.Contains(t, output, keyID2) // remove the remaining path, should not remove the delegation entirely output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", "--paths", "path1") assert.NoError(t, err) assert.Contains(t, output, "Removal of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see the same two keys, and no paths output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, ",") assert.NotContains(t, output, "path1") assert.NotContains(t, output, "path2") assert.NotContains(t, output, "path3") assert.Contains(t, output, keyID) assert.Contains(t, output, keyID2) // Add a bunch of individual paths so we can test a delegation remove --all-paths output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--paths", "abcdef,123456") assert.NoError(t, err) // Add more individual paths so we can test a delegation remove --all-paths output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--paths", "banana/split,apple/crumble/pie,orange.peel,kiwi") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see all of our paths output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "abcdef") assert.Contains(t, output, "123456") assert.Contains(t, output, "banana/split") assert.Contains(t, output, "apple/crumble/pie") assert.Contains(t, output, "orange.peel") assert.Contains(t, output, "kiwi") // Try adding "", and check that adding it with other paths clears out the others output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--paths", "\"\",grapefruit,pomegranate") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see all of our old paths, and "" output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "abcdef") assert.Contains(t, output, "123456") assert.Contains(t, output, "banana/split") assert.Contains(t, output, "apple/crumble/pie") assert.Contains(t, output, "orange.peel") assert.Contains(t, output, "kiwi") assert.Contains(t, output, "\"\"") assert.NotContains(t, output, "grapefruit") assert.NotContains(t, output, "pomegranate") // Try removing just "" output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", "--paths", "\"\"") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see all of our old paths without "" output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "abcdef") assert.Contains(t, output, "123456") assert.Contains(t, output, "banana/split") assert.Contains(t, output, "apple/crumble/pie") assert.Contains(t, output, "orange.peel") assert.Contains(t, output, "kiwi") assert.NotContains(t, output, "\"\"") // Remove --all-paths to clear out all paths from this delegation output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", "--all-paths") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see all of our paths output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.NotContains(t, output, "abcdef") assert.NotContains(t, output, "123456") assert.NotContains(t, output, "banana/split") assert.NotContains(t, output, "apple/crumble/pie") assert.NotContains(t, output, "orange.peel") assert.NotContains(t, output, "kiwi") // Check that we ignore other --paths if we pass in --all-paths on an add output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--all-paths", "--paths", "grapefruit,pomegranate") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should only see "", and not the other paths specified output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "\"\"") assert.NotContains(t, output, "grapefruit") assert.NotContains(t, output, "pomegranate") // Add those extra paths we ignored to set up the next test output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/delegation", "--paths", "grapefruit,pomegranate") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // Check that we ignore other --paths if we pass in --all-paths on a remove output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", "--all-paths", "--paths", "pomegranate") assert.NoError(t, err) // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see no paths output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.NotContains(t, output, "\"\"") assert.NotContains(t, output, "grapefruit") assert.NotContains(t, output, "pomegranate") // remove by force to delete the delegation entirely output, err = runCommand(t, tempDir, "delegation", "remove", "gun", "targets/delegation", "-y") assert.NoError(t, err) assert.Contains(t, output, "Forced removal (including all keys and paths) of delegation role") // publish repo _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") assert.NoError(t, err) // list delegations - we should see no delegations output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") assert.NoError(t, err) assert.Contains(t, output, "No delegations present in this repository.") }
// TestClientCertInteraction func TestClientCertInteraction(t *testing.T) { // -- setup -- setUp(t) tempDir := tempDirWithConfig(t, "{}") defer os.RemoveAll(tempDir) server := setupServer() defer server.Close() // -- tests -- _, err := runCommand(t, tempDir, "-s", server.URL, "init", "gun1") assert.NoError(t, err) _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun2") assert.NoError(t, err) certs := assertNumCerts(t, tempDir, 2) // root is always on disk, because even if there's a yubikey a backup is created assertNumKeys(t, tempDir, 1, 4, true) // remove certs for one gun _, err = runCommand(t, tempDir, "cert", "remove", "-g", "gun1", "-y") assert.NoError(t, err) certs = assertNumCerts(t, tempDir, 1) // assert that when we remove cert by gun, we do not remove repo signing keys // (root is always on disk, because even if there's a yubikey a backup is created) assertNumKeys(t, tempDir, 1, 4, true) // assert that when we remove cert by gun, we also remove TUF metadata _, err = os.Stat(filepath.Join(tempDir, "tuf", "gun1")) assert.Error(t, err) // remove a single cert certID := strings.Fields(certs[0])[1] // passing an empty gun here because the string for the previous gun has // has already been stored (a drawback of running these commands without) // shelling out _, err = runCommand(t, tempDir, "cert", "remove", certID, "-y", "-g", "") assert.NoError(t, err) assertNumCerts(t, tempDir, 0) // assert that when we remove the last cert ID for a gun, we also remove TUF metadata _, err = os.Stat(filepath.Join(tempDir, "tuf", "gun2")) assert.Error(t, err) // Setup certificate with nonexistent repo GUN // Check that we can only remove one certificate when specifying one ID startTime := time.Now() privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) assert.NoError(t, err) noGunCert, err := cryptoservice.GenerateCertificate( privKey, "nonexistent", startTime, startTime.AddDate(10, 0, 0)) assert.NoError(t, err) certStore, err := trustmanager.NewX509FileStore(filepath.Join(tempDir, "trusted_certificates")) assert.NoError(t, err) err = certStore.AddCert(noGunCert) assert.NoError(t, err) certs = assertNumCerts(t, tempDir, 1) certID = strings.Fields(certs[0])[1] privKey, err = trustmanager.GenerateECDSAKey(rand.Reader) assert.NoError(t, err) noGunCert2, err := cryptoservice.GenerateCertificate( privKey, "nonexistent", startTime, startTime.AddDate(10, 0, 0)) assert.NoError(t, err) err = certStore.AddCert(noGunCert2) assert.NoError(t, err) certs = assertNumCerts(t, tempDir, 2) // passing an empty gun to overwrite previously stored gun _, err = runCommand(t, tempDir, "cert", "remove", certID, "-y", "-g", "") assert.NoError(t, err) // Since another cert with the same GUN exists, we didn't remove everything assertNumCerts(t, tempDir, 1) }