1package client 2 3import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "net/url" 10 "os" 11 "path/filepath" 12 "strings" 13 "time" 14 15 "github.com/sirupsen/logrus" 16 "github.com/docker/notary" 17 "github.com/docker/notary/client/changelist" 18 "github.com/docker/notary/cryptoservice" 19 store "github.com/docker/notary/storage" 20 "github.com/docker/notary/trustmanager" 21 "github.com/docker/notary/trustpinning" 22 "github.com/docker/notary/tuf" 23 "github.com/docker/notary/tuf/data" 24 "github.com/docker/notary/tuf/signed" 25 "github.com/docker/notary/tuf/utils" 26) 27 28func init() { 29 data.SetDefaultExpiryTimes(notary.NotaryDefaultExpiries) 30} 31 32// ErrRepoNotInitialized is returned when trying to publish an uninitialized 33// notary repository 34type ErrRepoNotInitialized struct{} 35 36func (err ErrRepoNotInitialized) Error() string { 37 return "repository has not been initialized" 38} 39 40// ErrInvalidRemoteRole is returned when the server is requested to manage 41// a key type that is not permitted 42type ErrInvalidRemoteRole struct { 43 Role string 44} 45 46func (err ErrInvalidRemoteRole) Error() string { 47 return fmt.Sprintf( 48 "notary does not permit the server managing the %s key", err.Role) 49} 50 51// ErrInvalidLocalRole is returned when the client wants to manage 52// a key type that is not permitted 53type ErrInvalidLocalRole struct { 54 Role string 55} 56 57func (err ErrInvalidLocalRole) Error() string { 58 return fmt.Sprintf( 59 "notary does not permit the client managing the %s key", err.Role) 60} 61 62// ErrRepositoryNotExist is returned when an action is taken on a remote 63// repository that doesn't exist 64type ErrRepositoryNotExist struct { 65 remote string 66 gun string 67} 68 69func (err ErrRepositoryNotExist) Error() string { 70 return fmt.Sprintf("%s does not have trust data for %s", err.remote, err.gun) 71} 72 73const ( 74 tufDir = "tuf" 75) 76 77// NotaryRepository stores all the information needed to operate on a notary 78// repository. 79type NotaryRepository struct { 80 baseDir string 81 gun string 82 baseURL string 83 tufRepoPath string 84 fileStore store.MetadataStore 85 CryptoService signed.CryptoService 86 tufRepo *tuf.Repo 87 invalid *tuf.Repo // known data that was parsable but deemed invalid 88 roundTrip http.RoundTripper 89 trustPinning trustpinning.TrustPinConfig 90} 91 92// repositoryFromKeystores is a helper function for NewNotaryRepository that 93// takes some basic NotaryRepository parameters as well as keystores (in order 94// of usage preference), and returns a NotaryRepository. 95func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper, 96 keyStores []trustmanager.KeyStore, trustPin trustpinning.TrustPinConfig) (*NotaryRepository, error) { 97 98 cryptoService := cryptoservice.NewCryptoService(keyStores...) 99 100 nRepo := &NotaryRepository{ 101 gun: gun, 102 baseDir: baseDir, 103 baseURL: baseURL, 104 tufRepoPath: filepath.Join(baseDir, tufDir, filepath.FromSlash(gun)), 105 CryptoService: cryptoService, 106 roundTrip: rt, 107 trustPinning: trustPin, 108 } 109 110 fileStore, err := store.NewFilesystemStore( 111 nRepo.tufRepoPath, 112 "metadata", 113 "json", 114 ) 115 if err != nil { 116 return nil, err 117 } 118 nRepo.fileStore = fileStore 119 120 return nRepo, nil 121} 122 123// Target represents a simplified version of the data TUF operates on, so external 124// applications don't have to depend on TUF data types. 125type Target struct { 126 Name string // the name of the target 127 Hashes data.Hashes // the hash of the target 128 Length int64 // the size in bytes of the target 129} 130 131// TargetWithRole represents a Target that exists in a particular role - this is 132// produced by ListTargets and GetTargetByName 133type TargetWithRole struct { 134 Target 135 Role string 136} 137 138// NewTarget is a helper method that returns a Target 139func NewTarget(targetName string, targetPath string) (*Target, error) { 140 b, err := ioutil.ReadFile(targetPath) 141 if err != nil { 142 return nil, err 143 } 144 145 meta, err := data.NewFileMeta(bytes.NewBuffer(b), data.NotaryDefaultHashes...) 146 if err != nil { 147 return nil, err 148 } 149 150 return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length}, nil 151} 152 153func rootCertKey(gun string, privKey data.PrivateKey) (data.PublicKey, error) { 154 // Hard-coded policy: the generated certificate expires in 10 years. 155 startTime := time.Now() 156 cert, err := cryptoservice.GenerateCertificate( 157 privKey, gun, startTime, startTime.Add(notary.Year*10)) 158 if err != nil { 159 return nil, err 160 } 161 162 x509PublicKey := utils.CertToKey(cert) 163 if x509PublicKey == nil { 164 return nil, fmt.Errorf( 165 "cannot use regenerated certificate: format %s", cert.PublicKeyAlgorithm) 166 } 167 168 return x509PublicKey, nil 169} 170 171// Initialize creates a new repository by using rootKey as the root Key for the 172// TUF repository. The server must be reachable (and is asked to generate a 173// timestamp key and possibly other serverManagedRoles), but the created repository 174// result is only stored on local disk, not published to the server. To do that, 175// use r.Publish() eventually. 176func (r *NotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...string) error { 177 privKeys := make([]data.PrivateKey, 0, len(rootKeyIDs)) 178 for _, keyID := range rootKeyIDs { 179 privKey, _, err := r.CryptoService.GetPrivateKey(keyID) 180 if err != nil { 181 return err 182 } 183 privKeys = append(privKeys, privKey) 184 } 185 186 // currently we only support server managing timestamps and snapshots, and 187 // nothing else - timestamps are always managed by the server, and implicit 188 // (do not have to be passed in as part of `serverManagedRoles`, so that 189 // the API of Initialize doesn't change). 190 var serverManagesSnapshot bool 191 locallyManagedKeys := []string{ 192 data.CanonicalTargetsRole, 193 data.CanonicalSnapshotRole, 194 // root is also locally managed, but that should have been created 195 // already 196 } 197 remotelyManagedKeys := []string{data.CanonicalTimestampRole} 198 for _, role := range serverManagedRoles { 199 switch role { 200 case data.CanonicalTimestampRole: 201 continue // timestamp is already in the right place 202 case data.CanonicalSnapshotRole: 203 // because we put Snapshot last 204 locallyManagedKeys = []string{data.CanonicalTargetsRole} 205 remotelyManagedKeys = append( 206 remotelyManagedKeys, data.CanonicalSnapshotRole) 207 serverManagesSnapshot = true 208 default: 209 return ErrInvalidRemoteRole{Role: role} 210 } 211 } 212 213 rootKeys := make([]data.PublicKey, 0, len(privKeys)) 214 for _, privKey := range privKeys { 215 rootKey, err := rootCertKey(r.gun, privKey) 216 if err != nil { 217 return err 218 } 219 rootKeys = append(rootKeys, rootKey) 220 } 221 222 var ( 223 rootRole = data.NewBaseRole( 224 data.CanonicalRootRole, 225 notary.MinThreshold, 226 rootKeys..., 227 ) 228 timestampRole data.BaseRole 229 snapshotRole data.BaseRole 230 targetsRole data.BaseRole 231 ) 232 233 // we want to create all the local keys first so we don't have to 234 // make unnecessary network calls 235 for _, role := range locallyManagedKeys { 236 // This is currently hardcoding the keys to ECDSA. 237 key, err := r.CryptoService.Create(role, r.gun, data.ECDSAKey) 238 if err != nil { 239 return err 240 } 241 switch role { 242 case data.CanonicalSnapshotRole: 243 snapshotRole = data.NewBaseRole( 244 role, 245 notary.MinThreshold, 246 key, 247 ) 248 case data.CanonicalTargetsRole: 249 targetsRole = data.NewBaseRole( 250 role, 251 notary.MinThreshold, 252 key, 253 ) 254 } 255 } 256 for _, role := range remotelyManagedKeys { 257 // This key is generated by the remote server. 258 key, err := getRemoteKey(r.baseURL, r.gun, role, r.roundTrip) 259 if err != nil { 260 return err 261 } 262 logrus.Debugf("got remote %s %s key with keyID: %s", 263 role, key.Algorithm(), key.ID()) 264 switch role { 265 case data.CanonicalSnapshotRole: 266 snapshotRole = data.NewBaseRole( 267 role, 268 notary.MinThreshold, 269 key, 270 ) 271 case data.CanonicalTimestampRole: 272 timestampRole = data.NewBaseRole( 273 role, 274 notary.MinThreshold, 275 key, 276 ) 277 } 278 } 279 280 r.tufRepo = tuf.NewRepo(r.CryptoService) 281 282 err := r.tufRepo.InitRoot( 283 rootRole, 284 timestampRole, 285 snapshotRole, 286 targetsRole, 287 false, 288 ) 289 if err != nil { 290 logrus.Debug("Error on InitRoot: ", err.Error()) 291 return err 292 } 293 _, err = r.tufRepo.InitTargets(data.CanonicalTargetsRole) 294 if err != nil { 295 logrus.Debug("Error on InitTargets: ", err.Error()) 296 return err 297 } 298 err = r.tufRepo.InitSnapshot() 299 if err != nil { 300 logrus.Debug("Error on InitSnapshot: ", err.Error()) 301 return err 302 } 303 304 return r.saveMetadata(serverManagesSnapshot) 305} 306 307// adds a TUF Change template to the given roles 308func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...string) error { 309 310 if len(roles) == 0 { 311 roles = []string{data.CanonicalTargetsRole} 312 } 313 314 var changes []changelist.Change 315 for _, role := range roles { 316 // Ensure we can only add targets to the CanonicalTargetsRole, 317 // or a Delegation role (which is <CanonicalTargetsRole>/something else) 318 if role != data.CanonicalTargetsRole && !data.IsDelegation(role) && !data.IsWildDelegation(role) { 319 return data.ErrInvalidRole{ 320 Role: role, 321 Reason: "cannot add targets to this role", 322 } 323 } 324 325 changes = append(changes, changelist.NewTUFChange( 326 c.Action(), 327 role, 328 c.Type(), 329 c.Path(), 330 c.Content(), 331 )) 332 } 333 334 for _, c := range changes { 335 if err := cl.Add(c); err != nil { 336 return err 337 } 338 } 339 return nil 340} 341 342// AddTarget creates new changelist entries to add a target to the given roles 343// in the repository when the changelist gets applied at publish time. 344// If roles are unspecified, the default role is "targets" 345func (r *NotaryRepository) AddTarget(target *Target, roles ...string) error { 346 347 if len(target.Hashes) == 0 { 348 return fmt.Errorf("no hashes specified for target \"%s\"", target.Name) 349 } 350 cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) 351 if err != nil { 352 return err 353 } 354 defer cl.Close() 355 logrus.Debugf("Adding target \"%s\" with sha256 \"%x\" and size %d bytes.\n", target.Name, target.Hashes["sha256"], target.Length) 356 357 meta := data.FileMeta{Length: target.Length, Hashes: target.Hashes} 358 metaJSON, err := json.Marshal(meta) 359 if err != nil { 360 return err 361 } 362 363 template := changelist.NewTUFChange( 364 changelist.ActionCreate, "", changelist.TypeTargetsTarget, 365 target.Name, metaJSON) 366 return addChange(cl, template, roles...) 367} 368 369// RemoveTarget creates new changelist entries to remove a target from the given 370// roles in the repository when the changelist gets applied at publish time. 371// If roles are unspecified, the default role is "target". 372func (r *NotaryRepository) RemoveTarget(targetName string, roles ...string) error { 373 374 cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) 375 if err != nil { 376 return err 377 } 378 logrus.Debugf("Removing target \"%s\"", targetName) 379 template := changelist.NewTUFChange(changelist.ActionDelete, "", 380 changelist.TypeTargetsTarget, targetName, nil) 381 return addChange(cl, template, roles...) 382} 383 384// ListTargets lists all targets for the current repository. The list of 385// roles should be passed in order from highest to lowest priority. 386// 387// IMPORTANT: if you pass a set of roles such as [ "targets/a", "targets/x" 388// "targets/a/b" ], even though "targets/a/b" is part of the "targets/a" subtree 389// its entries will be strictly shadowed by those in other parts of the "targets/a" 390// subtree and also the "targets/x" subtree, as we will defer parsing it until 391// we explicitly reach it in our iteration of the provided list of roles. 392func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, error) { 393 if err := r.Update(false); err != nil { 394 return nil, err 395 } 396 397 if len(roles) == 0 { 398 roles = []string{data.CanonicalTargetsRole} 399 } 400 targets := make(map[string]*TargetWithRole) 401 for _, role := range roles { 402 // Define an array of roles to skip for this walk (see IMPORTANT comment above) 403 skipRoles := utils.StrSliceRemove(roles, role) 404 405 // Define a visitor function to populate the targets map in priority order 406 listVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { 407 // We found targets so we should try to add them to our targets map 408 for targetName, targetMeta := range tgt.Signed.Targets { 409 // Follow the priority by not overriding previously set targets 410 // and check that this path is valid with this role 411 if _, ok := targets[targetName]; ok || !validRole.CheckPaths(targetName) { 412 continue 413 } 414 targets[targetName] = &TargetWithRole{ 415 Target: Target{ 416 Name: targetName, 417 Hashes: targetMeta.Hashes, 418 Length: targetMeta.Length, 419 }, 420 Role: validRole.Name, 421 } 422 } 423 return nil 424 } 425 426 r.tufRepo.WalkTargets("", role, listVisitorFunc, skipRoles...) 427 } 428 429 var targetList []*TargetWithRole 430 for _, v := range targets { 431 targetList = append(targetList, v) 432 } 433 434 return targetList, nil 435} 436 437// GetTargetByName returns a target by the given name. If no roles are passed 438// it uses the targets role and does a search of the entire delegation 439// graph, finding the first entry in a breadth first search of the delegations. 440// If roles are passed, they should be passed in descending priority and 441// the target entry found in the subtree of the highest priority role 442// will be returned. 443// See the IMPORTANT section on ListTargets above. Those roles also apply here. 444func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*TargetWithRole, error) { 445 if err := r.Update(false); err != nil { 446 return nil, err 447 } 448 449 if len(roles) == 0 { 450 roles = append(roles, data.CanonicalTargetsRole) 451 } 452 var resultMeta data.FileMeta 453 var resultRoleName string 454 var foundTarget bool 455 for _, role := range roles { 456 // Define an array of roles to skip for this walk (see IMPORTANT comment above) 457 skipRoles := utils.StrSliceRemove(roles, role) 458 459 // Define a visitor function to find the specified target 460 getTargetVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { 461 if tgt == nil { 462 return nil 463 } 464 // We found the target and validated path compatibility in our walk, 465 // so we should stop our walk and set the resultMeta and resultRoleName variables 466 if resultMeta, foundTarget = tgt.Signed.Targets[name]; foundTarget { 467 resultRoleName = validRole.Name 468 return tuf.StopWalk{} 469 } 470 return nil 471 } 472 // Check that we didn't error, and that we assigned to our target 473 if err := r.tufRepo.WalkTargets(name, role, getTargetVisitorFunc, skipRoles...); err == nil && foundTarget { 474 return &TargetWithRole{Target: Target{Name: name, Hashes: resultMeta.Hashes, Length: resultMeta.Length}, Role: resultRoleName}, nil 475 } 476 } 477 return nil, fmt.Errorf("No trust data for %s", name) 478 479} 480 481// TargetSignedStruct is a struct that contains a Target, the role it was found in, and the list of signatures for that role 482type TargetSignedStruct struct { 483 Role data.DelegationRole 484 Target Target 485 Signatures []data.Signature 486} 487 488// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all 489// roles, and returns a list of TargetSignedStructs for each time it finds the specified target. 490// If given an empty string for a target name, it will return back all targets signed into the repository in every role 491func (r *NotaryRepository) GetAllTargetMetadataByName(name string) ([]TargetSignedStruct, error) { 492 if err := r.Update(false); err != nil { 493 return nil, err 494 } 495 496 var targetInfoList []TargetSignedStruct 497 498 // Define a visitor function to find the specified target 499 getAllTargetInfoByNameVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { 500 if tgt == nil { 501 return nil 502 } 503 // We found a target and validated path compatibility in our walk, 504 // so add it to our list if we have a match 505 // if we have an empty name, add all targets, else check if we have it 506 var targetMetaToAdd data.Files 507 if name == "" { 508 targetMetaToAdd = tgt.Signed.Targets 509 } else { 510 if meta, ok := tgt.Signed.Targets[name]; ok { 511 targetMetaToAdd = data.Files{name: meta} 512 } 513 } 514 515 for targetName, resultMeta := range targetMetaToAdd { 516 targetInfo := TargetSignedStruct{ 517 Role: validRole, 518 Target: Target{Name: targetName, Hashes: resultMeta.Hashes, Length: resultMeta.Length}, 519 Signatures: tgt.Signatures, 520 } 521 targetInfoList = append(targetInfoList, targetInfo) 522 } 523 // continue walking to all child roles 524 return nil 525 } 526 527 // Check that we didn't error, and that we found the target at least once 528 if err := r.tufRepo.WalkTargets(name, "", getAllTargetInfoByNameVisitorFunc); err != nil { 529 return nil, err 530 } 531 if len(targetInfoList) == 0 { 532 return nil, fmt.Errorf("No valid trust data for %s", name) 533 } 534 return targetInfoList, nil 535} 536 537// GetChangelist returns the list of the repository's unpublished changes 538func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) { 539 changelistDir := filepath.Join(r.tufRepoPath, "changelist") 540 cl, err := changelist.NewFileChangelist(changelistDir) 541 if err != nil { 542 logrus.Debug("Error initializing changelist") 543 return nil, err 544 } 545 return cl, nil 546} 547 548// RoleWithSignatures is a Role with its associated signatures 549type RoleWithSignatures struct { 550 Signatures []data.Signature 551 data.Role 552} 553 554// ListRoles returns a list of RoleWithSignatures objects for this repo 555// This represents the latest metadata for each role in this repo 556func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) { 557 // Update to latest repo state 558 if err := r.Update(false); err != nil { 559 return nil, err 560 } 561 562 // Get all role info from our updated keysDB, can be empty 563 roles := r.tufRepo.GetAllLoadedRoles() 564 565 var roleWithSigs []RoleWithSignatures 566 567 // Populate RoleWithSignatures with Role from keysDB and signatures from TUF metadata 568 for _, role := range roles { 569 roleWithSig := RoleWithSignatures{Role: *role, Signatures: nil} 570 switch role.Name { 571 case data.CanonicalRootRole: 572 roleWithSig.Signatures = r.tufRepo.Root.Signatures 573 case data.CanonicalTargetsRole: 574 roleWithSig.Signatures = r.tufRepo.Targets[data.CanonicalTargetsRole].Signatures 575 case data.CanonicalSnapshotRole: 576 roleWithSig.Signatures = r.tufRepo.Snapshot.Signatures 577 case data.CanonicalTimestampRole: 578 roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures 579 default: 580 if !data.IsDelegation(role.Name) { 581 continue 582 } 583 if _, ok := r.tufRepo.Targets[role.Name]; ok { 584 // We'll only find a signature if we've published any targets with this delegation 585 roleWithSig.Signatures = r.tufRepo.Targets[role.Name].Signatures 586 } 587 } 588 roleWithSigs = append(roleWithSigs, roleWithSig) 589 } 590 return roleWithSigs, nil 591} 592 593// Publish pushes the local changes in signed material to the remote notary-server 594// Conceptually it performs an operation similar to a `git rebase` 595func (r *NotaryRepository) Publish() error { 596 cl, err := r.GetChangelist() 597 if err != nil { 598 return err 599 } 600 if err = r.publish(cl); err != nil { 601 return err 602 } 603 if err = cl.Clear(""); err != nil { 604 // This is not a critical problem when only a single host is pushing 605 // but will cause weird behaviour if changelist cleanup is failing 606 // and there are multiple hosts writing to the repo. 607 logrus.Warn("Unable to clear changelist. You may want to manually delete the folder ", filepath.Join(r.tufRepoPath, "changelist")) 608 } 609 return nil 610} 611 612// publish pushes the changes in the given changelist to the remote notary-server 613// Conceptually it performs an operation similar to a `git rebase` 614func (r *NotaryRepository) publish(cl changelist.Changelist) error { 615 var initialPublish bool 616 // update first before publishing 617 if err := r.Update(true); err != nil { 618 // If the remote is not aware of the repo, then this is being published 619 // for the first time. Try to load from disk instead for publishing. 620 if _, ok := err.(ErrRepositoryNotExist); ok { 621 err := r.bootstrapRepo() 622 if err != nil { 623 logrus.Debugf("Unable to load repository from local files: %s", 624 err.Error()) 625 if _, ok := err.(store.ErrMetaNotFound); ok { 626 return ErrRepoNotInitialized{} 627 } 628 return err 629 } 630 // Ensure we will push the initial root and targets file. Either or 631 // both of the root and targets may not be marked as Dirty, since 632 // there may not be any changes that update them, so use a 633 // different boolean. 634 initialPublish = true 635 } else { 636 // We could not update, so we cannot publish. 637 logrus.Error("Could not publish Repository since we could not update: ", err.Error()) 638 return err 639 } 640 } 641 // apply the changelist to the repo 642 if err := applyChangelist(r.tufRepo, r.invalid, cl); err != nil { 643 logrus.Debug("Error applying changelist") 644 return err 645 } 646 647 // these are the TUF files we will need to update, serialized as JSON before 648 // we send anything to remote 649 updatedFiles := make(map[string][]byte) 650 651 // check if our root file is nearing expiry or dirty. Resign if it is. If 652 // root is not dirty but we are publishing for the first time, then just 653 // publish the existing root we have. 654 if nearExpiry(r.tufRepo.Root.Signed.SignedCommon) || r.tufRepo.Root.Dirty { 655 rootJSON, err := serializeCanonicalRole(r.tufRepo, data.CanonicalRootRole) 656 if err != nil { 657 return err 658 } 659 updatedFiles[data.CanonicalRootRole] = rootJSON 660 } else if initialPublish { 661 rootJSON, err := r.tufRepo.Root.MarshalJSON() 662 if err != nil { 663 return err 664 } 665 updatedFiles[data.CanonicalRootRole] = rootJSON 666 } 667 668 // iterate through all the targets files - if they are dirty, sign and update 669 for roleName, roleObj := range r.tufRepo.Targets { 670 if roleObj.Dirty || (roleName == data.CanonicalTargetsRole && initialPublish) { 671 targetsJSON, err := serializeCanonicalRole(r.tufRepo, roleName) 672 if err != nil { 673 return err 674 } 675 updatedFiles[roleName] = targetsJSON 676 } 677 } 678 679 // if we initialized the repo while designating the server as the snapshot 680 // signer, then there won't be a snapshots file. However, we might now 681 // have a local key (if there was a rotation), so initialize one. 682 if r.tufRepo.Snapshot == nil { 683 if err := r.tufRepo.InitSnapshot(); err != nil { 684 return err 685 } 686 } 687 688 snapshotJSON, err := serializeCanonicalRole( 689 r.tufRepo, data.CanonicalSnapshotRole) 690 691 if err == nil { 692 // Only update the snapshot if we've successfully signed it. 693 updatedFiles[data.CanonicalSnapshotRole] = snapshotJSON 694 } else if signErr, ok := err.(signed.ErrInsufficientSignatures); ok && signErr.FoundKeys == 0 { 695 // If signing fails due to us not having the snapshot key, then 696 // assume the server is going to sign, and do not include any snapshot 697 // data. 698 logrus.Debugf("Client does not have the key to sign snapshot. " + 699 "Assuming that server should sign the snapshot.") 700 } else { 701 logrus.Debugf("Client was unable to sign the snapshot: %s", err.Error()) 702 return err 703 } 704 705 remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip) 706 if err != nil { 707 return err 708 } 709 710 return remote.SetMulti(updatedFiles) 711} 712 713// bootstrapRepo loads the repository from the local file system (i.e. 714// a not yet published repo or a possibly obsolete local copy) into 715// r.tufRepo. This attempts to load metadata for all roles. Since server 716// snapshots are supported, if the snapshot metadata fails to load, that's ok. 717// This assumes that bootstrapRepo is only used by Publish() or RotateKey() 718func (r *NotaryRepository) bootstrapRepo() error { 719 b := tuf.NewRepoBuilder(r.gun, r.CryptoService, r.trustPinning) 720 721 logrus.Debugf("Loading trusted collection.") 722 723 for _, role := range data.BaseRoles { 724 jsonBytes, err := r.fileStore.GetSized(role, store.NoSizeLimit) 725 if err != nil { 726 if _, ok := err.(store.ErrMetaNotFound); ok && 727 // server snapshots are supported, and server timestamp management 728 // is required, so if either of these fail to load that's ok - especially 729 // if the repo is new 730 role == data.CanonicalSnapshotRole || role == data.CanonicalTimestampRole { 731 continue 732 } 733 return err 734 } 735 if err := b.Load(role, jsonBytes, 1, true); err != nil { 736 return err 737 } 738 } 739 740 tufRepo, _, err := b.Finish() 741 if err == nil { 742 r.tufRepo = tufRepo 743 } 744 return nil 745} 746 747// saveMetadata saves contents of r.tufRepo onto the local disk, creating 748// signatures as necessary, possibly prompting for passphrases. 749func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error { 750 logrus.Debugf("Saving changes to Trusted Collection.") 751 752 rootJSON, err := serializeCanonicalRole(r.tufRepo, data.CanonicalRootRole) 753 if err != nil { 754 return err 755 } 756 err = r.fileStore.Set(data.CanonicalRootRole, rootJSON) 757 if err != nil { 758 return err 759 } 760 761 targetsToSave := make(map[string][]byte) 762 for t := range r.tufRepo.Targets { 763 signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires(data.CanonicalTargetsRole)) 764 if err != nil { 765 return err 766 } 767 targetsJSON, err := json.Marshal(signedTargets) 768 if err != nil { 769 return err 770 } 771 targetsToSave[t] = targetsJSON 772 } 773 774 for role, blob := range targetsToSave { 775 parentDir := filepath.Dir(role) 776 os.MkdirAll(parentDir, 0755) 777 r.fileStore.Set(role, blob) 778 } 779 780 if ignoreSnapshot { 781 return nil 782 } 783 784 snapshotJSON, err := serializeCanonicalRole(r.tufRepo, data.CanonicalSnapshotRole) 785 if err != nil { 786 return err 787 } 788 789 return r.fileStore.Set(data.CanonicalSnapshotRole, snapshotJSON) 790} 791 792// returns a properly constructed ErrRepositoryNotExist error based on this 793// repo's information 794func (r *NotaryRepository) errRepositoryNotExist() error { 795 host := r.baseURL 796 parsed, err := url.Parse(r.baseURL) 797 if err == nil { 798 host = parsed.Host // try to exclude the scheme and any paths 799 } 800 return ErrRepositoryNotExist{remote: host, gun: r.gun} 801} 802 803// Update bootstraps a trust anchor (root.json) before updating all the 804// metadata from the repo. 805func (r *NotaryRepository) Update(forWrite bool) error { 806 c, err := r.bootstrapClient(forWrite) 807 if err != nil { 808 if _, ok := err.(store.ErrMetaNotFound); ok { 809 return r.errRepositoryNotExist() 810 } 811 return err 812 } 813 repo, invalid, err := c.Update() 814 if err != nil { 815 // notFound.Resource may include a checksum so when the role is root, 816 // it will be root or root.<checksum>. Therefore best we can 817 // do it match a "root." prefix 818 if notFound, ok := err.(store.ErrMetaNotFound); ok && strings.HasPrefix(notFound.Resource, data.CanonicalRootRole+".") { 819 return r.errRepositoryNotExist() 820 } 821 return err 822 } 823 // we can be assured if we are at this stage that the repo we built is good 824 // no need to test the following function call for an error as it will always be fine should the repo be good- it is! 825 r.tufRepo = repo 826 r.invalid = invalid 827 warnRolesNearExpiry(repo) 828 return nil 829} 830 831// bootstrapClient attempts to bootstrap a root.json to be used as the trust 832// anchor for a repository. The checkInitialized argument indicates whether 833// we should always attempt to contact the server to determine if the repository 834// is initialized or not. If set to true, we will always attempt to download 835// and return an error if the remote repository errors. 836// 837// Populates a tuf.RepoBuilder with this root metadata (only use 838// TUFClient.Update to load the rest). 839// 840// Fails if the remote server is reachable and does not know the repo 841// (i.e. before the first r.Publish()), in which case the error is 842// store.ErrMetaNotFound, or if the root metadata (from whichever source is used) 843// is not trusted. 844// 845// Returns a TUFClient for the remote server, which may not be actually 846// operational (if the URL is invalid but a root.json is cached). 847func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*TUFClient, error) { 848 minVersion := 1 849 // the old root on disk should not be validated against any trust pinning configuration 850 // because if we have an old root, it itself is the thing that pins trust 851 oldBuilder := tuf.NewRepoBuilder(r.gun, r.CryptoService, trustpinning.TrustPinConfig{}) 852 853 // by default, we want to use the trust pinning configuration on any new root that we download 854 newBuilder := tuf.NewRepoBuilder(r.gun, r.CryptoService, r.trustPinning) 855 856 // Try to read root from cache first. We will trust this root until we detect a problem 857 // during update which will cause us to download a new root and perform a rotation. 858 // If we have an old root, and it's valid, then we overwrite the newBuilder to be one 859 // preloaded with the old root or one which uses the old root for trust bootstrapping. 860 if rootJSON, err := r.fileStore.GetSized(data.CanonicalRootRole, store.NoSizeLimit); err == nil { 861 // if we can't load the cached root, fail hard because that is how we pin trust 862 if err := oldBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, true); err != nil { 863 return nil, err 864 } 865 866 // again, the root on disk is the source of trust pinning, so use an empty trust 867 // pinning configuration 868 newBuilder = tuf.NewRepoBuilder(r.gun, r.CryptoService, trustpinning.TrustPinConfig{}) 869 870 if err := newBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, false); err != nil { 871 // Ok, the old root is expired - we want to download a new one. But we want to use the 872 // old root to verify the new root, so bootstrap a new builder with the old builder 873 // but use the trustpinning to validate the new root 874 minVersion = oldBuilder.GetLoadedVersion(data.CanonicalRootRole) 875 newBuilder = oldBuilder.BootstrapNewBuilderWithNewTrustpin(r.trustPinning) 876 } 877 } 878 879 remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip) 880 if remoteErr != nil { 881 logrus.Error(remoteErr) 882 } else if !newBuilder.IsLoaded(data.CanonicalRootRole) || checkInitialized { 883 // remoteErr was nil and we were not able to load a root from cache or 884 // are specifically checking for initialization of the repo. 885 886 // if remote store successfully set up, try and get root from remote 887 // We don't have any local data to determine the size of root, so try the maximum (though it is restricted at 100MB) 888 tmpJSON, err := remote.GetSized(data.CanonicalRootRole, store.NoSizeLimit) 889 if err != nil { 890 // we didn't have a root in cache and were unable to load one from 891 // the server. Nothing we can do but error. 892 return nil, err 893 } 894 895 if !newBuilder.IsLoaded(data.CanonicalRootRole) { 896 // we always want to use the downloaded root if we couldn't load from cache 897 if err := newBuilder.Load(data.CanonicalRootRole, tmpJSON, minVersion, false); err != nil { 898 return nil, err 899 } 900 901 err = r.fileStore.Set(data.CanonicalRootRole, tmpJSON) 902 if err != nil { 903 // if we can't write cache we should still continue, just log error 904 logrus.Errorf("could not save root to cache: %s", err.Error()) 905 } 906 } 907 } 908 909 // We can only get here if remoteErr != nil (hence we don't download any new root), 910 // and there was no root on disk 911 if !newBuilder.IsLoaded(data.CanonicalRootRole) { 912 return nil, ErrRepoNotInitialized{} 913 } 914 915 return NewTUFClient(oldBuilder, newBuilder, remote, r.fileStore), nil 916} 917 918// RotateKey removes all existing keys associated with the role, and either 919// creates and adds one new key or delegates managing the key to the server. 920// These changes are staged in a changelist until publish is called. 921func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error { 922 // We currently support remotely managing timestamp and snapshot keys 923 canBeRemoteKey := role == data.CanonicalTimestampRole || role == data.CanonicalSnapshotRole 924 // And locally managing root, targets, and snapshot keys 925 canBeLocalKey := (role == data.CanonicalSnapshotRole || role == data.CanonicalTargetsRole || 926 role == data.CanonicalRootRole) 927 928 switch { 929 case !data.ValidRole(role) || data.IsDelegation(role): 930 return fmt.Errorf("notary does not currently permit rotating the %s key", role) 931 case serverManagesKey && !canBeRemoteKey: 932 return ErrInvalidRemoteRole{Role: role} 933 case !serverManagesKey && !canBeLocalKey: 934 return ErrInvalidLocalRole{Role: role} 935 } 936 937 var ( 938 pubKey data.PublicKey 939 err error 940 errFmtMsg string 941 ) 942 switch serverManagesKey { 943 case true: 944 pubKey, err = rotateRemoteKey(r.baseURL, r.gun, role, r.roundTrip) 945 errFmtMsg = "unable to rotate remote key: %s" 946 default: 947 pubKey, err = r.CryptoService.Create(role, r.gun, data.ECDSAKey) 948 errFmtMsg = "unable to generate key: %s" 949 } 950 951 if err != nil { 952 return fmt.Errorf(errFmtMsg, err) 953 } 954 955 // if this is a root role, generate a root cert for the public key 956 if role == data.CanonicalRootRole { 957 privKey, _, err := r.CryptoService.GetPrivateKey(pubKey.ID()) 958 if err != nil { 959 return err 960 } 961 pubKey, err = rootCertKey(r.gun, privKey) 962 if err != nil { 963 return err 964 } 965 } 966 967 cl := changelist.NewMemChangelist() 968 if err := r.rootFileKeyChange(cl, role, changelist.ActionCreate, pubKey); err != nil { 969 return err 970 } 971 return r.publish(cl) 972} 973 974func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role, action string, key data.PublicKey) error { 975 kl := make(data.KeyList, 0, 1) 976 kl = append(kl, key) 977 meta := changelist.TUFRootData{ 978 RoleName: role, 979 Keys: kl, 980 } 981 metaJSON, err := json.Marshal(meta) 982 if err != nil { 983 return err 984 } 985 986 c := changelist.NewTUFChange( 987 action, 988 changelist.ScopeRoot, 989 changelist.TypeRootRole, 990 role, 991 metaJSON, 992 ) 993 return cl.Add(c) 994} 995 996// DeleteTrustData removes the trust data stored for this repo in the TUF cache on the client side 997// Note that we will not delete any private key material from local storage 998func (r *NotaryRepository) DeleteTrustData(deleteRemote bool) error { 999 // Remove the tufRepoPath directory, which includes local TUF metadata files and changelist information 1000 if err := os.RemoveAll(r.tufRepoPath); err != nil { 1001 return fmt.Errorf("error clearing TUF repo data: %v", err) 1002 } 1003 // Note that this will require admin permission in this NotaryRepository's roundtripper 1004 if deleteRemote { 1005 remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip) 1006 if err != nil { 1007 return err 1008 } 1009 if err := remote.RemoveAll(); err != nil { 1010 return err 1011 } 1012 } 1013 return nil 1014} 1015