func saveRepo(repo *tuf.TufRepo, filestore store.MetadataStore) error { fmt.Println("Saving changes to Trusted Collection.") signedRoot, err := repo.SignRoot(data.DefaultExpires("root")) if err != nil { return err } rootJSON, _ := json.Marshal(signedRoot) filestore.SetMeta("root", rootJSON) for r, _ := range repo.Targets { signedTargets, err := repo.SignTargets(r, data.DefaultExpires("targets")) if err != nil { return err } targetsJSON, _ := json.Marshal(signedTargets) parentDir := filepath.Dir(r) os.MkdirAll(parentDir, 0755) filestore.SetMeta(r, targetsJSON) } signedSnapshot, err := repo.SignSnapshot(data.DefaultExpires("snapshot")) if err != nil { return err } snapshotJSON, _ := json.Marshal(signedSnapshot) filestore.SetMeta("snapshot", snapshotJSON) signedTimestamp, err := repo.SignTimestamp(data.DefaultExpires("timestamp")) if err != nil { return err } timestampJSON, _ := json.Marshal(signedTimestamp) filestore.SetMeta("timestamp", timestampJSON) return nil }
// InitRepo creates the base files for a repo. It inspects data.ValidRoles and // data.ValidTypes to determine what the role names and filename should be. It // also relies on the keysDB having already been populated with the keys and // roles. func (tr *TufRepo) InitRepo(consistent bool) error { rootRoles := make(map[string]*data.RootRole) rootKeys := make(map[string]*data.PublicKey) for _, r := range data.ValidRoles { role := tr.keysDB.GetRole(r) if role == nil { return errors.ErrInvalidRole{} } rootRoles[r] = &role.RootRole for _, kid := range role.KeyIDs { // don't need to check if GetKey returns nil, Key presence was // checked by KeyDB when role was added. key := tr.keysDB.GetKey(kid) // Create new key object to doubly ensure private key is excluded k := data.NewPublicKey(key.Cipher(), key.Public()) rootKeys[kid] = k } } root, err := data.NewRoot(rootKeys, rootRoles, consistent) if err != nil { return err } tr.Root = root targets := data.NewTargets() tr.Targets[data.ValidRoles["targets"]] = targets signedRoot, err := tr.SignRoot(data.DefaultExpires("root")) if err != nil { return err } signedTargets, err := tr.SignTargets("targets", data.DefaultExpires("targets")) if err != nil { return err } snapshot, err := data.NewSnapshot(signedRoot, signedTargets) if err != nil { return err } tr.Snapshot = snapshot signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot")) if err != nil { return err } timestamp, err := data.NewTimestamp(signedSnapshot) if err != nil { return err } tr.Timestamp = timestamp return nil }
func (tr *TufRepo) SignSnapshot(expires time.Time, signer *signed.Signer) (*data.Signed, error) { logrus.Debug("SignSnapshot") if tr.Root.Dirty { signedRoot, err := tr.SignRoot(data.DefaultExpires("root"), signer) if err != nil { return nil, err } err = tr.UpdateSnapshot("root", signedRoot) if err != nil { return nil, err } tr.Root.Dirty = false // root dirty until changes captures in snapshot } for role, targets := range tr.Targets { if !targets.Dirty { continue } signedTargets, err := tr.SignTargets(role, data.DefaultExpires("targets"), signer) if err != nil { return nil, err } err = tr.UpdateSnapshot(role, signedTargets) if err != nil { return nil, err } tr.Targets[role].Dirty = false // target role dirty until changes captured in snapshot } if tr.Snapshot.Dirty { tr.Snapshot.Signed.Version++ signed, err := tr.Snapshot.ToSigned() if err != nil { return nil, err } snapshot := tr.keysDB.GetRole(data.ValidRoles["snapshot"]) signed, err = tr.sign(signed, *snapshot, signer) if err != nil { return nil, err } tr.Snapshot.Signatures = signed.Signatures return signed, nil } else { signed, err := tr.Snapshot.ToSigned() if err != nil { return nil, err } return signed, nil } }
func (tr *TufRepo) SignTimestamp(expires time.Time, signer *signed.Signer) (*data.Signed, error) { logrus.Debug("SignTimestamp") if tr.Snapshot.Dirty { signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot"), signer) if err != nil { return nil, err } err = tr.UpdateTimestamp(signedSnapshot) if err != nil { return nil, err } } if tr.Timestamp.Dirty { tr.Timestamp.Signed.Version++ signed, err := tr.Timestamp.ToSigned() if err != nil { return nil, err } timestamp := tr.keysDB.GetRole(data.ValidRoles["timestamp"]) signed, err = tr.sign(signed, *timestamp, signer) if err != nil { return nil, err } tr.Timestamp.Signatures = signed.Signatures tr.Snapshot.Dirty = false // snapshot is dirty until changes have been captured in timestamp return signed, nil } else { signed, err := tr.Timestamp.ToSigned() if err != nil { return nil, err } return signed, nil } }
func (tr *TufRepo) InitSnapshot() error { signedRoot, err := tr.SignRoot(data.DefaultExpires("root"), nil) if err != nil { return err } signedTargets, err := tr.SignTargets("targets", data.DefaultExpires("targets"), nil) if err != nil { return err } snapshot, err := data.NewSnapshot(signedRoot, signedTargets) if err != nil { return err } tr.Snapshot = snapshot return nil }
func (r *NotaryRepository) saveMetadata(rootCryptoService signed.CryptoService) error { logrus.Debugf("Saving changes to Trusted Collection.") signedRoot, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), rootCryptoService) if err != nil { return err } rootJSON, err := json.Marshal(signedRoot) if err != nil { return err } targetsToSave := make(map[string][]byte) for t := range r.tufRepo.Targets { signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires("targets"), nil) if err != nil { return err } targetsJSON, err := json.Marshal(signedTargets) if err != nil { return err } targetsToSave[t] = targetsJSON } signedSnapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { return err } snapshotJSON, err := json.Marshal(signedSnapshot) if err != nil { return err } err = r.fileStore.SetMeta("root", rootJSON) if err != nil { return err } for role, blob := range targetsToSave { parentDir := filepath.Dir(role) os.MkdirAll(parentDir, 0755) r.fileStore.SetMeta(role, blob) } return r.fileStore.SetMeta("snapshot", snapshotJSON) }
func (r *NotaryRepository) saveMetadata(rootSigner *signed.Signer) error { signedRoot, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), rootSigner) if err != nil { return err } rootJSON, _ := json.Marshal(signedRoot) return r.fileStore.SetMeta("root", rootJSON) }
func TestDownloadTargetsHappy(t *testing.T) { kdb, repo, _ := testutils.EmptyRepo() localStorage := store.NewMemoryStore(nil, nil) remoteStorage := store.NewMemoryStore(nil, nil) client := NewClient(repo, remoteStorage, kdb, localStorage) signedOrig, err := repo.SignTargets("targets", data.DefaultExpires("targets"), nil) assert.NoError(t, err) orig, err := json.Marshal(signedOrig) assert.NoError(t, err) err = remoteStorage.SetMeta("targets", orig) assert.NoError(t, err) // call repo.SignSnapshot to update the targets role in the snapshot repo.SignSnapshot(data.DefaultExpires("snapshot"), nil) err = client.downloadTargets("targets") assert.NoError(t, err) }
// Sign signs all top level roles in a repo in the appropriate order func Sign(repo *tuf.Repo) (root, targets, snapshot, timestamp *data.Signed, err error) { root, err = repo.SignRoot(data.DefaultExpires("root"), nil) if err != nil { return nil, nil, nil, nil, err } targets, err = repo.SignTargets("targets", data.DefaultExpires("targets"), nil) if err != nil { return nil, nil, nil, nil, err } snapshot, err = repo.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { return nil, nil, nil, nil, err } timestamp, err = repo.SignTimestamp(data.DefaultExpires("timestamp"), nil) if err != nil { return nil, nil, nil, nil, err } return }
func writeRepo(t *testing.T, dir string, repo *TufRepo) { //err := os.Remove(dir) //if err != nil { // t.Fatal(err) //} err := os.MkdirAll(dir, 0755) if err != nil { t.Fatal(err) } signedRoot, err := repo.SignRoot(data.DefaultExpires("root"), nil) if err != nil { t.Fatal(err) } rootJSON, _ := json.Marshal(signedRoot) ioutil.WriteFile(dir+"/root.json", rootJSON, 0755) for r, _ := range repo.Targets { signedTargets, err := repo.SignTargets(r, data.DefaultExpires("targets"), nil) if err != nil { t.Fatal(err) } targetsJSON, _ := json.Marshal(signedTargets) p := path.Join(dir, r+".json") parentDir := filepath.Dir(p) os.MkdirAll(parentDir, 0755) ioutil.WriteFile(p, targetsJSON, 0755) } signedSnapshot, err := repo.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { t.Fatal(err) } snapshotJSON, _ := json.Marshal(signedSnapshot) ioutil.WriteFile(dir+"/snapshot.json", snapshotJSON, 0755) signedTimestamp, err := repo.SignTimestamp(data.DefaultExpires("timestamp"), nil) if err != nil { t.Fatal(err) } timestampJSON, _ := json.Marshal(signedTimestamp) ioutil.WriteFile(dir+"/timestamp.json", timestampJSON, 0755) }
func TestUpdateDownloadRootHappy(t *testing.T) { kdb, repo, _ := testutils.EmptyRepo() localStorage := store.NewMemoryStore(nil, nil) remoteStorage := store.NewMemoryStore(nil, nil) client := NewClient(repo, remoteStorage, kdb, localStorage) // create and "upload" sample root, snapshot, and timestamp signedOrig, err := repo.SignRoot(data.DefaultExpires("root"), nil) assert.NoError(t, err) orig, err := json.Marshal(signedOrig) assert.NoError(t, err) err = remoteStorage.SetMeta("root", orig) assert.NoError(t, err) // sign snapshot to make root meta in snapshot get updated signedOrig, err = repo.SignSnapshot(data.DefaultExpires("snapshot"), nil) err = client.downloadRoot() assert.NoError(t, err) }
func (r *NotaryRepository) snapshot() error { fmt.Println("Saving changes to Trusted Collection.") for t := range r.tufRepo.Targets { signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires("targets"), nil) if err != nil { return err } targetsJSON, _ := json.Marshal(signedTargets) parentDir := filepath.Dir(t) os.MkdirAll(parentDir, 0755) r.fileStore.SetMeta(t, targetsJSON) } signedSnapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { return err } snapshotJSON, _ := json.Marshal(signedSnapshot) return r.fileStore.SetMeta("snapshot", snapshotJSON) }
func (tr *TufRepo) InitTimestamp() error { signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { return err } timestamp, err := data.NewTimestamp(signedSnapshot) if err != nil { return err } tr.Timestamp = timestamp return nil }
func TestDownloadSnapshotBadChecksum(t *testing.T) { kdb, repo, _ := testutils.EmptyRepo() localStorage := store.NewMemoryStore(nil, nil) remoteStorage := store.NewMemoryStore(nil, nil) client := NewClient(repo, remoteStorage, kdb, localStorage) // sign timestamp to ensure it has a checksum for snapshot _, err := repo.SignTimestamp(data.DefaultExpires("timestamp"), nil) assert.NoError(t, err) // create and "upload" sample snapshot and timestamp signedOrig, err := repo.SignSnapshot(data.DefaultExpires("snapshot"), nil) assert.NoError(t, err) orig, err := json.Marshal(signedOrig) assert.NoError(t, err) err = remoteStorage.SetMeta("snapshot", orig) assert.NoError(t, err) // by not signing timestamp again we ensure it has the wrong checksum err = client.downloadSnapshot() assert.IsType(t, ErrChecksumMismatch{}, err) }
func TestUpdateDownloadRootBadChecksum(t *testing.T) { kdb, repo, _ := testutils.EmptyRepo() localStorage := store.NewMemoryStore(nil, nil) remoteStorage := store.NewMemoryStore(nil, nil) client := NewClient(repo, remoteStorage, kdb, localStorage) // sign snapshot to make sure we have a checksum for root _, err := repo.SignSnapshot(data.DefaultExpires("snapshot"), nil) assert.NoError(t, err) // create and "upload" sample root, snapshot, and timestamp signedOrig, err := repo.SignRoot(data.DefaultExpires("root"), nil) assert.NoError(t, err) orig, err := json.Marshal(signedOrig) assert.NoError(t, err) err = remoteStorage.SetMeta("root", orig) assert.NoError(t, err) // don't sign snapshot again to ensure checksum is out of date (bad) err = client.downloadRoot() assert.IsType(t, ErrChecksumMismatch{}, err) }
func TestDownloadSnapshotHappy(t *testing.T) { kdb, repo, _ := testutils.EmptyRepo() localStorage := store.NewMemoryStore(nil, nil) remoteStorage := store.NewMemoryStore(nil, nil) client := NewClient(repo, remoteStorage, kdb, localStorage) // create and "upload" sample snapshot and timestamp signedOrig, err := repo.SignSnapshot(data.DefaultExpires("snapshot"), nil) assert.NoError(t, err) orig, err := json.Marshal(signedOrig) assert.NoError(t, err) err = remoteStorage.SetMeta("snapshot", orig) assert.NoError(t, err) signedOrig, err = repo.SignTimestamp(data.DefaultExpires("timestamp"), nil) assert.NoError(t, err) orig, err = json.Marshal(signedOrig) assert.NoError(t, err) err = remoteStorage.SetMeta("timestamp", orig) assert.NoError(t, err) err = client.downloadSnapshot() assert.NoError(t, err) }
func TestDownloadSnapshotNoChecksum(t *testing.T) { kdb, repo, _ := testutils.EmptyRepo() localStorage := store.NewMemoryStore(nil, nil) remoteStorage := store.NewMemoryStore(nil, nil) client := NewClient(repo, remoteStorage, kdb, localStorage) // create and "upload" sample snapshot and timestamp signedOrig, err := repo.SignSnapshot(data.DefaultExpires("snapshot"), nil) assert.NoError(t, err) orig, err := json.Marshal(signedOrig) assert.NoError(t, err) err = remoteStorage.SetMeta("snapshot", orig) assert.NoError(t, err) delete(repo.Timestamp.Signed.Meta["snapshot"].Hashes, "sha256") err = client.downloadSnapshot() assert.IsType(t, ErrMissingMeta{}, err) }
// TestDownloadTargetsNoSnapshot: it's never valid to download any targets // role (incl. delegations) when a checksum is not available. func TestDownloadTargetsNoSnapshot(t *testing.T) { kdb, repo, _ := testutils.EmptyRepo() localStorage := store.NewMemoryStore(nil, nil) remoteStorage := store.NewMemoryStore(nil, nil) client := NewClient(repo, remoteStorage, kdb, localStorage) // create and "upload" sample targets signedOrig, err := repo.SignTargets("targets", data.DefaultExpires("targets"), nil) assert.NoError(t, err) orig, err := json.Marshal(signedOrig) assert.NoError(t, err) err = remoteStorage.SetMeta("targets", orig) assert.NoError(t, err) repo.Snapshot = nil err = client.downloadTargets("targets") assert.IsType(t, ErrMissingMeta{}, err) }
func TestBootstrapDownloadRootHappy(t *testing.T) { kdb, repo, _ := testutils.EmptyRepo() localStorage := store.NewMemoryStore(nil, nil) remoteStorage := store.NewMemoryStore(nil, nil) client := NewClient(repo, remoteStorage, kdb, localStorage) // create and "upload" sample root signedOrig, err := repo.SignRoot(data.DefaultExpires("root"), nil) assert.NoError(t, err) orig, err := json.Marshal(signedOrig) assert.NoError(t, err) err = remoteStorage.SetMeta("root", orig) assert.NoError(t, err) // unset snapshot as if we're bootstrapping from nothing repo.Snapshot = nil err = client.downloadRoot() assert.NoError(t, err) }
func TestDownloadTargetChecksumMismatch(t *testing.T) { kdb, repo, _ := testutils.EmptyRepo() localStorage := store.NewMemoryStore(nil, nil) remoteStorage := store.NewMemoryStore(nil, nil) client := NewClient(repo, remoteStorage, kdb, localStorage) // create and "upload" sample targets signedOrig, err := repo.SignTargets("targets", data.DefaultExpires("targets"), nil) assert.NoError(t, err) orig, err := json.Marshal(signedOrig) assert.NoError(t, err) origSha256 := sha256.Sum256(orig) orig[0] = '}' // corrupt data, should be a { err = remoteStorage.SetMeta("targets", orig) assert.NoError(t, err) // create local snapshot with targets file // It's necessary to do it this way rather than calling repo.SignSnapshot // so that we have the wrong sha256 in the snapshot. snap := data.SignedSnapshot{ Signed: data.Snapshot{ Meta: data.Files{ "targets": data.FileMeta{ Length: int64(len(orig)), Hashes: data.Hashes{ "sha256": origSha256[:], }, }, }, }, } repo.Snapshot = &snap err = client.downloadTargets("targets") assert.IsType(t, ErrChecksumMismatch{}, err) }
// Publish pushes the local changes in signed material to the remote notary-server // Conceptually it performs an operation similar to a `git rebase` func (r *NotaryRepository) Publish(getPass passwordRetriever) error { var updateRoot bool var root *data.Signed // attempt to initialize the repo from the remote store c, err := r.bootstrapClient() if err != nil { if _, ok := err.(*store.ErrMetaNotFound); ok { // if the remote store return a 404 (translated into ErrMetaNotFound), // the repo hasn't been initialized yet. Attempt to load it from disk. err := r.bootstrapRepo() if err != nil { // Repo hasn't been initialized, It must be initialized before // it can be published. Return an error and let caller determine // what it wants to do. logrus.Debug("Repository not initialized during Publish") return &ErrRepoNotInitialized{} } // We had local data but the server doesn't know about the repo yet, // ensure we will push the initial root file root, err = r.tufRepo.Root.ToSigned() if err != nil { return err } updateRoot = true } else { // The remote store returned an error other than 404. We're // unable to determine if the repo has been initialized or not. logrus.Error("Could not publish Repository: ", err.Error()) return err } } else { // If we were successfully able to bootstrap the client (which only pulls // root.json), update it the rest of the tuf metadata in preparation for // applying the changelist. err = c.Update() if err != nil { return err } } // load the changelist for this repo cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { logrus.Debug("Error initializing changelist") return err } // apply the changelist to the repo err = applyChangelist(r.tufRepo, cl) if err != nil { logrus.Debug("Error applying changelist") return err } // check if our root file is nearing expiry. Resign if it is. if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty { passphrase, err := getPass() if err != nil { return err } rootKeyID := r.tufRepo.Root.Signed.Roles["root"].KeyIDs[0] rootSigner, err := r.GetRootSigner(rootKeyID, passphrase) if err != nil { return err } root, err = r.tufRepo.SignRoot(data.DefaultExpires("root"), rootSigner.signer) if err != nil { return err } updateRoot = true } // we will always resign targets and snapshots targets, err := r.tufRepo.SignTargets("targets", data.DefaultExpires("targets"), nil) if err != nil { return err } snapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { return err } remote, err := getRemoteStore(r.baseURL, r.Gun) if err != nil { return err } // ensure we can marshal all the json before sending anything to remote targetsJSON, err := json.Marshal(targets) if err != nil { return err } snapshotJSON, err := json.Marshal(snapshot) if err != nil { return err } // if we need to update the root, marshal it and push the update to remote if updateRoot { rootJSON, err := json.Marshal(root) if err != nil { return err } err = remote.SetMeta("root", rootJSON) if err != nil { return err } } err = remote.SetMeta("targets", targetsJSON) if err != nil { return err } err = remote.SetMeta("snapshot", snapshotJSON) if err != nil { return err } return nil }
func testAddListTarget(t *testing.T, rootType data.KeyAlgorithm) { // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) gun := "docker.com/notary" ts, mux := createTestServer(t) defer ts.Close() repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) assert.NoError(t, err, "error creating repository: %s", err) rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String()) assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retreiving root key: %s", err) err = repo.Initialize(rootCryptoService) assert.NoError(t, err, "error creating repository: %s", err) // tests need to manually boostrap timestamp as client doesn't generate it err = repo.tufRepo.InitTimestamp() assert.NoError(t, err, "error creating repository: %s", err) // Add fixtures/intermediate-ca.crt as a target. There's no particular reason // for using this file except that it happens to be available as // a fixture. latestTarget, err := NewTarget("latest", "../fixtures/intermediate-ca.crt") assert.NoError(t, err, "error creating target") err = repo.AddTarget(latestTarget) assert.NoError(t, err, "error adding target") // Look for the changelist file changelistDirPath := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "changelist") changelistDir, err := os.Open(changelistDirPath) assert.NoError(t, err, "could not open changelist directory") fileInfos, err := changelistDir.Readdir(0) assert.NoError(t, err, "could not read changelist directory") // Should only be one file in the directory assert.Len(t, fileInfos, 1, "wrong number of changelist files found") clName := fileInfos[0].Name() raw, err := ioutil.ReadFile(filepath.Join(changelistDirPath, clName)) assert.NoError(t, err, "could not read changelist file %s", clName) c := &changelist.TufChange{} err = json.Unmarshal(raw, c) assert.NoError(t, err, "could not unmarshal changelist file %s", clName) assert.EqualValues(t, changelist.ActionCreate, c.Actn) assert.Equal(t, "targets", c.Role) assert.Equal(t, "target", c.ChangeType) assert.Equal(t, "latest", c.ChangePath) assert.NotEmpty(t, c.Data) changelistDir.Close() // Create a second target currentTarget, err := NewTarget("current", "../fixtures/intermediate-ca.crt") assert.NoError(t, err, "error creating target") err = repo.AddTarget(currentTarget) assert.NoError(t, err, "error adding target") changelistDir, err = os.Open(changelistDirPath) assert.NoError(t, err, "could not open changelist directory") // There should now be a second file in the directory fileInfos, err = changelistDir.Readdir(0) assert.NoError(t, err, "could not read changelist directory") assert.Len(t, fileInfos, 2, "wrong number of changelist files found") newFileFound := false for _, fileInfo := range fileInfos { if fileInfo.Name() != clName { clName2 := fileInfo.Name() raw, err := ioutil.ReadFile(filepath.Join(changelistDirPath, clName2)) assert.NoError(t, err, "could not read changelist file %s", clName2) c := &changelist.TufChange{} err = json.Unmarshal(raw, c) assert.NoError(t, err, "could not unmarshal changelist file %s", clName2) assert.EqualValues(t, changelist.ActionCreate, c.Actn) assert.Equal(t, "targets", c.Role) assert.Equal(t, "target", c.ChangeType) assert.Equal(t, "current", c.ChangePath) assert.NotEmpty(t, c.Data) newFileFound = true break } } assert.True(t, newFileFound, "second changelist file not found") changelistDir.Close() // Now test ListTargets. In preparation, we need to expose some signed // metadata files on the internal HTTP server. // Apply the changelist. Normally, this would be done by Publish // load the changelist for this repo cl, err := changelist.NewFileChangelist(filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "changelist")) assert.NoError(t, err, "could not open changelist") // apply the changelist to the repo err = applyChangelist(repo.tufRepo, cl) assert.NoError(t, err, "could not apply changelist") var tempKey data.TUFKey json.Unmarshal([]byte(timestampECDSAKeyJSON), &tempKey) repo.KeyStoreManager.NonRootKeyStore().AddKey(filepath.Join(filepath.FromSlash(gun), tempKey.ID()), "nonroot", &tempKey) // Because ListTargets will clear this savedTUFRepo := repo.tufRepo rootJSONFile := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "metadata", "root.json") rootFileBytes, err := ioutil.ReadFile(rootJSONFile) signedTargets, err := savedTUFRepo.SignTargets("targets", data.DefaultExpires("targets"), nil) assert.NoError(t, err) signedSnapshot, err := savedTUFRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil) assert.NoError(t, err) signedTimestamp, err := savedTUFRepo.SignTimestamp(data.DefaultExpires("timestamp"), nil) assert.NoError(t, err) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/root.json", func(w http.ResponseWriter, r *http.Request) { assert.NoError(t, err) fmt.Fprint(w, string(rootFileBytes)) }) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/timestamp.json", func(w http.ResponseWriter, r *http.Request) { timestampJSON, _ := json.Marshal(signedTimestamp) fmt.Fprint(w, string(timestampJSON)) }) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/snapshot.json", func(w http.ResponseWriter, r *http.Request) { snapshotJSON, _ := json.Marshal(signedSnapshot) fmt.Fprint(w, string(snapshotJSON)) }) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/targets.json", func(w http.ResponseWriter, r *http.Request) { targetsJSON, _ := json.Marshal(signedTargets) fmt.Fprint(w, string(targetsJSON)) }) targets, err := repo.ListTargets() assert.NoError(t, err) // Should be two targets assert.Len(t, targets, 2, "unexpected number of targets returned by ListTargets") if targets[0].Name == "latest" { assert.Equal(t, latestTarget, targets[0], "latest target does not match") assert.Equal(t, currentTarget, targets[1], "current target does not match") } else if targets[0].Name == "current" { assert.Equal(t, currentTarget, targets[0], "current target does not match") assert.Equal(t, latestTarget, targets[1], "latest target does not match") } else { t.Fatalf("unexpected target name: %s", targets[0].Name) } // Also test GetTargetByName newLatestTarget, err := repo.GetTargetByName("latest") assert.NoError(t, err) assert.Equal(t, latestTarget, newLatestTarget, "latest target does not match") newCurrentTarget, err := repo.GetTargetByName("current") assert.NoError(t, err) assert.Equal(t, currentTarget, newCurrentTarget, "current target does not match") }
func validateRootSuccessfully(t *testing.T, rootType data.KeyAlgorithm) { // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) gun := "docker.com/notary" ts, mux := createTestServer(t) defer ts.Close() repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) assert.NoError(t, err, "error creating repository: %s", err) rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String()) assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo.Initialize(rootCryptoService) assert.NoError(t, err, "error creating repository: %s", err) // tests need to manually boostrap timestamp as client doesn't generate it err = repo.tufRepo.InitTimestamp() assert.NoError(t, err, "error creating repository: %s", err) // Initialize is supposed to have created new certificate for this repository // Lets check for it and store it for later use allCerts := repo.KeyStoreManager.TrustedCertificateStore().GetCertificates() assert.Len(t, allCerts, 1) // Now test ListTargets. In preparation, we need to expose some signed // metadata files on the internal HTTP server. var tempKey data.TUFKey json.Unmarshal([]byte(timestampECDSAKeyJSON), &tempKey) repo.KeyStoreManager.NonRootKeyStore().AddKey(filepath.Join(filepath.FromSlash(gun), tempKey.ID()), "root", &tempKey) // Because ListTargets will clear this savedTUFRepo := repo.tufRepo rootJSONFile := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "metadata", "root.json") rootFileBytes, err := ioutil.ReadFile(rootJSONFile) signedTargets, err := savedTUFRepo.SignTargets("targets", data.DefaultExpires("targets"), nil) assert.NoError(t, err) signedSnapshot, err := savedTUFRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil) assert.NoError(t, err) signedTimestamp, err := savedTUFRepo.SignTimestamp(data.DefaultExpires("timestamp"), nil) assert.NoError(t, err) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/root.json", func(w http.ResponseWriter, r *http.Request) { assert.NoError(t, err) fmt.Fprint(w, string(rootFileBytes)) }) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/timestamp.json", func(w http.ResponseWriter, r *http.Request) { timestampJSON, _ := json.Marshal(signedTimestamp) fmt.Fprint(w, string(timestampJSON)) }) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/snapshot.json", func(w http.ResponseWriter, r *http.Request) { snapshotJSON, _ := json.Marshal(signedSnapshot) fmt.Fprint(w, string(snapshotJSON)) }) mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/targets.json", func(w http.ResponseWriter, r *http.Request) { targetsJSON, _ := json.Marshal(signedTargets) fmt.Fprint(w, string(targetsJSON)) }) _, err = repo.ListTargets() assert.NoError(t, err) // // Test TOFUS logic. We remove all certs and expect a new one to be added after ListTargets // err = repo.KeyStoreManager.TrustedCertificateStore().RemoveAll() assert.NoError(t, err) assert.Len(t, repo.KeyStoreManager.TrustedCertificateStore().GetCertificates(), 0) // This list targets is expected to succeed and the certificate store to have the new certificate _, err = repo.ListTargets() assert.NoError(t, err) assert.Len(t, repo.KeyStoreManager.TrustedCertificateStore().GetCertificates(), 1) // // Test certificate mismatch logic. We remove all certs, add a different cert to the // same CN, and expect ValidateRoot to fail // // First, remove all certs err = repo.KeyStoreManager.TrustedCertificateStore().RemoveAll() assert.NoError(t, err) assert.Len(t, repo.KeyStoreManager.TrustedCertificateStore().GetCertificates(), 0) // Add a previously generated certificate with CN=docker.com/notary err = repo.KeyStoreManager.TrustedCertificateStore().AddCertFromFile("../fixtures/self-signed_docker.com-notary.crt") assert.NoError(t, err) // This list targets is expected to fail, since there already exists a certificate // in the store for the dnsName docker.com/notary, so TOFUS doesn't apply _, err = repo.ListTargets() if assert.Error(t, err, "An error was expected") { assert.Equal(t, err, &keystoremanager.ErrValidationFail{Reason: "failed to validate data with current trusted certificates"}) } }
func TestValidateRootRotation(t *testing.T) { _, repo, crypto := testutils.EmptyRepo() store := storage.NewMemStorage() r, tg, sn, ts, err := testutils.Sign(repo) assert.NoError(t, err) root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) assert.NoError(t, err) store.UpdateCurrent( "testGUN", storage.MetaUpdate{ Role: "root", Version: 1, Data: root, }, ) oldRootRole := repo.Root.Signed.Roles["root"] oldRootKey := repo.Root.Signed.Keys[oldRootRole.KeyIDs[0]] rootKey, err := crypto.Create("root", data.ED25519Key) assert.NoError(t, err) rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil) assert.NoError(t, err) delete(repo.Root.Signed.Keys, oldRootRole.KeyIDs[0]) repo.Root.Signed.Roles["root"] = &rootRole.RootRole repo.Root.Signed.Keys[rootKey.ID()] = data.NewPrivateKey(rootKey.Algorithm(), rootKey.Public(), nil) r, err = repo.SignRoot(data.DefaultExpires(data.CanonicalRootRole), nil) assert.NoError(t, err) err = signed.Sign(crypto, r, rootKey, oldRootKey) assert.NoError(t, err) rt, err := data.RootFromSigned(r) assert.NoError(t, err) repo.SetRoot(rt) sn, err = repo.SignSnapshot(data.DefaultExpires(data.CanonicalSnapshotRole), nil) assert.NoError(t, err) root, targets, snapshot, timestamp, err = testutils.Serialize(r, tg, sn, ts) assert.NoError(t, err) updates := []storage.MetaUpdate{ { Role: "root", Version: 1, Data: root, }, { Role: "targets", Version: 1, Data: targets, }, { Role: "snapshot", Version: 1, Data: snapshot, }, { Role: "timestamp", Version: 1, Data: timestamp, }, } err = validateUpdate("testGUN", updates, store) assert.NoError(t, err) }
// Publish pushes the local changes in signed material to the remote notary-server // Conceptually it performs an operation similar to a `git rebase` func (r *NotaryRepository) Publish() error { var updateRoot bool var root *data.Signed // attempt to initialize the repo from the remote store c, err := r.bootstrapClient() if err != nil { if _, ok := err.(store.ErrMetaNotFound); ok { // if the remote store return a 404 (translated into ErrMetaNotFound), // the repo hasn't been initialized yet. Attempt to load it from disk. err := r.bootstrapRepo() if err != nil { // Repo hasn't been initialized, It must be initialized before // it can be published. Return an error and let caller determine // what it wants to do. logrus.Debug(err.Error()) logrus.Debug("Repository not initialized during Publish") return &ErrRepoNotInitialized{} } // We had local data but the server doesn't know about the repo yet, // ensure we will push the initial root file root, err = r.tufRepo.Root.ToSigned() if err != nil { return err } updateRoot = true } else { // The remote store returned an error other than 404. We're // unable to determine if the repo has been initialized or not. logrus.Error("Could not publish Repository: ", err.Error()) return err } } else { // If we were successfully able to bootstrap the client (which only pulls // root.json), update it the rest of the tuf metadata in preparation for // applying the changelist. err = c.Update() if err != nil { if err, ok := err.(signed.ErrExpired); ok { return ErrExpired{err} } return err } } // load the changelist for this repo changelistDir := filepath.Join(r.tufRepoPath, "changelist") cl, err := changelist.NewFileChangelist(changelistDir) if err != nil { logrus.Debug("Error initializing changelist") return err } // apply the changelist to the repo err = applyChangelist(r.tufRepo, cl) if err != nil { logrus.Debug("Error applying changelist") return err } // check if our root file is nearing expiry. Resign if it is. if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty { if err != nil { return err } rootKeyID := r.tufRepo.Root.Signed.Roles["root"].KeyIDs[0] rootCryptoService, err := r.KeyStoreManager.GetRootCryptoService(rootKeyID) if err != nil { return err } root, err = r.tufRepo.SignRoot(data.DefaultExpires("root"), rootCryptoService.CryptoService) if err != nil { return err } updateRoot = true } // we will always resign targets and snapshots targets, err := r.tufRepo.SignTargets("targets", data.DefaultExpires("targets"), nil) if err != nil { return err } snapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { return err } remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip) if err != nil { return err } // ensure we can marshal all the json before sending anything to remote targetsJSON, err := json.Marshal(targets) if err != nil { return err } snapshotJSON, err := json.Marshal(snapshot) if err != nil { return err } update := make(map[string][]byte) // if we need to update the root, marshal it and push the update to remote if updateRoot { rootJSON, err := json.Marshal(root) if err != nil { return err } update["root"] = rootJSON } update["targets"] = targetsJSON update["snapshot"] = snapshotJSON err = remote.SetMultiMeta(update) if err != nil { return err } err = cl.Clear("") if err != nil { // This is not a critical problem when only a single host is pushing // but will cause weird behaviour if changelist cleanup is failing // and there are multiple hosts writing to the repo. logrus.Warn("Unable to clear changelist. You may want to manually delete the folder ", changelistDir) } return nil }