├── example └── cmd │ ├── .gitignore │ ├── README.md │ └── main.go ├── tuf ├── testdata │ ├── server │ │ ├── key.pem │ │ └── cert.pem │ ├── delegation │ │ ├── 0 │ │ │ ├── timestamp.json │ │ │ ├── targets │ │ │ │ ├── bar.json │ │ │ │ ├── publisher.json │ │ │ │ ├── role │ │ │ │ │ └── foo.json │ │ │ │ ├── role.json │ │ │ │ └── project.json │ │ │ ├── snapshot.json │ │ │ ├── root.json │ │ │ └── targets.json │ │ ├── 1 │ │ │ ├── timestamp.json │ │ │ ├── targets │ │ │ │ ├── bar.json │ │ │ │ ├── publisher.json │ │ │ │ ├── role │ │ │ │ │ └── foo.json │ │ │ │ ├── role.json │ │ │ │ └── project.json │ │ │ ├── snapshot.json │ │ │ ├── root.json │ │ │ └── targets.json │ │ ├── 2 │ │ │ ├── timestamp.json │ │ │ ├── targets │ │ │ │ ├── bar.json │ │ │ │ ├── role │ │ │ │ │ └── foo.json │ │ │ │ └── role.json │ │ │ ├── snapshot.json │ │ │ ├── root.json │ │ │ └── targets.json │ │ └── 3 │ │ │ ├── timestamp.json │ │ │ ├── targets │ │ │ ├── bar.json │ │ │ ├── role │ │ │ │ └── foo.json │ │ │ └── role.json │ │ │ ├── snapshot.json │ │ │ ├── root.json │ │ │ └── targets.json │ ├── kolide │ │ └── agent │ │ │ └── linux │ │ │ ├── timestamp.0.json │ │ │ ├── timestamp.1.json │ │ │ ├── timestamp.2.json │ │ │ ├── timestamp.3.json │ │ │ ├── timestamp.4.json │ │ │ ├── targets.3.json │ │ │ ├── targets.4.json │ │ │ ├── verify │ │ │ ├── targets.json │ │ │ ├── snapshot.json │ │ │ ├── timestamp.json │ │ │ └── root.json │ │ │ ├── snapshot.0.json │ │ │ ├── snapshot.1.json │ │ │ ├── snapshot.2.json │ │ │ ├── snapshot.3.json │ │ │ ├── snapshot.4.json │ │ │ ├── targets.1.json │ │ │ ├── targets.2.json │ │ │ ├── targets.0.json │ │ │ ├── target.0.0 │ │ │ ├── target.0.3 │ │ │ ├── target.0.4 │ │ │ ├── target.1.3 │ │ │ ├── target.1.4 │ │ │ ├── target.0.1 │ │ │ ├── target.0.2 │ │ │ ├── target.1.0 │ │ │ ├── root.0.json │ │ │ ├── root.1.json │ │ │ ├── root.2.json │ │ │ ├── root.3.json │ │ │ ├── root.4.json │ │ │ ├── target.1.1 │ │ │ └── target.1.2 │ ├── data │ │ ├── timestamp.json │ │ ├── snapshot.json │ │ ├── root-ca.crt │ │ ├── root.json │ │ └── targets.json │ └── mirror │ │ ├── 2 │ │ └── edge │ │ │ └── target │ │ └── 3 │ │ └── latest │ │ └── target ├── client_test.go ├── repo_test.go ├── local_repo_test.go ├── local_repo.go ├── fim.go ├── verify.go ├── verify_test.go ├── roles_test.go ├── persistence_test.go ├── repo.go ├── persistence.go ├── roles.go ├── remote_repo_test.go ├── remote_repo.go ├── tuf_test.go └── client.go ├── .gitignore ├── .circleci └── config.yml ├── go.mod ├── LICENSE ├── go.sum └── CONTRIBUTING.md /example/cmd/.gitignore: -------------------------------------------------------------------------------- 1 | repo/ 2 | staging/ 3 | filerepo/ 4 | -------------------------------------------------------------------------------- /tuf/testdata/server/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIA8KFbYo3Saz5b7IEDOV/skEB9JeMQMz3bCgpLmROwL/oAoGCCqGSM49 3 | AwEHoUQDQgAEkwRBsGfKSHKYuB8LN5UJpuagh18pVizDFUhoLYsS8QonmlIuOIMg 4 | ad9Un1ZD1+Xd6ll3rIwwpB7YQKzvY/qHFg== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | vendor/ 17 | bindata.go 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build-go1.11: 4 | docker: 5 | - image: golang:1.11 6 | working_directory: /go/src/github.com/kolide/updater 7 | steps: &steps 8 | - checkout 9 | - run: GO111MODULE=on go mod download 10 | - run: GO111MODULE=on go test -race -cover -v $(go list ./... | grep -v /vendor/) 11 | 12 | workflows: 13 | version: 2 14 | build: 15 | jobs: 16 | - build-go1.11 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kolide/updater 2 | 3 | require ( 4 | github.com/WatchBeam/clock v0.0.0-20161028195133-dc1b57477882 5 | github.com/davecgh/go-spew v1.1.1 // indirect 6 | github.com/docker/go v1.5.1-1 7 | github.com/go-kit/kit v0.8.0 8 | github.com/go-logfmt/logfmt v0.4.0 // indirect 9 | github.com/go-stack/stack v1.8.0 // indirect 10 | github.com/pkg/errors v0.8.0 11 | github.com/pmezard/go-difflib v1.0.0 // indirect 12 | github.com/stretchr/testify v1.1.4 13 | ) 14 | -------------------------------------------------------------------------------- /tuf/testdata/delegation/0/timestamp.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Timestamp","expires":"2017-07-12T20:42:16.926502987Z","meta":{"snapshot":{"hashes":{"sha256":"zxNeWMyo7HI1UUx+FyeDjnpUf3Eyvum7wfHcfsP+n/k=","sha512":"Gw0so7IeSFKOj8u0VMQwQw022eUjUqYP6bWXNbDaooaLCS2SN4nvoG+emvgWuOcFt0mMXpHHvPCM0utwr1rBNA=="},"length":1683}},"version":27},"signatures":[{"keyid":"9a035dab9e6401a8fccc283572249b28ef2d37be080eaa41898303d0538aef03","method":"ecdsa","sig":"xwWJys/OnfCqVJmX+48SJ2/reTclvBib0ELcUMPQ71cWyEiPi9g6KeM4gMRaQzizetLeO/XPmIfrXD6XXJqsGQ=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/1/timestamp.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Timestamp","expires":"2017-07-12T20:42:57.661501326Z","meta":{"snapshot":{"hashes":{"sha256":"6kbjAKrnqE+4QROhLRAPS8+vZIEiFtOJn8UQNWDQqO4=","sha512":"eJ0I3y2g9ggu7DDdp8CLalvSm1pAo19hfLxyJfCO4hl0jKkCKI0b/53+JyE8Rkv8D7WnDtppIFTZHoEE+ij0Sg=="},"length":1683}},"version":28},"signatures":[{"keyid":"9a035dab9e6401a8fccc283572249b28ef2d37be080eaa41898303d0538aef03","method":"ecdsa","sig":"Uxz/s3Cv9DVIeuiGcAsORc7wjqnE1it5QKobKiIFXcIrLhdspu5GcSmS32djkT19J3IxH0j1jC1XDlQo2OzIPw=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/2/timestamp.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Timestamp","expires":"2017-07-16T21:55:32.442504851Z","meta":{"snapshot":{"hashes":{"sha256":"thI5XljvaG2ZyrFiMSYq3qtPPYarJ+jYpP8F8dmUGCw=","sha512":"TswyhZHXuaCMaci5PWLKLUxgZP6FSYYKU+TT5M0OhZHAhrzG4Bw+zm2DsXtYNCzm6WUs61DDTRTQmm106rX3bA=="},"length":1683}},"version":30},"signatures":[{"keyid":"9a035dab9e6401a8fccc283572249b28ef2d37be080eaa41898303d0538aef03","method":"ecdsa","sig":"x2zLnNmNiRG3R1AbEbxaLs5VmELxbxar5/w1quLO9pU9rwX0HOHw93+a5XcAsJasYp5b+jl/7gr0Q2I5eGc/WA=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/3/timestamp.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Timestamp","expires":"2017-07-17T01:36:47.196886653Z","meta":{"snapshot":{"hashes":{"sha256":"nZTLsjXB5zYAavW8za6z0ykRi0A2fduEWa9hutrJP4w=","sha512":"V/37xmFr+mileZ+Gi+3Pa//J26Zkx/wuLo9BcK9IOBAlJyuZ+hwDuVBuGU4rX3xLFzrSl/tKBZvwiUe9m606jA=="},"length":1683}},"version":31},"signatures":[{"keyid":"9a035dab9e6401a8fccc283572249b28ef2d37be080eaa41898303d0538aef03","method":"ecdsa","sig":"H0x8ZAVwCUqk7/QeNHH6407KgUfJAWpY6FsM2H1zEjXmTPoYItMxh/xhY+JJoYwet63aLdo3F7uVRHEf8/e8ZQ=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/timestamp.0.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Timestamp","expires":"2017-07-02T21:47:30.298634213Z","meta":{"snapshot":{"hashes":{"sha256":"vgjVL5HJo2K3RQ+ZCRYwVxB1WCHwUDbpQY4cBDpl0qY=","sha512":"PUYj0QOh6ijd9mcQPMoZUHp46Xh9K7g1/kiv1SMgfViToLmZeIrGOGRhKs73qMLoc+erH+tXDOv1me34ODXzWQ=="},"length":688}},"version":1},"signatures":[{"keyid":"e190d2a8a8a35dd7a364b0d7fadb35136acd0fe92cfda5aacc4dce17f574216a","method":"ecdsa","sig":"lK4IWZl5U4nKZDWp6GxG8S5d9fpNJCn34/YhADZobzbgAKZWeoedz3CO0N0HMb0uBSKyV3tCx32gbkPYBsS1pQ=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/timestamp.1.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Timestamp","expires":"2017-07-03T02:02:42.481047936Z","meta":{"snapshot":{"hashes":{"sha256":"1b2x8+ISlC1ZCTFejDThvqMNvFeJt6Xt1G+8/UgSOzw=","sha512":"IAOmy6gkiZXKBCAHkPJJX4nFyB8lWVd045aJJWlJ/0BfTa9ynU8iPc5G8VkC/qG/aW6Cy/DIeOP7SewMQyDpzA=="},"length":687}},"version":2},"signatures":[{"keyid":"e190d2a8a8a35dd7a364b0d7fadb35136acd0fe92cfda5aacc4dce17f574216a","method":"ecdsa","sig":"S7iVI9fT01OI2TbJ/KBqRutqbLctqGyrLG8UtViibY7P3IH99O8yrSwbbmF7YSn4kbFNdCDDWM/PAfDlnu2Y2w=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/timestamp.2.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Timestamp","expires":"2017-07-03T16:46:15.493037777Z","meta":{"snapshot":{"hashes":{"sha256":"mYLNbMcBezLB0IPOa0b4WW3wkwBFpwjalpR1B2c0rXE=","sha512":"q3ZKGYhY+kSPNO3wyV8QkNOPlN3OpoMDwgjMj8gNDVv3Jn7Gl4EkISXzaz18NwmLmMA0V+45/I+u9OnbElDj/A=="},"length":687}},"version":3},"signatures":[{"keyid":"e190d2a8a8a35dd7a364b0d7fadb35136acd0fe92cfda5aacc4dce17f574216a","method":"ecdsa","sig":"fN15OPLBFthchPBWZO9vXvMc7OMeq6i32qqfIjZE9LUqPyNy3pvKnTZptYuBUDPid5JovmHBavs2LBfF3xGj0Q=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/timestamp.3.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Timestamp","expires":"2017-07-09T21:06:48.25045235Z","meta":{"snapshot":{"hashes":{"sha256":"Wtkedrp9p5BPJs+NqMgyj5LPiqzJIiPRXxztVppzUds=","sha512":"TuSYOumC8Ww7JclOS+1hR1a1cK2Odk1MV90yNb1CPp4imd/0U+iA4xnla7vKZPi/WlEt7+3qU2MZ/2Ln0hJ5uA=="},"length":688}},"version":1},"signatures":[{"keyid":"4c24805d80f640c7ccb37abd58ce84365b72caeb1c501a12746fc162150e0611","method":"ecdsa","sig":"C7HwY2K9JZwUl7956xd2Es1KhZiX0UPzHX/muTuB0h6h46gpCdEuNNP2FplFP0YkY+O5oX+EG/e2c2bGtpf5xg=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/timestamp.4.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Timestamp","expires":"2017-07-09T21:29:10.545301139Z","meta":{"snapshot":{"hashes":{"sha256":"/k0fOKoQ5OKSt1nmJp1HEQZHNvtvJw8Qsip03lhOlkQ=","sha512":"HHXgZtkOKYt1Fe4DnZYzIhcwvUsAsD5Nc10ogy/Bz7kk58R9svscg6a4R/qWvIK6MnF0Bnl/m2ahNV9x7bXSVw=="},"length":688}},"version":2},"signatures":[{"keyid":"451bfbdcc37387ac0e71c7100c12d9af2cae59ed77e1b44f05dd66e5e40db0d1","method":"ecdsa","sig":"sllYTl3wY1CbQdQjUFOnTjlrETF++ZQlcwXA9vk0WY7lGVquhx09TEO4tkGTnEseWvuP0jzrDkts23cUd0u3ug=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/0/targets/bar.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-27T14:19:20.288984709-05:00","targets":{"latest/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":1},"signatures":[{"keyid":"0f5622d963a18defe16b0d78e63c6645d4a51dcc679b0f3fce49513c483b4318","method":"ecdsa","sig":"BbqMA3ICLfo5jccyLuvJ6w2+KwsgGxsgD2h+ibe1YdCTGUb15A5XnIg8mz0Kg/7+KlXc4OVfXo/78ayxaSdljg=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/1/targets/bar.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-27T14:19:20.288984709-05:00","targets":{"latest/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":1},"signatures":[{"keyid":"0f5622d963a18defe16b0d78e63c6645d4a51dcc679b0f3fce49513c483b4318","method":"ecdsa","sig":"BbqMA3ICLfo5jccyLuvJ6w2+KwsgGxsgD2h+ibe1YdCTGUb15A5XnIg8mz0Kg/7+KlXc4OVfXo/78ayxaSdljg=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/2/targets/bar.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-27T14:19:20.288984709-05:00","targets":{"latest/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":1},"signatures":[{"keyid":"0f5622d963a18defe16b0d78e63c6645d4a51dcc679b0f3fce49513c483b4318","method":"ecdsa","sig":"BbqMA3ICLfo5jccyLuvJ6w2+KwsgGxsgD2h+ibe1YdCTGUb15A5XnIg8mz0Kg/7+KlXc4OVfXo/78ayxaSdljg=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/3/targets/bar.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-07-01T20:36:47.167486296-05:00","targets":{"latest/target":{"hashes":{"sha256":"X3C/GKCGAHAW6UiwSu07ghA6Nr6kF1W2zd+vEKzjxu8=","sha512":"jvtPc8VlU1HEROsQkjDFVtOeLHYk6cEavJ4/tLm5JUIYzFCFtFSpaY0IXPqSGYSR8HpyO+RXStxwYXtz6wtkYQ=="},"length":1024}},"version":2},"signatures":[{"keyid":"0f5622d963a18defe16b0d78e63c6645d4a51dcc679b0f3fce49513c483b4318","method":"ecdsa","sig":"o96dRlZcZolKQYlKAj/zNO0FSXNbsQWJRCT0XNNJR2WHzOBIl9BbC395LSrB0vF6xxvKU5aDR/2RFd+ovl4CSg=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/0/targets/publisher.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-26T22:39:46.450455836-05:00","targets":{"latest/target":{"hashes":{"sha256":"LHCzisHN4FFQwuAeFNWVAYPVKBYWzlsCSrl4kBiYcsQ=","sha512":"rOy+AuF5Ox83LqHStvWK0MCykj3spkRSkBbYinrde3cClBwdKOrkzwYodGgWxL0SPMpNFtukzOJP6S3BVTd5Ag=="},"length":1491}},"version":1},"signatures":[{"keyid":"05e53198eca3db22e4015c066097978930b2bab63c4c0b6468a8d29e13d3d95d","method":"ecdsa","sig":"P5iVP+ZpQ/mVthOagZUwUd0NqFOS4yqrMCSXWmMBuY5mb5LxvplMwn6qpzoHc7cGN6WwuMlUcryDXzunD/oY3A=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/0/targets/role/foo.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-27T13:57:07.159752905-05:00","targets":{"edge/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":1},"signatures":[{"keyid":"f2ecf910cec07f2834b5c0e301cc64655cabdfe69188ca84f7461af40005f459","method":"ecdsa","sig":"ZByN7tsnr8OPSXjZdgjfoF1W0LPAFJlYgXLGAOSx33DNJzVoWLsDgfLx7X5nG18kcfVUFM4AyHgJVCqsRjIMgQ=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/1/targets/publisher.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-26T22:39:46.450455836-05:00","targets":{"latest/target":{"hashes":{"sha256":"LHCzisHN4FFQwuAeFNWVAYPVKBYWzlsCSrl4kBiYcsQ=","sha512":"rOy+AuF5Ox83LqHStvWK0MCykj3spkRSkBbYinrde3cClBwdKOrkzwYodGgWxL0SPMpNFtukzOJP6S3BVTd5Ag=="},"length":1491}},"version":1},"signatures":[{"keyid":"05e53198eca3db22e4015c066097978930b2bab63c4c0b6468a8d29e13d3d95d","method":"ecdsa","sig":"P5iVP+ZpQ/mVthOagZUwUd0NqFOS4yqrMCSXWmMBuY5mb5LxvplMwn6qpzoHc7cGN6WwuMlUcryDXzunD/oY3A=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/1/targets/role/foo.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-27T13:57:07.159752905-05:00","targets":{"edge/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":1},"signatures":[{"keyid":"f2ecf910cec07f2834b5c0e301cc64655cabdfe69188ca84f7461af40005f459","method":"ecdsa","sig":"ZByN7tsnr8OPSXjZdgjfoF1W0LPAFJlYgXLGAOSx33DNJzVoWLsDgfLx7X5nG18kcfVUFM4AyHgJVCqsRjIMgQ=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/2/targets/role/foo.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-07-01T16:55:32.414798737-05:00","targets":{"edge/target":{"hashes":{"sha256":"X3C/GKCGAHAW6UiwSu07ghA6Nr6kF1W2zd+vEKzjxu8=","sha512":"jvtPc8VlU1HEROsQkjDFVtOeLHYk6cEavJ4/tLm5JUIYzFCFtFSpaY0IXPqSGYSR8HpyO+RXStxwYXtz6wtkYQ=="},"length":1024}},"version":2},"signatures":[{"keyid":"f2ecf910cec07f2834b5c0e301cc64655cabdfe69188ca84f7461af40005f459","method":"ecdsa","sig":"F0RusRjIwj9LOIvgqXghlrE3LoD7GZpFhjLxg350ti8RMqR6eOXFlEMj0XiPnWKI9SEGlYW3yhcQ8NooiQU1Hw=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/3/targets/role/foo.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-07-01T16:55:32.414798737-05:00","targets":{"edge/target":{"hashes":{"sha256":"X3C/GKCGAHAW6UiwSu07ghA6Nr6kF1W2zd+vEKzjxu8=","sha512":"jvtPc8VlU1HEROsQkjDFVtOeLHYk6cEavJ4/tLm5JUIYzFCFtFSpaY0IXPqSGYSR8HpyO+RXStxwYXtz6wtkYQ=="},"length":1024}},"version":2},"signatures":[{"keyid":"f2ecf910cec07f2834b5c0e301cc64655cabdfe69188ca84f7461af40005f459","method":"ecdsa","sig":"F0RusRjIwj9LOIvgqXghlrE3LoD7GZpFhjLxg350ti8RMqR6eOXFlEMj0XiPnWKI9SEGlYW3yhcQ8NooiQU1Hw=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/targets.3.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-24T16:06:45.222060722-05:00","targets":{"latest/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":2},"signatures":[{"keyid":"32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608","method":"ecdsa","sig":"2b2s6cgbtkC9UwktOOxunvT314vPGCpNnyhrCzLNDD5KZQ+DrQyuGrxbGxfjwvbHMuEegTs195GOLLKtvsglDw=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/targets.4.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-24T16:06:45.222060722-05:00","targets":{"latest/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":2},"signatures":[{"keyid":"32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608","method":"ecdsa","sig":"2b2s6cgbtkC9UwktOOxunvT314vPGCpNnyhrCzLNDD5KZQ+DrQyuGrxbGxfjwvbHMuEegTs195GOLLKtvsglDw=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/verify/targets.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-15T16:25:41.929539716-05:00","targets":{"bin/notary":{"hashes":{"sha256":"V99O1CKYadiVZwdObiOqOydVGrlr5s/jmn2cuKFWFn4=","sha512":"hhiFfyqjFLON2j4BCaz8HK2f9PBE+7WvKpYaZaQTvIvwwuBEitlfFq6/1I3dW1/NueoBwjIttyR2I4RpCHxEBA=="},"length":9562228}},"version":3},"signatures":[{"keyid":"cae7c918e084b954430deefd8d25acea34c97b58b1c16ce550e9014aa82b3a3a","method":"ecdsa","sig":"zlsV1/osHrz1qVy9TZQK4dNk0QnTGU4L4L0Z5Oa3T4dWUNIlmHmKTK3nAp+2cc+HrhFJVzFZKL4ADMRO+4cPYA=="}]} -------------------------------------------------------------------------------- /tuf/testdata/server/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBhjCCASygAwIBAgIRAMZO6HiPZKeBXBzcZbmQeEEwCgYIKoZIzj0EAwIwEjEQ 3 | MA4GA1UEChMHQWNtZSBDbzAeFw0xNzA2MjYwMTAzMDRaFw0xODA2MjYwMTAzMDRa 4 | MBIxEDAOBgNVBAoTB0FjbWUgQ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAST 5 | BEGwZ8pIcpi4Hws3lQmm5qCHXylWLMMVSGgtixLxCieaUi44gyBp31SfVkPX5d3q 6 | WXesjDCkHthArO9j+ocWo2MwYTAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYI 7 | KwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNVHREEIjAggg5sb2NhbGhvc3Q6 8 | ODg4OIIOMTI3LjAuMC4xOjg4ODgwCgYIKoZIzj0EAwIDSAAwRQIgcqZVDXPATgiE 9 | WGy/8fxueJaQNTs9my5k8PQMW4rTTKcCIQDp0as5/78jrWyU/kMuFd/mv5CzkewM 10 | YOenVKxvwdrNAw== 11 | -----END CERTIFICATE----- 12 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/snapshot.0.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Snapshot","expires":"2020-06-17T16:47:27.232915336-05:00","meta":{"root":{"hashes":{"sha256":"lW4mpIaEX97apZ7TboNboZ99yQKOBBI2+h5M9N9eVOo=","sha512":"eZJjfe23mL0S0/3XOTCVHu4zXXNkVzBqLyWz3uGJV0WyAmp65cmi5gueqwWIjIyW55lix2r7ofmr787yYskNfQ=="},"length":2373},"targets":{"hashes":{"sha256":"QWjJd7LnGbwwRGzs6UWzB+bqGLcsSpfc5KkWChDutq8=","sha512":"aT2A+vFhFPaaiCj7XmW4J/KxE2FF0VVUhTjTwHayPAPfHaWyRZOjWFIPkHWfhtcmFRxE4VwpTfGO7Qd/TD2ZDg=="},"length":749}},"version":2},"signatures":[{"keyid":"0866586701391bd1871f9a428b2e579e9354f812aa5fb44b4691f27064299233","method":"ecdsa","sig":"wF9/WCoVs+zgCwWimWsvqbsiCc3oTchTp7loOJATUbmXZ4dVTpqMNjlcXmZzyDTD2kH6UdupNQ0dRmApJiP7dw=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/snapshot.1.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Snapshot","expires":"2020-06-17T21:02:36.15239787-05:00","meta":{"root":{"hashes":{"sha256":"lW4mpIaEX97apZ7TboNboZ99yQKOBBI2+h5M9N9eVOo=","sha512":"eZJjfe23mL0S0/3XOTCVHu4zXXNkVzBqLyWz3uGJV0WyAmp65cmi5gueqwWIjIyW55lix2r7ofmr787yYskNfQ=="},"length":2373},"targets":{"hashes":{"sha256":"eFwgF//nKaj3D6ZioJaaCqV0fsMdF6wPT0Gx8UKmVos=","sha512":"7toOpaeBFR7HjCWKBlUU7A7zp56OZQXrmYziaqPmW5GK1/Qjl8oBABTB3AvmVlhAcKnj7dQ2jpt0WCzAzrwwSw=="},"length":746}},"version":3},"signatures":[{"keyid":"0866586701391bd1871f9a428b2e579e9354f812aa5fb44b4691f27064299233","method":"ecdsa","sig":"UKO6+Zf2QnZ+4LzzDZC31P5yGH579dIKQBPRqEihvYj7zZfqYj5bbJ2c7jmuOkBkguEon2I18A3ZLN/ucVulZg=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/snapshot.2.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Snapshot","expires":"2020-06-18T11:46:15.47395226-05:00","meta":{"root":{"hashes":{"sha256":"IDff4MVA3eQhB8bOYvRlmicQx15lwtzUti7NrkdiD8k=","sha512":"2q5DnPqUdnauqEqeu62UHjHyk6MYSZO83OAFAcpkGnX+rAGAUp/2JnCrr2wY6YYIfuXKnsuwDIp1vTTN36hYgA=="},"length":2372},"targets":{"hashes":{"sha256":"eFwgF//nKaj3D6ZioJaaCqV0fsMdF6wPT0Gx8UKmVos=","sha512":"7toOpaeBFR7HjCWKBlUU7A7zp56OZQXrmYziaqPmW5GK1/Qjl8oBABTB3AvmVlhAcKnj7dQ2jpt0WCzAzrwwSw=="},"length":746}},"version":4},"signatures":[{"keyid":"7772b89eebdbc9a398c4ce1a9d6d3d524cd8c5bb6f67897f3db4601068e7e2a1","method":"ecdsa","sig":"g3zftqG4MhWq8/QhrMPWXqpWtJL8YBw2LvpL8b9KLUIs9BsOP5Jjfi46T6W/0m2xNXE6SXUn2BCDd9VORhWH3g=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/snapshot.3.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Snapshot","expires":"2020-06-24T16:06:45.222434927-05:00","meta":{"root":{"hashes":{"sha256":"dxW661GjF2tZP0sdmkxy+16Q5/s73e9HHT6r+/ha0/o=","sha512":"mSf+ZbQ66x0w08OyD0fAxRjetDuKb+17nTlu6lczqYvB5+ncUqBEKAmO8ZvbB9ayCTYlSP0VPaJA531ZSae8xQ=="},"length":2385},"targets":{"hashes":{"sha256":"nkA8/UMESZ8saRWul4eXS7BIlMwMhKcxeEb6uCLiFjo=","sha512":"mdYMXUx/ZFHgSEZwo1QrnzW+lXobMPykTya7McL5nYWLfUfQ01HMHIAO4LZg8B5fshyQr4FZ0k8rCdCtnEE4Lg=="},"length":544}},"version":2},"signatures":[{"keyid":"449f919e6855f22170d86a74b3b98ec5dbde9609306fe8d92eb309e2f97a29b5","method":"ecdsa","sig":"uc+TlsjYw1QVzomaqKDOUc3/OVry38eGW3ZBMrW/SYp4QCyA3nWjK3jMf+eyX7r7w0gz+PMh8RAonu+fM5/y/Q=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/snapshot.4.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Snapshot","expires":"2020-06-24T16:29:05.870066317-05:00","meta":{"root":{"hashes":{"sha256":"7esGEdimqEzVBjmR7L+W/n6gYHilCAbJhXOT+TJhtkA=","sha512":"hkGZwWEBtFN2aNQ+fdp969OH74SrCwytV6fvx2udHkdUsreFc0c1+ALNUnpsk3VW2+iRDX0L2KAj/TzeJcofkA=="},"length":2385},"targets":{"hashes":{"sha256":"nkA8/UMESZ8saRWul4eXS7BIlMwMhKcxeEb6uCLiFjo=","sha512":"mdYMXUx/ZFHgSEZwo1QrnzW+lXobMPykTya7McL5nYWLfUfQ01HMHIAO4LZg8B5fshyQr4FZ0k8rCdCtnEE4Lg=="},"length":544}},"version":3},"signatures":[{"keyid":"449f919e6855f22170d86a74b3b98ec5dbde9609306fe8d92eb309e2f97a29b5","method":"ecdsa","sig":"Z1xypQIf9jrzcAQdeIiZxotgzxG6R+jgiQ8f5g9gqR3hbkTdEVlWBZnilknJSD8ZGTnH+8lPQvDnsicGjD38EA=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/verify/snapshot.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Snapshot","expires":"2020-06-15T16:25:41.929942377-05:00","meta":{"root":{"hashes":{"sha256":"1kCRDR3I7b3DEVDThHEpRKaMhXWXueP6PZ3MpikYHKk=","sha512":"4i6L3EVT3BZDLxoeoJzE8jxtJB+X1uNej9drCxFgXWa6DroGSZfDNIzonZeod3f8md2swtpKF0x3ZZArVChLWw=="},"length":2376},"targets":{"hashes":{"sha256":"k23JWuc1XcLOaeSXvL0vKLf26MAQdTPUkf4B4pppTu0=","sha512":"H/uXGwyhx07xkn/X9+8jFuMOppXj3PCJNJdn01yQvlw8d0HFnqfNdfmtSjODUtGWxTpPqlFoG/F5MwxBdloKxQ=="},"length":544}},"version":3},"signatures":[{"keyid":"df9c78125f2fa08052253850f62930e3ca66dfc789ea77d6b04405b10289dd6e","method":"ecdsa","sig":"4bXlkE1BTEKT9ZAI1eguB///wUYm5EBYjTqRuJ+R4zvOzZ6vjXh1Wxh8o7+Xnl/vztuC4DuR6uG0FhV9Z1FhQA=="}]} -------------------------------------------------------------------------------- /tuf/client_test.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/WatchBeam/clock" 7 | ) 8 | 9 | func withClock(mc clock.Clock) Option { 10 | return func(c *Client) { 11 | c.clock = mc 12 | } 13 | } 14 | 15 | func loadOnStart(load bool) Option { 16 | return func(c *Client) { 17 | c.loadOnStart = load 18 | } 19 | } 20 | 21 | func (c *Client) getFIMMap() (FimMap, error) { 22 | type res struct { 23 | fims FimMap 24 | err error 25 | } 26 | resultC := make(chan res) 27 | c.jobs <- func(rm *repoMan) { 28 | if rm.targets == nil { 29 | resultC <- res{nil, errors.New("no targets")} 30 | return 31 | } 32 | resultC <- res{rm.targets.paths.clone(), nil} 33 | } 34 | result := <-resultC 35 | return result.fims, result.err 36 | } 37 | -------------------------------------------------------------------------------- /tuf/testdata/data/timestamp.json: -------------------------------------------------------------------------------- 1 | { 2 | "signed": { 3 | "_type": "Timestamp", 4 | "expires": "2017-06-26T19:32:36.967988706Z", 5 | "meta": { 6 | "snapshot": { 7 | "hashes": { 8 | "sha256": "14/Mhpey56v+ADoOpo9mTuno3acs53DsMEgPmlgPMnI=", 9 | "sha512": "6WwFGKoNRKGv+fMfjOnTXmGv2/vzQHwYGmN94qL72t/vWirP475hT6+qCKmWhF/PpxHLVrZXf7vuLZ4SeeIDNg==" 10 | }, 11 | "length": 688 12 | } 13 | }, 14 | "version": 3 15 | }, 16 | "signatures": [ 17 | { 18 | "keyid": "1b52d9751b119e2567dcc3ad68a8f99ccff2ba727d354c74173338133aeb3f87", 19 | "method": "ecdsa", 20 | "sig": "92b83fCK0Ozy6y5SUzzBEVeWvcQjf8MwK0nbKu6nZ3NKwOEcW1nn1ZKkwNihn9CePvCZaVlTMnltDoN9/W6ByA==" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/targets.1.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-17T21:02:36.151985-05:00","targets":{"somedir/target.0":{"hashes":{"sha256":"LHCzisHN4FFQwuAeFNWVAYPVKBYWzlsCSrl4kBiYcsQ=","sha512":"rOy+AuF5Ox83LqHStvWK0MCykj3spkRSkBbYinrde3cClBwdKOrkzwYodGgWxL0SPMpNFtukzOJP6S3BVTd5Ag=="},"length":1491},"somedir/target.1":{"hashes":{"sha256":"0dhwNA+jtSf2sHTiL5EeFLvV1QSRfwtKTaL8WuGV37Y=","sha512":"2+9b9+LJfvcvzVxw+M0HwnZ/Ev3wwRa9Pb2JDw7AfxawgxZtcoZ9QTLhzyrFcCSoMGjGziXoX+/bo+zV2t3quQ=="},"length":2710}},"version":3},"signatures":[{"keyid":"a822db495e448ff983d6560f28b80c600f11ae1ff408ff5ed69e8ec9ef1ba8c4","method":"ecdsa","sig":"doZ8bJPqxlnFobkjA4GTpT/ADnfglRhGhz/6Qt1GSSUolTPbB3H54t1VqL3y8QThJ58CZv2LqaP7rI0HllfTQA=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/targets.2.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-17T21:02:36.151985-05:00","targets":{"somedir/target.0":{"hashes":{"sha256":"LHCzisHN4FFQwuAeFNWVAYPVKBYWzlsCSrl4kBiYcsQ=","sha512":"rOy+AuF5Ox83LqHStvWK0MCykj3spkRSkBbYinrde3cClBwdKOrkzwYodGgWxL0SPMpNFtukzOJP6S3BVTd5Ag=="},"length":1491},"somedir/target.1":{"hashes":{"sha256":"0dhwNA+jtSf2sHTiL5EeFLvV1QSRfwtKTaL8WuGV37Y=","sha512":"2+9b9+LJfvcvzVxw+M0HwnZ/Ev3wwRa9Pb2JDw7AfxawgxZtcoZ9QTLhzyrFcCSoMGjGziXoX+/bo+zV2t3quQ=="},"length":2710}},"version":3},"signatures":[{"keyid":"a822db495e448ff983d6560f28b80c600f11ae1ff408ff5ed69e8ec9ef1ba8c4","method":"ecdsa","sig":"doZ8bJPqxlnFobkjA4GTpT/ADnfglRhGhz/6Qt1GSSUolTPbB3H54t1VqL3y8QThJ58CZv2LqaP7rI0HllfTQA=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/targets.0.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{},"roles":[]},"expires":"2020-06-17T16:47:27.232525153-05:00","targets":{"somedir/target.0":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357},"somedir/target.1":{"hashes":{"sha256":"r7M87YVp08XSgre3PeSSbgGtdYvgWxq/J1j326z4jzA=","sha512":"o2I8/ob1wS6fav7E1L0hO3+1SKsHxyHkxMeU5GEIvURfWyiv0a0VynT3EOiu1T9Vde//ZJRpimiGtcmjiMWglg=="},"length":2032}},"version":2},"signatures":[{"keyid":"a822db495e448ff983d6560f28b80c600f11ae1ff408ff5ed69e8ec9ef1ba8c4","method":"ecdsa","sig":"ck7fNcGP1uBm+yz4JDiGQD8WqnSdOsHofVvLc2ipReCGfZbGK+nyGKDUsdbM09Z2g6wwjb8Ro84DZSsPK0Q/Tg=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/verify/timestamp.json: -------------------------------------------------------------------------------- 1 | { 2 | "signed": { 3 | "_type": "Timestamp", 4 | "expires": "2017-06-30T21:25:46.935653533Z", 5 | "meta": { 6 | "snapshot": { 7 | "hashes": { 8 | "sha256": "SnY8/Ah19D2wFSxKcnNcTWWGhqB5iPBmhG2qCMHZka4=", 9 | "sha512": "9GROeVs8KjY0L/Qej+TP/wZUwhdmsDZbrSargadRj+vUQU7vND0rXwvWvDi1n3lMU7YtBeo8VKJUgcud2zUsAw==" 10 | }, 11 | "length": 688 12 | } 13 | }, 14 | "version": 2 15 | }, 16 | "signatures": [ 17 | { 18 | "keyid": "bacd525eac2e9495f10d9543917f3a078ffbb2a982f4cd59f1c825f9dfec7e13", 19 | "method": "ecdsa", 20 | "sig": "lQoxozLtKz4Wty+U3U7HIuYUTO8eIlIPnI9UqG4FoOmV7wy/Kl2OB03PeQhA+qC/Y5DXM+yYldJCZQIMISF1GQ==" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tuf/testdata/mirror/2/edge/target: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuf/testdata/mirror/3/latest/target: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuf/testdata/data/snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "signed": { 3 | "_type": "Snapshot", 4 | "expires": "2020-06-11T14:32:32.161365749-05:00", 5 | "meta": { 6 | "root": { 7 | "hashes": { 8 | "sha256": "hmw3Q5satzmtz87gsrOkM3uCmXsRZfradwTLiFrJxgo=", 9 | "sha512": "EU+fVRkpw9n1UIzx1LCE8uhZEPya9sMG27QA/6vMOXn7jamJ710C9q0C6B3TwOVaSJcZMMA9CT9mMs5d6hCpjQ==" 10 | }, 11 | "length": 2357 12 | }, 13 | "targets": { 14 | "hashes": { 15 | "sha256": "nwg+cF2+A+YbfL9x02jY4DTBTExBH/lE/ErlOF1l07Y=", 16 | "sha512": "GGye6UL/7r+qzZg+YRwTfHtoshhipiFpGX8JJGuU51H9PiioZGwEwaySGzJHoscM+NWmr9wDytrD2TilvbvWbw==" 17 | }, 18 | "length": 727 19 | } 20 | }, 21 | "version": 4 22 | }, 23 | "signatures": [ 24 | { 25 | "keyid": "cf5d1ca7177c947066404459dcdbfdfed1b684e7cd00d89ed7e513f108df3982", 26 | "method": "ecdsa", 27 | "sig": "Pqth0PIvWkYWfgZ1kRVhfa920AAtoujVQePy/HvP9hCS7vGMwrlWX+doDQxiU8Wtdk8WpIgJpYNxui2rF4rNEw==" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kolide 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tuf/repo_test.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestIsRoleCorrect(t *testing.T) { 10 | tt := []struct { 11 | match bool 12 | r role 13 | cls interface{} 14 | msg string 15 | }{ 16 | {true, roleRoot, Root{}, "matching root"}, 17 | {true, roleRoot, &Root{}, "matching root pointer"}, 18 | {true, roleSnapshot, Snapshot{}, "matching snapshot"}, 19 | {true, roleSnapshot, &Snapshot{}, "matching snapshot pointer"}, 20 | {true, roleTimestamp, Timestamp{}, "matching timestamp"}, 21 | {true, roleTimestamp, &Timestamp{}, "matching timestamp pointer"}, 22 | {true, roleTargets, Targets{}, "matching targets"}, 23 | {true, roleTargets, &Targets{}, "matching targets pointer"}, 24 | {false, roleRoot, Targets{}, "root role and target class"}, 25 | {false, roleTargets, Root{}, "targets role and root class"}, 26 | {false, roleTargets, &Snapshot{}, "targets role and snapshot pointer"}, 27 | {false, role("garbage"), Root{}, "non exiting role and root class"}, 28 | {false, roleRoot, "a string", "root role and non role class"}, 29 | } 30 | 31 | for _, testCase := range tt { 32 | if testCase.match { 33 | assert.NotPanics(t, func() { isRoleCorrect(testCase.r, testCase.cls) }, testCase.msg) 34 | } else { 35 | assert.Panics(t, func() { isRoleCorrect(testCase.r, testCase.cls) }, testCase.msg) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/target.0.0: -------------------------------------------------------------------------------- 1 | 9ULT0AFmvZH4KhxrCpm5oHtOLxz+/ANYseK5qs/xkumH5oRCwA01C8JQRJwQUqAL 2 | j9HjjHtpqMarAePzKtJjwFY5kCQEH2YxvsTvSZG2JBpK4dlLlAYDhlUe50o/B7RF 3 | h16BhbcjlQ6eIV9lZdApsfxGeJizn4uNINS407E7VWpH/Lc1LJ+j7pZX552FOYYr 4 | lZrz2SUONQqOi090Lx5eEzzDT2ylFjjJvZh/O4Sw2thf5QCK700Z864JWSnXboWl 5 | ptiCFa7qYIA98pZwCljahfVpFJTCEOdOzNZeu/7NJ3lR8krdxzpnNj0PWEexjYHc 6 | 9xOu6j2vMkmITEujNX7tZ05TZgbbGaQzwb3F0fjdqUQzSjUl2RUCEIJxq3KFzUqZ 7 | FQ78oyeGJl72mqgNTjyP4mjy3Xg4C3SDDPe+rf42Fz9GBoQ31xjPpZDW2zCoyxLQ 8 | ump4OLc9SJCmWtHyxMSYGu8ZEmXnp0OHVORETjFaqBlQ/xNHIzvWWczhnrFKX0qi 9 | weJ0avm8WCMExeakN/4PA+1aC/mYwwNunq+f1cMxBSTrpbgm67ZKWrttozbrGQGz 10 | z3qu1oELTE0HJysxokPcrWn6T56ZO9wvbgO10zn/J03xH+T3vN2fSf1ify4nSrSd 11 | iYSW4ZgBK5dBubMU0coX6y0UGY1vlHiORoB1SlErW/l2Km3HDytr8xSM1GrgrGPw 12 | WABQQAJ/d5h6q9t2+K+Y37GMOYB0XZB3EWfxRrHyDruWIWrbHtZ8mfQifkdZXQYb 13 | 98mHjP9xCfNNfNTmNm03AayFfSa1GQ+MSJGaxGgBG0nnAMUE2Tg2n9y5kAPd2G3H 14 | irNwEnTd6tuUkydYJV5ouM6LfUQZYCIkhx6qI+FATNerfAkWGC+RfSVRAxKDROm2 15 | IRMTEOKOhrrIBlmQqbVgJa0U+NzbXcGDsiuTuxvhXgY7nVA26eSnt5X2kDdOtLCg 16 | ak4W2geclIFOhiRivV9tZoIoJE1fY5SJrjha80XO3mJs1duYUgz+sJrJ51xwz1mV 17 | KCbUulCBTiv0+5WjOXuT3QfBh9Tee58ZDxJv8riX4VEWtyaRaZGKiwLaGw34+UDb 18 | jfNBQk60u/muiBvPYwWEDdRUBsipYq77c31TR0hHHZYjW/FoyjaNOvokR23HNOMV 19 | urfkfaTM/sTrPJrr+4QPHSo4wtXOG1mkddcpZnKIb9cgUJa9fqtDpu9tF9CtX9UV 20 | El6kd98yOnmazcZb88+F6ddlZ/wxVyT6rSBt4j1ZoTP1AC87Y25nq4BA3/1/krFx 21 | YURv0lxfg72NY6dMGsMkMZ5X+I5k10HBElMd6VJ5RMwhu5quHHDC8g== 22 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/target.0.3: -------------------------------------------------------------------------------- 1 | 9ULT0AFmvZH4KhxrCpm5oHtOLxz+/ANYseK5qs/xkumH5oRCwA01C8JQRJwQUqAL 2 | j9HjjHtpqMarAePzKtJjwFY5kCQEH2YxvsTvSZG2JBpK4dlLlAYDhlUe50o/B7RF 3 | h16BhbcjlQ6eIV9lZdApsfxGeJizn4uNINS407E7VWpH/Lc1LJ+j7pZX552FOYYr 4 | lZrz2SUONQqOi090Lx5eEzzDT2ylFjjJvZh/O4Sw2thf5QCK700Z864JWSnXboWl 5 | ptiCFa7qYIA98pZwCljahfVpFJTCEOdOzNZeu/7NJ3lR8krdxzpnNj0PWEexjYHc 6 | 9xOu6j2vMkmITEujNX7tZ05TZgbbGaQzwb3F0fjdqUQzSjUl2RUCEIJxq3KFzUqZ 7 | FQ78oyeGJl72mqgNTjyP4mjy3Xg4C3SDDPe+rf42Fz9GBoQ31xjPpZDW2zCoyxLQ 8 | ump4OLc9SJCmWtHyxMSYGu8ZEmXnp0OHVORETjFaqBlQ/xNHIzvWWczhnrFKX0qi 9 | weJ0avm8WCMExeakN/4PA+1aC/mYwwNunq+f1cMxBSTrpbgm67ZKWrttozbrGQGz 10 | z3qu1oELTE0HJysxokPcrWn6T56ZO9wvbgO10zn/J03xH+T3vN2fSf1ify4nSrSd 11 | iYSW4ZgBK5dBubMU0coX6y0UGY1vlHiORoB1SlErW/l2Km3HDytr8xSM1GrgrGPw 12 | WABQQAJ/d5h6q9t2+K+Y37GMOYB0XZB3EWfxRrHyDruWIWrbHtZ8mfQifkdZXQYb 13 | 98mHjP9xCfNNfNTmNm03AayFfSa1GQ+MSJGaxGgBG0nnAMUE2Tg2n9y5kAPd2G3H 14 | irNwEnTd6tuUkydYJV5ouM6LfUQZYCIkhx6qI+FATNerfAkWGC+RfSVRAxKDROm2 15 | IRMTEOKOhrrIBlmQqbVgJa0U+NzbXcGDsiuTuxvhXgY7nVA26eSnt5X2kDdOtLCg 16 | ak4W2geclIFOhiRivV9tZoIoJE1fY5SJrjha80XO3mJs1duYUgz+sJrJ51xwz1mV 17 | KCbUulCBTiv0+5WjOXuT3QfBh9Tee58ZDxJv8riX4VEWtyaRaZGKiwLaGw34+UDb 18 | jfNBQk60u/muiBvPYwWEDdRUBsipYq77c31TR0hHHZYjW/FoyjaNOvokR23HNOMV 19 | urfkfaTM/sTrPJrr+4QPHSo4wtXOG1mkddcpZnKIb9cgUJa9fqtDpu9tF9CtX9UV 20 | El6kd98yOnmazcZb88+F6ddlZ/wxVyT6rSBt4j1ZoTP1AC87Y25nq4BA3/1/krFx 21 | YURv0lxfg72NY6dMGsMkMZ5X+I5k10HBElMd6VJ5RMwhu5quHHDC8g== 22 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/target.0.4: -------------------------------------------------------------------------------- 1 | 9ULT0AFmvZH4KhxrCpm5oHtOLxz+/ANYseK5qs/xkumH5oRCwA01C8JQRJwQUqAL 2 | j9HjjHtpqMarAePzKtJjwFY5kCQEH2YxvsTvSZG2JBpK4dlLlAYDhlUe50o/B7RF 3 | h16BhbcjlQ6eIV9lZdApsfxGeJizn4uNINS407E7VWpH/Lc1LJ+j7pZX552FOYYr 4 | lZrz2SUONQqOi090Lx5eEzzDT2ylFjjJvZh/O4Sw2thf5QCK700Z864JWSnXboWl 5 | ptiCFa7qYIA98pZwCljahfVpFJTCEOdOzNZeu/7NJ3lR8krdxzpnNj0PWEexjYHc 6 | 9xOu6j2vMkmITEujNX7tZ05TZgbbGaQzwb3F0fjdqUQzSjUl2RUCEIJxq3KFzUqZ 7 | FQ78oyeGJl72mqgNTjyP4mjy3Xg4C3SDDPe+rf42Fz9GBoQ31xjPpZDW2zCoyxLQ 8 | ump4OLc9SJCmWtHyxMSYGu8ZEmXnp0OHVORETjFaqBlQ/xNHIzvWWczhnrFKX0qi 9 | weJ0avm8WCMExeakN/4PA+1aC/mYwwNunq+f1cMxBSTrpbgm67ZKWrttozbrGQGz 10 | z3qu1oELTE0HJysxokPcrWn6T56ZO9wvbgO10zn/J03xH+T3vN2fSf1ify4nSrSd 11 | iYSW4ZgBK5dBubMU0coX6y0UGY1vlHiORoB1SlErW/l2Km3HDytr8xSM1GrgrGPw 12 | WABQQAJ/d5h6q9t2+K+Y37GMOYB0XZB3EWfxRrHyDruWIWrbHtZ8mfQifkdZXQYb 13 | 98mHjP9xCfNNfNTmNm03AayFfSa1GQ+MSJGaxGgBG0nnAMUE2Tg2n9y5kAPd2G3H 14 | irNwEnTd6tuUkydYJV5ouM6LfUQZYCIkhx6qI+FATNerfAkWGC+RfSVRAxKDROm2 15 | IRMTEOKOhrrIBlmQqbVgJa0U+NzbXcGDsiuTuxvhXgY7nVA26eSnt5X2kDdOtLCg 16 | ak4W2geclIFOhiRivV9tZoIoJE1fY5SJrjha80XO3mJs1duYUgz+sJrJ51xwz1mV 17 | KCbUulCBTiv0+5WjOXuT3QfBh9Tee58ZDxJv8riX4VEWtyaRaZGKiwLaGw34+UDb 18 | jfNBQk60u/muiBvPYwWEDdRUBsipYq77c31TR0hHHZYjW/FoyjaNOvokR23HNOMV 19 | urfkfaTM/sTrPJrr+4QPHSo4wtXOG1mkddcpZnKIb9cgUJa9fqtDpu9tF9CtX9UV 20 | El6kd98yOnmazcZb88+F6ddlZ/wxVyT6rSBt4j1ZoTP1AC87Y25nq4BA3/1/krFx 21 | YURv0lxfg72NY6dMGsMkMZ5X+I5k10HBElMd6VJ5RMwhu5quHHDC8g== 22 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/target.1.3: -------------------------------------------------------------------------------- 1 | 9ULT0AFmvZH4KhxrCpm5oHtOLxz+/ANYseK5qs/xkumH5oRCwA01C8JQRJwQUqAL 2 | j9HjjHtpqMarAePzKtJjwFY5kCQEH2YxvsTvSZG2JBpK4dlLlAYDhlUe50o/B7RF 3 | h16BhbcjlQ6eIV9lZdApsfxGeJizn4uNINS407E7VWpH/Lc1LJ+j7pZX552FOYYr 4 | lZrz2SUONQqOi090Lx5eEzzDT2ylFjjJvZh/O4Sw2thf5QCK700Z864JWSnXboWl 5 | ptiCFa7qYIA98pZwCljahfVpFJTCEOdOzNZeu/7NJ3lR8krdxzpnNj0PWEexjYHc 6 | 9xOu6j2vMkmITEujNX7tZ05TZgbbGaQzwb3F0fjdqUQzSjUl2RUCEIJxq3KFzUqZ 7 | FQ78oyeGJl72mqgNTjyP4mjy3Xg4C3SDDPe+rf42Fz9GBoQ31xjPpZDW2zCoyxLQ 8 | ump4OLc9SJCmWtHyxMSYGu8ZEmXnp0OHVORETjFaqBlQ/xNHIzvWWczhnrFKX0qi 9 | weJ0avm8WCMExeakN/4PA+1aC/mYwwNunq+f1cMxBSTrpbgm67ZKWrttozbrGQGz 10 | z3qu1oELTE0HJysxokPcrWn6T56ZO9wvbgO10zn/J03xH+T3vN2fSf1ify4nSrSd 11 | iYSW4ZgBK5dBubMU0coX6y0UGY1vlHiORoB1SlErW/l2Km3HDytr8xSM1GrgrGPw 12 | WABQQAJ/d5h6q9t2+K+Y37GMOYB0XZB3EWfxRrHyDruWIWrbHtZ8mfQifkdZXQYb 13 | 98mHjP9xCfNNfNTmNm03AayFfSa1GQ+MSJGaxGgBG0nnAMUE2Tg2n9y5kAPd2G3H 14 | irNwEnTd6tuUkydYJV5ouM6LfUQZYCIkhx6qI+FATNerfAkWGC+RfSVRAxKDROm2 15 | IRMTEOKOhrrIBlmQqbVgJa0U+NzbXcGDsiuTuxvhXgY7nVA26eSnt5X2kDdOtLCg 16 | ak4W2geclIFOhiRivV9tZoIoJE1fY5SJrjha80XO3mJs1duYUgz+sJrJ51xwz1mV 17 | KCbUulCBTiv0+5WjOXuT3QfBh9Tee58ZDxJv8riX4VEWtyaRaZGKiwLaGw34+UDb 18 | jfNBQk60u/muiBvPYwWEDdRUBsipYq77c31TR0hHHZYjW/FoyjaNOvokR23HNOMV 19 | urfkfaTM/sTrPJrr+4QPHSo4wtXOG1mkddcpZnKIb9cgUJa9fqtDpu9tF9CtX9UV 20 | El6kd98yOnmazcZb88+F6ddlZ/wxVyT6rSBt4j1ZoTP1AC87Y25nq4BA3/1/krFx 21 | YURv0lxfg72NY6dMGsMkMZ5X+I5k10HBElMd6VJ5RMwhu5quHHDC8g== 22 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/target.1.4: -------------------------------------------------------------------------------- 1 | 9ULT0AFmvZH4KhxrCpm5oHtOLxz+/ANYseK5qs/xkumH5oRCwA01C8JQRJwQUqAL 2 | j9HjjHtpqMarAePzKtJjwFY5kCQEH2YxvsTvSZG2JBpK4dlLlAYDhlUe50o/B7RF 3 | h16BhbcjlQ6eIV9lZdApsfxGeJizn4uNINS407E7VWpH/Lc1LJ+j7pZX552FOYYr 4 | lZrz2SUONQqOi090Lx5eEzzDT2ylFjjJvZh/O4Sw2thf5QCK700Z864JWSnXboWl 5 | ptiCFa7qYIA98pZwCljahfVpFJTCEOdOzNZeu/7NJ3lR8krdxzpnNj0PWEexjYHc 6 | 9xOu6j2vMkmITEujNX7tZ05TZgbbGaQzwb3F0fjdqUQzSjUl2RUCEIJxq3KFzUqZ 7 | FQ78oyeGJl72mqgNTjyP4mjy3Xg4C3SDDPe+rf42Fz9GBoQ31xjPpZDW2zCoyxLQ 8 | ump4OLc9SJCmWtHyxMSYGu8ZEmXnp0OHVORETjFaqBlQ/xNHIzvWWczhnrFKX0qi 9 | weJ0avm8WCMExeakN/4PA+1aC/mYwwNunq+f1cMxBSTrpbgm67ZKWrttozbrGQGz 10 | z3qu1oELTE0HJysxokPcrWn6T56ZO9wvbgO10zn/J03xH+T3vN2fSf1ify4nSrSd 11 | iYSW4ZgBK5dBubMU0coX6y0UGY1vlHiORoB1SlErW/l2Km3HDytr8xSM1GrgrGPw 12 | WABQQAJ/d5h6q9t2+K+Y37GMOYB0XZB3EWfxRrHyDruWIWrbHtZ8mfQifkdZXQYb 13 | 98mHjP9xCfNNfNTmNm03AayFfSa1GQ+MSJGaxGgBG0nnAMUE2Tg2n9y5kAPd2G3H 14 | irNwEnTd6tuUkydYJV5ouM6LfUQZYCIkhx6qI+FATNerfAkWGC+RfSVRAxKDROm2 15 | IRMTEOKOhrrIBlmQqbVgJa0U+NzbXcGDsiuTuxvhXgY7nVA26eSnt5X2kDdOtLCg 16 | ak4W2geclIFOhiRivV9tZoIoJE1fY5SJrjha80XO3mJs1duYUgz+sJrJ51xwz1mV 17 | KCbUulCBTiv0+5WjOXuT3QfBh9Tee58ZDxJv8riX4VEWtyaRaZGKiwLaGw34+UDb 18 | jfNBQk60u/muiBvPYwWEDdRUBsipYq77c31TR0hHHZYjW/FoyjaNOvokR23HNOMV 19 | urfkfaTM/sTrPJrr+4QPHSo4wtXOG1mkddcpZnKIb9cgUJa9fqtDpu9tF9CtX9UV 20 | El6kd98yOnmazcZb88+F6ddlZ/wxVyT6rSBt4j1ZoTP1AC87Y25nq4BA3/1/krFx 21 | YURv0lxfg72NY6dMGsMkMZ5X+I5k10HBElMd6VJ5RMwhu5quHHDC8g== 22 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/target.0.1: -------------------------------------------------------------------------------- 1 | nWmwYV06SiY6x7ibKx9dFuaMTO0WvqiZNQLZ2OrvJWobwqw7XI9sZD2joWjnUzQB 2 | 1HvFeU3qW7E9wVjB5gnw8JqIlSHS8l2ORCFzkSwINVI3+KS/vzKJblWCaBc5/94X 3 | dI68EIQ/RL88vLmSbyW5mKPW/Eo72Y4QrI37vLRLQB9oOevfFIUDa4Nxb/EX8dVr 4 | 0/0GeaJfL1PiK5LfUMeUun5bf8bKDGcjxuBGMOy2+L+1ODjfBTIL9dN4ik7/EW85 5 | gHtiDabvZzsU9tDmOY7kPCnMyDUAE64HQlzs5lR4SP6mx1umS7yi87DiTB8zozIF 6 | 7RQP1FNawKmaI/DExl47nPINfy6wDkpLOZFHrn1uXTLBS/K94ZG5i3TMqF4kqNpB 7 | /q8U/9RlIZbgi+LfZbMTBkOl9MGV82aUs2rnbuobDkv5okCCOmScJPCfMJYhh0Wq 8 | F83rgYK5ClKOjLD/ZZ7rUzH1itn85XGlcMwfjjR9+UoghtmWKMymzwKL1m1uFjat 9 | mo1gKiqS1+rTqnlrMxSAwFQuNhVQuHJhWnv3rzuk3XtjAu2qrUUSsgAV7l0pgeVG 10 | BUn5Gxb2XPVm61F+G7lLQWb3+mIskrk8ZdxXi3vyz4C7fjq/iiezay67uS2gMfPr 11 | RWyPCy1gXYitAoj1yisnEjQnUihb4gWKKaGf3wCnD1VCXbjBBhEyPQtT+Bp9zZZD 12 | mSMZvZF6au091SZZBWj4epzRXvd3+0rE3c7VNSb3Df1ONXXfRyf4lKytwHDiX/m9 13 | ZTElQ8XGKgDj3b9+vtdQlapq74PFckIFnhq/o0dRpY25bXevMMbtVJBu9dpuGso1 14 | AlxlRM3g1pTXQeqWD3ANGttsyhKTu4nBqnaWi1BkftEQ0S+j9wgDO46+970ceb73 15 | Ggx1NVqyMw9H2IXTdh0Ti2OVnsRVNImdF38XEBIsWxIxnIE9k99Jshf05fXvaduu 16 | HmOsiwSwfkpnbzVn/U2nI0LxNNEzRLWsGLKAEPEe+xCo9AFDrmjpQj4uk6RagriT 17 | FEh3GGwkVETnIDPM4qQ4xl7fFPgWlkMoDFzKG5dagF/ScGOD1CMpEn+U2CIkHdUY 18 | 5cneM+sQcJ8+Mwsw7seFGWimoodaDGTa4nXMv98Lve8LLtMbxtIF5zvTClZB5GKM 19 | s6gNkjYDi3Sv40cmOc37g3+6xQed59HQPCp3vkjJQ7QWe+LIb0DPDksQ1XLN+Nzg 20 | c2CvBmyLl5yAYuBaVGPdacSAimF97/3SGsDyEozSwXXLbKo0z1NITcB6AblsULPp 21 | Qpvi46yvY1ijud7SvKGFiSfNDKxLRV/Z8aV2RqcllBHqQ7pNSjxsZ3NQR9lLMHcL 22 | Yyt1qWJYcyZqdoKeXuBpbnlHTbuf7HrydnELYnw8QZCeozUrxferp8bfOEOupmBo 23 | jhlqIgtHXQ95YQ+a005UKArfUPjLCy/y71lo7BVa3YucPGUB6YtdttIutso= 24 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/target.0.2: -------------------------------------------------------------------------------- 1 | nWmwYV06SiY6x7ibKx9dFuaMTO0WvqiZNQLZ2OrvJWobwqw7XI9sZD2joWjnUzQB 2 | 1HvFeU3qW7E9wVjB5gnw8JqIlSHS8l2ORCFzkSwINVI3+KS/vzKJblWCaBc5/94X 3 | dI68EIQ/RL88vLmSbyW5mKPW/Eo72Y4QrI37vLRLQB9oOevfFIUDa4Nxb/EX8dVr 4 | 0/0GeaJfL1PiK5LfUMeUun5bf8bKDGcjxuBGMOy2+L+1ODjfBTIL9dN4ik7/EW85 5 | gHtiDabvZzsU9tDmOY7kPCnMyDUAE64HQlzs5lR4SP6mx1umS7yi87DiTB8zozIF 6 | 7RQP1FNawKmaI/DExl47nPINfy6wDkpLOZFHrn1uXTLBS/K94ZG5i3TMqF4kqNpB 7 | /q8U/9RlIZbgi+LfZbMTBkOl9MGV82aUs2rnbuobDkv5okCCOmScJPCfMJYhh0Wq 8 | F83rgYK5ClKOjLD/ZZ7rUzH1itn85XGlcMwfjjR9+UoghtmWKMymzwKL1m1uFjat 9 | mo1gKiqS1+rTqnlrMxSAwFQuNhVQuHJhWnv3rzuk3XtjAu2qrUUSsgAV7l0pgeVG 10 | BUn5Gxb2XPVm61F+G7lLQWb3+mIskrk8ZdxXi3vyz4C7fjq/iiezay67uS2gMfPr 11 | RWyPCy1gXYitAoj1yisnEjQnUihb4gWKKaGf3wCnD1VCXbjBBhEyPQtT+Bp9zZZD 12 | mSMZvZF6au091SZZBWj4epzRXvd3+0rE3c7VNSb3Df1ONXXfRyf4lKytwHDiX/m9 13 | ZTElQ8XGKgDj3b9+vtdQlapq74PFckIFnhq/o0dRpY25bXevMMbtVJBu9dpuGso1 14 | AlxlRM3g1pTXQeqWD3ANGttsyhKTu4nBqnaWi1BkftEQ0S+j9wgDO46+970ceb73 15 | Ggx1NVqyMw9H2IXTdh0Ti2OVnsRVNImdF38XEBIsWxIxnIE9k99Jshf05fXvaduu 16 | HmOsiwSwfkpnbzVn/U2nI0LxNNEzRLWsGLKAEPEe+xCo9AFDrmjpQj4uk6RagriT 17 | FEh3GGwkVETnIDPM4qQ4xl7fFPgWlkMoDFzKG5dagF/ScGOD1CMpEn+U2CIkHdUY 18 | 5cneM+sQcJ8+Mwsw7seFGWimoodaDGTa4nXMv98Lve8LLtMbxtIF5zvTClZB5GKM 19 | s6gNkjYDi3Sv40cmOc37g3+6xQed59HQPCp3vkjJQ7QWe+LIb0DPDksQ1XLN+Nzg 20 | c2CvBmyLl5yAYuBaVGPdacSAimF97/3SGsDyEozSwXXLbKo0z1NITcB6AblsULPp 21 | Qpvi46yvY1ijud7SvKGFiSfNDKxLRV/Z8aV2RqcllBHqQ7pNSjxsZ3NQR9lLMHcL 22 | Yyt1qWJYcyZqdoKeXuBpbnlHTbuf7HrydnELYnw8QZCeozUrxferp8bfOEOupmBo 23 | jhlqIgtHXQ95YQ+a005UKArfUPjLCy/y71lo7BVa3YucPGUB6YtdttIutso= 24 | -------------------------------------------------------------------------------- /tuf/testdata/delegation/0/targets/role.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{"f2ecf910cec07f2834b5c0e301cc64655cabdfe69188ca84f7461af40005f459":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJtVENDQVQrZ0F3SUJBZ0lRTkErVGJDbDdpbEJLYnQzREUxMmxlVEFLQmdncWhrak9QUVFEQWpBU01SQXcKRGdZRFZRUUtFd2RCWTIxbElFTnZNQjRYRFRFM01EWXlPREU0TkRBd04xb1hEVEU0TURZeU9ERTROREF3TjFvdwpFakVRTUE0R0ExVUVDaE1IUVdOdFpTQkRiekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCR01yCnRJYzVuUmFDT1VNd3JMN1lzbmxUb0h4Z2IvbEE5cGJqNitRSlFNcC9Sdkk0OCsycFJGSUZQb3RQeStUY2xsc0wKd0JmUmlwcisrUFNXKy9VaEQrbWpkekIxTUE0R0ExVWREd0VCL3dRRUF3SUNwREFUQmdOVkhTVUVEREFLQmdncgpCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01EMEdBMVVkRVFRMk1EU0NEbXh2WTJGc2FHOXpkRG8wCk5EUXpnZzR4TWpjdU1DNHdMakU2TkRRME00SVNibTkwWVhKNUxYTmxjblpsY2pvME5EUXpNQW9HQ0NxR1NNNDkKQkFNQ0EwZ0FNRVVDSVFEODNySjBtcXVuOFZEQW96U3FIeXJJVkE2OXA2UWdYV1phZVVuZWtyQUxrQUlnUm1kYgpkem9rTkNDQXpEYmU1SzlqS2V0cWlZeVR3WlpuNmRZREs3bkIyWlE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}}},"roles":[{"keyids":["f2ecf910cec07f2834b5c0e301cc64655cabdfe69188ca84f7461af40005f459"],"name":"targets/role/foo","paths":["edge","v1"],"threshold":1}]},"expires":"2020-06-27T15:42:16.894151944-05:00","targets":{"latest/target":{"hashes":{"sha256":"LHCzisHN4FFQwuAeFNWVAYPVKBYWzlsCSrl4kBiYcsQ=","sha512":"rOy+AuF5Ox83LqHStvWK0MCykj3spkRSkBbYinrde3cClBwdKOrkzwYodGgWxL0SPMpNFtukzOJP6S3BVTd5Ag=="},"length":1491}},"version":3},"signatures":[{"keyid":"5e46d7553a7f2a2b98dd8753a94398df6ff6e53772e08fd373f3f30533c6c067","method":"ecdsa","sig":"abZMp3TgtAnsCH33tKfmUzbBfilA7vGAX815bT7S9A9NLvdWwM3uP2kM8dezhLMAC2b1CXTE6rAEms91v4QWJA=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/1/targets/role.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{"f2ecf910cec07f2834b5c0e301cc64655cabdfe69188ca84f7461af40005f459":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJtVENDQVQrZ0F3SUJBZ0lRTkErVGJDbDdpbEJLYnQzREUxMmxlVEFLQmdncWhrak9QUVFEQWpBU01SQXcKRGdZRFZRUUtFd2RCWTIxbElFTnZNQjRYRFRFM01EWXlPREU0TkRBd04xb1hEVEU0TURZeU9ERTROREF3TjFvdwpFakVRTUE0R0ExVUVDaE1IUVdOdFpTQkRiekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCR01yCnRJYzVuUmFDT1VNd3JMN1lzbmxUb0h4Z2IvbEE5cGJqNitRSlFNcC9Sdkk0OCsycFJGSUZQb3RQeStUY2xsc0wKd0JmUmlwcisrUFNXKy9VaEQrbWpkekIxTUE0R0ExVWREd0VCL3dRRUF3SUNwREFUQmdOVkhTVUVEREFLQmdncgpCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01EMEdBMVVkRVFRMk1EU0NEbXh2WTJGc2FHOXpkRG8wCk5EUXpnZzR4TWpjdU1DNHdMakU2TkRRME00SVNibTkwWVhKNUxYTmxjblpsY2pvME5EUXpNQW9HQ0NxR1NNNDkKQkFNQ0EwZ0FNRVVDSVFEODNySjBtcXVuOFZEQW96U3FIeXJJVkE2OXA2UWdYV1phZVVuZWtyQUxrQUlnUm1kYgpkem9rTkNDQXpEYmU1SzlqS2V0cWlZeVR3WlpuNmRZREs3bkIyWlE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}}},"roles":[{"keyids":["f2ecf910cec07f2834b5c0e301cc64655cabdfe69188ca84f7461af40005f459"],"name":"targets/role/foo","paths":["edge","v1"],"threshold":1}]},"expires":"2020-06-27T15:42:57.625107118-05:00","targets":{"latest/target":{"hashes":{"sha256":"LHCzisHN4FFQwuAeFNWVAYPVKBYWzlsCSrl4kBiYcsQ=","sha512":"rOy+AuF5Ox83LqHStvWK0MCykj3spkRSkBbYinrde3cClBwdKOrkzwYodGgWxL0SPMpNFtukzOJP6S3BVTd5Ag=="},"length":1491}},"version":4},"signatures":[{"keyid":"5e46d7553a7f2a2b98dd8753a94398df6ff6e53772e08fd373f3f30533c6c067","method":"ecdsa","sig":"hNEEKxqWggzfCcaWity55LJi9nIZ+8dqz0srxTkUkfvBRCOKSkH4D/uzfXjbPw5AdEazlfDplB5d3OxVY02gQw=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/2/targets/role.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{"f2ecf910cec07f2834b5c0e301cc64655cabdfe69188ca84f7461af40005f459":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJtVENDQVQrZ0F3SUJBZ0lRTkErVGJDbDdpbEJLYnQzREUxMmxlVEFLQmdncWhrak9QUVFEQWpBU01SQXcKRGdZRFZRUUtFd2RCWTIxbElFTnZNQjRYRFRFM01EWXlPREU0TkRBd04xb1hEVEU0TURZeU9ERTROREF3TjFvdwpFakVRTUE0R0ExVUVDaE1IUVdOdFpTQkRiekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCR01yCnRJYzVuUmFDT1VNd3JMN1lzbmxUb0h4Z2IvbEE5cGJqNitRSlFNcC9Sdkk0OCsycFJGSUZQb3RQeStUY2xsc0wKd0JmUmlwcisrUFNXKy9VaEQrbWpkekIxTUE0R0ExVWREd0VCL3dRRUF3SUNwREFUQmdOVkhTVUVEREFLQmdncgpCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01EMEdBMVVkRVFRMk1EU0NEbXh2WTJGc2FHOXpkRG8wCk5EUXpnZzR4TWpjdU1DNHdMakU2TkRRME00SVNibTkwWVhKNUxYTmxjblpsY2pvME5EUXpNQW9HQ0NxR1NNNDkKQkFNQ0EwZ0FNRVVDSVFEODNySjBtcXVuOFZEQW96U3FIeXJJVkE2OXA2UWdYV1phZVVuZWtyQUxrQUlnUm1kYgpkem9rTkNDQXpEYmU1SzlqS2V0cWlZeVR3WlpuNmRZREs3bkIyWlE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}}},"roles":[{"keyids":["f2ecf910cec07f2834b5c0e301cc64655cabdfe69188ca84f7461af40005f459"],"name":"targets/role/foo","paths":["edge","v1"],"threshold":1}]},"expires":"2020-06-27T17:23:26.021315985-05:00","targets":{"latest/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":5},"signatures":[{"keyid":"5e46d7553a7f2a2b98dd8753a94398df6ff6e53772e08fd373f3f30533c6c067","method":"ecdsa","sig":"gcINvJyJg8Y+IbrPwISJE9lRBzRNpWw5u27aYH4KO628h55GlWZkdkk33ywgThRBfMgu8IPm9fYHbc5BLBwM2g=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/3/targets/role.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{"f2ecf910cec07f2834b5c0e301cc64655cabdfe69188ca84f7461af40005f459":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJtVENDQVQrZ0F3SUJBZ0lRTkErVGJDbDdpbEJLYnQzREUxMmxlVEFLQmdncWhrak9QUVFEQWpBU01SQXcKRGdZRFZRUUtFd2RCWTIxbElFTnZNQjRYRFRFM01EWXlPREU0TkRBd04xb1hEVEU0TURZeU9ERTROREF3TjFvdwpFakVRTUE0R0ExVUVDaE1IUVdOdFpTQkRiekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCR01yCnRJYzVuUmFDT1VNd3JMN1lzbmxUb0h4Z2IvbEE5cGJqNitRSlFNcC9Sdkk0OCsycFJGSUZQb3RQeStUY2xsc0wKd0JmUmlwcisrUFNXKy9VaEQrbWpkekIxTUE0R0ExVWREd0VCL3dRRUF3SUNwREFUQmdOVkhTVUVEREFLQmdncgpCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01EMEdBMVVkRVFRMk1EU0NEbXh2WTJGc2FHOXpkRG8wCk5EUXpnZzR4TWpjdU1DNHdMakU2TkRRME00SVNibTkwWVhKNUxYTmxjblpsY2pvME5EUXpNQW9HQ0NxR1NNNDkKQkFNQ0EwZ0FNRVVDSVFEODNySjBtcXVuOFZEQW96U3FIeXJJVkE2OXA2UWdYV1phZVVuZWtyQUxrQUlnUm1kYgpkem9rTkNDQXpEYmU1SzlqS2V0cWlZeVR3WlpuNmRZREs3bkIyWlE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}}},"roles":[{"keyids":["f2ecf910cec07f2834b5c0e301cc64655cabdfe69188ca84f7461af40005f459"],"name":"targets/role/foo","paths":["edge","v1"],"threshold":1}]},"expires":"2020-06-27T17:23:26.021315985-05:00","targets":{"latest/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":5},"signatures":[{"keyid":"5e46d7553a7f2a2b98dd8753a94398df6ff6e53772e08fd373f3f30533c6c067","method":"ecdsa","sig":"gcINvJyJg8Y+IbrPwISJE9lRBzRNpWw5u27aYH4KO628h55GlWZkdkk33ywgThRBfMgu8IPm9fYHbc5BLBwM2g=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/0/snapshot.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Snapshot","expires":"2020-06-27T20:42:16.913197283Z","meta":{"root":{"hashes":{"sha256":"3epIedOr4tsvE2SYEbt5gg1RfBHVOINag/7uaSaDFOw=","sha512":"vDECLsKk4PHWFJ0FJyhC9IazvTLPAnMcVUFylhVj1EvWFl9/N/s/U12gFirp75DHohNuX8x65YhjHSwjS2m7PA=="},"length":2384},"targets":{"hashes":{"sha256":"Wq0Ys2F7y9YcPuhykvE41C2WECPUee1V7amhQTes8vc=","sha512":"IOiHVRFy7YbedSx8czkWR5wCR9C8DQHtJ0V12LzPRKLJF108nl6ygTu+82D4jIfaG5DseykPIMOzewPRbuq1WA=="},"length":2711},"targets/bar":{"hashes":{"sha256":"zHbA8PKaFuu9SLrVQz+SNdCiuTEDzQ8EtLRTANk5hXg=","sha512":"A9416bMBOaGIcepTqhDEPbOy4pl8vg5iKCGbZnzW+NmBHy9dLtQDdDMt3zeyhOyjKcmgK/Kj8pOGqkOZiNEO2g=="},"length":544},"targets/project":{"hashes":{"sha256":"OYDbo400XPElGFxC/c9MR7p6SmB3MANKH6ZDYf6pXCE=","sha512":"DPwb3XgRi23U3OXqUgby9/dsXqFirq28XfAEPKODAKJnjpFIlb6waszjI85RP31KNIP7if8NqohFKuFGfdqzcA=="},"length":7010},"targets/publisher":{"hashes":{"sha256":"wVe9l/T2k7fiWtdZhvE95Ev0BiU8FRApazzzAD48eSI=","sha512":"nrBaCyMo3+8xUOJmIXmMBsWKWiW8z2xek/w9BpygO5RHPNcqNo3JNGAZO6e6B76Ivv3HkaQcS2ZkZeswrDlJ0A=="},"length":544},"targets/role":{"hashes":{"sha256":"i4nxMm6VMCRiLSmC5Ns+gCNW41JPjrBskr7K7xSX6Uo=","sha512":"FBgdnC1blFOoTwQSCQL2UjxuWvl/J7QCpwKxy+dzslTWTnzSmmBulNlrFuHmw0QG4wB/OriQxa8G0WLmlodiuA=="},"length":1634},"targets/role/foo":{"hashes":{"sha256":"HP6aDI2dl/mNlWAnteSMcpMH2Aoq4jTj45WMsM0Iiro=","sha512":"cX23xhJg7urCw7WxUC13vGJEBMjKl2RvtSWeOi+RYCOeDwyrjJmgbRH/KThidbJo0UVcT/IygsC31eOET3En6A=="},"length":542}},"version":28},"signatures":[{"keyid":"d8dffe6221786cc12001f4d0c6e71f85fc6e6348cf8d3a4ed2dcf6c056716f04","method":"ecdsa","sig":"lUUqo7HO1aldECXrrpgYSywpEYJ73IjWshxee2KCN70a2rhjGfiB5UMSuZJ+K4ydYx/dCzpIngQetycSYhCVFQ=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/1/snapshot.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Snapshot","expires":"2020-06-27T20:42:57.640318436Z","meta":{"root":{"hashes":{"sha256":"3epIedOr4tsvE2SYEbt5gg1RfBHVOINag/7uaSaDFOw=","sha512":"vDECLsKk4PHWFJ0FJyhC9IazvTLPAnMcVUFylhVj1EvWFl9/N/s/U12gFirp75DHohNuX8x65YhjHSwjS2m7PA=="},"length":2384},"targets":{"hashes":{"sha256":"Wq0Ys2F7y9YcPuhykvE41C2WECPUee1V7amhQTes8vc=","sha512":"IOiHVRFy7YbedSx8czkWR5wCR9C8DQHtJ0V12LzPRKLJF108nl6ygTu+82D4jIfaG5DseykPIMOzewPRbuq1WA=="},"length":2711},"targets/bar":{"hashes":{"sha256":"zHbA8PKaFuu9SLrVQz+SNdCiuTEDzQ8EtLRTANk5hXg=","sha512":"A9416bMBOaGIcepTqhDEPbOy4pl8vg5iKCGbZnzW+NmBHy9dLtQDdDMt3zeyhOyjKcmgK/Kj8pOGqkOZiNEO2g=="},"length":544},"targets/project":{"hashes":{"sha256":"OYDbo400XPElGFxC/c9MR7p6SmB3MANKH6ZDYf6pXCE=","sha512":"DPwb3XgRi23U3OXqUgby9/dsXqFirq28XfAEPKODAKJnjpFIlb6waszjI85RP31KNIP7if8NqohFKuFGfdqzcA=="},"length":7010},"targets/publisher":{"hashes":{"sha256":"wVe9l/T2k7fiWtdZhvE95Ev0BiU8FRApazzzAD48eSI=","sha512":"nrBaCyMo3+8xUOJmIXmMBsWKWiW8z2xek/w9BpygO5RHPNcqNo3JNGAZO6e6B76Ivv3HkaQcS2ZkZeswrDlJ0A=="},"length":544},"targets/role":{"hashes":{"sha256":"yYzN0FC7/Wj9mJC25fsAHUiDoM9kHE/MKxIlL6clgyI=","sha512":"hLjiior8YHwqo8WI/NK1VJlpr1ByHL4tRv8YrVCM4QwcJgF+iCgirtw3K6Zs13Yrxl2Ziv70ICZ5ILSjDonNUw=="},"length":1634},"targets/role/foo":{"hashes":{"sha256":"HP6aDI2dl/mNlWAnteSMcpMH2Aoq4jTj45WMsM0Iiro=","sha512":"cX23xhJg7urCw7WxUC13vGJEBMjKl2RvtSWeOi+RYCOeDwyrjJmgbRH/KThidbJo0UVcT/IygsC31eOET3En6A=="},"length":542}},"version":29},"signatures":[{"keyid":"d8dffe6221786cc12001f4d0c6e71f85fc6e6348cf8d3a4ed2dcf6c056716f04","method":"ecdsa","sig":"yWMmsLntm6V40B9ml6h/0XX88Tpt/oVJSylIibYMnoOmCMr5KlOuCtbcN0cAJVDCuFxUzxziAyl5TwVHQqi7kA=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/2/snapshot.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Snapshot","expires":"2020-07-01T21:55:32.430410043Z","meta":{"root":{"hashes":{"sha256":"3epIedOr4tsvE2SYEbt5gg1RfBHVOINag/7uaSaDFOw=","sha512":"vDECLsKk4PHWFJ0FJyhC9IazvTLPAnMcVUFylhVj1EvWFl9/N/s/U12gFirp75DHohNuX8x65YhjHSwjS2m7PA=="},"length":2384},"targets":{"hashes":{"sha256":"Wq0Ys2F7y9YcPuhykvE41C2WECPUee1V7amhQTes8vc=","sha512":"IOiHVRFy7YbedSx8czkWR5wCR9C8DQHtJ0V12LzPRKLJF108nl6ygTu+82D4jIfaG5DseykPIMOzewPRbuq1WA=="},"length":2711},"targets/bar":{"hashes":{"sha256":"zHbA8PKaFuu9SLrVQz+SNdCiuTEDzQ8EtLRTANk5hXg=","sha512":"A9416bMBOaGIcepTqhDEPbOy4pl8vg5iKCGbZnzW+NmBHy9dLtQDdDMt3zeyhOyjKcmgK/Kj8pOGqkOZiNEO2g=="},"length":544},"targets/project":{"hashes":{"sha256":"OYDbo400XPElGFxC/c9MR7p6SmB3MANKH6ZDYf6pXCE=","sha512":"DPwb3XgRi23U3OXqUgby9/dsXqFirq28XfAEPKODAKJnjpFIlb6waszjI85RP31KNIP7if8NqohFKuFGfdqzcA=="},"length":7010},"targets/publisher":{"hashes":{"sha256":"wVe9l/T2k7fiWtdZhvE95Ev0BiU8FRApazzzAD48eSI=","sha512":"nrBaCyMo3+8xUOJmIXmMBsWKWiW8z2xek/w9BpygO5RHPNcqNo3JNGAZO6e6B76Ivv3HkaQcS2ZkZeswrDlJ0A=="},"length":544},"targets/role":{"hashes":{"sha256":"EI0db2QNxOm5CWxoGG1CZrS2y/+Vza56ZpLOdY6g3c8=","sha512":"9NdVuIP0ElYVxz3lU9Nh7WdVOqnzHzQL0sVCQN0FiXFnYsfneG305LDwtVWkVSiaXhohQfSbPe+gIaR1bcabAQ=="},"length":1634},"targets/role/foo":{"hashes":{"sha256":"bwQY82IM2KRW1hg8gKbC+MB7R1u0jShTj1B0rtq3Muo=","sha512":"8M78K+zIwgQrXvynmjvl0YxN814NE2ttFnK8ydLAFcGr82+tWWcZIqWKKodjtH40PyBODL6HZxMJrEVrP5C7pA=="},"length":542}},"version":31},"signatures":[{"keyid":"d8dffe6221786cc12001f4d0c6e71f85fc6e6348cf8d3a4ed2dcf6c056716f04","method":"ecdsa","sig":"q7D0Yc3Exz4H5ydx4t48NJD3g50FIcIRhiY4omI+UPn4FfRivsPYX8W9GtcUPLWl6PhvC0PsRth0VU5CUT8K9Q=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/3/snapshot.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Snapshot","expires":"2020-07-02T01:36:47.181139168Z","meta":{"root":{"hashes":{"sha256":"3epIedOr4tsvE2SYEbt5gg1RfBHVOINag/7uaSaDFOw=","sha512":"vDECLsKk4PHWFJ0FJyhC9IazvTLPAnMcVUFylhVj1EvWFl9/N/s/U12gFirp75DHohNuX8x65YhjHSwjS2m7PA=="},"length":2384},"targets":{"hashes":{"sha256":"Wq0Ys2F7y9YcPuhykvE41C2WECPUee1V7amhQTes8vc=","sha512":"IOiHVRFy7YbedSx8czkWR5wCR9C8DQHtJ0V12LzPRKLJF108nl6ygTu+82D4jIfaG5DseykPIMOzewPRbuq1WA=="},"length":2711},"targets/bar":{"hashes":{"sha256":"s1CUFrJ+37d+NpODtX8wKEOnDSqAjFIR0FNSk0vsgoA=","sha512":"jwXK/YOPKjbN/cckBeTyqVTGDwzXg93Ur1GlcBgdBvix06OoW3lrF/rCEy1L5+W+gApE3DqxZlW16uDjAq44Xw=="},"length":544},"targets/project":{"hashes":{"sha256":"OYDbo400XPElGFxC/c9MR7p6SmB3MANKH6ZDYf6pXCE=","sha512":"DPwb3XgRi23U3OXqUgby9/dsXqFirq28XfAEPKODAKJnjpFIlb6waszjI85RP31KNIP7if8NqohFKuFGfdqzcA=="},"length":7010},"targets/publisher":{"hashes":{"sha256":"wVe9l/T2k7fiWtdZhvE95Ev0BiU8FRApazzzAD48eSI=","sha512":"nrBaCyMo3+8xUOJmIXmMBsWKWiW8z2xek/w9BpygO5RHPNcqNo3JNGAZO6e6B76Ivv3HkaQcS2ZkZeswrDlJ0A=="},"length":544},"targets/role":{"hashes":{"sha256":"EI0db2QNxOm5CWxoGG1CZrS2y/+Vza56ZpLOdY6g3c8=","sha512":"9NdVuIP0ElYVxz3lU9Nh7WdVOqnzHzQL0sVCQN0FiXFnYsfneG305LDwtVWkVSiaXhohQfSbPe+gIaR1bcabAQ=="},"length":1634},"targets/role/foo":{"hashes":{"sha256":"bwQY82IM2KRW1hg8gKbC+MB7R1u0jShTj1B0rtq3Muo=","sha512":"8M78K+zIwgQrXvynmjvl0YxN814NE2ttFnK8ydLAFcGr82+tWWcZIqWKKodjtH40PyBODL6HZxMJrEVrP5C7pA=="},"length":542}},"version":32},"signatures":[{"keyid":"d8dffe6221786cc12001f4d0c6e71f85fc6e6348cf8d3a4ed2dcf6c056716f04","method":"ecdsa","sig":"F8kFOc8RHPAz4NhgSiC5zKY8HCcZtKAYxl8XxDx3pcrw2lvgwfxBmNmz8B3PxOhkMklR2y3MtzLv9P0AkQZpwQ=="}]} -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/WatchBeam/clock v0.0.0-20161028195133-dc1b57477882 h1:kRJ38enVwWTeAi/agkNVRgzmewLptlJGq3Kg4Bkaa6I= 2 | github.com/WatchBeam/clock v0.0.0-20161028195133-dc1b57477882/go.mod h1:N5eJIl14rhNCrE5I3O10HIyhZ1HpjaRHT9WDg1eXxtI= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/docker/go v1.5.1-1 h1:hr4w35acWBPhGBXlzPoHpmZ/ygPjnmFVxGxxGnMyP7k= 6 | github.com/docker/go v1.5.1-1/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= 7 | github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= 8 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 9 | github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= 10 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 11 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 12 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 13 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= 14 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 15 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 16 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/stretchr/testify v1.1.4 h1:ToftOQTytwshuOSj6bDSolVUa3GINfJP/fg3OkkOzQQ= 20 | github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 21 | -------------------------------------------------------------------------------- /tuf/local_repo_test.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | "time" 12 | 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func setupLocalTests(t *testing.T) string { 18 | baseDir, err := ioutil.TempDir("", "test") 19 | require.Nil(t, err) 20 | roles := []role{roleRoot, roleTargets, roleSnapshot, roleTimestamp} 21 | for _, r := range roles { 22 | func() { 23 | buff := testAsset(t, fmt.Sprintf("testdata/data/%s.json", r)) 24 | f, err := os.OpenFile(filepath.Join(baseDir, fmt.Sprintf("%s.json", r)), os.O_CREATE|os.O_WRONLY, 0644) 25 | require.Nil(t, err) 26 | defer f.Close() 27 | _, err = io.Copy(f, bytes.NewBuffer(buff)) 28 | require.Nil(t, err) 29 | }() 30 | } 31 | return baseDir 32 | } 33 | 34 | func TestGetLocalRoles(t *testing.T) { 35 | baseDir := setupLocalTests(t) 36 | defer os.RemoveAll(baseDir) 37 | 38 | l, err := newLocalRepo(baseDir) 39 | require.Nil(t, err) 40 | root, err := l.root() 41 | require.Nil(t, err) 42 | require.NotNil(t, root) 43 | assert.Equal(t, "2027-06-10T13:25:45.170347322-05:00", root.Signed.Expires.Format(time.RFC3339Nano)) 44 | 45 | ts, err := l.timestamp() 46 | require.Nil(t, err) 47 | require.NotNil(t, ts) 48 | assert.Equal(t, "2017-06-26T19:32:36.967988706Z", ts.Signed.Expires.Format(time.RFC3339Nano)) 49 | 50 | ss, err := l.snapshot() 51 | require.Nil(t, err) 52 | require.NotNil(t, ss) 53 | assert.Equal(t, "2020-06-11T14:32:32.161365749-05:00", ss.Signed.Expires.Format(time.RFC3339Nano)) 54 | 55 | } 56 | 57 | func TestGetLocalTargets(t *testing.T) { 58 | l := localRepo{} 59 | trg, err := l.targets(&mockLocalRepoReader{"testdata/delegation/0/"}) 60 | require.Nil(t, err) 61 | require.NotNil(t, trg) 62 | assert.Equal(t, "2020-06-27T14:16:29.4823129-05:00", trg.Signed.Expires.Format(time.RFC3339Nano)) 63 | } 64 | -------------------------------------------------------------------------------- /tuf/local_repo.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | type localTargetFetcher struct { 13 | baseDir string 14 | } 15 | 16 | func (rdr *localTargetFetcher) fetch(role string) (*Targets, error) { 17 | f, err := os.Open(filepath.Join(rdr.baseDir, fmt.Sprintf("%s.json", role))) 18 | if err != nil { 19 | return nil, errors.Wrap(err, "local target read from file") 20 | } 21 | defer f.Close() 22 | var result Targets 23 | if err = json.NewDecoder(f).Decode(&result); err != nil { 24 | return nil, errors.Wrap(err, "decoding json reading local target") 25 | } 26 | return &result, nil 27 | } 28 | 29 | func (r *localRepo) root(opts ...repoOption) (*Root, error) { 30 | var root Root 31 | err := r.getRole(roleRoot, &root) 32 | if err != nil { 33 | return nil, errors.Wrap(err, "getting local root role") 34 | } 35 | return &root, nil 36 | } 37 | 38 | func (r *localRepo) timestamp() (*Timestamp, error) { 39 | var ts Timestamp 40 | err := r.getRole(roleTimestamp, &ts) 41 | if err != nil { 42 | return nil, errors.Wrap(err, "getting local timestamp role") 43 | } 44 | return &ts, nil 45 | } 46 | 47 | func (r *localRepo) snapshot(opts ...repoOption) (*Snapshot, error) { 48 | var ss Snapshot 49 | err := r.getRole(roleSnapshot, &ss) 50 | if err != nil { 51 | return nil, errors.Wrap(err, "getting local snapshot role") 52 | } 53 | return &ss, nil 54 | } 55 | 56 | func (r *localRepo) targets(fetcher roleFetcher) (*RootTarget, error) { 57 | rootTarget, err := targetTreeBuilder(fetcher) 58 | if err != nil { 59 | return nil, errors.Wrap(err, "getting local targets role") 60 | } 61 | return rootTarget, nil 62 | } 63 | 64 | func (r *localRepo) getRole(name role, val interface{}) error { 65 | err := validateRole(name) 66 | if err != nil { 67 | return err 68 | } 69 | f, err := os.Open(filepath.Join(r.repoPath, fmt.Sprintf("%s.json", name))) 70 | if err != nil { 71 | return errors.Wrap(err, "getting role") 72 | } 73 | defer f.Close() 74 | return json.NewDecoder(f).Decode(val) 75 | } 76 | -------------------------------------------------------------------------------- /tuf/testdata/data/root-ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 31 | rba7O3YGSuScQakjt9mw2Q5ISwImkRHF3qE= 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/target.1.0: -------------------------------------------------------------------------------- 1 | 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 32 | 9bkeQbelqq+wVaZ3 33 | -------------------------------------------------------------------------------- /tuf/fim.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "crypto/subtle" 5 | "encoding/base64" 6 | "hash" 7 | "io" 8 | "io/ioutil" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | // FileIntegrityMeta hashes and length of a file based resource to help ensure 14 | // the binary footprint of the file hasn't been tampered with 15 | type FileIntegrityMeta struct { 16 | Hashes map[hashingMethod]string `json:"hashes"` 17 | Length int64 `json:"length"` 18 | } 19 | 20 | func newFileIntegrityMeta() *FileIntegrityMeta { 21 | return &FileIntegrityMeta{ 22 | Hashes: make(map[hashingMethod]string), 23 | } 24 | } 25 | 26 | func (fim FileIntegrityMeta) clone() *FileIntegrityMeta { 27 | h := make(map[hashingMethod]string) 28 | for k, v := range fim.Hashes { 29 | h[k] = v 30 | } 31 | return &FileIntegrityMeta{h, fim.Length} 32 | } 33 | 34 | // Equal is deep comparison of two FileIntegrityMeta 35 | func (fim FileIntegrityMeta) Equal(fimTarget FileIntegrityMeta) bool { 36 | if fim.Length != fimTarget.Length { 37 | return false 38 | } 39 | if len(fim.Hashes) != len(fimTarget.Hashes) { 40 | return false 41 | } 42 | for algo, hash := range fim.Hashes { 43 | h, ok := fimTarget.Hashes[algo] 44 | if !ok { 45 | return false 46 | } 47 | if h != hash { 48 | return false 49 | } 50 | } 51 | return true 52 | } 53 | 54 | // File hash and length validation per TUF 5.5.2 55 | func (fim FileIntegrityMeta) verify(rdr io.Reader) error { 56 | var hashes []hashInfo 57 | for algo, expectedHash := range fim.Hashes { 58 | var hashFunc hash.Hash 59 | valid, err := base64.StdEncoding.DecodeString(expectedHash) 60 | if err != nil { 61 | return errors.New("invalid hash in verify") 62 | } 63 | hashFunc, err = getHasher(algo) 64 | if err != nil { 65 | return err 66 | } 67 | rdr = io.TeeReader(rdr, hashFunc) 68 | hashes = append(hashes, hashInfo{hashFunc, valid}) 69 | } 70 | length, err := io.Copy(ioutil.Discard, rdr) 71 | if err != nil { 72 | return err 73 | } 74 | if length != fim.Length { 75 | return errLengthIncorrect 76 | } 77 | for _, h := range hashes { 78 | if subtle.ConstantTimeCompare(h.valid, h.h.Sum(nil)) != 1 { 79 | return errHashIncorrect 80 | } 81 | } 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/root.0.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2027-06-16T16:42:03.265214245-05:00","keys":{"0866586701391bd1871f9a428b2e579e9354f812aa5fb44b4691f27064299233":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwVLZF44vaL6FF7HyhQqAKATIuwmSJpPhWFgciU5h35zbesIElJ9NWG+AbEnCTB0mAwyoU+NGSqEq/gb0CxdU1Q=="}},"18daae20502fe6a7eb4ff89acb31d06af1b271c55c94f7b9d7e1e66e624e1642":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJiVENDQVJPZ0F3SUJBZ0lRRFB0a0tBU2NYcTNyZmhYdElEd05LekFLQmdncWhrak9QUVFEQWpBZE1Sc3cKR1FZRFZRUURFeEpyYjJ4cFpHVXZZV2RsYm5RdmJHbHVkWGd3SGhjTk1UY3dOakU0TWpFME1UUTJXaGNOTWpjdwpOakUyTWpFME1UUTJXakFkTVJzd0dRWURWUVFERXhKcmIyeHBaR1V2WVdkbGJuUXZiR2x1ZFhnd1dUQVRCZ2NxCmhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUUZiNC8vOVBvbExwc2l0c0VZU0w1djFkUFlWOHZadFdSQnNUMWoKdDlGQTRQeEQxY1haM0w0MDlOZHAvY0htYUp1Ullta3hOM2h4WFRxUTNSVDhFWFdxb3pVd016QU9CZ05WSFE4QgpBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUhBd013REFZRFZSMFRBUUgvQkFJd0FEQUtCZ2dxCmhrak9QUVFEQWdOSUFEQkZBaUVBcGpLd253NEpmanR6U1Z6R2RJbzlmR001MTlaMTZTdk1YWllLUkFVSlRTY0MKSUFhQ1k1MG1adTVHQ0ppczFOWnFJU2Q1MzlrbkRTYUNQYTZzWEJHaVpkYlYKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}},"a822db495e448ff983d6560f28b80c600f11ae1ff408ff5ed69e8ec9ef1ba8c4":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbWvdIa85k/icSqN4i/rqvmw3JpyTVtcFHxSJm4UREp3A7vasKw6GRBaH3/t7k7MytnASYRDlHVyTvryEq7Xpnw=="}},"e190d2a8a8a35dd7a364b0d7fadb35136acd0fe92cfda5aacc4dce17f574216a":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZ3oTV9C/qREqBd4A3+EVC3lkHWofcfJTpkk8DKJO0eLr5H5Sc6ZtbpRGbgrQ4yX4a7W7Zk1SFnb7gsRSH2FYLA=="}}},"roles":{"root":{"keyids":["18daae20502fe6a7eb4ff89acb31d06af1b271c55c94f7b9d7e1e66e624e1642"],"threshold":1},"snapshot":{"keyids":["0866586701391bd1871f9a428b2e579e9354f812aa5fb44b4691f27064299233"],"threshold":1},"targets":{"keyids":["a822db495e448ff983d6560f28b80c600f11ae1ff408ff5ed69e8ec9ef1ba8c4"],"threshold":1},"timestamp":{"keyids":["e190d2a8a8a35dd7a364b0d7fadb35136acd0fe92cfda5aacc4dce17f574216a"],"threshold":1}},"version":1},"signatures":[{"keyid":"18daae20502fe6a7eb4ff89acb31d06af1b271c55c94f7b9d7e1e66e624e1642","method":"ecdsa","sig":"1Vfb+UpEfIQlzpEXSIXVL4JOz7ZSS3vLWBpoXcEuBg9kH0vpUxwvNCu7cwDXrAuxAw+IN9pqgYhBpbtiirNj3A=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/root.1.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2027-06-16T16:42:03.265214245-05:00","keys":{"0866586701391bd1871f9a428b2e579e9354f812aa5fb44b4691f27064299233":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwVLZF44vaL6FF7HyhQqAKATIuwmSJpPhWFgciU5h35zbesIElJ9NWG+AbEnCTB0mAwyoU+NGSqEq/gb0CxdU1Q=="}},"18daae20502fe6a7eb4ff89acb31d06af1b271c55c94f7b9d7e1e66e624e1642":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJiVENDQVJPZ0F3SUJBZ0lRRFB0a0tBU2NYcTNyZmhYdElEd05LekFLQmdncWhrak9QUVFEQWpBZE1Sc3cKR1FZRFZRUURFeEpyYjJ4cFpHVXZZV2RsYm5RdmJHbHVkWGd3SGhjTk1UY3dOakU0TWpFME1UUTJXaGNOTWpjdwpOakUyTWpFME1UUTJXakFkTVJzd0dRWURWUVFERXhKcmIyeHBaR1V2WVdkbGJuUXZiR2x1ZFhnd1dUQVRCZ2NxCmhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUUZiNC8vOVBvbExwc2l0c0VZU0w1djFkUFlWOHZadFdSQnNUMWoKdDlGQTRQeEQxY1haM0w0MDlOZHAvY0htYUp1Ullta3hOM2h4WFRxUTNSVDhFWFdxb3pVd016QU9CZ05WSFE4QgpBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUhBd013REFZRFZSMFRBUUgvQkFJd0FEQUtCZ2dxCmhrak9QUVFEQWdOSUFEQkZBaUVBcGpLd253NEpmanR6U1Z6R2RJbzlmR001MTlaMTZTdk1YWllLUkFVSlRTY0MKSUFhQ1k1MG1adTVHQ0ppczFOWnFJU2Q1MzlrbkRTYUNQYTZzWEJHaVpkYlYKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}},"a822db495e448ff983d6560f28b80c600f11ae1ff408ff5ed69e8ec9ef1ba8c4":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbWvdIa85k/icSqN4i/rqvmw3JpyTVtcFHxSJm4UREp3A7vasKw6GRBaH3/t7k7MytnASYRDlHVyTvryEq7Xpnw=="}},"e190d2a8a8a35dd7a364b0d7fadb35136acd0fe92cfda5aacc4dce17f574216a":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZ3oTV9C/qREqBd4A3+EVC3lkHWofcfJTpkk8DKJO0eLr5H5Sc6ZtbpRGbgrQ4yX4a7W7Zk1SFnb7gsRSH2FYLA=="}}},"roles":{"root":{"keyids":["18daae20502fe6a7eb4ff89acb31d06af1b271c55c94f7b9d7e1e66e624e1642"],"threshold":1},"snapshot":{"keyids":["0866586701391bd1871f9a428b2e579e9354f812aa5fb44b4691f27064299233"],"threshold":1},"targets":{"keyids":["a822db495e448ff983d6560f28b80c600f11ae1ff408ff5ed69e8ec9ef1ba8c4"],"threshold":1},"timestamp":{"keyids":["e190d2a8a8a35dd7a364b0d7fadb35136acd0fe92cfda5aacc4dce17f574216a"],"threshold":1}},"version":1},"signatures":[{"keyid":"18daae20502fe6a7eb4ff89acb31d06af1b271c55c94f7b9d7e1e66e624e1642","method":"ecdsa","sig":"1Vfb+UpEfIQlzpEXSIXVL4JOz7ZSS3vLWBpoXcEuBg9kH0vpUxwvNCu7cwDXrAuxAw+IN9pqgYhBpbtiirNj3A=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/root.2.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2027-06-17T11:46:10.15933352-05:00","keys":{"18daae20502fe6a7eb4ff89acb31d06af1b271c55c94f7b9d7e1e66e624e1642":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJiVENDQVJPZ0F3SUJBZ0lRRFB0a0tBU2NYcTNyZmhYdElEd05LekFLQmdncWhrak9QUVFEQWpBZE1Sc3cKR1FZRFZRUURFeEpyYjJ4cFpHVXZZV2RsYm5RdmJHbHVkWGd3SGhjTk1UY3dOakU0TWpFME1UUTJXaGNOTWpjdwpOakUyTWpFME1UUTJXakFkTVJzd0dRWURWUVFERXhKcmIyeHBaR1V2WVdkbGJuUXZiR2x1ZFhnd1dUQVRCZ2NxCmhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUUZiNC8vOVBvbExwc2l0c0VZU0w1djFkUFlWOHZadFdSQnNUMWoKdDlGQTRQeEQxY1haM0w0MDlOZHAvY0htYUp1Ullta3hOM2h4WFRxUTNSVDhFWFdxb3pVd016QU9CZ05WSFE4QgpBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUhBd013REFZRFZSMFRBUUgvQkFJd0FEQUtCZ2dxCmhrak9QUVFEQWdOSUFEQkZBaUVBcGpLd253NEpmanR6U1Z6R2RJbzlmR001MTlaMTZTdk1YWllLUkFVSlRTY0MKSUFhQ1k1MG1adTVHQ0ppczFOWnFJU2Q1MzlrbkRTYUNQYTZzWEJHaVpkYlYKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}},"7772b89eebdbc9a398c4ce1a9d6d3d524cd8c5bb6f67897f3db4601068e7e2a1":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErMLExlbq4qdggRNQhzd/GYX17SqGKyyBo4aQsto4WHsx2SRTuh034mXPy5GRqud5Vw8bMQem/PpppO9zdvm05w=="}},"a822db495e448ff983d6560f28b80c600f11ae1ff408ff5ed69e8ec9ef1ba8c4":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbWvdIa85k/icSqN4i/rqvmw3JpyTVtcFHxSJm4UREp3A7vasKw6GRBaH3/t7k7MytnASYRDlHVyTvryEq7Xpnw=="}},"e190d2a8a8a35dd7a364b0d7fadb35136acd0fe92cfda5aacc4dce17f574216a":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZ3oTV9C/qREqBd4A3+EVC3lkHWofcfJTpkk8DKJO0eLr5H5Sc6ZtbpRGbgrQ4yX4a7W7Zk1SFnb7gsRSH2FYLA=="}}},"roles":{"root":{"keyids":["18daae20502fe6a7eb4ff89acb31d06af1b271c55c94f7b9d7e1e66e624e1642"],"threshold":1},"snapshot":{"keyids":["7772b89eebdbc9a398c4ce1a9d6d3d524cd8c5bb6f67897f3db4601068e7e2a1"],"threshold":1},"targets":{"keyids":["a822db495e448ff983d6560f28b80c600f11ae1ff408ff5ed69e8ec9ef1ba8c4"],"threshold":1},"timestamp":{"keyids":["e190d2a8a8a35dd7a364b0d7fadb35136acd0fe92cfda5aacc4dce17f574216a"],"threshold":1}},"version":2},"signatures":[{"keyid":"18daae20502fe6a7eb4ff89acb31d06af1b271c55c94f7b9d7e1e66e624e1642","method":"ecdsa","sig":"vqpeAsYdEZ3cXX9D5jtR2um9FjQO2SbykKrbjrTqznsnPo6ND6hCIz+yjxICMgc1D8RBkY+mFk7pVFaQywajKQ=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/0/root.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2027-06-25T17:14:12.14321374-05:00","keys":{"32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4zLyQZdgzFkc8u47M/NGT6ANLqgb45eVDODez2wVYtPB3lrP5pFqC7qsxUgTSRJc065FuYXRFk7TOjmooYMJSg=="}},"884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJjekNDQVJtZ0F3SUJBZ0lRRHlaUDFEeDVjRGc0NEc5UjcrRkZXVEFLQmdncWhrak9QUVFEQWpBZ01SNHcKSEFZRFZRUURFeFZyYjJ4cFpHVXZaM0psWlhSbGNpOWtZWEozYVc0d0hoY05NVGN3TmpJMU1qRXdNak13V2hjTgpNamN3TmpJek1qRXdNak13V2pBZ01SNHdIQVlEVlFRREV4VnJiMnhwWkdVdlozSmxaWFJsY2k5a1lYSjNhVzR3CldUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTUy90ekF3bEJ2SS9qdjhZVm50d2JLKzRtT2ZFV1EKVG54REJxb2VTbm04dUxKQ3lBRmZMYnptc1NRaFJzazRJNUtXeGtJV1F6TUVWdkE2c3RrTjdhbDBvelV3TXpBTwpCZ05WSFE4QkFmOEVCQU1DQmFBd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdEQVlEVlIwVEFRSC9CQUl3CkFEQUtCZ2dxaGtqT1BRUURBZ05JQURCRkFpRUFuOUcxUk43U3o4VEFOTkRSR1lSUUxWaSt5RkZXYWhFWkUzK1MKTjllWXlUd0NJQVVuTXJtVzFwNDFjTDVCM0xRSWEvWDJQbjF0U1diWmxFVFNMN0tSYnZESgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="}},"9a035dab9e6401a8fccc283572249b28ef2d37be080eaa41898303d0538aef03":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER6SOeD5OPgJfxVgE049ImSrz1AjiN5RQsZAXc50t/ccqCzjYK/j9G+eAtQVkNooRR0n8xMGAeDP38KJq9UgqiA=="}},"d8dffe6221786cc12001f4d0c6e71f85fc6e6348cf8d3a4ed2dcf6c056716f04":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEa4gK9y6rqpGWrxtwG9regA8b3SDukcJYXdAt0joe6KvpUKQkh/AWxUPaagXj23wHXNLVoiWJkiHUAAZ6XkZtCg=="}}},"roles":{"root":{"keyids":["884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f"],"threshold":1},"snapshot":{"keyids":["d8dffe6221786cc12001f4d0c6e71f85fc6e6348cf8d3a4ed2dcf6c056716f04"],"threshold":1},"targets":{"keyids":["32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608"],"threshold":1},"timestamp":{"keyids":["9a035dab9e6401a8fccc283572249b28ef2d37be080eaa41898303d0538aef03"],"threshold":1}},"version":4},"signatures":[{"keyid":"884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f","method":"ecdsa","sig":"Fd6Bnf8Ao4RSXCHLuznUi1W/cOA4ovejO9ijHA4DFQOzrv4lPsv76twmnU3jZ88XJIZSe+is7YnyAK6m0u1WUg=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/1/root.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2027-06-25T17:14:12.14321374-05:00","keys":{"32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4zLyQZdgzFkc8u47M/NGT6ANLqgb45eVDODez2wVYtPB3lrP5pFqC7qsxUgTSRJc065FuYXRFk7TOjmooYMJSg=="}},"884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJjekNDQVJtZ0F3SUJBZ0lRRHlaUDFEeDVjRGc0NEc5UjcrRkZXVEFLQmdncWhrak9QUVFEQWpBZ01SNHcKSEFZRFZRUURFeFZyYjJ4cFpHVXZaM0psWlhSbGNpOWtZWEozYVc0d0hoY05NVGN3TmpJMU1qRXdNak13V2hjTgpNamN3TmpJek1qRXdNak13V2pBZ01SNHdIQVlEVlFRREV4VnJiMnhwWkdVdlozSmxaWFJsY2k5a1lYSjNhVzR3CldUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTUy90ekF3bEJ2SS9qdjhZVm50d2JLKzRtT2ZFV1EKVG54REJxb2VTbm04dUxKQ3lBRmZMYnptc1NRaFJzazRJNUtXeGtJV1F6TUVWdkE2c3RrTjdhbDBvelV3TXpBTwpCZ05WSFE4QkFmOEVCQU1DQmFBd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdEQVlEVlIwVEFRSC9CQUl3CkFEQUtCZ2dxaGtqT1BRUURBZ05JQURCRkFpRUFuOUcxUk43U3o4VEFOTkRSR1lSUUxWaSt5RkZXYWhFWkUzK1MKTjllWXlUd0NJQVVuTXJtVzFwNDFjTDVCM0xRSWEvWDJQbjF0U1diWmxFVFNMN0tSYnZESgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="}},"9a035dab9e6401a8fccc283572249b28ef2d37be080eaa41898303d0538aef03":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER6SOeD5OPgJfxVgE049ImSrz1AjiN5RQsZAXc50t/ccqCzjYK/j9G+eAtQVkNooRR0n8xMGAeDP38KJq9UgqiA=="}},"d8dffe6221786cc12001f4d0c6e71f85fc6e6348cf8d3a4ed2dcf6c056716f04":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEa4gK9y6rqpGWrxtwG9regA8b3SDukcJYXdAt0joe6KvpUKQkh/AWxUPaagXj23wHXNLVoiWJkiHUAAZ6XkZtCg=="}}},"roles":{"root":{"keyids":["884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f"],"threshold":1},"snapshot":{"keyids":["d8dffe6221786cc12001f4d0c6e71f85fc6e6348cf8d3a4ed2dcf6c056716f04"],"threshold":1},"targets":{"keyids":["32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608"],"threshold":1},"timestamp":{"keyids":["9a035dab9e6401a8fccc283572249b28ef2d37be080eaa41898303d0538aef03"],"threshold":1}},"version":4},"signatures":[{"keyid":"884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f","method":"ecdsa","sig":"Fd6Bnf8Ao4RSXCHLuznUi1W/cOA4ovejO9ijHA4DFQOzrv4lPsv76twmnU3jZ88XJIZSe+is7YnyAK6m0u1WUg=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/2/root.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2027-06-25T17:14:12.14321374-05:00","keys":{"32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4zLyQZdgzFkc8u47M/NGT6ANLqgb45eVDODez2wVYtPB3lrP5pFqC7qsxUgTSRJc065FuYXRFk7TOjmooYMJSg=="}},"884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJjekNDQVJtZ0F3SUJBZ0lRRHlaUDFEeDVjRGc0NEc5UjcrRkZXVEFLQmdncWhrak9QUVFEQWpBZ01SNHcKSEFZRFZRUURFeFZyYjJ4cFpHVXZaM0psWlhSbGNpOWtZWEozYVc0d0hoY05NVGN3TmpJMU1qRXdNak13V2hjTgpNamN3TmpJek1qRXdNak13V2pBZ01SNHdIQVlEVlFRREV4VnJiMnhwWkdVdlozSmxaWFJsY2k5a1lYSjNhVzR3CldUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTUy90ekF3bEJ2SS9qdjhZVm50d2JLKzRtT2ZFV1EKVG54REJxb2VTbm04dUxKQ3lBRmZMYnptc1NRaFJzazRJNUtXeGtJV1F6TUVWdkE2c3RrTjdhbDBvelV3TXpBTwpCZ05WSFE4QkFmOEVCQU1DQmFBd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdEQVlEVlIwVEFRSC9CQUl3CkFEQUtCZ2dxaGtqT1BRUURBZ05JQURCRkFpRUFuOUcxUk43U3o4VEFOTkRSR1lSUUxWaSt5RkZXYWhFWkUzK1MKTjllWXlUd0NJQVVuTXJtVzFwNDFjTDVCM0xRSWEvWDJQbjF0U1diWmxFVFNMN0tSYnZESgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="}},"9a035dab9e6401a8fccc283572249b28ef2d37be080eaa41898303d0538aef03":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER6SOeD5OPgJfxVgE049ImSrz1AjiN5RQsZAXc50t/ccqCzjYK/j9G+eAtQVkNooRR0n8xMGAeDP38KJq9UgqiA=="}},"d8dffe6221786cc12001f4d0c6e71f85fc6e6348cf8d3a4ed2dcf6c056716f04":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEa4gK9y6rqpGWrxtwG9regA8b3SDukcJYXdAt0joe6KvpUKQkh/AWxUPaagXj23wHXNLVoiWJkiHUAAZ6XkZtCg=="}}},"roles":{"root":{"keyids":["884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f"],"threshold":1},"snapshot":{"keyids":["d8dffe6221786cc12001f4d0c6e71f85fc6e6348cf8d3a4ed2dcf6c056716f04"],"threshold":1},"targets":{"keyids":["32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608"],"threshold":1},"timestamp":{"keyids":["9a035dab9e6401a8fccc283572249b28ef2d37be080eaa41898303d0538aef03"],"threshold":1}},"version":4},"signatures":[{"keyid":"884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f","method":"ecdsa","sig":"Fd6Bnf8Ao4RSXCHLuznUi1W/cOA4ovejO9ijHA4DFQOzrv4lPsv76twmnU3jZ88XJIZSe+is7YnyAK6m0u1WUg=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/3/root.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2027-06-25T17:14:12.14321374-05:00","keys":{"32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4zLyQZdgzFkc8u47M/NGT6ANLqgb45eVDODez2wVYtPB3lrP5pFqC7qsxUgTSRJc065FuYXRFk7TOjmooYMJSg=="}},"884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJjekNDQVJtZ0F3SUJBZ0lRRHlaUDFEeDVjRGc0NEc5UjcrRkZXVEFLQmdncWhrak9QUVFEQWpBZ01SNHcKSEFZRFZRUURFeFZyYjJ4cFpHVXZaM0psWlhSbGNpOWtZWEozYVc0d0hoY05NVGN3TmpJMU1qRXdNak13V2hjTgpNamN3TmpJek1qRXdNak13V2pBZ01SNHdIQVlEVlFRREV4VnJiMnhwWkdVdlozSmxaWFJsY2k5a1lYSjNhVzR3CldUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTUy90ekF3bEJ2SS9qdjhZVm50d2JLKzRtT2ZFV1EKVG54REJxb2VTbm04dUxKQ3lBRmZMYnptc1NRaFJzazRJNUtXeGtJV1F6TUVWdkE2c3RrTjdhbDBvelV3TXpBTwpCZ05WSFE4QkFmOEVCQU1DQmFBd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdEQVlEVlIwVEFRSC9CQUl3CkFEQUtCZ2dxaGtqT1BRUURBZ05JQURCRkFpRUFuOUcxUk43U3o4VEFOTkRSR1lSUUxWaSt5RkZXYWhFWkUzK1MKTjllWXlUd0NJQVVuTXJtVzFwNDFjTDVCM0xRSWEvWDJQbjF0U1diWmxFVFNMN0tSYnZESgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="}},"9a035dab9e6401a8fccc283572249b28ef2d37be080eaa41898303d0538aef03":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER6SOeD5OPgJfxVgE049ImSrz1AjiN5RQsZAXc50t/ccqCzjYK/j9G+eAtQVkNooRR0n8xMGAeDP38KJq9UgqiA=="}},"d8dffe6221786cc12001f4d0c6e71f85fc6e6348cf8d3a4ed2dcf6c056716f04":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEa4gK9y6rqpGWrxtwG9regA8b3SDukcJYXdAt0joe6KvpUKQkh/AWxUPaagXj23wHXNLVoiWJkiHUAAZ6XkZtCg=="}}},"roles":{"root":{"keyids":["884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f"],"threshold":1},"snapshot":{"keyids":["d8dffe6221786cc12001f4d0c6e71f85fc6e6348cf8d3a4ed2dcf6c056716f04"],"threshold":1},"targets":{"keyids":["32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608"],"threshold":1},"timestamp":{"keyids":["9a035dab9e6401a8fccc283572249b28ef2d37be080eaa41898303d0538aef03"],"threshold":1}},"version":4},"signatures":[{"keyid":"884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f","method":"ecdsa","sig":"Fd6Bnf8Ao4RSXCHLuznUi1W/cOA4ovejO9ijHA4DFQOzrv4lPsv76twmnU3jZ88XJIZSe+is7YnyAK6m0u1WUg=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/root.3.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2027-06-23T16:02:49.850174995-05:00","keys":{"32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4zLyQZdgzFkc8u47M/NGT6ANLqgb45eVDODez2wVYtPB3lrP5pFqC7qsxUgTSRJc065FuYXRFk7TOjmooYMJSg=="}},"449f919e6855f22170d86a74b3b98ec5dbde9609306fe8d92eb309e2f97a29b5":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBuZCVKlwA0rbaWuVrTaiIoOeHwK7N1Vfw8366AdWrTYRAmNDhEVr++jPg7SohSk9rbF5BVhP/dfWWeYnUIL+VQ=="}},"4c24805d80f640c7ccb37abd58ce84365b72caeb1c501a12746fc162150e0611":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEccThrTZh+ASlHe5xv+mEJSauHxtOY/yrVtQGhxC7ibIlVA/r6EuRZDzPld0WiSncaa6TQNugLKQvlaFqn/FJNA=="}},"884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJjekNDQVJtZ0F3SUJBZ0lRRHlaUDFEeDVjRGc0NEc5UjcrRkZXVEFLQmdncWhrak9QUVFEQWpBZ01SNHcKSEFZRFZRUURFeFZyYjJ4cFpHVXZaM0psWlhSbGNpOWtZWEozYVc0d0hoY05NVGN3TmpJMU1qRXdNak13V2hjTgpNamN3TmpJek1qRXdNak13V2pBZ01SNHdIQVlEVlFRREV4VnJiMnhwWkdVdlozSmxaWFJsY2k5a1lYSjNhVzR3CldUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTUy90ekF3bEJ2SS9qdjhZVm50d2JLKzRtT2ZFV1EKVG54REJxb2VTbm04dUxKQ3lBRmZMYnptc1NRaFJzazRJNUtXeGtJV1F6TUVWdkE2c3RrTjdhbDBvelV3TXpBTwpCZ05WSFE4QkFmOEVCQU1DQmFBd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdEQVlEVlIwVEFRSC9CQUl3CkFEQUtCZ2dxaGtqT1BRUURBZ05JQURCRkFpRUFuOUcxUk43U3o4VEFOTkRSR1lSUUxWaSt5RkZXYWhFWkUzK1MKTjllWXlUd0NJQVVuTXJtVzFwNDFjTDVCM0xRSWEvWDJQbjF0U1diWmxFVFNMN0tSYnZESgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="}}},"roles":{"root":{"keyids":["884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f"],"threshold":1},"snapshot":{"keyids":["449f919e6855f22170d86a74b3b98ec5dbde9609306fe8d92eb309e2f97a29b5"],"threshold":1},"targets":{"keyids":["32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608"],"threshold":1},"timestamp":{"keyids":["4c24805d80f640c7ccb37abd58ce84365b72caeb1c501a12746fc162150e0611"],"threshold":1}},"version":1},"signatures":[{"keyid":"884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f","method":"ecdsa","sig":"eQn1z0CweKnwQ/eqnSCpRi9hqv3ENlhrTk3hHoEH4WXKOT9XHHmu37Bp/8vIsEykKU0yx36WXWg1KUm1l2w+3Q=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/root.4.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2027-06-23T16:29:00.566742645-05:00","keys":{"32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4zLyQZdgzFkc8u47M/NGT6ANLqgb45eVDODez2wVYtPB3lrP5pFqC7qsxUgTSRJc065FuYXRFk7TOjmooYMJSg=="}},"449f919e6855f22170d86a74b3b98ec5dbde9609306fe8d92eb309e2f97a29b5":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBuZCVKlwA0rbaWuVrTaiIoOeHwK7N1Vfw8366AdWrTYRAmNDhEVr++jPg7SohSk9rbF5BVhP/dfWWeYnUIL+VQ=="}},"451bfbdcc37387ac0e71c7100c12d9af2cae59ed77e1b44f05dd66e5e40db0d1":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJCXztb7HrmVG7A69dfvCGG9r9HNz7lNYDttTglKcrbj5UEvm+2MblkCdh4UHGNTCqXSXNW9rnhulBi90Fmj9Cg=="}},"884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJjekNDQVJtZ0F3SUJBZ0lRRHlaUDFEeDVjRGc0NEc5UjcrRkZXVEFLQmdncWhrak9QUVFEQWpBZ01SNHcKSEFZRFZRUURFeFZyYjJ4cFpHVXZaM0psWlhSbGNpOWtZWEozYVc0d0hoY05NVGN3TmpJMU1qRXdNak13V2hjTgpNamN3TmpJek1qRXdNak13V2pBZ01SNHdIQVlEVlFRREV4VnJiMnhwWkdVdlozSmxaWFJsY2k5a1lYSjNhVzR3CldUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTUy90ekF3bEJ2SS9qdjhZVm50d2JLKzRtT2ZFV1EKVG54REJxb2VTbm04dUxKQ3lBRmZMYnptc1NRaFJzazRJNUtXeGtJV1F6TUVWdkE2c3RrTjdhbDBvelV3TXpBTwpCZ05WSFE4QkFmOEVCQU1DQmFBd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdEQVlEVlIwVEFRSC9CQUl3CkFEQUtCZ2dxaGtqT1BRUURBZ05JQURCRkFpRUFuOUcxUk43U3o4VEFOTkRSR1lSUUxWaSt5RkZXYWhFWkUzK1MKTjllWXlUd0NJQVVuTXJtVzFwNDFjTDVCM0xRSWEvWDJQbjF0U1diWmxFVFNMN0tSYnZESgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="}}},"roles":{"root":{"keyids":["884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f"],"threshold":1},"snapshot":{"keyids":["449f919e6855f22170d86a74b3b98ec5dbde9609306fe8d92eb309e2f97a29b5"],"threshold":1},"targets":{"keyids":["32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608"],"threshold":1},"timestamp":{"keyids":["451bfbdcc37387ac0e71c7100c12d9af2cae59ed77e1b44f05dd66e5e40db0d1"],"threshold":1}},"version":2},"signatures":[{"keyid":"884a2b9fbb4d6e27589087880122e06783fa13551d63c8417f89f154af06dc3f","method":"ecdsa","sig":"E+sLDq0tx0XPiT/J9sAyv9BouMPbNAhsVI30IaXkO4Ipr6RVIKXCcTFSnSAiKp72fBhG52REyBQIbZHHRdlTgA=="}]} -------------------------------------------------------------------------------- /tuf/verify.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "crypto" 5 | "crypto/ecdsa" 6 | "crypto/sha256" 7 | "crypto/x509" 8 | "encoding/pem" 9 | "math/big" 10 | 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | var errSignatureCheckFailed = errors.New("signature check failed") 15 | var errSignatureThresholdNotMet = errors.New("signature threshold not met") 16 | var errInvalidKeyType = errors.New("invalid key type") 17 | var errHashMismatch = errors.New("hash of file was not correct") 18 | 19 | type verifier interface { 20 | verify(digest []byte, key *Key, sig *Signature) error 21 | } 22 | 23 | type signingMethodECDSA struct{} 24 | 25 | func newVerifier(method signingMethod) (verifier, error) { 26 | if method == methodECDSA { 27 | return &signingMethodECDSA{}, nil 28 | } 29 | return nil, errors.Errorf("signing method %q is not supported", method) 30 | } 31 | 32 | func (sm *signingMethodECDSA) verify(signed []byte, key *Key, sig *Signature) error { 33 | var publicKey crypto.PublicKey 34 | 35 | switch key.KeyType { 36 | case keyTypeECDSAx509: 37 | rawBuff, err := key.base64Decoded() 38 | if err != nil { 39 | return errors.Wrap(err, "base 64 decoding public key") 40 | } 41 | pemCert, _ := pem.Decode(rawBuff) 42 | if pemCert == nil { 43 | return errors.New("failed to decode PEM x509 cert") 44 | } 45 | cert, err := x509.ParseCertificate(pemCert.Bytes) 46 | if err != nil { 47 | return errors.Wrap(err, "ecdsa verification") 48 | } 49 | publicKey = cert.PublicKey 50 | case keyTypeECDSA: 51 | rawBuff, err := key.base64Decoded() 52 | if err != nil { 53 | return errors.Wrap(err, "base 64 decoding public key") 54 | } 55 | publicKey, err = x509.ParsePKIXPublicKey(rawBuff) 56 | if err != nil { 57 | return errors.Wrap(err, "failed to parse public key in ecdsa verify") 58 | } 59 | default: 60 | return errInvalidKeyType 61 | } 62 | 63 | ecdsaPublicKey, ok := publicKey.(*ecdsa.PublicKey) 64 | if !ok { 65 | return errors.New("expected ecdsa public key, got something else") 66 | } 67 | expectedOctetLen := 2 * ((ecdsaPublicKey.Params().BitSize + 7) >> 3) 68 | sigBuff, err := sig.base64Decoded() 69 | if err != nil { 70 | return errors.Wrap(err, "base 64 decoding signature failed") 71 | } 72 | sigLen := len(sigBuff) 73 | if sigLen != expectedOctetLen { 74 | return errors.New("signature length is incorrect") 75 | } 76 | 77 | rBuff, sBuff := sigBuff[:sigLen/2], sigBuff[sigLen/2:] 78 | r := new(big.Int).SetBytes(rBuff) 79 | s := new(big.Int).SetBytes(sBuff) 80 | digest := sha256.Sum256(signed) 81 | if !ecdsa.Verify(ecdsaPublicKey, digest[:], r, s) { 82 | return errSignatureCheckFailed 83 | } 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /tuf/testdata/delegation/0/targets.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{"0f5622d963a18defe16b0d78e63c6645d4a51dcc679b0f3fce49513c483b4318":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJtVENDQVVDZ0F3SUJBZ0lSQUwwd2hWSWVCNVd6RkxnbGttMmI1NDh3Q2dZSUtvWkl6ajBFQXdJd0VqRVEKTUE0R0ExVUVDaE1IUVdOdFpTQkRiekFlRncweE56QTJNamd4T1RFME1qZGFGdzB4T0RBMk1qZ3hPVEUwTWpkYQpNQkl4RURBT0JnTlZCQW9UQjBGamJXVWdRMjh3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFlCkp1dExNZVZaUkJudTlrbkpmN0ljZ25WeG5OeE9PWitrdVlaTjdmdFM2OWNQdnhJN09kSzc3VmtOeHltTVNGTU4KVXRTWE43OXcwRmdxWVN3M2dPcDRvM2N3ZFRBT0JnTlZIUThCQWY4RUJBTUNBcVF3RXdZRFZSMGxCQXd3Q2dZSQpLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QTlCZ05WSFJFRU5qQTBnZzVzYjJOaGJHaHZjM1E2Ck5EUTBNNElPTVRJM0xqQXVNQzR4T2pRME5ET0NFbTV2ZEdGeWVTMXpaWEoyWlhJNk5EUTBNekFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBOFl5K2ZGRnlYOUExWVdOOU5lcDJyL21Ga3lHdEdPYlFiVXg0MlBwK3lXZ0lnQzJjRwpISm9lSlpiOGpBY2FQT2N4ME9DVWhNUkhNeHZ4ZTdZL3pIcGJseGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}},"5e46d7553a7f2a2b98dd8753a94398df6ff6e53772e08fd373f3f30533c6c067":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJtVENDQVQrZ0F3SUJBZ0lRVkNod3NUUmpoVGUwZ1V1VE9tcEc3ekFLQmdncWhrak9QUVFEQWpBU01SQXcKRGdZRFZRUUtFd2RCWTIxbElFTnZNQjRYRFRFM01EWXlPREUwTlRFeU0xb1hEVEU0TURZeU9ERTBOVEV5TTFvdwpFakVRTUE0R0ExVUVDaE1IUVdOdFpTQkRiekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSSs5CjNsQ2V2QlVHRTZQaURQNCtpQjNYVVdQcWhKOFRFNDJOa2ZRZXVuMmNNTi9sTGNwRmdBalBScWphQU5WVVN0WEgKbFdhVzVVdDBmVmV3dk5ZZWduZWpkekIxTUE0R0ExVWREd0VCL3dRRUF3SUNwREFUQmdOVkhTVUVEREFLQmdncgpCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01EMEdBMVVkRVFRMk1EU0NEbXh2WTJGc2FHOXpkRG8wCk5EUXpnZzR4TWpjdU1DNHdMakU2TkRRME00SVNibTkwWVhKNUxYTmxjblpsY2pvME5EUXpNQW9HQ0NxR1NNNDkKQkFNQ0EwZ0FNRVVDSVFDeUZFNnNmWTFSb3dOdy9GdlE5RG5VVTFvVllsZXl0dEEyUUhRWjF6WXExQUlnSE5ybgo4V2RSZW1TN0NYeFJHSGlFanpkcFQ0Z24vM2xhc2ZVMmsydk1raU09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}}},"roles":[{"keyids":["5e46d7553a7f2a2b98dd8753a94398df6ff6e53772e08fd373f3f30533c6c067"],"name":"targets/role","paths":[""],"threshold":1},{"keyids":["0f5622d963a18defe16b0d78e63c6645d4a51dcc679b0f3fce49513c483b4318"],"name":"targets/bar","paths":["edge","latest"],"threshold":1}]},"expires":"2020-06-27T14:16:29.4823129-05:00","targets":{"latest/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":14},"signatures":[{"keyid":"32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608","method":"ecdsa","sig":"0ttw7aAn/su3ELtVV3Z8p+16Dyrhsl7IBhaQCouqvj3VOnBTiZgW3uNFH/NxFndBAgrkTQghgDbAppP8fuTL0Q=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/1/targets.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{"0f5622d963a18defe16b0d78e63c6645d4a51dcc679b0f3fce49513c483b4318":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJtVENDQVVDZ0F3SUJBZ0lSQUwwd2hWSWVCNVd6RkxnbGttMmI1NDh3Q2dZSUtvWkl6ajBFQXdJd0VqRVEKTUE0R0ExVUVDaE1IUVdOdFpTQkRiekFlRncweE56QTJNamd4T1RFME1qZGFGdzB4T0RBMk1qZ3hPVEUwTWpkYQpNQkl4RURBT0JnTlZCQW9UQjBGamJXVWdRMjh3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFlCkp1dExNZVZaUkJudTlrbkpmN0ljZ25WeG5OeE9PWitrdVlaTjdmdFM2OWNQdnhJN09kSzc3VmtOeHltTVNGTU4KVXRTWE43OXcwRmdxWVN3M2dPcDRvM2N3ZFRBT0JnTlZIUThCQWY4RUJBTUNBcVF3RXdZRFZSMGxCQXd3Q2dZSQpLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QTlCZ05WSFJFRU5qQTBnZzVzYjJOaGJHaHZjM1E2Ck5EUTBNNElPTVRJM0xqQXVNQzR4T2pRME5ET0NFbTV2ZEdGeWVTMXpaWEoyWlhJNk5EUTBNekFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBOFl5K2ZGRnlYOUExWVdOOU5lcDJyL21Ga3lHdEdPYlFiVXg0MlBwK3lXZ0lnQzJjRwpISm9lSlpiOGpBY2FQT2N4ME9DVWhNUkhNeHZ4ZTdZL3pIcGJseGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}},"5e46d7553a7f2a2b98dd8753a94398df6ff6e53772e08fd373f3f30533c6c067":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJtVENDQVQrZ0F3SUJBZ0lRVkNod3NUUmpoVGUwZ1V1VE9tcEc3ekFLQmdncWhrak9QUVFEQWpBU01SQXcKRGdZRFZRUUtFd2RCWTIxbElFTnZNQjRYRFRFM01EWXlPREUwTlRFeU0xb1hEVEU0TURZeU9ERTBOVEV5TTFvdwpFakVRTUE0R0ExVUVDaE1IUVdOdFpTQkRiekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSSs5CjNsQ2V2QlVHRTZQaURQNCtpQjNYVVdQcWhKOFRFNDJOa2ZRZXVuMmNNTi9sTGNwRmdBalBScWphQU5WVVN0WEgKbFdhVzVVdDBmVmV3dk5ZZWduZWpkekIxTUE0R0ExVWREd0VCL3dRRUF3SUNwREFUQmdOVkhTVUVEREFLQmdncgpCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01EMEdBMVVkRVFRMk1EU0NEbXh2WTJGc2FHOXpkRG8wCk5EUXpnZzR4TWpjdU1DNHdMakU2TkRRME00SVNibTkwWVhKNUxYTmxjblpsY2pvME5EUXpNQW9HQ0NxR1NNNDkKQkFNQ0EwZ0FNRVVDSVFDeUZFNnNmWTFSb3dOdy9GdlE5RG5VVTFvVllsZXl0dEEyUUhRWjF6WXExQUlnSE5ybgo4V2RSZW1TN0NYeFJHSGlFanpkcFQ0Z24vM2xhc2ZVMmsydk1raU09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}}},"roles":[{"keyids":["5e46d7553a7f2a2b98dd8753a94398df6ff6e53772e08fd373f3f30533c6c067"],"name":"targets/role","paths":[""],"threshold":1},{"keyids":["0f5622d963a18defe16b0d78e63c6645d4a51dcc679b0f3fce49513c483b4318"],"name":"targets/bar","paths":["edge","latest"],"threshold":1}]},"expires":"2020-06-27T14:16:29.4823129-05:00","targets":{"latest/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":14},"signatures":[{"keyid":"32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608","method":"ecdsa","sig":"0ttw7aAn/su3ELtVV3Z8p+16Dyrhsl7IBhaQCouqvj3VOnBTiZgW3uNFH/NxFndBAgrkTQghgDbAppP8fuTL0Q=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/2/targets.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{"0f5622d963a18defe16b0d78e63c6645d4a51dcc679b0f3fce49513c483b4318":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJtVENDQVVDZ0F3SUJBZ0lSQUwwd2hWSWVCNVd6RkxnbGttMmI1NDh3Q2dZSUtvWkl6ajBFQXdJd0VqRVEKTUE0R0ExVUVDaE1IUVdOdFpTQkRiekFlRncweE56QTJNamd4T1RFME1qZGFGdzB4T0RBMk1qZ3hPVEUwTWpkYQpNQkl4RURBT0JnTlZCQW9UQjBGamJXVWdRMjh3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFlCkp1dExNZVZaUkJudTlrbkpmN0ljZ25WeG5OeE9PWitrdVlaTjdmdFM2OWNQdnhJN09kSzc3VmtOeHltTVNGTU4KVXRTWE43OXcwRmdxWVN3M2dPcDRvM2N3ZFRBT0JnTlZIUThCQWY4RUJBTUNBcVF3RXdZRFZSMGxCQXd3Q2dZSQpLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QTlCZ05WSFJFRU5qQTBnZzVzYjJOaGJHaHZjM1E2Ck5EUTBNNElPTVRJM0xqQXVNQzR4T2pRME5ET0NFbTV2ZEdGeWVTMXpaWEoyWlhJNk5EUTBNekFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBOFl5K2ZGRnlYOUExWVdOOU5lcDJyL21Ga3lHdEdPYlFiVXg0MlBwK3lXZ0lnQzJjRwpISm9lSlpiOGpBY2FQT2N4ME9DVWhNUkhNeHZ4ZTdZL3pIcGJseGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}},"5e46d7553a7f2a2b98dd8753a94398df6ff6e53772e08fd373f3f30533c6c067":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJtVENDQVQrZ0F3SUJBZ0lRVkNod3NUUmpoVGUwZ1V1VE9tcEc3ekFLQmdncWhrak9QUVFEQWpBU01SQXcKRGdZRFZRUUtFd2RCWTIxbElFTnZNQjRYRFRFM01EWXlPREUwTlRFeU0xb1hEVEU0TURZeU9ERTBOVEV5TTFvdwpFakVRTUE0R0ExVUVDaE1IUVdOdFpTQkRiekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSSs5CjNsQ2V2QlVHRTZQaURQNCtpQjNYVVdQcWhKOFRFNDJOa2ZRZXVuMmNNTi9sTGNwRmdBalBScWphQU5WVVN0WEgKbFdhVzVVdDBmVmV3dk5ZZWduZWpkekIxTUE0R0ExVWREd0VCL3dRRUF3SUNwREFUQmdOVkhTVUVEREFLQmdncgpCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01EMEdBMVVkRVFRMk1EU0NEbXh2WTJGc2FHOXpkRG8wCk5EUXpnZzR4TWpjdU1DNHdMakU2TkRRME00SVNibTkwWVhKNUxYTmxjblpsY2pvME5EUXpNQW9HQ0NxR1NNNDkKQkFNQ0EwZ0FNRVVDSVFDeUZFNnNmWTFSb3dOdy9GdlE5RG5VVTFvVllsZXl0dEEyUUhRWjF6WXExQUlnSE5ybgo4V2RSZW1TN0NYeFJHSGlFanpkcFQ0Z24vM2xhc2ZVMmsydk1raU09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}}},"roles":[{"keyids":["5e46d7553a7f2a2b98dd8753a94398df6ff6e53772e08fd373f3f30533c6c067"],"name":"targets/role","paths":[""],"threshold":1},{"keyids":["0f5622d963a18defe16b0d78e63c6645d4a51dcc679b0f3fce49513c483b4318"],"name":"targets/bar","paths":["edge","latest"],"threshold":1}]},"expires":"2020-06-27T14:16:29.4823129-05:00","targets":{"latest/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":14},"signatures":[{"keyid":"32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608","method":"ecdsa","sig":"0ttw7aAn/su3ELtVV3Z8p+16Dyrhsl7IBhaQCouqvj3VOnBTiZgW3uNFH/NxFndBAgrkTQghgDbAppP8fuTL0Q=="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/3/targets.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{"0f5622d963a18defe16b0d78e63c6645d4a51dcc679b0f3fce49513c483b4318":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJtVENDQVVDZ0F3SUJBZ0lSQUwwd2hWSWVCNVd6RkxnbGttMmI1NDh3Q2dZSUtvWkl6ajBFQXdJd0VqRVEKTUE0R0ExVUVDaE1IUVdOdFpTQkRiekFlRncweE56QTJNamd4T1RFME1qZGFGdzB4T0RBMk1qZ3hPVEUwTWpkYQpNQkl4RURBT0JnTlZCQW9UQjBGamJXVWdRMjh3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFlCkp1dExNZVZaUkJudTlrbkpmN0ljZ25WeG5OeE9PWitrdVlaTjdmdFM2OWNQdnhJN09kSzc3VmtOeHltTVNGTU4KVXRTWE43OXcwRmdxWVN3M2dPcDRvM2N3ZFRBT0JnTlZIUThCQWY4RUJBTUNBcVF3RXdZRFZSMGxCQXd3Q2dZSQpLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QTlCZ05WSFJFRU5qQTBnZzVzYjJOaGJHaHZjM1E2Ck5EUTBNNElPTVRJM0xqQXVNQzR4T2pRME5ET0NFbTV2ZEdGeWVTMXpaWEoyWlhJNk5EUTBNekFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBOFl5K2ZGRnlYOUExWVdOOU5lcDJyL21Ga3lHdEdPYlFiVXg0MlBwK3lXZ0lnQzJjRwpISm9lSlpiOGpBY2FQT2N4ME9DVWhNUkhNeHZ4ZTdZL3pIcGJseGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}},"5e46d7553a7f2a2b98dd8753a94398df6ff6e53772e08fd373f3f30533c6c067":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJtVENDQVQrZ0F3SUJBZ0lRVkNod3NUUmpoVGUwZ1V1VE9tcEc3ekFLQmdncWhrak9QUVFEQWpBU01SQXcKRGdZRFZRUUtFd2RCWTIxbElFTnZNQjRYRFRFM01EWXlPREUwTlRFeU0xb1hEVEU0TURZeU9ERTBOVEV5TTFvdwpFakVRTUE0R0ExVUVDaE1IUVdOdFpTQkRiekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSSs5CjNsQ2V2QlVHRTZQaURQNCtpQjNYVVdQcWhKOFRFNDJOa2ZRZXVuMmNNTi9sTGNwRmdBalBScWphQU5WVVN0WEgKbFdhVzVVdDBmVmV3dk5ZZWduZWpkekIxTUE0R0ExVWREd0VCL3dRRUF3SUNwREFUQmdOVkhTVUVEREFLQmdncgpCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01EMEdBMVVkRVFRMk1EU0NEbXh2WTJGc2FHOXpkRG8wCk5EUXpnZzR4TWpjdU1DNHdMakU2TkRRME00SVNibTkwWVhKNUxYTmxjblpsY2pvME5EUXpNQW9HQ0NxR1NNNDkKQkFNQ0EwZ0FNRVVDSVFDeUZFNnNmWTFSb3dOdy9GdlE5RG5VVTFvVllsZXl0dEEyUUhRWjF6WXExQUlnSE5ybgo4V2RSZW1TN0NYeFJHSGlFanpkcFQ0Z24vM2xhc2ZVMmsydk1raU09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}}},"roles":[{"keyids":["5e46d7553a7f2a2b98dd8753a94398df6ff6e53772e08fd373f3f30533c6c067"],"name":"targets/role","paths":[""],"threshold":1},{"keyids":["0f5622d963a18defe16b0d78e63c6645d4a51dcc679b0f3fce49513c483b4318"],"name":"targets/bar","paths":["edge","latest"],"threshold":1}]},"expires":"2020-06-27T14:16:29.4823129-05:00","targets":{"latest/target":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":14},"signatures":[{"keyid":"32f9bcae11fe7035a0cd8674e1ffb09469c295b2a12165d48155cb9b7601b608","method":"ecdsa","sig":"0ttw7aAn/su3ELtVV3Z8p+16Dyrhsl7IBhaQCouqvj3VOnBTiZgW3uNFH/NxFndBAgrkTQghgDbAppP8fuTL0Q=="}]} -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/target.1.1: -------------------------------------------------------------------------------- 1 | 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 43 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/target.1.2: -------------------------------------------------------------------------------- 1 | 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 43 | -------------------------------------------------------------------------------- /tuf/testdata/data/root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signed": { 3 | "_type": "Root", 4 | "consistent_snapshot": false, 5 | "expires": "2027-06-10T13:25:45.170347322-05:00", 6 | "keys": { 7 | "1b52d9751b119e2567dcc3ad68a8f99ccff2ba727d354c74173338133aeb3f87": { 8 | "keytype": "ecdsa", 9 | "keyval": { 10 | "private": null, 11 | "public": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE87bSeMJ3dJUnBp4ZXbDuF3ldAr7otctNjzU5cZRNynGuWcNWQXe+OPf9Oivjas3xzeMfB+ABEWgbP0HRR4heYg==" 12 | } 13 | }, 14 | "cf5d1ca7177c947066404459dcdbfdfed1b684e7cd00d89ed7e513f108df3982": { 15 | "keytype": "ecdsa", 16 | "keyval": { 17 | "private": null, 18 | "public": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUhpEsCjxu5V/srt3x375DJdp5v35JqjwCiWeUpEUDUBrI+gqFLqbX/4xzcBGthQiWvN+PRH+iHkCbcnvvhRTqw==" 19 | } 20 | }, 21 | "d24c36bfeb612b6900df04a71a4a8e5a3c9847d3acbc2d27a3ef820895e5d42c": { 22 | "keytype": "ecdsa", 23 | "keyval": { 24 | "private": null, 25 | "public": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyEXNXnkFvROWFtkIPtaGSCA9ruUTQ0B85KPQM9xqusaCYoFNVyrN8xIzrZX4f5xKWtmfJwmyI+l2xjRxiZDLWw==" 26 | } 27 | }, 28 | "db897a1fb0c62fb8e8a43c5fdd9fd5fbe2c1581b675046a64ff1138902ecdcd7": { 29 | "keytype": "ecdsa-x509", 30 | "keyval": { 31 | "private": null, 32 | "public": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJZakNDQVFlZ0F3SUJBZ0lRVlB6TGR2SkZPdkZuTldYM2doNjRnVEFLQmdncWhrak9QUVFEQWpBWE1SVXcKRXdZRFZRUURFd3hyYjJ4cFpHVXZZV2RsYm5Rd0hoY05NVGN3TmpFeU1UZ3lOVEkxV2hjTk1qY3dOakV3TVRneQpOVEkxV2pBWE1SVXdFd1lEVlFRREV3eHJiMnhwWkdVdllXZGxiblF3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPClBRTUJCd05DQUFRRmI0Ly85UG9sTHBzaXRzRVlTTDV2MWRQWVY4dlp0V1JCc1QxanQ5RkE0UHhEMWNYWjNMNDAKOU5kcC9jSG1hSnVSWW1reE4zaHhYVHFRM1JUOEVYV3FvelV3TXpBT0JnTlZIUThCQWY4RUJBTUNCYUF3RXdZRApWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdEQVlEVlIwVEFRSC9CQUl3QURBS0JnZ3Foa2pPUFFRREFnTkpBREJHCkFpRUEyVGdLMEhHc2V1MFhPQkd4YnpyU3h3N1RWY2xnL0pQUlZJL0R6NHhNNGxrQ0lRQ0doczNUMzR5TElnZjYKNkdqSlBXT0ZUTUtJc2toYXVOQkRsU3JjSVVYMVZBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" 33 | } 34 | } 35 | }, 36 | "roles": { 37 | "root": { 38 | "keyids": [ 39 | "db897a1fb0c62fb8e8a43c5fdd9fd5fbe2c1581b675046a64ff1138902ecdcd7" 40 | ], 41 | "threshold": 1 42 | }, 43 | "snapshot": { 44 | "keyids": [ 45 | "cf5d1ca7177c947066404459dcdbfdfed1b684e7cd00d89ed7e513f108df3982" 46 | ], 47 | "threshold": 1 48 | }, 49 | "targets": { 50 | "keyids": [ 51 | "d24c36bfeb612b6900df04a71a4a8e5a3c9847d3acbc2d27a3ef820895e5d42c" 52 | ], 53 | "threshold": 1 54 | }, 55 | "timestamp": { 56 | "keyids": [ 57 | "1b52d9751b119e2567dcc3ad68a8f99ccff2ba727d354c74173338133aeb3f87" 58 | ], 59 | "threshold": 1 60 | } 61 | }, 62 | "version": 1 63 | }, 64 | "signatures": [ 65 | { 66 | "keyid": "db897a1fb0c62fb8e8a43c5fdd9fd5fbe2c1581b675046a64ff1138902ecdcd7", 67 | "method": "ecdsa", 68 | "sig": "9ibr3RQubULaF8maMuFbxX3s6dhlOzC7f8lgQ5m9YZpsFBwKLdrmT4Gm96cFQSMml0FkKXGHgabRGA0efsroXA==" 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /tuf/testdata/kolide/agent/linux/verify/root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signed": { 3 | "_type": "Root", 4 | "consistent_snapshot": false, 5 | "expires": "2027-06-14T16:19:05.86333442-05:00", 6 | "keys": { 7 | "84b02b3bb67b6cee2243c8b6b53651d832c14feb9d1f6135b255d18a285a49c2": { 8 | "keytype": "ecdsa-x509", 9 | "keyval": { 10 | "private": null, 11 | "public": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJiekNDQVJTZ0F3SUJBZ0lSQVBGNHYySmV4VkJmYmgzMzFpaTFwTTB3Q2dZSUtvWkl6ajBFQXdJd0hURWIKTUJrR0ExVUVBeE1TYTI5c2FXUmxMMkZuWlc1MEwyeHBiblY0TUI0WERURTNNRFl4TmpJeE1UZzBOVm9YRFRJMwpNRFl4TkRJeE1UZzBOVm93SFRFYk1Ca0dBMVVFQXhNU2EyOXNhV1JsTDJGblpXNTBMMnhwYm5WNE1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVCVytQLy9UNkpTNmJJcmJCR0VpK2I5WFQyRmZMMmJWa1FiRTkKWTdmUlFPRDhROVhGMmR5K05QVFhhZjNCNW1pYmtXSnBNVGQ0Y1YwNmtOMFUvQkYxcXFNMU1ETXdEZ1lEVlIwUApBUUgvQkFRREFnV2dNQk1HQTFVZEpRUU1NQW9HQ0NzR0FRVUZCd01ETUF3R0ExVWRFd0VCL3dRQ01BQXdDZ1lJCktvWkl6ajBFQXdJRFNRQXdSZ0loQU5xOXZndTQ1UFhXQ3hhWmRvSkhjN05sclAvVm5yUVRjbWRqVkFVRVF4WG4KQWlFQWpIeEN6RjlqVlZuUWNGUTExYU5acndVVEFTeEJVN0tHN3lDZzNzZmowQXc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" 12 | } 13 | }, 14 | "bacd525eac2e9495f10d9543917f3a078ffbb2a982f4cd59f1c825f9dfec7e13": { 15 | "keytype": "ecdsa", 16 | "keyval": { 17 | "private": null, 18 | "public": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7i7c7Fxfwg37EIrFDZlJ29zxQEsKXP+a5iP4Dui2J+CJzk7Ssd+1DXLZljuUb9fRmica/FLXdPSGhxzPwgyP8A==" 19 | } 20 | }, 21 | "cae7c918e084b954430deefd8d25acea34c97b58b1c16ce550e9014aa82b3a3a": { 22 | "keytype": "ecdsa", 23 | "keyval": { 24 | "private": null, 25 | "public": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEI+CjPK5PZwwzuzbN14VadPY32yHkLm/Ssizt/xvEm/G6xq6L4MTJl3CgVMKulFkE908lydSgKPUP8yaXObfTbg==" 26 | } 27 | }, 28 | "df9c78125f2fa08052253850f62930e3ca66dfc789ea77d6b04405b10289dd6e": { 29 | "keytype": "ecdsa", 30 | "keyval": { 31 | "private": null, 32 | "public": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtTZDUH5sixrP10MxBSjdTn9IPFgcGWxnLFs5JW068kjoYeDJrQjVMdyGsd59PSNuJGrZmKjGKxI44Mr90+cJyw==" 33 | } 34 | } 35 | }, 36 | "roles": { 37 | "root": { 38 | "keyids": [ 39 | "84b02b3bb67b6cee2243c8b6b53651d832c14feb9d1f6135b255d18a285a49c2" 40 | ], 41 | "threshold": 1 42 | }, 43 | "snapshot": { 44 | "keyids": [ 45 | "df9c78125f2fa08052253850f62930e3ca66dfc789ea77d6b04405b10289dd6e" 46 | ], 47 | "threshold": 1 48 | }, 49 | "targets": { 50 | "keyids": [ 51 | "cae7c918e084b954430deefd8d25acea34c97b58b1c16ce550e9014aa82b3a3a" 52 | ], 53 | "threshold": 1 54 | }, 55 | "timestamp": { 56 | "keyids": [ 57 | "bacd525eac2e9495f10d9543917f3a078ffbb2a982f4cd59f1c825f9dfec7e13" 58 | ], 59 | "threshold": 1 60 | } 61 | }, 62 | "version": 1 63 | }, 64 | "signatures": [ 65 | { 66 | "keyid": "84b02b3bb67b6cee2243c8b6b53651d832c14feb9d1f6135b255d18a285a49c2", 67 | "method": "ecdsa", 68 | "sig": "cqejcJEvs2gOcU984J0rWlhnEs5gx2m2U35tfUzWjAtQawUJwyZF/NPHEnWu7bPrxaEV11gyawLSlOgB9ZNPRA==" 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /tuf/verify_test.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | // ECDSA with x5009ECDSA public key 13 | func TestECDSAx509Verify(t *testing.T) { 14 | buff := testAsset(t, "testdata/data/root.json") 15 | var root Root 16 | err := json.NewDecoder(bytes.NewBuffer(buff)).Decode(&root) 17 | require.Nil(t, err) 18 | signed, err := root.Signed.canonicalJSON() 19 | require.Nil(t, err) 20 | require.Len(t, root.Signatures, 1) 21 | sig := root.Signatures[0] 22 | verifier, err := newVerifier(sig.SigningMethod) 23 | require.Nil(t, err) 24 | require.NotNil(t, verifier) 25 | require.IsType(t, &signingMethodECDSA{}, verifier) 26 | key, ok := root.Signed.Keys[sig.KeyID] 27 | require.True(t, ok) 28 | err = verifier.verify(signed, &key, &sig) 29 | assert.Nil(t, err) 30 | } 31 | 32 | // ECDSA with x509ECDSA public key 33 | func TestECDSAx509VerifyTampered(t *testing.T) { 34 | buff := testAsset(t, "testdata/data/root.json") 35 | var root Root 36 | err := json.NewDecoder(bytes.NewBuffer(buff)).Decode(&root) 37 | require.Nil(t, err) 38 | // tamper with object 39 | role, ok := root.Signed.Roles[roleTimestamp] 40 | require.True(t, ok) 41 | role.Threshold = 0 42 | root.Signed.Roles[roleTimestamp] = role 43 | signed, err := root.Signed.canonicalJSON() 44 | require.Nil(t, err) 45 | require.Len(t, root.Signatures, 1) 46 | sig := root.Signatures[0] 47 | verifier, err := newVerifier(sig.SigningMethod) 48 | require.Nil(t, err) 49 | require.NotNil(t, verifier) 50 | require.IsType(t, &signingMethodECDSA{}, verifier) 51 | key, ok := root.Signed.Keys[sig.KeyID] 52 | require.True(t, ok) 53 | err = verifier.verify(signed, &key, &sig) 54 | require.NotNil(t, err) 55 | assert.Equal(t, errSignatureCheckFailed, err) 56 | } 57 | 58 | func TestECDSAVerify(t *testing.T) { 59 | buff := testAsset(t, "testdata/data/targets.json") 60 | var targ Targets 61 | err := json.NewDecoder(bytes.NewBuffer(buff)).Decode(&targ) 62 | require.Nil(t, err) 63 | 64 | buff = testAsset(t, "testdata/data/root.json") 65 | var root Root 66 | err = json.NewDecoder(bytes.NewBuffer(buff)).Decode(&root) 67 | require.Nil(t, err) 68 | 69 | signed, err := targ.Signed.canonicalJSON() 70 | require.Nil(t, err) 71 | require.Len(t, targ.Signatures, 1) 72 | sig := targ.Signatures[0] 73 | verifier, err := newVerifier(sig.SigningMethod) 74 | require.Nil(t, err) 75 | require.NotNil(t, verifier) 76 | require.IsType(t, &signingMethodECDSA{}, verifier) 77 | key, ok := root.Signed.Keys[sig.KeyID] 78 | require.True(t, ok) 79 | err = verifier.verify(signed, &key, &sig) 80 | require.Nil(t, err) 81 | 82 | // test invalid key type 83 | key.KeyType = keyTypeRSAx509 84 | err = verifier.verify(signed, &key, &sig) 85 | require.NotNil(t, err) 86 | assert.Equal(t, errInvalidKeyType, err) 87 | } 88 | 89 | func TestHashTesters(t *testing.T) { 90 | buff := testAsset(t, "testdata/kolide/agent/linux/verify/timestamp.json") 91 | var ts Timestamp 92 | err := json.NewDecoder(bytes.NewBuffer(buff)).Decode(&ts) 93 | require.Nil(t, err) 94 | 95 | snapshot := testAsset(t, "testdata/kolide/agent/linux/verify/snapshot.json") 96 | 97 | ssMeta, ok := ts.Signed.Meta[roleSnapshot] 98 | require.True(t, ok) 99 | 100 | for algo, expected := range ssMeta.Hashes { 101 | t.Run(string(algo), func(t *testing.T) { 102 | hi, err := newHashInfo(algo, []byte(expected)) 103 | require.Nil(t, err) 104 | require.NotNil(t, hi) 105 | assert.Implements(t, (*tester)(nil), hi) 106 | assert.Nil(t, hi.test(snapshot)) 107 | }) 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Filing issues 2 | 3 | When filing an issue, make sure to answer these five questions: 4 | 5 | 1. What version of the updater library are you using? 6 | 2. What operating system are you using? 7 | 3. What did you do? 8 | 4. What did you expect to happen? 9 | 5. What happened instead? 10 | 11 | Sensitive security-related issues should be reported to 12 | [security@kolide.co](mailto:security@kolide.co) before a public issue is made. 13 | 14 | ## Code of Conduct 15 | 16 | ### Our Pledge 17 | 18 | In the interest of fostering an open and welcoming environment, we as 19 | contributors and maintainers pledge to making participation in our project and 20 | our community a harassment-free experience for everyone, regardless of age, body 21 | size, disability, ethnicity, gender identity and expression, level of 22 | experience, nationality, personal appearance, race, religion, or sexual identity 23 | and orientation. 24 | 25 | ### Our Standards 26 | 27 | Examples of behavior that contributes to creating a positive environment 28 | include: 29 | 30 | - Using welcoming and inclusive language 31 | - Being respectful of differing viewpoints and experiences 32 | - Gracefully accepting constructive criticism 33 | - Focusing on what is best for the community 34 | - Showing empathy towards other community members 35 | 36 | Examples of unacceptable behavior by participants include: 37 | 38 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 39 | - Trolling, insulting/derogatory comments, and personal or political attacks 40 | - Public or private harassment 41 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 42 | - Other conduct which could reasonably be considered inappropriate in a professional setting 43 | 44 | ### Our Responsibilities 45 | 46 | Project maintainers are responsible for clarifying the standards of acceptable 47 | behavior and are expected to take appropriate and fair corrective action in 48 | response to any instances of unacceptable behavior. 49 | 50 | Project maintainers have the right and responsibility to remove, edit, or reject 51 | comments, commits, code, wiki edits, issues, and other contributions that are 52 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 53 | contributor for other behaviors that they deem inappropriate, threatening, 54 | offensive, or harmful. 55 | 56 | ### Scope 57 | 58 | This Code of Conduct applies both within project spaces and in public spaces 59 | when an individual is representing the project or its community. Examples of 60 | representing a project or community include using an official project e-mail 61 | address, posting via an official social media account, or acting as an appointed 62 | representative at an online or offline event. Representation of a project may be 63 | further defined and clarified by project maintainers. 64 | 65 | ### Enforcement 66 | 67 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 68 | reported by contacting the project team at hello@kolide.co. All complaints will 69 | be reviewed and investigated and will result in a response that is deemed 70 | necessary and appropriate to the circumstances. The project team is obligated to 71 | maintain confidentiality with regard to the reporter of an incident. Further 72 | details of specific enforcement policies may be posted separately. 73 | 74 | Project maintainers who do not follow or enforce the Code of Conduct in good 75 | faith may face temporary or permanent repercussions as determined by other 76 | members of the project's leadership. 77 | 78 | ### Attribution 79 | 80 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 81 | available at http://contributor-covenant.org/version/1/4. 82 | -------------------------------------------------------------------------------- /tuf/testdata/data/targets.json: -------------------------------------------------------------------------------- 1 | { 2 | "signed": { 3 | "_type": "Targets", 4 | "delegations": { 5 | "keys": { 6 | "894776e7a27799cd2e1f18f988360bd65b75d07488e16009db92102b7ef9b458": { 7 | "keytype": "rsa-x509", 8 | "keyval": { 9 | "private": null, 10 | "public": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZuVENDQTRXZ0F3SUJBZ0lKQUpUUk1pdjB6VHVJTUEwR0NTcUdTSWIzRFFFQkRRVUFNRDB4Q3pBSkJnTlYKQkFZVEFsVlRNUXN3Q1FZRFZRUUlFd0pKUVRFaE1COEdBMVVFQ2hNWVNXNTBaWEp1WlhRZ1YybGtaMmwwY3lCUQpkSGtnVEhSa01CNFhEVEUzTURZeE1qSXdNemN6TVZvWERUSTFNVEV3TWpJd016Y3pNVm93UFRFTE1Ba0dBMVVFCkJoTUNWVk14Q3pBSkJnTlZCQWdUQWtsQk1TRXdId1lEVlFRS0V4aEpiblJsY201bGRDQlhhV1JuYVhSeklGQjAKZVNCTWRHUXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDRHdBd2dnSUtBb0lDQVFDd3czcFNPK2JrdjlzWQpwQUdpaDBlWER3SXJNYk1iQVJFOXBLRC9GclFIMDJJS2RxeCtINkxUM2hPakx0UzdSbkNkQXN1TkhLT0V0R0liCjdwOUdnWGEyalFkWDFBVVRuLzM0RzZxcm5ibHk0S0czQjBGd054dHNJdHVRS2t0RGllVWVKcDhwMkI0WVQzalcKU3VIQTJRQ0tud3U3bHVTR2ttOFpnaVlqNlF5REs3ZlJiUTJ3RGNsNzQyZzdjVnMxUWs1d1dRcHk1VHpxbnlLNQpjbFJiZkxzZVFlWVF3dE5pYzZSTXVmNWtVRWN2U0d6L21QMG5WczJPNWIwNVkwbVZEUkZHbXVxMUxvSHo5QWhDCjlyTk80bm10N3JtejNrVWxSZjNPelJHZEhoMWdCYXlaUHAwZTBiaXJwclVVNC9CcVFBbENnOVc1TnVmcjVKUTUKYUdqWlVpVTlRbHFaNThLcElqc1pYUVJpcjF0WXhHWHVrSkErQjJTeDRLR2kxU2RFaGxPREZWSFNFNW9VTUUrQwpOeHRxbmcyTE4rMEx5bFdtZ29UYWgrOFlxUTlSNEZJMDZkTnhjZTBXNUxEWnZ6UXQ0NzhSNEE1Y3BJd0c2SXk4CjVkUzB0SjdHTFlmbDdFdkloWmI5WUhDdFB4Z0FLbDBjbS9XK3VkVU9veVZreFdCcTFobzRwNlQzbENyRE0zL0EKSEsvY3BSMzlQVGVvL0FBQ3E0OXVFbEpwL1FDMG1XS1dxWU5WRWVtNW5MbUdwTW9iQmx6cDJkQjMyT3FzNzRmVgpCUVMxbjF6Ri9WYTRhdThyUDMrNkpVc0JYeUZEbUxycGVURllIcDFVOWIxeFJseVVvcy9LR2NpVkVlSHg2N1FlCjBuVVNFUTczZHNzazBzZ2hTek8vWWR0dEF6c05jUUlEQVFBQm80R2ZNSUdjTUIwR0ExVWREZ1FXQkJSMy9CbmUKTkIwK0R3Lytjcjk2czJWSTVYY1lwakJ0QmdOVkhTTUVaakJrZ0JSMy9CbmVOQjArRHcvK2NyOTZzMlZJNVhjWQpwcUZCcEQ4d1BURUxNQWtHQTFVRUJoTUNWVk14Q3pBSkJnTlZCQWdUQWtsQk1TRXdId1lEVlFRS0V4aEpiblJsCmNtNWxkQ0JYYVdSbmFYUnpJRkIwZVNCTWRHU0NDUUNVMFRJcjlNMDdpREFNQmdOVkhSTUVCVEFEQVFIL01BMEcKQ1NxR1NJYjNEUUVCRFFVQUE0SUNBUUFrWEorc0w2UVVhOThvYU84a3EyRnBjc21jNzFZdXlmSklCVi9UL1V5NApWbGJyQW93NDNDaElxZWJMU0NncUpiL1R0ZURWT01WYjVnQVdZZUR3aHMrUUE1aHppNDBuZWxtSSsyOW9ncTZGCmtYUmRKZnNZNzFmR2RNQmU5akZYQlM5aDFWZExic2E2b1VqYU5QN21nWHM1bGJkcXZZSzBPR3NXSlhzTzBCVEMKaEV1cURzNXl1Y09GbXN0WFEyZEY3K2c4VzJGczV2VU0xUE12UHhWSXJycUc0MzZxbnBFVjBkdUx2YldSb2F0bwoyTlpmckk3YUlIcXEvVHR6bktLd2hka2c1Q01Ram01VC9pcVJ4SnFxSHN5MysvUi94QVp2R3YrMmYrN0lHWFRvCmw0NTdZRGNvTUxYemZCU1dqQVE3RVZZYXVLejF4M1hJb0NiQ3JualprOWoydUdhUWdmOGhKdzVPRDhxN2Fpd1QKMTN5Y0I1RFF4WDUvckhzUk1iTGo3QlBncVpPWU1Lb2twYi9EZzRIbkQ3M014UlhtaVNHMVBYZU9zMU1nT3BxOQpvVkgycVZPQmFjano4Z3VBaGN6bjFldjU0bWRWeXNjMzdUa1FZNTdrMytEb0l2RDQ3bG5zSXJQWkJPOHI2VDNaClJ2ajVNY1ZxckxBUGJ1SjZqelJIcW0veE5hMVkwS3BRdkJoQ2orejBiZXdoWXlHS2JvbFpzK0Nqc2lTOXFYTSsKb29jdFhaeDhSdk5UQjVkMlZ0WU5TaTc5OUxYT1YxMnp2Z1A3K1J4bXhTSXNvNncxQi9hT3VEczM0V3NFNm5PYgo3eFBHSzF1N0JPeTJTNFRiZDdlcE9qMjVydmNoajJhRnpCNkNKa3k0ZVludUxORHNFYVAyQncxc3VBeldjajlpCnNRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" 11 | } 12 | } 13 | }, 14 | "roles": [ 15 | { 16 | "keyids": [ 17 | "894776e7a27799cd2e1f18f988360bd65b75d07488e16009db92102b7ef9b458" 18 | ], 19 | "name": "targets/releases", 20 | "paths": [ 21 | "tools" 22 | ], 23 | "threshold": 1 24 | } 25 | ] 26 | }, 27 | "expires": "2020-06-11T16:02:16.180597846-05:00", 28 | "targets": { 29 | "1.0.0": { 30 | "hashes": { 31 | "sha256": "xdD9jvFLoCYvTNYiyDMyX054paEjI88NddSVAv8fZXI=", 32 | "sha512": "A59YwEN9Kc89vZXNWiUxRs+xl9IavixIkY59BykIa3bkj+gZsg832zTm/zxOLi39sR3Q5etxc5qTK97ZMeuSbQ==" 33 | }, 34 | "length": 3453 35 | }, 36 | "1.0.1": { 37 | "hashes": { 38 | "sha256": "xdD9jvFLoCYvTNYiyDMyX054paEjI88NddSVAv8fZXI=", 39 | "sha512": "A59YwEN9Kc89vZXNWiUxRs+xl9IavixIkY59BykIa3bkj+gZsg832zTm/zxOLi39sR3Q5etxc5qTK97ZMeuSbQ==" 40 | }, 41 | "length": 3453 42 | } 43 | }, 44 | "version": 5 45 | }, 46 | "signatures": [ 47 | { 48 | "keyid": "d24c36bfeb612b6900df04a71a4a8e5a3c9847d3acbc2d27a3ef820895e5d42c", 49 | "method": "ecdsa", 50 | "sig": "XZQ3BgSgdKzdpKd8sDhrfm/PvD4zof2vQNly9/16hgGCU5X882t7Hq5OcdX/Ov6zVKxC0J2LpM+vMIx4HSjGjA==" 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /example/cmd/README.md: -------------------------------------------------------------------------------- 1 | # Updater Example Application 2 | 3 | This example demonstrates a simple application that uses Updater. 4 | 5 | ### Set Up 6 | 7 | 1. Create a directory to hold the local TUF role files. For example in `example/cmd` 8 | type `mkdir repo`. 9 | 2. Create a staging directory where the Updater will place the validated files 10 | that it downloads and validates. `mkdir staging `. 11 | 3. Set up Notary 12 | - Clone a local copy of Notary `git clone ssh://git@github.com/docker/notary` 13 | - cd to the Notary project root. 14 | - Start up a local Notary server and copy the config file and testing 15 | certificates to your local Notary directory. 16 | ``` 17 | $ docker-compose build 18 | $ docker-compose up 19 | $ mkdir -p ~/.notary && cp cmd/notary/config.json cmd/notary/root-ca.crt ~/.notary 20 | ``` 21 | - Add `127.0.0.1 notary-server` to your `/etc/hosts` file. 22 | 23 | 4. Create a Notary repository, using a descriptive GUN (Globally Unique Identifier) 24 | ``` 25 | $ notary init kolide/greeter/darwin 26 | ``` 27 | Notary will prompt you to create several passwords for the keys it produces. By convention the target name is of the form `/` 28 | 29 | 5. Add a target to your new repository. The `-p` flag will cause the added 30 | target to be published immediately. 31 | ``` 32 | $ notary add kolide/greeter/darwin latest/target myfile.tgz -p 33 | ``` 34 | 35 | 6. Upload the files you added to notary to the mirror. In a production environment this would be something like a public s3 bucket or static file server. 36 | In this example, you just have to add it to the `filerepo/$GUN` folder. The example will server the path on port 8888. 37 | ``` 38 | ├── README.md 39 | ├── filerepo 40 | │   └── kolide 41 | │   └── greeter 42 | │   └── darwin 43 | │   └── latest 44 | │   └── target 45 | ``` 46 | 47 | *Important! Do not modify the target file in any way before uploading it to the mirror.* 48 | Updater expects to find targets hosted on the mirror at a URL of the form 49 | 50 | 7. Create your local repository. cd to the `repo` directory you defined in 51 | step 1 and run the following curl commands to get the necessary files from Notary. 52 | 53 | Using the provided script: 54 | 55 | ```Go 56 | go run main.go -bootstrap 57 | ``` 58 | 59 | or manually with curl: 60 | 61 | ``` bash 62 | $ curl -k https://notary-server:4443/v2/kolide/greeter/darwin/_trust/tuf/root.json > root.json 63 | $ curl -k https://notary-server:4443/v2/kolide/greeter/darwin/_trust/tuf/snapshot.json > snapshot.json 64 | $ curl -k https://notary-server:4443/v2/kolide/greeter/darwin/_trust/tuf/timestamp.json > timestamp.json 65 | $ curl -k https://notary-server:4443/v2/kolide/greeter/darwin/_trust/tuf/targets.json > targets.json 66 | ``` 67 | 8. Define your settings in the example program. 68 | 69 | ```Go 70 | settings := updater.Settings{ 71 | LocalRepoPath: filepath.Join(baseDir, "repo"), 72 | NotaryURL: "https://notary-server:4443", 73 | StagingPath: filepath.Join(baseDir, "staging"), 74 | GUN: "kolide/greeter/darwin", 75 | TargetName: "latest/target", 76 | InsecureSkipVerify: true, 77 | MirrorURL: "https://storage.googleapis.com/kolide_test_mirror", 78 | } 79 | ``` 80 | 9. Run it! Note in this example the repo and staging directory are located relative 81 | to the current working directory. 82 | ``` 83 | $ go run main.go 84 | ``` 85 | 10. If you want to see the example program handle an update add another target. The easy 86 | way to do this is just re-add the file in Step 5. Even though you are adding the same 87 | file, Notary will detect the the timestamp has changed and trigger and update. 88 | 89 | 90 | ### Full usage 91 | 92 | ``` 93 | Usage of ./example: 94 | -base-directory string 95 | the directory where all the things are (default "./") 96 | -bootstrap 97 | set up local repository for the GUN from the local notary-server 98 | -filerepo string 99 | path to file repo which will serve static assets (default "filerepo") 100 | -gun string 101 | the globally unique identifier (default "kolide/greeter/darwin") 102 | -server-certificates string 103 | path to folder with server certs. must be named cert.pem and key.pem respectively (default "../../test/server") 104 | ``` 105 | -------------------------------------------------------------------------------- /example/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "os" 11 | "path" 12 | "path/filepath" 13 | 14 | "github.com/kolide/updater/tuf" 15 | ) 16 | 17 | func main() { 18 | var ( 19 | baseDir = flag.String("base-directory", "./", "the directory where all the things are") 20 | flRepo = flag.String("filerepo", "filerepo", "path to file repo which will serve static assets") 21 | flServerCerts = flag.String("server-certificates", "../../test/server", "path to folder with server certs. must be named cert.pem and key.pem respectively") 22 | flGUN = flag.String("gun", "kolide/greeter/darwin", "the globally unique identifier") 23 | flBootstrap = flag.Bool("bootstrap", false, "set up local repository for the GUN from the local notary-server") 24 | flDownoad = flag.String("download", "", "download a specific target") 25 | ) 26 | flag.Parse() 27 | 28 | settings := tuf.Settings{ 29 | LocalRepoPath: filepath.Join(*baseDir, "repo"), 30 | NotaryURL: "https://notary-server:4443", 31 | GUN: *flGUN, 32 | MirrorURL: "https://localhost:8888/repo", 33 | } 34 | 35 | if *flBootstrap { 36 | // download all the necessary JSON files from the notary server into the "repo" directory. 37 | roles := []string{"root.json", "snapshot.json", "timestamp.json", "targets.json"} 38 | repoPath := filepath.Join(*baseDir, "repo") 39 | os.MkdirAll(repoPath, 0755) 40 | client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}} 41 | for _, role := range roles { 42 | urlstring := settings.NotaryURL + path.Join("/v2/", settings.GUN, "_trust/tuf", role) 43 | resp, err := client.Get(urlstring) 44 | if err != nil { 45 | log.Fatalf("could not download %s: %s\n", urlstring, err) 46 | } 47 | defer resp.Body.Close() 48 | data, err := ioutil.ReadAll(resp.Body) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | roleFile := filepath.Join(repoPath, role) 53 | if err := ioutil.WriteFile(roleFile, data, 0644); err != nil { 54 | log.Fatalf("could not save %s to %s: %s\n", urlstring, repoPath, err) 55 | } 56 | fmt.Printf("saved %s to %s\n", urlstring, roleFile) 57 | } 58 | os.Exit(0) 59 | } 60 | // Callback function that will be invoked when an update is triggered. The 61 | // stagingDir argument will be the location of the file downloaded from a mirror. 62 | // It is up to the application to perform whatever subsequent actions need to take 63 | // place. For example the application might install and restart itself. 64 | updateHandler := func(stagingDir string, err error) { 65 | if err != nil { 66 | fmt.Printf("error: %q\n", err) 67 | return 68 | } 69 | // Do app specific stuff here. 70 | fmt.Printf("success: %q\n", stagingDir) 71 | } 72 | 73 | StagingPath := filepath.Join(*baseDir, "staging") 74 | TargetName := "latest/target" 75 | update, err := tuf.NewClient( 76 | &settings, 77 | tuf.WithAutoUpdate(TargetName, StagingPath, updateHandler), 78 | tuf.WithHTTPClient(insecureClient()), 79 | ) 80 | 81 | if err != nil { 82 | fmt.Printf("could not create updater: %q", err) 83 | os.Exit(1) 84 | } 85 | 86 | defer update.Stop() 87 | 88 | // serve the static files from a local mirror 89 | go func() { 90 | http.Handle("/", staticStaticRepo("/repo/", *flRepo)) 91 | cert, _ := filepath.Abs(filepath.Join(*flServerCerts, "cert.pem")) 92 | key, _ := filepath.Abs(filepath.Join(*flServerCerts, "key.pem")) 93 | log.Fatal(http.ListenAndServeTLS(":8888", cert, key, nil)) 94 | }() 95 | 96 | if *flDownoad != "" { 97 | f, err := ioutil.TempFile(os.TempDir(), "osqueryd") 98 | defer f.Close() 99 | if err != nil { 100 | log.Fatal(err) 101 | } 102 | fmt.Printf("downloading %s to %s\n", TargetName, f.Name()) 103 | if err := update.Download("latest/target", f); err != nil { 104 | log.Fatal(err) 105 | } 106 | } 107 | 108 | fmt.Print("Hit enter to stop me: ") 109 | fmt.Scanln() 110 | 111 | fmt.Println("done...") 112 | } 113 | 114 | func insecureClient() *http.Client { 115 | return &http.Client{ 116 | Transport: &http.Transport{ 117 | TLSClientConfig: &tls.Config{ 118 | InsecureSkipVerify: true, 119 | }, 120 | }, 121 | } 122 | } 123 | 124 | func staticStaticRepo(path, dir string) http.Handler { 125 | return http.StripPrefix(path, http.FileServer(http.Dir(dir))) 126 | } 127 | -------------------------------------------------------------------------------- /tuf/roles_test.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "regexp" 7 | "strings" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestTargetsJson(t *testing.T) { 16 | buff := testAsset(t, "testdata/data/targets.json") 17 | var targets Targets 18 | err := json.NewDecoder(bytes.NewBuffer(buff)).Decode(&targets) 19 | require.Nil(t, err) 20 | key, ok := targets.Signed.Delegations.Keys["894776e7a27799cd2e1f18f988360bd65b75d07488e16009db92102b7ef9b458"] 21 | require.True(t, ok) 22 | assert.Regexp(t, regexp.MustCompile(`^LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JS`), key.KeyVal.Public) 23 | assert.Equal(t, keyTypeRSAx509, key.KeyType) 24 | assert.Equal(t, string(roleTargets), strings.ToLower(targets.Signed.Type)) 25 | require.Len(t, targets.Signed.Delegations.Roles, 1) 26 | role := targets.Signed.Delegations.Roles[0] 27 | require.Len(t, role.KeyIDs, 1) 28 | assert.Equal(t, "894776e7a27799cd2e1f18f988360bd65b75d07488e16009db92102b7ef9b458", role.KeyIDs[0]) 29 | assert.Equal(t, "targets/releases", role.Name) 30 | assert.Equal(t, "2020-06-11T16:02:16.180597846-05:00", targets.Signed.Expires.Format(time.RFC3339Nano)) 31 | assert.Equal(t, 5, targets.Signed.Version) 32 | target, ok := targets.Signed.Targets["1.0.0"] 33 | require.True(t, ok) 34 | assert.Equal(t, int64(3453), target.Length) 35 | hash, ok := target.Hashes[hashSHA256] 36 | require.True(t, ok) 37 | assert.Equal(t, "xdD9jvFLoCYvTNYiyDMyX054paEjI88NddSVAv8fZXI=", hash) 38 | require.Len(t, targets.Signatures, 1) 39 | sig := targets.Signatures[0] 40 | assert.Equal(t, methodECDSA, sig.SigningMethod) 41 | assert.Equal(t, keyID("d24c36bfeb612b6900df04a71a4a8e5a3c9847d3acbc2d27a3ef820895e5d42c"), sig.KeyID) 42 | assert.Equal(t, "XZQ3BgSgdKzdpKd8sDhrfm/PvD4zof2vQNly9/16hgGCU5X882t7Hq5OcdX/Ov6zVKxC0J2LpM+vMIx4HSjGjA==", sig.Value) 43 | } 44 | 45 | func TestRootJson(t *testing.T) { 46 | buff := testAsset(t, "testdata/data/root.json") 47 | var root Root 48 | err := json.NewDecoder(bytes.NewBuffer(buff)).Decode(&root) 49 | require.Nil(t, err) 50 | signed := root.Signed 51 | assert.Equal(t, "2027-06-10T13:25:45.170347322-05:00", signed.Expires.Format(time.RFC3339Nano)) 52 | assert.False(t, signed.ConsistentSnapshot) 53 | assert.Equal(t, 1, signed.Version) 54 | assert.Len(t, signed.Roles, 4) 55 | for _, v := range []role{roleRoot, roleSnapshot, roleTargets, roleTimestamp} { 56 | r, ok := signed.Roles[v] 57 | require.True(t, ok) 58 | assert.Equal(t, 1, r.Threshold) 59 | for _, kid := range r.KeyIDs { 60 | _, ok := signed.Keys[keyID(kid)] 61 | assert.True(t, ok) 62 | } 63 | } 64 | require.Len(t, root.Signatures, 1) 65 | sig := root.Signatures[0] 66 | assert.Equal(t, keyID("db897a1fb0c62fb8e8a43c5fdd9fd5fbe2c1581b675046a64ff1138902ecdcd7"), sig.KeyID) 67 | assert.Equal(t, methodECDSA, sig.SigningMethod) 68 | assert.Equal(t, "9ibr3RQubULaF8maMuFbxX3s6dhlOzC7f8lgQ5m9YZpsFBwKLdrmT4Gm96cFQSMml0FkKXGHgabRGA0efsroXA==", sig.Value) 69 | } 70 | 71 | func TestSnapshotJson(t *testing.T) { 72 | buff := testAsset(t, "testdata/data/snapshot.json") 73 | var snapshot Snapshot 74 | err := json.NewDecoder(bytes.NewBuffer(buff)).Decode(&snapshot) 75 | require.Nil(t, err) 76 | require.Len(t, snapshot.Signatures, 1) 77 | sig := snapshot.Signatures[0] 78 | assert.Equal(t, keyID("cf5d1ca7177c947066404459dcdbfdfed1b684e7cd00d89ed7e513f108df3982"), sig.KeyID) 79 | assert.Equal(t, methodECDSA, sig.SigningMethod) 80 | assert.Equal(t, "Pqth0PIvWkYWfgZ1kRVhfa920AAtoujVQePy/HvP9hCS7vGMwrlWX+doDQxiU8Wtdk8WpIgJpYNxui2rF4rNEw==", sig.Value) 81 | signed := snapshot.Signed 82 | tt := []struct { 83 | present bool 84 | role role 85 | sha256 string 86 | sha512 string 87 | length int64 88 | }{ 89 | {true, roleRoot, `hmw3Q5sat`, `EU+fVRkpw9n1UIzx1`, 2357}, 90 | {true, roleTargets, `nwg+cF2+A+Ybf`, `GGye6UL/7r+qz`, 727}, 91 | {false, roleSnapshot, "", "", 0}, 92 | {false, roleTimestamp, "", "", 0}, 93 | } 94 | for _, ts := range tt { 95 | meta, ok := signed.Meta[ts.role] 96 | assert.Equal(t, ok, ts.present) 97 | if ok { 98 | assert.Contains(t, meta.Hashes[hashSHA256], ts.sha256) 99 | assert.Contains(t, meta.Hashes[hashSHA512], ts.sha512) 100 | assert.Equal(t, ts.length, meta.Length) 101 | } 102 | } 103 | assert.Equal(t, 4, signed.Version) 104 | } 105 | 106 | func TestTimestampJson(t *testing.T) { 107 | buff := testAsset(t, "testdata/data/timestamp.json") 108 | var ts Timestamp 109 | err := json.NewDecoder(bytes.NewBuffer(buff)).Decode(&ts) 110 | require.Nil(t, err) 111 | require.Len(t, ts.Signatures, 1) 112 | sig := ts.Signatures[0] 113 | assert.Equal(t, keyID("1b52d9751b119e2567dcc3ad68a8f99ccff2ba727d354c74173338133aeb3f87"), sig.KeyID) 114 | assert.Equal(t, methodECDSA, sig.SigningMethod) 115 | assert.Equal(t, "92b83fCK0Ozy6y5SUzzBEVeWvcQjf8MwK0nbKu6nZ3NKwOEcW1nn1ZKkwNihn9CePvCZaVlTMnltDoN9/W6ByA==", sig.Value) 116 | signed := ts.Signed 117 | tt := []struct { 118 | present bool 119 | role role 120 | sha256 string 121 | sha512 string 122 | length int64 123 | }{ 124 | {false, roleRoot, "", "", 0}, 125 | {false, roleTargets, "", "", 0}, 126 | {true, roleSnapshot, `14/Mhpey56v+ADoOpo9mTun`, `6WwFGKoNRKGv+fMfjOnTXmGv2/vzQHwYGmN`, 688}, 127 | {false, roleTimestamp, "", "", 0}, 128 | } 129 | for _, ts := range tt { 130 | meta, ok := signed.Meta[ts.role] 131 | assert.Equal(t, ok, ts.present) 132 | if ok { 133 | assert.Contains(t, meta.Hashes[hashSHA256], ts.sha256) 134 | assert.Contains(t, meta.Hashes[hashSHA512], ts.sha512) 135 | assert.Equal(t, ts.length, meta.Length) 136 | } 137 | } 138 | assert.Equal(t, 3, signed.Version) 139 | } 140 | -------------------------------------------------------------------------------- /tuf/persistence_test.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "testing" 12 | "time" 13 | 14 | "github.com/pkg/errors" 15 | "github.com/stretchr/testify/assert" 16 | "github.com/stretchr/testify/require" 17 | ) 18 | 19 | func TestCheckForDirectoryPresence(t *testing.T) { 20 | dir, err := ioutil.TempDir("", "") 21 | require.Nil(t, err) 22 | defer os.RemoveAll(dir) 23 | // directory present 24 | err = checkForDirectoryPresence(dir) 25 | assert.Nil(t, err) 26 | err = os.RemoveAll(dir) 27 | require.Nil(t, err) 28 | // directory missing 29 | err = checkForDirectoryPresence(dir) 30 | assert.NotNil(t, err) 31 | // path present but not a directory 32 | f, err := ioutil.TempFile("", "") 33 | require.Nil(t, err) 34 | defer os.Remove(f.Name()) 35 | err = checkForDirectoryPresence(f.Name()) 36 | assert.NotNil(t, err) 37 | } 38 | 39 | func createMockRepo(sources []string) (string, []string, error) { 40 | tufDir, err := ioutil.TempDir("", "") 41 | if err != nil { 42 | return "", nil, err 43 | } 44 | locations := make([]string, 0) 45 | for _, source := range sources { 46 | fileName := filepath.Base(source) 47 | stripped := strings.Replace(source, "testdata/delegation/0/", "", 1) 48 | pathPart := filepath.Dir(stripped) 49 | target := filepath.Join(tufDir, pathPart, fileName) 50 | locations = append(locations, target) 51 | buff, err := ioutil.ReadFile(source) 52 | if err != nil { 53 | return "", nil, errors.Wrap(err, "read source") 54 | } 55 | pathPart = filepath.Dir(target) 56 | if err = checkForDirectoryPresence(pathPart); err != nil { 57 | if err = os.MkdirAll(pathPart, 0755); err != nil { 58 | return "", nil, err 59 | } 60 | } 61 | err = func() error { 62 | f, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY, 0644) 63 | if err != nil { 64 | return err 65 | } 66 | defer f.Close() 67 | _, err = io.Copy(f, bytes.NewBuffer(buff)) 68 | return err 69 | }() 70 | if err != nil { 71 | return "", nil, err 72 | } 73 | } 74 | return tufDir, locations, nil 75 | } 76 | 77 | var testFilePaths = []string{ 78 | "testdata/delegation/0/root.json", 79 | "testdata/delegation/0/snapshot.json", 80 | "testdata/delegation/0/targets/bar.json", 81 | "testdata/delegation/0/targets/role/foo.json", 82 | "testdata/delegation/0/targets/role.json", 83 | "testdata/delegation/0/targets.json", 84 | "testdata/delegation/0/timestamp.json", 85 | } 86 | 87 | func TestBackupAndRecovery(t *testing.T) { 88 | olderBackupTag := time.Now().UTC().Add(-61 * time.Minute).Format(backupFileTimeTagFormat) 89 | newerBackupTag := time.Now().UTC().Add(-59 * time.Minute).Format(backupFileTimeTagFormat) 90 | 91 | repoDir, repoFileNames, err := createMockRepo(testFilePaths) 92 | require.Nil(t, err) 93 | defer os.RemoveAll(repoDir) 94 | // It should move repo files to backup 95 | err = backupTUFRepo(repoDir, olderBackupTag) 96 | assert.Nil(t, err) 97 | 98 | err = backupTUFRepo(repoDir, newerBackupTag) 99 | assert.Nil(t, err) 100 | 101 | for _, name := range repoFileNames { 102 | backupFile := strings.Replace(name, ".json", fmt.Sprintf(".%s.json", olderBackupTag), 1) 103 | t.Run("existence of "+filepath.Base(backupFile), func(t *testing.T) { 104 | _, err = os.Stat(backupFile) 105 | assert.Nil(t, err) 106 | // original file should still be present 107 | _, err = os.Stat(name) 108 | assert.Nil(t, err) 109 | }) 110 | // remove original after backup 111 | require.Nil(t, os.Remove(name), "removing repo file") 112 | } 113 | // It should restore original repo files (which were removed) 114 | err = restoreTUFRepo(repoDir, olderBackupTag) 115 | assert.Nil(t, err) 116 | 117 | for _, name := range repoFileNames { 118 | t.Run("recovered "+filepath.Base(name), func(t *testing.T) { 119 | _, err = os.Stat(name) 120 | assert.Nil(t, err) 121 | backupFile := strings.Replace(name, ".json", fmt.Sprintf(".%s.json", olderBackupTag), 1) 122 | _, err = os.Stat(backupFile) 123 | assert.Nil(t, err) 124 | }) 125 | } 126 | // It should remove the old backups, leave the newer ones 127 | err = removeAgedBackups(repoDir, 60*time.Minute) 128 | 129 | for _, name := range repoFileNames { 130 | oldBackup := strings.Replace(name, ".json", fmt.Sprintf(".%s.json", olderBackupTag), 1) 131 | newBackup := strings.Replace(name, ".json", fmt.Sprintf(".%s.json", newerBackupTag), 1) 132 | t.Run("backup removal "+filepath.Base(oldBackup), func(t *testing.T) { 133 | _, err = os.Stat(oldBackup) 134 | assert.True(t, os.IsNotExist(err)) 135 | _, err = os.Stat(newBackup) 136 | assert.Nil(t, err) 137 | }) 138 | } 139 | } 140 | 141 | func TestSaveWithTargetTree(t *testing.T) { 142 | repoDir, files, err := createMockRepo(testFilePaths) 143 | require.Nil(t, err) 144 | defer os.RemoveAll(repoDir) 145 | 146 | repo, err := newLocalRepo(repoDir) 147 | require.Nil(t, err) 148 | root, err := repo.root() 149 | require.Nil(t, err) 150 | snapshot, err := repo.snapshot() 151 | require.Nil(t, err) 152 | timestamp, err := repo.timestamp() 153 | targets, err := repo.targets(&localTargetFetcher{repoDir}) 154 | require.Nil(t, err) 155 | 156 | // get rid of files so we can check if they got saved 157 | for _, file := range files { 158 | err = os.Remove(file) 159 | require.Nil(t, err) 160 | } 161 | // get rid of the targets dir to test dir creation 162 | err = os.RemoveAll(filepath.Join(repoDir, "targets")) 163 | assert.Nil(t, err) 164 | 165 | ss := saveSettings{ 166 | tufRepositoryRootDir: repoDir, 167 | backupAge: defaultBackupAge, 168 | rootRole: root, 169 | snapshotRole: snapshot, 170 | timestampRole: timestamp, 171 | targetsRole: targets, 172 | } 173 | 174 | err = saveTufRepository(&ss) 175 | require.Nil(t, err) 176 | 177 | for _, file := range files { 178 | _, err = os.Stat(file) 179 | assert.Nil(t, err, "missing save "+file) 180 | } 181 | ss.tufRepositoryRootDir = ss.tufRepositoryRootDir + "xxx" 182 | 183 | err = saveTufRepository(&ss) 184 | require.NotNil(t, err) 185 | } 186 | -------------------------------------------------------------------------------- /tuf/repo.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "os" 7 | "regexp" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | const ( 13 | tufURLScheme = "https" 14 | tufAPIFormat = `/v2/%s/_trust/tuf/%s.json` 15 | healthzPath = `/_notary_server/health` 16 | roleRegex = `^root$|^[1-9]*[0-9]+\.root$|^snapshot$|^timestamp$|^targets$` 17 | // http headers 18 | cacheControl = "Cache-Control" 19 | cachePolicyNoStore = "no-store" 20 | 21 | maxDelegationCount = 50 22 | ) 23 | 24 | type tester interface { 25 | test([]byte) error 26 | } 27 | 28 | type rootOptions struct { 29 | version int 30 | } 31 | 32 | type roleOptions struct { 33 | expectedLength int64 34 | tests []tester 35 | } 36 | 37 | type repoOptions struct { 38 | rootOptions 39 | roleOptions 40 | } 41 | 42 | func withRootVersion(version int) repoOption { 43 | return func(opts *repoOptions) { 44 | opts.rootOptions.version = version 45 | } 46 | } 47 | 48 | func withRoleExpectedLength(len int64) repoOption { 49 | return func(opts *repoOptions) { 50 | opts.roleOptions.expectedLength = len 51 | } 52 | } 53 | 54 | func withRoleTest(t tester) repoOption { 55 | return func(opts *repoOptions) { 56 | opts.roleOptions.tests = append(opts.roleOptions.tests, t) 57 | } 58 | } 59 | 60 | type repoOption func(*repoOptions) 61 | 62 | type repo interface { 63 | root(opts ...repoOption) (*Root, error) 64 | snapshot(opts ...repoOption) (*Snapshot, error) 65 | targets(rdr roleFetcher) (*RootTarget, error) 66 | timestamp() (*Timestamp, error) 67 | } 68 | 69 | type remoteRepo interface { 70 | repo 71 | ping() error 72 | } 73 | 74 | type persistentRepo interface { 75 | repo 76 | baseDir() string 77 | } 78 | 79 | type localRepo struct { 80 | repoPath string 81 | } 82 | 83 | func (r localRepo) baseDir() string { return r.repoPath } 84 | 85 | type notaryRepo struct { 86 | url *url.URL 87 | gun string 88 | maxResponseSize int64 89 | client *http.Client 90 | } 91 | 92 | func newLocalRepo(repoPath string) (*localRepo, error) { 93 | // TODO: remove, repo path is already validated in settings.verify 94 | err := validatePath(repoPath) 95 | if err != nil { 96 | return nil, errors.Wrap(err, "new tuf repo") 97 | } 98 | repo := localRepo{ 99 | repoPath: repoPath, 100 | } 101 | 102 | return &repo, nil 103 | } 104 | 105 | func newNotaryRepo(settings *Settings, maxResponseSize int64, client *http.Client) (*notaryRepo, error) { 106 | r := ¬aryRepo{ 107 | maxResponseSize: maxResponseSize, 108 | gun: settings.GUN, 109 | client: client, 110 | } 111 | var err error 112 | // TODO remove, already validated in settings.verify 113 | r.url, err = validateURL(settings.NotaryURL) 114 | if err != nil { 115 | return nil, err 116 | } 117 | return r, nil 118 | } 119 | 120 | func validateURL(repoURL string) (*url.URL, error) { 121 | u, err := url.Parse(repoURL) 122 | if err != nil { 123 | return nil, errors.Wrap(err, "tuf remote repo url validation failed") 124 | } 125 | if u.Scheme != tufURLScheme { 126 | return nil, errors.Errorf("tuf url scheme must be %q", tufURLScheme) 127 | } 128 | return u, nil 129 | } 130 | 131 | // validatePath path must exist and be a directory, or a symlink to a directory 132 | func validatePath(repoPath string) error { 133 | fi, err := os.Stat(repoPath) 134 | if os.IsNotExist(err) { 135 | return errors.Wrap(err, "tuf repo path validation failed") 136 | } 137 | if !fi.IsDir() { 138 | return errors.Errorf("tuf repo path %q must be a directory", repoPath) 139 | } 140 | return nil 141 | } 142 | 143 | func validateRole(r role) error { 144 | if !regexp.MustCompile(roleRegex).MatchString(string(r)) { 145 | return errors.Errorf("%q is not a valid role", r) 146 | } 147 | return nil 148 | } 149 | 150 | func isRoleCorrect(r role, s interface{}) { 151 | var hit bool 152 | switch s.(type) { 153 | case Root, *Root: 154 | hit = r == roleRoot 155 | case Targets, *Targets: 156 | hit = r == roleTargets 157 | case Timestamp, *Timestamp: 158 | hit = r == roleTimestamp 159 | case Snapshot, *Snapshot: 160 | hit = r == roleSnapshot 161 | } 162 | if !hit { 163 | panic("Programmer error! Role name and role type mismatch.") 164 | } 165 | } 166 | 167 | // roleFetcher is an abstraction of a thing that fetches Targets. 168 | type roleFetcher interface { 169 | fetch(path string) (*Targets, error) 170 | } 171 | 172 | // 4.5. **Perform a preorder depth-first search for metadata about the desired 173 | // target, beginning with the top-level targets role.** 174 | // 175 | // targetTreeBuilder performs some special root node initialization and then 176 | // recursively calls getDelegatedTarget to do a preorder traversal of the 177 | // Targets tree. 178 | // 179 | // Each time a target node is encountered, it persists path information in proper 180 | // precedence according to the following section. 181 | // 4.5.1. If this role has been visited before, then skip this role (so that 182 | // cycles in the delegation graph are avoided). 183 | // Otherwise, if an application-specific maximum number of roles have been 184 | // visited, then go to step 5 (so that attackers cannot cause the client to 185 | // waste excessive bandwidth or time). 186 | // Otherwise, if this role contains metadata about the desired target, then go 187 | // to step 5. 188 | func targetTreeBuilder(fetcher roleFetcher) (*RootTarget, error) { 189 | targ, err := fetcher.fetch(string(roleTargets)) 190 | if err != nil { 191 | return nil, err 192 | } 193 | root := RootTarget{ 194 | Targets: targ, 195 | paths: make(FimMap), 196 | targetLookup: make(map[string]*Targets), 197 | } 198 | root.append(string(roleTargets), targ) 199 | 200 | for _, delegation := range root.Signed.Delegations.Roles { 201 | err = getDelegatedTarget(fetcher, &root, delegation.Name) 202 | if err != nil { 203 | return nil, err 204 | } 205 | } 206 | return &root, nil 207 | } 208 | 209 | func getDelegatedTarget(fetcher roleFetcher, root *RootTarget, roleName string) error { 210 | target, err := fetcher.fetch(roleName) 211 | if err != nil { 212 | return err 213 | } 214 | root.append(roleName, target) 215 | for _, role := range target.Signed.Delegations.Roles { 216 | err = getDelegatedTarget(fetcher, root, role.Name) 217 | // prevent cycles 218 | if err == errTargetSeen { 219 | continue 220 | } 221 | if err != nil { 222 | return err 223 | } 224 | } 225 | return nil 226 | } 227 | -------------------------------------------------------------------------------- /tuf/testdata/delegation/0/targets/project.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{"db6a90ea540907288a300bf4660d272ba8956c20a08e3d01c4b8d3349d8096c9":{"keytype":"rsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZpRENDQTNDZ0F3SUJBZ0lKQU5iMUZ5blE2K1UxTUEwR0NTcUdTSWIzRFFFQkRRVUFNRFl4Q3pBSkJnTlYKQkFZVEFsVlRNUXN3Q1FZRFZRUUlFd0pKUVRFTE1Ba0dBMVVFQnhNQ1VsSXhEVEFMQmdOVkJBb1RCRUZEVFVVdwpIaGNOTVRjd05qSTNNakV4TkRFd1doY05NVGd3TmpJM01qRXhOREV3V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVMCk1Ba0dBMVVFQ0JNQ1NVRXhDekFKQmdOVkJBY1RBbEpTTVEwd0N3WURWUVFLRXdSQlEwMUZNSUlDSWpBTkJna3EKaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUE2YnZOQ0VNZHg1cGZwRGpNNHMwY2JMK3E1TG1xNUpDQwpOYkUrVGpVQTNQeVBkZjhGNkVScHhhbGozQTd4enZDU29EUHZnNUorSVFHSzlhS1BMLzNUNHRYbmZCOVdwaEVqCmJHU0tLWktLZ3ExZUVlSFlodS9yMi81U2R1UDkwNXdyM0VmbU9HN0pVbVFQbEZGNkk5TVQwK3RXRFlvR3lKOXgKbTh5dEpqLy9GSTFVOE8vNUl1VWxwcXF4di9pa3N2VVhWTXlXdjdKc0lLMVBZS2NRd2hKREdjZ2EyRHlOU2toTQowcWFFUzVxaVVPVklkalZpK0pTWkpuWGxKb3oweXZYRVZjam8xRVd0ZDJ6WEdSUzRsYmJ5WVpJeE9sVEpJaS9wCkdrb0p0N2hNbk01YXdIdVRrS1lGQ2VDQTE1SXhCaHMzWDhLSlltQTVUbndyL0dDWjd2NFdtamZ4YVpsTzZaRWkKaC95ajFuVGVYWFM4R01pa0RqekJzRUdseFZoenZ4Z0orKy8zVU92TWxUMlkzTDBVNmFiRCtuYnRBVGRXdmU4bApFWElOSUV2djFBUzZLWkROYmlLZzkwS3ZGMUFhZFFaREM5Y3didm5SajFTRkxCSDBPeDM0T0VMbHNOMktuZUFwCklsaHhxeXY0aGVCM2pMbFpHV2sxcUY4N1VUZENsWEZrVEVSdFdZZGJPOXB4dXVxZWVyUng4Vkt3SWxPc1BOcGsKUUxqUXBaYmNsSXBDOENMN2hnSDNRSDVzRlVnWmVZVTR2dTBWczVvbHpQc3BXK0h0SzBPU0RxQ0JIZDQycFhLeQpOWVdlallwZEJ3b3BNVFQzU3hMWFJ5VStNYkxleENteEZPQjJBWE9HSUNLQTdLbDZnT2hoMW5xZ0p5UnRoUGh6Ck9rU2U3Zm93aGpFQ0F3RUFBYU9CbURDQmxUQWRCZ05WSFE0RUZnUVV6cnF4MmNOMmJmclg4bFZTNnliUE1UcXQKOHc0d1pnWURWUjBqQkY4d1hZQVV6cnF4MmNOMmJmclg4bFZTNnliUE1UcXQ4dzZoT3FRNE1EWXhDekFKQmdOVgpCQVlUQWxWVE1Rc3dDUVlEVlFRSUV3SkpRVEVMTUFrR0ExVUVCeE1DVWxJeERUQUxCZ05WQkFvVEJFRkRUVVdDCkNRRFc5UmNwME92bE5UQU1CZ05WSFJNRUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkRRVUFBNElDQVFBejJKc2IKbVlrQzJsTExDYXhlTkRORmNtZUdGUWVXeWJESEx4cWZyQUFZYTRkYWg1Rm12R0VySFJJcWFTQlRTOGNHN2ZKbApPejBJRDVNNkFvMEdDa1BycDFtTDlERHdoYjNnQ3NvdHJoVEdwMTFRb3h3azJPTHVMM2g0NndMR0FQellTaXFaClVMQXI4WDl4TlNpdmp3TWJ6QWdzMTg0alBzSjRrcUxodUI0d3JBcEc2elE3WkhGZDlIMFhJUmlVb2kyNktaQnIKa1B0S1Y3RWVkb3dUZlNZQWM2ZFg3bk4ycDdRUElhTG1DZVRJSVZkMlNCLzREbjlrNWVpVlRRZGt2dzBEaDJ6dApEUG44anZ0TkFhKzI0Mys3Z1dFWlRkL29pVmV2dDlHZVJtdENMOFhtSXhiQWR0NnErSWxJNDRUZ1U3aEJ3ckZzCjlUK1hwVVVjeTJiUGcrZDNOaHpxWWtBa2JpODZOVmpUY3hTSWdBK0NzSWhBbzJzS2h4THF2R3hxN3NZSDkxbksKSTNsZEpZcmdPdi9HWDZLbFp2OE1zdE5pNmJjbVJEaHdRaE5kWG94TzRrZXdPMzZ3K09vbjcwVVBVZ054d2JiTApKSGtOVWEvWlVnUU1pdEVtMFZWa1BxLzQzOXVTUEYyU2hLVjUzNlJzeXhxQUdMWFlZTDlFK2ZrWUlRZHM2alZyCm00V3JQS2Y2eFhtSzJMYnduaXUrVUhQejZ1NkM4eG1SM2c1Z0UyUXpOcVNkOEhZK1lvSXA2ZmJzMmR6VmxOK0UKZUc5M3F2dUdUZ0FUd2Q1MHZ0eXRKcGVhUXNmNnhwRzJVNWxxWGM2SUl6ZTdkS3p1dnhDajVnZzhtY092M1dFTgp5V293QjlXTG1EM01hRFUzbnJrU242bnhvbmF4TDRTSFVzS3Z1Zz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}},"f81d448ac7c6830b1a96b933942d79489c779895533c009fa8e6e07aba16a53e":{"keytype":"rsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZ4RENDQTZ5Z0F3SUJBZ0lKQUxkWHY1cG5lVkhITUEwR0NTcUdTSWIzRFFFQkRRVUFNRW94Q3pBSkJnTlYKQkFZVEFsVlRNUXN3Q1FZRFZRUUlFd0pKUVRFTE1Ba0dBMVVFQnhNQ1JrWXhJVEFmQmdOVkJBb1RHRWx1ZEdWeQpibVYwSUZkcFpHZHBkSE1nVUhSNUlFeDBaREFlRncweE56QTJNamN5TVRRMk1EWmFGdzB4T0RBMk1qY3lNVFEyCk1EWmFNRW94Q3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSUV3SkpRVEVMTUFrR0ExVUVCeE1DUmtZeElUQWYKQmdOVkJBb1RHRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MFpEQ0NBaUl3RFFZSktvWklodmNOQVFFQgpCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFOd0xpQ1draG9ZRGVxNVJyR1ZnNFRVSTJ6VDNmVHpOZEg2QXNFMEo4RUEyCk5xckYvbGorYjZ3NmVSbThXZHRkNW5RNU5YRG8vSGg5dE1UdzRRR3NvakJFZ2tlQlF4RmlhYmg3b0FpRkVzZ2cKUVlpZEVnUHU5OUMzN1kzaWsvbUtrZGZyejJQS0tURWdJOHNPTXBla1NDWit6ZUZJeUJ1US8xZk1xMW5wTW8rLwpPQ2ZJS3hVKy9GN2pjVEdZcHl6N1lldmpmU0padnlEcHVpdEl1VGRmM0ZNYWdISTBTV2IwUVRnUytqS1JNaWdICkMrZHhrVzc1RzVoY2lUMmt3bDIrd0JzbFExSzAwazM5Yk9Gb3pXaTZxQmlCNHMxUXV4KzBlMkEwOTlHdHR1SUwKRVhEMmZIWkRiRjlmSFJ2Y2R5WGVDenFHVVV1MW1teDgxKzR0Wlhtak5lZ01mSWZBL3Z0QzVPaWtwdGR4NlN2bQpvdjlsaWJZMUthZ2JLeEQ4Vy9WbEs3eHp2b2k5cXMzOW9xOG11aTRZeWRXeUlFaGVKK1psYVlKZXRiWUc2ZjdNCkNPNXBxOGN5Z09KWVduYnhpRkdvRW5DQURvd2ozSDhVZm5ob0pUd0x1NHQ0YWJ2VVI3ZE5takRIdExnMFFvL3QKSGlJeWFqZWlEakoyTnVNNUNZSElCNEVRTm9XWWttVk4wZSs4c2hiUkZIRWxMQnRQRm5wWWYzRU9GNGd4YXJmcwpVemVwZFpWRG5XWitlclRGVmNmeFp0NjVPZUNBY2dZWjUrN2JmbWp3YkVVSWNzekluSnZlVzNZdmVHVnlaRG9HCi9vZnVzQWdOZ1ZmR0pjNDQrVmNJWFoyYlZPeS90alRFeU03c3FiRjdaMTRiaWl3WjR3dXdMaXlMa0xiNWI1eWYKQWdNQkFBR2pnYXd3Z2Frd0hRWURWUjBPQkJZRUZBSUpxUWIwa3JkbzkwQWFCM1hFSnpic3BKM0JNSG9HQTFVZApJd1J6TUhHQUZBSUpxUWIwa3JkbzkwQWFCM1hFSnpic3BKM0JvVTZrVERCS01Rc3dDUVlEVlFRR0V3SlZVekVMCk1Ba0dBMVVFQ0JNQ1NVRXhDekFKQmdOVkJBY1RBa1pHTVNFd0h3WURWUVFLRXhoSmJuUmxjbTVsZENCWGFXUm4KYVhSeklGQjBlU0JNZEdTQ0NRQzNWNythWjNsUnh6QU1CZ05WSFJNRUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQgpEUVVBQTRJQ0FRREJjcHVISWZLcHEzc2FLUmFSdHdqTERtTFp4M3BPUDhJMnpoaWpSOHVOR2JIbjFzY1pEbW44CmdQWWlQUDFJU2ZmTGc0dm5SeXNPOHZ6TEphbXFLd2d3a20zU0tiYlRqTnhHaUVnczVJYlF3bjNmWm5xVUR6TUUKa0NjdTdTVnBNQ3ViS3FsWWVSNXFWcEZRdEVPVVdSWlRHRnEzTm5CdlBkdFlkRDlhRnlhQkhnTUpTZEpJYnFWbwpyWXV6YXRpNzl5S3E1UGtQSDdpSVNSSitkblBnM2MwMTcxOEszSS9FQm5xTE9raVN0S3ZvME9qUXdGajRrb3drCjFMdmRRdjB4eGxWdE5qSTBSUmFvTHgybHRIcEFTSlMrdnpidm1haDRBbXluWENqd2Q5bWNsZXVZUmVvTmRZMzUKSkFObkZzMEx4RkkxQ21HNnRLdTdLSEZNeS9XL00vaUlxR0d1MzZOZFZRNHprdGM4T3RQUDNsdGluS0t3QTByTgpBZzhPclY4dk9BY0EwY1BkRkRCK3VIdkxIeG43YUFMUHJVdDhQREtGb3UyTlNtdkNpVXgxNmlqS2hqN3BPL3VOClQ0dXRsRmlLajdVVklBa0RKd21hREFqMGVqdEtCNjZzZlVydFV0QkZhM1lxZjZ1NlVJOFJ5NHB4Y1d3aWpkNWsKYlJIQlFBOWpza3FsMkUrS3Y1NURkSFZyWjdSYlM0TlJGQmplQWNrOWxPNzlRemNVZmFLb1ROTTROT2RYSWZWTgpHbDlaMm9aa0VYRXBOWlo4dFVQallsWEhrdGhzRWtOUkViaFFXQXoxV2RTdElQaWtyb0JjMmZzM0ZsTHVEU3hXCjNYODgyU2N5UnFFWTJLZjI4amJkKzh5UXpHZmNHd1hQNW1zb1M5dk5FTUp3ZWVoblR5UGdQdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}}},"roles":[{"keyids":["db6a90ea540907288a300bf4660d272ba8956c20a08e3d01c4b8d3349d8096c9","f81d448ac7c6830b1a96b933942d79489c779895533c009fa8e6e07aba16a53e"],"name":"targets/project/item","paths":["project/file1","project/file2"],"threshold":1}]},"expires":"2020-06-26T18:20:56.060380258-05:00","targets":{"project":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":2},"signatures":[{"keyid":"db6a90ea540907288a300bf4660d272ba8956c20a08e3d01c4b8d3349d8096c9","method":"rsapss","sig":"CsNUZIKRUpRf/mu8/bJnKQuaUoH4k/RDkGrdnP/V8rLbGBowGUMJmn3IJn6MIes+K89BfuX81AeS4r7p2/eSzCRF+0wkzFyWeR/MmG+bZrutcdqvOzhJzK8lOHd55YbmelVs56EyUOBAyor9MheON0FNW6vo8RZn6NjtV0sGs2CoimLrY1wl3hnRu8pFkpZ07t52G+2GTqzIa9P4WJzqpoZus69178blhP6dxJ1beB46zFxmMF0F7eTU3wAhwHP9rolZ3Kog9IJ3HGXrbZYtTkOO5zvwOlVF5EmCps6wfHyb2Ejy8IpAZ7ab6BQd1RWO3+m81mL0CrFTxNGFpPX1R5m78d5EWoS8fCKj/bNILqBYjZxLRmuGwxl+MKMFzEWrSN9hGQD75UqnooN4e6h27YiNolxo5z4C2YzHtgx1Y/LygIIMhVLYaNqcbK8rpT8RAoI88WeCTDE0DGtIivgAngyoJdutQIJEp514ctFIMcLOkwHEcHcl+4HE9BIJYSeHTJgFG7KcpixfSFiWeXu223E2rR/xekPG5NW3XOI48w3bkTk5+4SH9kfwE3r3kupIgpULwli3/527VYkVHIk8YRApdRoH32z9Hhbp3LyGR9Uy5UhpPSgxmbZSZOgd3W2krgXxa0FF3X/0EBEE+bvA5ilQ84V0sYiqgzPeIOMkJKI="}]} -------------------------------------------------------------------------------- /tuf/testdata/delegation/1/targets/project.json: -------------------------------------------------------------------------------- 1 | {"signed":{"_type":"Targets","delegations":{"keys":{"db6a90ea540907288a300bf4660d272ba8956c20a08e3d01c4b8d3349d8096c9":{"keytype":"rsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZpRENDQTNDZ0F3SUJBZ0lKQU5iMUZ5blE2K1UxTUEwR0NTcUdTSWIzRFFFQkRRVUFNRFl4Q3pBSkJnTlYKQkFZVEFsVlRNUXN3Q1FZRFZRUUlFd0pKUVRFTE1Ba0dBMVVFQnhNQ1VsSXhEVEFMQmdOVkJBb1RCRUZEVFVVdwpIaGNOTVRjd05qSTNNakV4TkRFd1doY05NVGd3TmpJM01qRXhOREV3V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVMCk1Ba0dBMVVFQ0JNQ1NVRXhDekFKQmdOVkJBY1RBbEpTTVEwd0N3WURWUVFLRXdSQlEwMUZNSUlDSWpBTkJna3EKaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUE2YnZOQ0VNZHg1cGZwRGpNNHMwY2JMK3E1TG1xNUpDQwpOYkUrVGpVQTNQeVBkZjhGNkVScHhhbGozQTd4enZDU29EUHZnNUorSVFHSzlhS1BMLzNUNHRYbmZCOVdwaEVqCmJHU0tLWktLZ3ExZUVlSFlodS9yMi81U2R1UDkwNXdyM0VmbU9HN0pVbVFQbEZGNkk5TVQwK3RXRFlvR3lKOXgKbTh5dEpqLy9GSTFVOE8vNUl1VWxwcXF4di9pa3N2VVhWTXlXdjdKc0lLMVBZS2NRd2hKREdjZ2EyRHlOU2toTQowcWFFUzVxaVVPVklkalZpK0pTWkpuWGxKb3oweXZYRVZjam8xRVd0ZDJ6WEdSUzRsYmJ5WVpJeE9sVEpJaS9wCkdrb0p0N2hNbk01YXdIdVRrS1lGQ2VDQTE1SXhCaHMzWDhLSlltQTVUbndyL0dDWjd2NFdtamZ4YVpsTzZaRWkKaC95ajFuVGVYWFM4R01pa0RqekJzRUdseFZoenZ4Z0orKy8zVU92TWxUMlkzTDBVNmFiRCtuYnRBVGRXdmU4bApFWElOSUV2djFBUzZLWkROYmlLZzkwS3ZGMUFhZFFaREM5Y3didm5SajFTRkxCSDBPeDM0T0VMbHNOMktuZUFwCklsaHhxeXY0aGVCM2pMbFpHV2sxcUY4N1VUZENsWEZrVEVSdFdZZGJPOXB4dXVxZWVyUng4Vkt3SWxPc1BOcGsKUUxqUXBaYmNsSXBDOENMN2hnSDNRSDVzRlVnWmVZVTR2dTBWczVvbHpQc3BXK0h0SzBPU0RxQ0JIZDQycFhLeQpOWVdlallwZEJ3b3BNVFQzU3hMWFJ5VStNYkxleENteEZPQjJBWE9HSUNLQTdLbDZnT2hoMW5xZ0p5UnRoUGh6Ck9rU2U3Zm93aGpFQ0F3RUFBYU9CbURDQmxUQWRCZ05WSFE0RUZnUVV6cnF4MmNOMmJmclg4bFZTNnliUE1UcXQKOHc0d1pnWURWUjBqQkY4d1hZQVV6cnF4MmNOMmJmclg4bFZTNnliUE1UcXQ4dzZoT3FRNE1EWXhDekFKQmdOVgpCQVlUQWxWVE1Rc3dDUVlEVlFRSUV3SkpRVEVMTUFrR0ExVUVCeE1DVWxJeERUQUxCZ05WQkFvVEJFRkRUVVdDCkNRRFc5UmNwME92bE5UQU1CZ05WSFJNRUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkRRVUFBNElDQVFBejJKc2IKbVlrQzJsTExDYXhlTkRORmNtZUdGUWVXeWJESEx4cWZyQUFZYTRkYWg1Rm12R0VySFJJcWFTQlRTOGNHN2ZKbApPejBJRDVNNkFvMEdDa1BycDFtTDlERHdoYjNnQ3NvdHJoVEdwMTFRb3h3azJPTHVMM2g0NndMR0FQellTaXFaClVMQXI4WDl4TlNpdmp3TWJ6QWdzMTg0alBzSjRrcUxodUI0d3JBcEc2elE3WkhGZDlIMFhJUmlVb2kyNktaQnIKa1B0S1Y3RWVkb3dUZlNZQWM2ZFg3bk4ycDdRUElhTG1DZVRJSVZkMlNCLzREbjlrNWVpVlRRZGt2dzBEaDJ6dApEUG44anZ0TkFhKzI0Mys3Z1dFWlRkL29pVmV2dDlHZVJtdENMOFhtSXhiQWR0NnErSWxJNDRUZ1U3aEJ3ckZzCjlUK1hwVVVjeTJiUGcrZDNOaHpxWWtBa2JpODZOVmpUY3hTSWdBK0NzSWhBbzJzS2h4THF2R3hxN3NZSDkxbksKSTNsZEpZcmdPdi9HWDZLbFp2OE1zdE5pNmJjbVJEaHdRaE5kWG94TzRrZXdPMzZ3K09vbjcwVVBVZ054d2JiTApKSGtOVWEvWlVnUU1pdEVtMFZWa1BxLzQzOXVTUEYyU2hLVjUzNlJzeXhxQUdMWFlZTDlFK2ZrWUlRZHM2alZyCm00V3JQS2Y2eFhtSzJMYnduaXUrVUhQejZ1NkM4eG1SM2c1Z0UyUXpOcVNkOEhZK1lvSXA2ZmJzMmR6VmxOK0UKZUc5M3F2dUdUZ0FUd2Q1MHZ0eXRKcGVhUXNmNnhwRzJVNWxxWGM2SUl6ZTdkS3p1dnhDajVnZzhtY092M1dFTgp5V293QjlXTG1EM01hRFUzbnJrU242bnhvbmF4TDRTSFVzS3Z1Zz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}},"f81d448ac7c6830b1a96b933942d79489c779895533c009fa8e6e07aba16a53e":{"keytype":"rsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZ4RENDQTZ5Z0F3SUJBZ0lKQUxkWHY1cG5lVkhITUEwR0NTcUdTSWIzRFFFQkRRVUFNRW94Q3pBSkJnTlYKQkFZVEFsVlRNUXN3Q1FZRFZRUUlFd0pKUVRFTE1Ba0dBMVVFQnhNQ1JrWXhJVEFmQmdOVkJBb1RHRWx1ZEdWeQpibVYwSUZkcFpHZHBkSE1nVUhSNUlFeDBaREFlRncweE56QTJNamN5TVRRMk1EWmFGdzB4T0RBMk1qY3lNVFEyCk1EWmFNRW94Q3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSUV3SkpRVEVMTUFrR0ExVUVCeE1DUmtZeElUQWYKQmdOVkJBb1RHRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MFpEQ0NBaUl3RFFZSktvWklodmNOQVFFQgpCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFOd0xpQ1draG9ZRGVxNVJyR1ZnNFRVSTJ6VDNmVHpOZEg2QXNFMEo4RUEyCk5xckYvbGorYjZ3NmVSbThXZHRkNW5RNU5YRG8vSGg5dE1UdzRRR3NvakJFZ2tlQlF4RmlhYmg3b0FpRkVzZ2cKUVlpZEVnUHU5OUMzN1kzaWsvbUtrZGZyejJQS0tURWdJOHNPTXBla1NDWit6ZUZJeUJ1US8xZk1xMW5wTW8rLwpPQ2ZJS3hVKy9GN2pjVEdZcHl6N1lldmpmU0padnlEcHVpdEl1VGRmM0ZNYWdISTBTV2IwUVRnUytqS1JNaWdICkMrZHhrVzc1RzVoY2lUMmt3bDIrd0JzbFExSzAwazM5Yk9Gb3pXaTZxQmlCNHMxUXV4KzBlMkEwOTlHdHR1SUwKRVhEMmZIWkRiRjlmSFJ2Y2R5WGVDenFHVVV1MW1teDgxKzR0Wlhtak5lZ01mSWZBL3Z0QzVPaWtwdGR4NlN2bQpvdjlsaWJZMUthZ2JLeEQ4Vy9WbEs3eHp2b2k5cXMzOW9xOG11aTRZeWRXeUlFaGVKK1psYVlKZXRiWUc2ZjdNCkNPNXBxOGN5Z09KWVduYnhpRkdvRW5DQURvd2ozSDhVZm5ob0pUd0x1NHQ0YWJ2VVI3ZE5takRIdExnMFFvL3QKSGlJeWFqZWlEakoyTnVNNUNZSElCNEVRTm9XWWttVk4wZSs4c2hiUkZIRWxMQnRQRm5wWWYzRU9GNGd4YXJmcwpVemVwZFpWRG5XWitlclRGVmNmeFp0NjVPZUNBY2dZWjUrN2JmbWp3YkVVSWNzekluSnZlVzNZdmVHVnlaRG9HCi9vZnVzQWdOZ1ZmR0pjNDQrVmNJWFoyYlZPeS90alRFeU03c3FiRjdaMTRiaWl3WjR3dXdMaXlMa0xiNWI1eWYKQWdNQkFBR2pnYXd3Z2Frd0hRWURWUjBPQkJZRUZBSUpxUWIwa3JkbzkwQWFCM1hFSnpic3BKM0JNSG9HQTFVZApJd1J6TUhHQUZBSUpxUWIwa3JkbzkwQWFCM1hFSnpic3BKM0JvVTZrVERCS01Rc3dDUVlEVlFRR0V3SlZVekVMCk1Ba0dBMVVFQ0JNQ1NVRXhDekFKQmdOVkJBY1RBa1pHTVNFd0h3WURWUVFLRXhoSmJuUmxjbTVsZENCWGFXUm4KYVhSeklGQjBlU0JNZEdTQ0NRQzNWNythWjNsUnh6QU1CZ05WSFJNRUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQgpEUVVBQTRJQ0FRREJjcHVISWZLcHEzc2FLUmFSdHdqTERtTFp4M3BPUDhJMnpoaWpSOHVOR2JIbjFzY1pEbW44CmdQWWlQUDFJU2ZmTGc0dm5SeXNPOHZ6TEphbXFLd2d3a20zU0tiYlRqTnhHaUVnczVJYlF3bjNmWm5xVUR6TUUKa0NjdTdTVnBNQ3ViS3FsWWVSNXFWcEZRdEVPVVdSWlRHRnEzTm5CdlBkdFlkRDlhRnlhQkhnTUpTZEpJYnFWbwpyWXV6YXRpNzl5S3E1UGtQSDdpSVNSSitkblBnM2MwMTcxOEszSS9FQm5xTE9raVN0S3ZvME9qUXdGajRrb3drCjFMdmRRdjB4eGxWdE5qSTBSUmFvTHgybHRIcEFTSlMrdnpidm1haDRBbXluWENqd2Q5bWNsZXVZUmVvTmRZMzUKSkFObkZzMEx4RkkxQ21HNnRLdTdLSEZNeS9XL00vaUlxR0d1MzZOZFZRNHprdGM4T3RQUDNsdGluS0t3QTByTgpBZzhPclY4dk9BY0EwY1BkRkRCK3VIdkxIeG43YUFMUHJVdDhQREtGb3UyTlNtdkNpVXgxNmlqS2hqN3BPL3VOClQ0dXRsRmlLajdVVklBa0RKd21hREFqMGVqdEtCNjZzZlVydFV0QkZhM1lxZjZ1NlVJOFJ5NHB4Y1d3aWpkNWsKYlJIQlFBOWpza3FsMkUrS3Y1NURkSFZyWjdSYlM0TlJGQmplQWNrOWxPNzlRemNVZmFLb1ROTTROT2RYSWZWTgpHbDlaMm9aa0VYRXBOWlo4dFVQallsWEhrdGhzRWtOUkViaFFXQXoxV2RTdElQaWtyb0JjMmZzM0ZsTHVEU3hXCjNYODgyU2N5UnFFWTJLZjI4amJkKzh5UXpHZmNHd1hQNW1zb1M5dk5FTUp3ZWVoblR5UGdQdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}}},"roles":[{"keyids":["db6a90ea540907288a300bf4660d272ba8956c20a08e3d01c4b8d3349d8096c9","f81d448ac7c6830b1a96b933942d79489c779895533c009fa8e6e07aba16a53e"],"name":"targets/project/item","paths":["project/file1","project/file2"],"threshold":1}]},"expires":"2020-06-26T18:20:56.060380258-05:00","targets":{"project":{"hashes":{"sha256":"rdXJB/I43ugLGtfQMFKRlEq3b/hrS0duPDt9/M2tzsc=","sha512":"4QYReopQytVAvTFb+K14c+M0Zc15OtF/a/U2fFbCbXuUS9lyEsFqHmxXwdSLu5m4o7K8Px9OpecouqMYB0FURQ=="},"length":1357}},"version":2},"signatures":[{"keyid":"db6a90ea540907288a300bf4660d272ba8956c20a08e3d01c4b8d3349d8096c9","method":"rsapss","sig":"CsNUZIKRUpRf/mu8/bJnKQuaUoH4k/RDkGrdnP/V8rLbGBowGUMJmn3IJn6MIes+K89BfuX81AeS4r7p2/eSzCRF+0wkzFyWeR/MmG+bZrutcdqvOzhJzK8lOHd55YbmelVs56EyUOBAyor9MheON0FNW6vo8RZn6NjtV0sGs2CoimLrY1wl3hnRu8pFkpZ07t52G+2GTqzIa9P4WJzqpoZus69178blhP6dxJ1beB46zFxmMF0F7eTU3wAhwHP9rolZ3Kog9IJ3HGXrbZYtTkOO5zvwOlVF5EmCps6wfHyb2Ejy8IpAZ7ab6BQd1RWO3+m81mL0CrFTxNGFpPX1R5m78d5EWoS8fCKj/bNILqBYjZxLRmuGwxl+MKMFzEWrSN9hGQD75UqnooN4e6h27YiNolxo5z4C2YzHtgx1Y/LygIIMhVLYaNqcbK8rpT8RAoI88WeCTDE0DGtIivgAngyoJdutQIJEp514ctFIMcLOkwHEcHcl+4HE9BIJYSeHTJgFG7KcpixfSFiWeXu223E2rR/xekPG5NW3XOI48w3bkTk5+4SH9kfwE3r3kupIgpULwli3/527VYkVHIk8YRApdRoH32z9Hhbp3LyGR9Uy5UhpPSgxmbZSZOgd3W2krgXxa0FF3X/0EBEE+bvA5ilQ84V0sYiqgzPeIOMkJKI="}]} -------------------------------------------------------------------------------- /tuf/persistence.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | //////////////////////////////////////////////////////////////////////////////// 4 | // Methods used to save TUF roles that were downloaded from Notary, and rebuilding 5 | // the local TUF repository. saveTufRepository is the only method called outside 6 | // this file, other methods in this file are called from saveTufRepository. 7 | //////////////////////////////////////////////////////////////////////////////// 8 | import ( 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "os" 13 | "path/filepath" 14 | "regexp" 15 | "strings" 16 | "time" 17 | 18 | cjson "github.com/docker/go/canonical/json" 19 | "github.com/pkg/errors" 20 | ) 21 | 22 | const backupFileTimeTagFormat = "20060102150405" 23 | 24 | var ( 25 | suffixMatcher = regexp.MustCompile("\\.json$") 26 | backupMatcher = regexp.MustCompile("\\.[0-9]{14}\\.json$") 27 | ) 28 | 29 | // Backs up the local TUF repository. If finds all the TUF repository files 30 | // and makes copies of them using a tag which is a timestamp as part of the file name. 31 | // All backup files will have the same tag so we are able to associated a particular 32 | // group/version of backup files. 33 | func backupTUFRepo(tufRoot, tag string) error { 34 | if err := checkForDirectoryPresence(tufRoot); err != nil { 35 | return err 36 | } 37 | return filepath.Walk(tufRoot, func(oldPath string, fi os.FileInfo, err error) error { 38 | if err != nil { 39 | return err 40 | } 41 | if !fi.IsDir() { 42 | if suffixMatcher.MatchString(oldPath) && !backupMatcher.MatchString(oldPath) { 43 | base := filepath.Base(oldPath) 44 | dir := filepath.Dir(oldPath) 45 | newPath := filepath.Join(dir, strings.Replace(base, ".json", fmt.Sprintf(".%s.json", tag), 1)) 46 | if err = copy(oldPath, newPath); err != nil { 47 | return err 48 | } 49 | } 50 | } 51 | return nil 52 | }) 53 | } 54 | 55 | // Restores local TUF repository finding all backup files with a matching tag 56 | // and copying them to normal TUF files. 57 | func restoreTUFRepo(tufRoot, tag string) error { 58 | if err := checkForDirectoryPresence(tufRoot); err != nil { 59 | return err 60 | } 61 | tagMatcher := regexp.MustCompile("\\." + tag + "\\.json$") 62 | return filepath.Walk(tufRoot, func(oldPath string, fi os.FileInfo, err error) error { 63 | if err != nil { 64 | return err 65 | } 66 | if !fi.IsDir() { 67 | if tagMatcher.MatchString(oldPath) { 68 | base := filepath.Base(oldPath) 69 | dir := filepath.Dir(oldPath) 70 | newPath := filepath.Join(dir, strings.Replace(base, tag+".json", "json", 1)) 71 | if err = copy(oldPath, newPath); err != nil { 72 | return err 73 | } 74 | } 75 | } 76 | return nil 77 | }) 78 | } 79 | 80 | // Remove backups files that are older than the time duration specified by age. 81 | func removeAgedBackups(tufRoot string, age time.Duration) error { 82 | if err := checkForDirectoryPresence(tufRoot); err != nil { 83 | return err 84 | } 85 | if age < 0 { 86 | return errors.New("age parameter can't be less than zero") 87 | } 88 | return filepath.Walk(tufRoot, func(path string, fi os.FileInfo, err error) error { 89 | if err != nil { 90 | return err 91 | } 92 | if !fi.IsDir() { 93 | if backupMatcher.MatchString(path) { 94 | timePart := path[len(path)-19 : len(path)-5] 95 | backupTime, err := time.Parse(backupFileTimeTagFormat, timePart) 96 | if err != nil { 97 | return err 98 | } 99 | expirationTime := backupTime.Add(age) 100 | if time.Now().UTC().After(expirationTime) { 101 | if err = os.Remove(path); err != nil { 102 | return err 103 | } 104 | } 105 | } 106 | } 107 | return nil 108 | }) 109 | } 110 | 111 | type saveSettings struct { 112 | tufRepositoryRootDir string 113 | backupAge time.Duration 114 | rootRole *Root 115 | snapshotRole *Snapshot 116 | timestampRole *Timestamp 117 | targetsRole *RootTarget 118 | } 119 | 120 | // This function is used to save TUF data downloaded from Notary and save 121 | // it to the local TUF repository. The function first removes old backups, then 122 | // it creates a new backup of the existing local TUF repo, then it saves the 123 | // TUF data to the local repository, the operation is atomic in that it either 124 | // completely succeeds, or the existing local repository is restored to it's 125 | // original state. 126 | func saveTufRepository(ss *saveSettings) (err error) { 127 | // Create a timestamp tag to group backup files. 128 | tag := time.Now().UTC().Format(backupFileTimeTagFormat) 129 | // See if we have any old backup files hanging around and get rid of them. 130 | if err = removeAgedBackups(ss.tufRepositoryRootDir, ss.backupAge); err != nil { 131 | return errors.Wrap(err, "saving roles") 132 | } 133 | // Make a new backup of the local TUF repository. 134 | if err = backupTUFRepo(ss.tufRepositoryRootDir, tag); err != nil { 135 | return errors.Wrap(err, "saving roles") 136 | } 137 | // If something goes wrong restore the local TUF repository to it's original 138 | // state. 139 | defer func() { 140 | if err != nil { 141 | restoreTUFRepo(ss.tufRepositoryRootDir, tag) 142 | } 143 | }() 144 | // Save each cached notary role to a local file. 145 | fixedRoles := []struct { 146 | cached interface{} 147 | name role 148 | }{ 149 | {ss.rootRole, roleRoot}, 150 | {ss.timestampRole, roleTimestamp}, 151 | {ss.snapshotRole, roleSnapshot}, 152 | {ss.targetsRole, roleTargets}, 153 | } 154 | for _, fixedRole := range fixedRoles { 155 | if fixedRole.cached == nil { 156 | return errors.Errorf("required role %q not present", fixedRole.name) 157 | } 158 | err = saveRole(ss.tufRepositoryRootDir, string(fixedRole.name), fixedRole.cached) 159 | if err != nil { 160 | return errors.Wrap(err, "saving roles") 161 | } 162 | } 163 | // Save each delegate role 164 | for i, delegate := range ss.targetsRole.targetPrecedence { 165 | // The first Target will always be the root target, which we've 166 | // already written to file. 167 | if i == 0 { 168 | continue 169 | } 170 | if err = saveRole(ss.tufRepositoryRootDir, delegate.delegateRole, delegate); err != nil { 171 | return errors.Wrapf(err, "failed to save delegate %q", delegate.delegateRole) 172 | } 173 | } 174 | return nil 175 | } 176 | 177 | // Saves TUF data to a local repository file. Fixed TUF roles are saved as 178 | // top level files in the repository. Delegate roles are saved in a tree structure. 179 | func saveRole(tufRoot, roleName string, val interface{}) error { 180 | var fileName string 181 | if _, ok := val.(*Targets); ok { 182 | // if it's not a fixed role, we have a delegate so 183 | // we may have to create nested directories to store data 184 | fileName = fmt.Sprintf("%s.json", roleName) 185 | parentDir := filepath.Join(tufRoot, filepath.Dir(roleName)) 186 | _, err := os.Stat(parentDir) 187 | if os.IsNotExist(err) { 188 | if err = os.MkdirAll(parentDir, 0755); err != nil { 189 | return errors.Wrapf(err, "persisting delegate %q", parentDir) 190 | } 191 | } 192 | if err != nil { 193 | return errors.Wrap(err, "creating parent dir for delegate") 194 | } 195 | } else { 196 | fileName = fmt.Sprintf("%s.json", roleName) 197 | } 198 | buff, err := cjson.MarshalCanonical(val) 199 | if err != nil { 200 | return errors.Wrap(err, "marshalling role") 201 | } 202 | rolePath := filepath.Join(tufRoot, fileName) 203 | return ioutil.WriteFile(rolePath, buff, 0644) 204 | } 205 | 206 | func checkForDirectoryPresence(dir string) error { 207 | fs, err := os.Stat(dir) 208 | if err != nil { 209 | return errors.Wrapf(err, "checking for presence of %q", dir) 210 | } 211 | if !fs.IsDir() { 212 | return errors.Errorf("%q exists but it is not a directory", dir) 213 | } 214 | return nil 215 | } 216 | 217 | // Platform independent file copy. 218 | func copy(src, dst string) error { 219 | in, err := os.Open(src) 220 | if err != nil { 221 | return err 222 | } 223 | defer in.Close() 224 | out, err := os.Create(dst) 225 | if err != nil { 226 | return err 227 | } 228 | defer out.Close() 229 | _, err = io.Copy(out, in) 230 | if err != nil { 231 | return err 232 | } 233 | return err 234 | } 235 | -------------------------------------------------------------------------------- /tuf/roles.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "crypto/sha512" 7 | "crypto/subtle" 8 | "encoding/base64" 9 | "hash" 10 | "io" 11 | "time" 12 | 13 | cjson "github.com/docker/go/canonical/json" 14 | ) 15 | 16 | type keyID string 17 | type hashingMethod string 18 | type role string 19 | type signingMethod string 20 | 21 | const ( 22 | // Signing Methods 23 | methodRSA signingMethod = "rsa" 24 | methodED25519 signingMethod = "ed25519" 25 | methodECDSA signingMethod = "ecdsa" 26 | // Roles 27 | roleRoot role = "root" 28 | roleSnapshot role = "snapshot" 29 | roleTargets role = "targets" 30 | roleTimestamp role = "timestamp" 31 | 32 | // Key Types 33 | keyTypeRSAx509 = "rsa-x509" 34 | keyTypeECDSA = "ecdsa" 35 | keyTypeECDSAx509 = "ecdsa-x509" 36 | keyTypeED25519 = "ed25519" 37 | 38 | hashSHA256 hashingMethod = "sha256" 39 | hashSHA512 hashingMethod = "sha512" 40 | ) 41 | 42 | type marshaller interface { 43 | canonicalJSON() ([]byte, error) 44 | } 45 | 46 | type base64decoder interface { 47 | base64Decoded() ([]byte, error) 48 | } 49 | 50 | type keyfinder interface { 51 | keyMap() map[keyID]Key 52 | } 53 | 54 | type keyed interface { 55 | keys() map[keyID]Key 56 | } 57 | type signed interface { 58 | sigs() []Signature 59 | } 60 | type signedkeyed interface { 61 | keyed 62 | signed 63 | } 64 | 65 | // Root is the root role. It indicates 66 | // which keys are authorized for all top-level roles, including the root 67 | // role itself. 68 | type Root struct { 69 | Signed SignedRoot `json:"signed"` 70 | Signatures []Signature `json:"signatures"` 71 | } 72 | 73 | // Keys get key map for root role 74 | func (r *Root) keys() map[keyID]Key { 75 | return r.Signed.Keys 76 | } 77 | 78 | // Sigs get signatures for root role 79 | func (r *Root) sigs() []Signature { 80 | return r.Signatures 81 | } 82 | 83 | // SignedRoot signed contents of the root role 84 | type SignedRoot struct { 85 | Type string `json:"_type"` 86 | ConsistentSnapshot bool `json:"consistent_snapshot"` 87 | Expires time.Time `json:"expires"` 88 | Keys map[keyID]Key `json:"keys"` 89 | Roles map[role]Role `json:"roles"` 90 | Version int `json:"version"` 91 | } 92 | 93 | func (sr SignedRoot) canonicalJSON() ([]byte, error) { 94 | return cjson.MarshalCanonical(sr) 95 | } 96 | 97 | // Snapshot is the snapshot role. It lists the version 98 | // numbers of all metadata on the repository, excluding timestamp.json and 99 | // mirrors.json. 100 | type Snapshot struct { 101 | Signed SignedSnapshot `json:"signed"` 102 | Signatures []Signature `json:"signatures"` 103 | } 104 | 105 | // SignedSnapshot is the signed portion of the snapshot 106 | type SignedSnapshot struct { 107 | Type string `json:"_type"` 108 | Expires time.Time `json:"expires"` 109 | Version int `json:"version"` 110 | Meta map[role]FileIntegrityMeta `json:"meta"` 111 | } 112 | 113 | func (sr SignedSnapshot) canonicalJSON() ([]byte, error) { 114 | return cjson.MarshalCanonical(sr) 115 | } 116 | 117 | // Timestamp role indicates the latest versions of other files and is frequently resigned to limit the 118 | // amount of time a client can be kept unaware of interference with obtaining updates. 119 | type Timestamp struct { 120 | Signed SignedTimestamp `json:"signed"` 121 | Signatures []Signature `json:"signatures"` 122 | } 123 | 124 | // SignedTimestamp signed portion of timestamp role. 125 | type SignedTimestamp struct { 126 | Type string `json:"_type"` 127 | Expires time.Time `json:"expires"` 128 | Version int `json:"version"` 129 | Meta map[role]FileIntegrityMeta `json:"meta"` 130 | } 131 | 132 | func (sr SignedTimestamp) canonicalJSON() ([]byte, error) { 133 | return cjson.MarshalCanonical(sr) 134 | } 135 | 136 | // Targets represents TUF role of the same name. 137 | // See https://github.com/theupdateframework/tuf/blob/develop/docs/tuf-spec.txt 138 | type Targets struct { 139 | Signed SignedTarget `json:"signed"` 140 | Signatures []Signature `json:"signatures"` 141 | delegateRole string 142 | } 143 | 144 | // FimMap is used to map paths to hashes and length information about that 145 | // file which is used for verification purposes when the file is downloaded. 146 | type FimMap map[string]FileIntegrityMeta 147 | 148 | func (fm FimMap) clone() FimMap { 149 | result := make(FimMap) 150 | for k, v := range fm { 151 | result[k] = *v.clone() 152 | } 153 | return result 154 | } 155 | 156 | // RootTarget is the top level target it contains some bookeeping 157 | // information about targets 158 | type RootTarget struct { 159 | *Targets 160 | targetLookup map[string]*Targets 161 | // Contains all the paths (targets) we know about. The highest precedence 162 | // path is in the list, if a lower precedence item has the same path, 163 | // it is discarded 164 | paths FimMap 165 | targetPrecedence []*Targets 166 | } 167 | 168 | func (rt *RootTarget) append(role string, targ *Targets) { 169 | targ.delegateRole = role 170 | rt.targetLookup[role] = targ 171 | rt.targetPrecedence = append(rt.targetPrecedence, targ) 172 | // add each target to paths, if we added the target already we 173 | // ignore it because a higher precedence delegate has already 174 | // added it 175 | for targetName, fim := range targ.Signed.Targets { 176 | if _, ok := rt.paths[targetName]; !ok { 177 | rt.paths[targetName] = fim 178 | } 179 | } 180 | } 181 | 182 | // SignedTarget specifics of the Targets 183 | type SignedTarget struct { 184 | Type string `json:"_type"` 185 | Delegations Delegations `json:"delegations"` 186 | Expires time.Time `json:"expires"` 187 | Targets FimMap `json:"targets"` 188 | Version int `json:"version"` 189 | } 190 | 191 | func (sr SignedTarget) canonicalJSON() ([]byte, error) { 192 | return cjson.MarshalCanonical(sr) 193 | } 194 | 195 | // Signature information to validate digital signatures 196 | type Signature struct { 197 | KeyID keyID `json:"keyid"` 198 | SigningMethod signingMethod `json:"method"` 199 | Value string `json:"sig"` 200 | } 201 | 202 | func (sig *Signature) base64Decoded() ([]byte, error) { 203 | return base64.StdEncoding.DecodeString(sig.Value) 204 | } 205 | 206 | type hashInfo struct { 207 | h hash.Hash 208 | valid []byte 209 | } 210 | 211 | func newHashInfo(algoType hashingMethod, expected []byte) (*hashInfo, error) { 212 | h, err := getHasher(algoType) 213 | if err != nil { 214 | return nil, err 215 | } 216 | return &hashInfo{h, expected}, nil 217 | } 218 | 219 | func (hi *hashInfo) test(b []byte) error { 220 | var decoded bytes.Buffer 221 | decoder := base64.NewDecoder(base64.StdEncoding, bytes.NewBuffer(hi.valid)) 222 | io.Copy(&decoded, decoder) 223 | io.Copy(hi.h, bytes.NewBuffer(b)) 224 | hash := hi.h.Sum(nil) 225 | if subtle.ConstantTimeCompare(hash, decoded.Bytes()) != 1 { 226 | return errHashIncorrect 227 | } 228 | return nil 229 | } 230 | 231 | func getHasher(algoType hashingMethod) (hash.Hash, error) { 232 | var hashFunc hash.Hash 233 | switch algoType { 234 | case hashSHA256: 235 | hashFunc = sha256.New() 236 | case hashSHA512: 237 | hashFunc = sha512.New() 238 | default: 239 | return nil, errUnsupportedHash 240 | } 241 | return hashFunc, nil 242 | } 243 | 244 | // Delegations contain signing information for targets hosted by external principals. Delegations 245 | // are children of targets. 246 | type Delegations struct { 247 | Keys map[keyID]Key `json:"keys"` 248 | Roles []DelegationRole `json:"roles"` 249 | } 250 | 251 | // Role maps keys in role that are needed to check signatures. 252 | type Role struct { 253 | KeyIDs []string `json:"keyids"` 254 | Threshold int `json:"threshold"` 255 | } 256 | 257 | // DelegationRole contains information about targets delegated to other mirrors. 258 | type DelegationRole struct { 259 | Role 260 | Name string `json:"name"` 261 | Paths []string `json:"paths"` 262 | } 263 | 264 | // Key signing key with key type 265 | type Key struct { 266 | KeyType string `json:"keytype"` 267 | KeyVal KeyVal `json:"keyval"` 268 | } 269 | 270 | // we only really care about the public key 271 | func (k *Key) base64Decoded() ([]byte, error) { 272 | return base64.StdEncoding.DecodeString(k.KeyVal.Public) 273 | } 274 | 275 | // KeyVal the contents of the private and/or public keys 276 | type KeyVal struct { 277 | Private *string `json:"private"` 278 | Public string `json:"public"` 279 | } 280 | -------------------------------------------------------------------------------- /tuf/remote_repo_test.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "net/url" 9 | "regexp" 10 | "testing" 11 | "time" 12 | 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func TestBuildRoleURL(t *testing.T) { 18 | baseURL, _ := url.Parse("https://notary.kolide.com") 19 | r := notaryRepo{ 20 | gun: "kolide/agent/darwin", 21 | url: baseURL, 22 | } 23 | 24 | tt := []struct { 25 | valid bool 26 | expected string 27 | testRole role 28 | errText string 29 | }{ 30 | {true, "https://notary.kolide.com/v2/kolide/agent/darwin/_trust/tuf/1.root.json", "1.root", ""}, 31 | {true, "https://notary.kolide.com/v2/kolide/agent/darwin/_trust/tuf/root.json", "root", ""}, 32 | {true, "https://notary.kolide.com/v2/kolide/agent/darwin/_trust/tuf/targets.json", "targets", ""}, 33 | {true, "https://notary.kolide.com/v2/kolide/agent/darwin/_trust/tuf/snapshot.json", "snapshot", ""}, 34 | {true, "https://notary.kolide.com/v2/kolide/agent/darwin/_trust/tuf/timestamp.json", "timestamp", ""}, 35 | {false, "", "notarole", `"notarole" is not a valid role`}, 36 | {false, "", "roots", `"roots" is not a valid role`}, 37 | {false, "", "xtargets", `"xtargets" is not a valid role`}, 38 | {false, "", "2.targets", `"2.targets" is not a valid role`}, 39 | } 40 | 41 | for _, v := range tt { 42 | actual, err := r.buildRoleURL(v.testRole) 43 | if v.valid { 44 | assert.Nil(t, err) 45 | assert.Equal(t, v.expected, actual) 46 | } else { 47 | assert.NotNil(t, err) 48 | assert.EqualError(t, err, v.errText) 49 | } 50 | } 51 | } 52 | 53 | func TestGetRemoteRole(t *testing.T) { 54 | roles := []role{ 55 | roleRoot, 56 | roleSnapshot, 57 | roleTargets, 58 | roleTimestamp, 59 | "none", 60 | } 61 | for _, roleVal := range roles { 62 | svr := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 63 | if roleVal == "none" { 64 | w.WriteHeader(http.StatusNotFound) 65 | return 66 | } 67 | buff := testAsset(t, fmt.Sprintf("testdata/data/%s.json", roleVal)) 68 | w.Write(buff) 69 | })) 70 | defer svr.Close() 71 | 72 | baseURL, _ := url.Parse(svr.URL) 73 | r := notaryRepo{ 74 | gun: "kolide/agent/darwin", 75 | url: baseURL, 76 | maxResponseSize: defaultMaxResponseSize, 77 | client: &http.Client{ 78 | Transport: &http.Transport{ 79 | TLSClientConfig: &tls.Config{ 80 | InsecureSkipVerify: true, 81 | }, 82 | }, 83 | }, 84 | } 85 | 86 | var intf interface{} 87 | switch roleVal { 88 | case roleRoot: 89 | intf = &Root{} 90 | case roleSnapshot: 91 | intf = &Snapshot{} 92 | case roleTargets: 93 | intf = &Targets{} 94 | case roleTimestamp: 95 | intf = &Timestamp{} 96 | case "none": 97 | intf = &Root{} 98 | err := r.getRole("root", intf) 99 | assert.Equal(t, errNotFound, err) 100 | return 101 | } 102 | err := r.getRole(roleVal, intf) 103 | assert.Nil(t, err) 104 | } 105 | } 106 | 107 | func TestTheReadSizeLimitsAreEnforced(t *testing.T) { 108 | svr := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 109 | buff := testAsset(t, "testdata/data/snapshot.json") 110 | w.Write(buff) 111 | })) 112 | defer svr.Close() 113 | 114 | baseURL, _ := url.Parse(svr.URL) 115 | r := notaryRepo{ 116 | gun: "kolide/agent/darwin", 117 | url: baseURL, 118 | maxResponseSize: defaultMaxResponseSize, 119 | client: &http.Client{ 120 | Transport: &http.Transport{ 121 | TLSClientConfig: &tls.Config{ 122 | InsecureSkipVerify: true, 123 | }, 124 | }, 125 | }, 126 | } 127 | // it should fail if the expected size is smaller than the remote response 128 | _, err := r.snapshot(withRoleExpectedLength(901)) 129 | require.NotNil(t, err) 130 | assert.EqualError(t, err, "parsing json returned from server: unexpected EOF") 131 | // it should succeed if the expected size is the same as the actual size of 132 | // the remote response 133 | _, err = r.snapshot(withRoleExpectedLength(903)) 134 | require.Nil(t, err) 135 | 136 | } 137 | 138 | func TestGetVersionRoot(t *testing.T) { 139 | svr := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 140 | assert.Regexp(t, regexp.MustCompile(`1.root.json$`), r.RequestURI) 141 | buff := testAsset(t, "testdata/data/root.json") 142 | w.Write(buff) 143 | })) 144 | defer svr.Close() 145 | 146 | baseURL, _ := url.Parse(svr.URL) 147 | r := notaryRepo{ 148 | gun: "kolide/agent/darwin", 149 | url: baseURL, 150 | maxResponseSize: defaultMaxResponseSize, 151 | client: &http.Client{ 152 | Transport: &http.Transport{ 153 | TLSClientConfig: &tls.Config{ 154 | InsecureSkipVerify: true, 155 | }, 156 | }, 157 | }, 158 | } 159 | 160 | root, err := r.root(withRootVersion(1)) 161 | require.Nil(t, err) 162 | require.NotNil(t, root) 163 | 164 | assert.Equal(t, "2027-06-10T13:25:45.170347322-05:00", root.Signed.Expires.Format(time.RFC3339Nano)) 165 | } 166 | 167 | func TestGetRoot(t *testing.T) { 168 | svr := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 169 | 170 | buff := testAsset(t, "testdata/data/root.json") 171 | w.Write(buff) 172 | })) 173 | defer svr.Close() 174 | 175 | baseURL, _ := url.Parse(svr.URL) 176 | r := notaryRepo{ 177 | gun: "kolide/agent/darwin", 178 | url: baseURL, 179 | maxResponseSize: defaultMaxResponseSize, 180 | client: &http.Client{ 181 | Transport: &http.Transport{ 182 | TLSClientConfig: &tls.Config{ 183 | InsecureSkipVerify: true, 184 | }, 185 | }, 186 | }, 187 | } 188 | 189 | root, err := r.root() 190 | require.Nil(t, err) 191 | require.NotNil(t, root) 192 | 193 | assert.Equal(t, "2027-06-10T13:25:45.170347322-05:00", root.Signed.Expires.Format(time.RFC3339Nano)) 194 | } 195 | 196 | func TestGetTimestamp(t *testing.T) { 197 | svr := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 198 | buff := testAsset(t, "testdata/data/timestamp.json") 199 | w.Write(buff) 200 | })) 201 | defer svr.Close() 202 | 203 | baseURL, _ := url.Parse(svr.URL) 204 | r := notaryRepo{ 205 | gun: "kolide/agent/darwin", 206 | url: baseURL, 207 | maxResponseSize: defaultMaxResponseSize, 208 | client: &http.Client{ 209 | Transport: &http.Transport{ 210 | TLSClientConfig: &tls.Config{ 211 | InsecureSkipVerify: true, 212 | }, 213 | }, 214 | }, 215 | } 216 | 217 | timestamp, err := r.timestamp() 218 | require.Nil(t, err) 219 | require.NotNil(t, timestamp) 220 | 221 | assert.Equal(t, "2017-06-26T19:32:36.967988706Z", timestamp.Signed.Expires.Format(time.RFC3339Nano)) 222 | } 223 | 224 | func TestGetSnapshot(t *testing.T) { 225 | svr := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 226 | buff := testAsset(t, "testdata/data/snapshot.json") 227 | w.Write(buff) 228 | })) 229 | defer svr.Close() 230 | 231 | baseURL, _ := url.Parse(svr.URL) 232 | r := notaryRepo{ 233 | gun: "kolide/agent/darwin", 234 | url: baseURL, 235 | maxResponseSize: defaultMaxResponseSize, 236 | client: &http.Client{ 237 | Transport: &http.Transport{ 238 | TLSClientConfig: &tls.Config{ 239 | InsecureSkipVerify: true, 240 | }, 241 | }, 242 | }, 243 | } 244 | 245 | snapshot, err := r.snapshot() 246 | require.Nil(t, err) 247 | require.NotNil(t, snapshot) 248 | 249 | assert.Equal(t, "2020-06-11T14:32:32.161365749-05:00", snapshot.Signed.Expires.Format(time.RFC3339Nano)) 250 | } 251 | 252 | func Test404ErrorPassesThrough(t *testing.T) { 253 | svr := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 254 | w.WriteHeader(http.StatusNotFound) 255 | })) 256 | defer svr.Close() 257 | 258 | baseURL, _ := url.Parse(svr.URL) 259 | r := notaryRepo{ 260 | gun: "kolide/agent/darwin", 261 | url: baseURL, 262 | maxResponseSize: defaultMaxResponseSize, 263 | client: &http.Client{ 264 | Transport: &http.Transport{ 265 | TLSClientConfig: &tls.Config{ 266 | InsecureSkipVerify: true, 267 | }, 268 | }, 269 | }, 270 | } 271 | 272 | _, err := r.snapshot() 273 | require.NotNil(t, err) 274 | require.Equal(t, errNotFound, err) 275 | } 276 | 277 | func TestPingSuccess(t *testing.T) { 278 | svr := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 279 | assert.Regexp(t, regexp.MustCompile(`/_notary_server/health$`), r.RequestURI) 280 | })) 281 | defer svr.Close() 282 | 283 | baseURL, _ := url.Parse(svr.URL) 284 | r := notaryRepo{ 285 | gun: "kolide/agent/darwin", 286 | url: baseURL, 287 | maxResponseSize: defaultMaxResponseSize, 288 | client: &http.Client{ 289 | Transport: &http.Transport{ 290 | TLSClientConfig: &tls.Config{ 291 | InsecureSkipVerify: true, 292 | }, 293 | }, 294 | }, 295 | } 296 | 297 | err := r.ping() 298 | assert.Nil(t, err) 299 | } 300 | 301 | func TestPingFail(t *testing.T) { 302 | svr := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 303 | w.WriteHeader(http.StatusInternalServerError) 304 | })) 305 | defer svr.Close() 306 | 307 | baseURL, _ := url.Parse(svr.URL) 308 | r := notaryRepo{ 309 | gun: "kolide/agent/darwin", 310 | url: baseURL, 311 | maxResponseSize: defaultMaxResponseSize, 312 | client: &http.Client{ 313 | Transport: &http.Transport{ 314 | TLSClientConfig: &tls.Config{ 315 | InsecureSkipVerify: true, 316 | }, 317 | }, 318 | }, 319 | } 320 | 321 | err := r.ping() 322 | assert.NotNil(t, err) 323 | } 324 | -------------------------------------------------------------------------------- /tuf/remote_repo.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | 11 | "github.com/WatchBeam/clock" 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | type notaryTargetFetcherSettings struct { 16 | gun string 17 | url string 18 | maxResponseSize int64 19 | client *http.Client 20 | rootRole *Root 21 | snapshotRole *Snapshot 22 | localRootTarget *RootTarget 23 | clock clock.Clock 24 | } 25 | 26 | type notaryTargetFetcher struct { 27 | settings *notaryTargetFetcherSettings 28 | url *url.URL 29 | seen map[string]struct{} 30 | keys map[keyID]Key 31 | roles map[string]Role 32 | } 33 | 34 | func newNotaryTargetFetcher(settings *notaryTargetFetcherSettings) (*notaryTargetFetcher, error) { 35 | u, err := url.Parse(settings.url) 36 | if err != nil { 37 | return nil, errors.Wrap(err, "instantianting remote target reader") 38 | } 39 | rdr := ¬aryTargetFetcher{ 40 | settings: settings, 41 | url: u, 42 | seen: make(map[string]struct{}), 43 | keys: make(map[keyID]Key), 44 | roles: make(map[string]Role), 45 | } 46 | targetRole := settings.rootRole.Signed.Roles[roleTargets] 47 | for _, id := range targetRole.KeyIDs { 48 | key, ok := settings.rootRole.Signed.Keys[keyID(id)] 49 | if !ok { 50 | return nil, errors.New("no key present for key id") 51 | } 52 | rdr.keys[keyID(id)] = key 53 | } 54 | rdr.roles[string(roleTargets)] = targetRole 55 | return rdr, nil 56 | } 57 | 58 | // 5. **Verify the desired target against its targets metadata.** 59 | func (rdr *notaryTargetFetcher) fetch(delegate string) (*Targets, error) { 60 | // 4.5.1. If this role has been visited before, then skip this role (so that 61 | // cycles in the delegation graph are avoided). 62 | // Otherwise, if an application-specific maximum number of roles have been 63 | // visited, then go to step 5 (so that attackers cannot cause the client to 64 | // waste excessive bandwidth or time). 65 | if len(rdr.seen) > maxDelegationCount { 66 | return nil, errTooManyDelegates 67 | } 68 | // prevent cycles in target tree 69 | if _, ok := rdr.seen[delegate]; ok { 70 | return nil, errTargetSeen 71 | } 72 | rdr.seen[delegate] = struct{}{} 73 | path, err := url.Parse(fmt.Sprintf(tufAPIFormat, rdr.settings.gun, delegate)) 74 | if err != nil { 75 | return nil, errors.Wrap(err, "bad url in remote target read") 76 | } 77 | roleLocation := rdr.url.ResolveReference(path).String() 78 | resp, err := rdr.settings.client.Get(roleLocation) 79 | if err != nil { 80 | return nil, errors.Wrap(err, "fetching remote target") 81 | } 82 | defer resp.Body.Close() 83 | if resp.StatusCode != http.StatusOK { 84 | return nil, errors.Errorf("notary server request status %q", resp.Status) 85 | } 86 | // get hashes and length from snapshot for step 4.1 87 | fim, ok := rdr.settings.snapshotRole.Signed.Meta[role(delegate)] 88 | if !ok { 89 | return nil, errors.Errorf("fim data missing for %q", delegate) 90 | } 91 | inStream := io.LimitReader(resp.Body, fim.Length) 92 | var validated bytes.Buffer 93 | // 4.1. **Check against snapshot metadata.** The hashes (if any), and version 94 | // number of this metadata file MUST match the snapshot metadata. This is 95 | // done, in part, to prevent a mix-and-match attack by man-in-the-middle 96 | // attackers. 97 | err = fim.verify(io.TeeReader(inStream, &validated)) 98 | if err != nil { 99 | return nil, errors.Wrapf(err, "file integrity checks failed for %q", delegate) 100 | } 101 | var target Targets 102 | err = json.NewDecoder(&validated).Decode(&target) 103 | if err != nil { 104 | return nil, errors.Wrap(err, "target json could not be decoded") 105 | } 106 | role, ok := rdr.roles[delegate] 107 | if !ok { 108 | return nil, errors.Errorf("unable to find role info for %q", delegate) 109 | } 110 | // 4.5.2.1. If the current delegation is a multi-role delegation, recursively 111 | // visit each role, and check that each has signed exactly the same non-custom 112 | // metadata (i.e., length and hashes) about the target (or the lack of any 113 | // such metadata). 114 | err = verifySignatures(target.Signed, rdr.keys, target.Signatures, role.Threshold) 115 | if err != nil { 116 | return nil, errors.Wrapf(err, "signature validation failed for role %q", delegate) 117 | } 118 | // Do further checks, validating against previous version. 119 | err = rdr.compareToExistingTarget(delegate, &target) 120 | if err != nil { 121 | return nil, errors.Wrapf(err, "comparing local delegate to notary delegate %q", delegate) 122 | } 123 | // we have a valid target at so save it's keys to validate the next target 124 | // because we are doing pre-order traversal the parent target's keys will 125 | // always be available to check the signatures of children 126 | rdr.saveKeysForRoles(&target) 127 | return &target, nil 128 | } 129 | 130 | func (rdr *notaryTargetFetcher) compareToExistingTarget(delegate string, target *Targets) error { 131 | // check for previous delegation, it may not exist if a new delegation was created 132 | previous, ok := rdr.settings.localRootTarget.targetLookup[delegate] 133 | if !ok { 134 | return nil 135 | } 136 | // 4.3. **Check for a rollback attack.** The version number of the previous 137 | // targets metadata file, if any, MUST be less than or equal to the version 138 | // number of this targets metadata file. 139 | if previous.Signed.Version > target.Signed.Version { 140 | return errRollbackAttack 141 | } 142 | // 4.4. **Check for a freeze attack.** The latest known time should be lower 143 | // than the expiration timestamp in this metadata file. 144 | if rdr.settings.clock.Now().After(target.Signed.Expires) { 145 | return errFreezeAttack 146 | } 147 | return nil 148 | } 149 | 150 | func (rdr *notaryTargetFetcher) saveKeysForRoles(target *Targets) { 151 | for id, key := range target.Signed.Delegations.Keys { 152 | rdr.keys[id] = key 153 | } 154 | for _, delegate := range target.Signed.Delegations.Roles { 155 | rdr.roles[delegate.Name] = delegate.Role 156 | } 157 | } 158 | 159 | func (r *notaryRepo) root(opts ...repoOption) (*Root, error) { 160 | var optVal repoOptions 161 | for _, opt := range opts { 162 | opt(&optVal) 163 | } 164 | roleVal := roleRoot 165 | if optVal.rootOptions.version > 0 { 166 | roleVal = role(fmt.Sprintf("%d.%s", optVal.rootOptions.version, roleRoot)) 167 | } 168 | var root Root 169 | err := r.getRole(roleVal, &root) 170 | if err != nil { 171 | return nil, err 172 | } 173 | return &root, nil 174 | } 175 | 176 | func (r *notaryRepo) targets(fetcher roleFetcher) (*RootTarget, error) { 177 | rootTarget, err := targetTreeBuilder(fetcher) 178 | if err != nil { 179 | return nil, errors.Wrap(err, "getting remote target role") 180 | } 181 | return rootTarget, nil 182 | } 183 | 184 | func (r *notaryRepo) timestamp() (*Timestamp, error) { 185 | var timestamp Timestamp 186 | err := r.getRole(roleTimestamp, ×tamp) 187 | if err != nil { 188 | return nil, err 189 | } 190 | return ×tamp, nil 191 | } 192 | 193 | func (r *notaryRepo) snapshot(opts ...repoOption) (*Snapshot, error) { 194 | var snapshot Snapshot 195 | err := r.getRole(roleSnapshot, &snapshot, opts...) 196 | if err != nil { 197 | return nil, err 198 | } 199 | return &snapshot, nil 200 | } 201 | 202 | // Returns nil if notary server is responding 203 | func (r *notaryRepo) ping() error { 204 | path, err := url.Parse(healthzPath) 205 | if err != nil { 206 | return errors.Wrap(err, "ping") 207 | } 208 | pingURL := r.url.ResolveReference(path).String() 209 | 210 | resp, err := r.client.Get(pingURL) 211 | if err != nil { 212 | return errors.Wrap(err, "ping") 213 | } 214 | defer resp.Body.Close() 215 | if resp.StatusCode != http.StatusOK { 216 | return errors.Errorf("notary ping failed with %q", resp.Status) 217 | } 218 | return nil 219 | } 220 | 221 | func (r *notaryRepo) buildRoleURL(roleName role) (string, error) { 222 | err := validateRole(roleName) 223 | if err != nil { 224 | return "", err 225 | } 226 | path, err := url.Parse(fmt.Sprintf(tufAPIFormat, r.gun, roleName)) 227 | if err != nil { 228 | return "", errors.Wrap(err, "building path for remote repo") 229 | } 230 | return r.url.ResolveReference(path).String(), nil 231 | } 232 | 233 | func (r *notaryRepo) getRole(roleName role, role interface{}, opts ...repoOption) error { 234 | maxResponseSize := r.maxResponseSize 235 | var testers []tester 236 | var optVal repoOptions 237 | for _, opt := range opts { 238 | opt(&optVal) 239 | } 240 | if optVal.roleOptions.expectedLength > 0 { 241 | maxResponseSize = optVal.roleOptions.expectedLength 242 | } 243 | if len(optVal.roleOptions.tests) > 0 { 244 | testers = optVal.roleOptions.tests 245 | } 246 | roleURL, err := r.buildRoleURL(roleName) 247 | if err != nil { 248 | return errors.Wrap(err, "getting remote role") 249 | } 250 | resp, err := r.client.Get(roleURL) 251 | if err != nil { 252 | return errors.Wrap(err, "fetching role from remote repo") 253 | } 254 | defer resp.Body.Close() 255 | // Read up to a number of bytes. The can be specified from the previous role, 256 | // or in the case of root no more than defaultMaxResponseSize 257 | limitedReader := io.LimitReader(resp.Body, maxResponseSize) 258 | if resp.StatusCode != http.StatusOK { 259 | // It's legitimate not to find roles in some circumstances 260 | if resp.StatusCode == http.StatusNotFound { 261 | return errNotFound 262 | } 263 | return errors.Wrap(err, "notary server error") 264 | } 265 | var buff bytes.Buffer 266 | _, err = io.Copy(&buff, limitedReader) 267 | if err != nil { 268 | return errors.Wrap(err, "reading response from notary") 269 | } 270 | for _, ts := range testers { 271 | err = ts.test(buff.Bytes()) 272 | if err != nil { 273 | return errors.Wrap(err, "validating response from notary") 274 | } 275 | } 276 | err = json.NewDecoder(&buff).Decode(role) 277 | if err != nil { 278 | return errors.Wrap(err, "parsing json returned from server") 279 | } 280 | return nil 281 | } 282 | -------------------------------------------------------------------------------- /tuf/tuf_test.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "net/http/httptest" 11 | "os" 12 | "path" 13 | "path/filepath" 14 | "regexp" 15 | "testing" 16 | "time" 17 | 18 | "github.com/WatchBeam/clock" 19 | "github.com/stretchr/testify/assert" 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestURLValidation(t *testing.T) { 24 | settings := &Settings{ 25 | NotaryURL: "https://foo.com/zip.json", 26 | GUN: "kolide/agent/linux", 27 | } 28 | 29 | hclient := testHTTPClient() 30 | r, err := newNotaryRepo(settings, defaultMaxResponseSize, hclient) 31 | require.Nil(t, err) 32 | assert.NotNil(t, r) 33 | assert.NotNil(t, r.url) 34 | assert.Equal(t, "kolide/agent/linux", r.gun) 35 | settings.NotaryURL = "HtTps://foo.com/zip.json" 36 | r, err = newNotaryRepo(settings, defaultMaxResponseSize, hclient) 37 | require.Nil(t, err) 38 | assert.NotNil(t, r) 39 | settings.NotaryURL = "http://foo.com/zip.json" 40 | r, err = newNotaryRepo(settings, defaultMaxResponseSize, hclient) 41 | require.NotNil(t, err) 42 | assert.Nil(t, r) 43 | settings.NotaryURL = "garbage" 44 | r, err = newNotaryRepo(settings, defaultMaxResponseSize, hclient) 45 | require.NotNil(t, err) 46 | assert.Nil(t, r) 47 | } 48 | 49 | func TestPathValidation(t *testing.T) { 50 | tempFile, err := ioutil.TempFile("", "test") 51 | require.Nil(t, err) 52 | defer func() { 53 | tempFile.Close() 54 | os.Remove(tempFile.Name()) 55 | }() 56 | // path must be a directory or symlink, not a regular file 57 | r, err := newLocalRepo(tempFile.Name()) 58 | assert.NotNil(t, err) 59 | assert.Nil(t, r) 60 | expected := path.Dir(tempFile.Name()) 61 | r, err = newLocalRepo(expected) 62 | require.Nil(t, err) 63 | require.NotNil(t, r) 64 | assert.Equal(t, expected, r.repoPath) 65 | } 66 | 67 | func createLocalRepo(version int, location string, t *testing.T) { 68 | roles := []string{"root", "timestamp", "snapshot", "targets"} 69 | for _, role := range roles { 70 | source := fmt.Sprintf("testdata/kolide/agent/linux/%s.%d.json", role, version) 71 | buff := testAsset(t, source) 72 | func() { 73 | target := filepath.Join(location, fmt.Sprintf("%s.json", role)) 74 | f, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY, 0644) 75 | require.Nil(t, err) 76 | defer f.Close() 77 | _, err = io.Copy(f, bytes.NewBuffer(buff)) 78 | require.Nil(t, err) 79 | }() 80 | } 81 | } 82 | 83 | // returns local repo path and staging path 84 | func setupTufLocal(version int, t *testing.T) (string, string) { 85 | localRepoPath, err := ioutil.TempDir("", "repo") 86 | require.Nil(t, err) 87 | stagingPath, err := ioutil.TempDir("", "staging") 88 | require.Nil(t, err) 89 | createLocalRepo(version, localRepoPath, t) 90 | return localRepoPath, stagingPath 91 | } 92 | 93 | type mockHandler struct { 94 | matcher *regexp.Regexp 95 | handler func(w http.ResponseWriter, r *http.Request) 96 | } 97 | 98 | // Returns a mock notary server, and a mock mirror 99 | // The notfoundversion should be set to one greater than the version of the root 100 | // file we are using. 101 | func setupTufRemote(version int, notfoundVersion string, t *testing.T) (*httptest.Server, *httptest.Server) { 102 | roleHandlers := []mockHandler{ 103 | mockHandler{ 104 | matcher: regexp.MustCompile(`root\.json$`), 105 | handler: func(w http.ResponseWriter, r *http.Request) { 106 | 107 | if regexp.MustCompile(notfoundVersion + `\.root\.json$`).MatchString(r.RequestURI) { 108 | w.WriteHeader(http.StatusNotFound) 109 | return 110 | } 111 | source := fmt.Sprintf("testdata/kolide/agent/linux/root.%d.json", version) 112 | buff := testAsset(t, source) 113 | w.Write(buff) 114 | }, 115 | }, 116 | mockHandler{ 117 | matcher: regexp.MustCompile(`timestamp\.json$`), 118 | handler: func(w http.ResponseWriter, r *http.Request) { 119 | source := fmt.Sprintf("testdata/kolide/agent/linux/timestamp.%d.json", version) 120 | buff := testAsset(t, source) 121 | w.Write(buff) 122 | }, 123 | }, 124 | mockHandler{ 125 | matcher: regexp.MustCompile(`snapshot\.json$`), 126 | handler: func(w http.ResponseWriter, r *http.Request) { 127 | source := fmt.Sprintf("testdata/kolide/agent/linux/snapshot.%d.json", version) 128 | buff := testAsset(t, source) 129 | w.Write(buff) 130 | }, 131 | }, 132 | mockHandler{ 133 | matcher: regexp.MustCompile(`targets\.json$`), 134 | handler: func(w http.ResponseWriter, r *http.Request) { 135 | source := fmt.Sprintf("testdata/kolide/agent/linux/targets.%d.json", version) 136 | buff := testAsset(t, source) 137 | w.Write(buff) 138 | }, 139 | }, 140 | } 141 | 142 | notary := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 143 | for _, mock := range roleHandlers { 144 | if mock.matcher.MatchString(r.RequestURI) { 145 | mock.handler(w, r) 146 | } 147 | } 148 | })) 149 | mirror := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 150 | switch r.RequestURI { 151 | case "/kolide/agent/linux/somedir/target.0": 152 | buff := testAsset(t, fmt.Sprintf("testdata/kolide/agent/linux/target.0.%d", version)) 153 | w.Write(buff) 154 | case "/kolide/agent/linux/somedir/target.1": 155 | buff := testAsset(t, fmt.Sprintf("testdata/kolide/agent/linux/target.1.%d", version)) 156 | w.Write(buff) 157 | default: 158 | require.FailNow(t, "invalid uri", r.RequestURI) 159 | } 160 | })) 161 | 162 | return notary, mirror 163 | } 164 | 165 | // these tests work on versioned role files and targets that we create interactively 166 | // using notary and then save them in bin data so we can mimic key rotations, 167 | // updating distributables etc. 168 | func TestClientNoUpdates(t *testing.T) { 169 | localRepoPath, stagingPath := setupTufLocal(0, t) 170 | defer os.RemoveAll(localRepoPath) 171 | defer os.RemoveAll(stagingPath) 172 | notary, mirror := setupTufRemote(0, "2", t) 173 | defer notary.Close() 174 | defer mirror.Close() 175 | settings := testSettings(localRepoPath, notary, mirror) 176 | testTime, _ := time.Parse(time.UnixDate, "Sat Jul 1 18:00:00 CST 2017") 177 | mockClock := clock.NewMockClock(testTime) 178 | client, err := NewClient(settings, WithHTTPClient(testHTTPClient()), withClock(mockClock)) 179 | require.Nil(t, err) 180 | fims, latest, err := client.Update() 181 | require.Nil(t, err) 182 | require.True(t, latest) 183 | assert.Len(t, fims, 2) 184 | 185 | } 186 | 187 | func testSettings(localRepo string, notary, mirror *httptest.Server) *Settings { 188 | settings := Settings{ 189 | LocalRepoPath: localRepo, 190 | MirrorURL: mirror.URL, 191 | NotaryURL: notary.URL, 192 | GUN: "kolide/agent/linux", 193 | } 194 | return &settings 195 | } 196 | 197 | func testHTTPClient() *http.Client { 198 | return &http.Client{ 199 | Transport: &http.Transport{ 200 | TLSClientConfig: &tls.Config{ 201 | InsecureSkipVerify: true, 202 | }, 203 | }, 204 | } 205 | } 206 | 207 | func TestClientWithUpdates(t *testing.T) { 208 | testTime, _ := time.Parse(time.UnixDate, "Sat Jul 1 18:00:00 CST 2017") 209 | localRepoPath, stagingPath := setupTufLocal(0, t) 210 | defer os.RemoveAll(localRepoPath) 211 | defer os.RemoveAll(stagingPath) 212 | notary, mirror := setupTufRemote(1, "2", t) 213 | defer notary.Close() 214 | defer mirror.Close() 215 | 216 | settings := testSettings(localRepoPath, notary, mirror) 217 | 218 | client, err := NewClient(settings, WithHTTPClient(testHTTPClient()), withClock(clock.NewMockClock(testTime))) 219 | require.Nil(t, err) 220 | 221 | _, latest, err := client.Update() 222 | require.Nil(t, err) 223 | require.False(t, latest) 224 | 225 | // make sure all the files we are supposed to create are there 226 | files := []string{ 227 | filepath.Join(localRepoPath, "root.json"), 228 | filepath.Join(localRepoPath, "timestamp.json"), 229 | filepath.Join(localRepoPath, "snapshot.json"), 230 | filepath.Join(localRepoPath, "targets.json"), 231 | } 232 | 233 | for _, f := range files { 234 | fs, err := os.Stat(f) 235 | require.False(t, os.IsNotExist(err)) 236 | require.NotNil(t, fs) 237 | } 238 | } 239 | 240 | func TestWithRootKeyRotation(t *testing.T) { 241 | testTime, _ := time.Parse(time.UnixDate, "Sat Jul 1 18:00:00 CST 2017") 242 | localRepoPath, stagingPath := setupTufLocal(1, t) 243 | defer os.RemoveAll(localRepoPath) 244 | defer os.RemoveAll(stagingPath) 245 | notary, mirror := setupTufRemote(2, "3", t) 246 | defer notary.Close() 247 | defer mirror.Close() 248 | settings := testSettings(localRepoPath, notary, mirror) 249 | 250 | client, err := NewClient(settings, WithHTTPClient(testHTTPClient()), withClock(clock.NewMockClock(testTime))) 251 | require.Nil(t, err) 252 | 253 | _, latest, err := client.Update() 254 | require.Nil(t, err) 255 | require.True(t, latest) 256 | 257 | // make sure all the files we are supposed to create are there 258 | files := []string{ 259 | filepath.Join(localRepoPath, "root.json"), 260 | filepath.Join(localRepoPath, "timestamp.json"), 261 | filepath.Join(localRepoPath, "snapshot.json"), 262 | filepath.Join(localRepoPath, "targets.json"), 263 | } 264 | 265 | for _, f := range files { 266 | fs, err := os.Stat(f) 267 | require.False(t, os.IsNotExist(err)) 268 | require.NotNil(t, fs) 269 | } 270 | } 271 | 272 | func TestWithTimestampKeyRotation(t *testing.T) { 273 | testTime, _ := time.Parse(time.UnixDate, "Sat Jul 1 18:00:00 CST 2017") 274 | localRepoPath, stagingPath := setupTufLocal(3, t) 275 | defer os.RemoveAll(localRepoPath) 276 | defer os.RemoveAll(stagingPath) 277 | notary, mirror := setupTufRemote(4, "3", t) 278 | defer notary.Close() 279 | defer mirror.Close() 280 | settings := testSettings(localRepoPath, notary, mirror) 281 | 282 | client, err := NewClient(settings, WithHTTPClient(testHTTPClient()), withClock(clock.NewMockClock(testTime))) 283 | require.Nil(t, err) 284 | 285 | _, latest, err := client.Update() 286 | require.Nil(t, err) 287 | require.True(t, latest) 288 | 289 | // make sure all the files we are supposed to create are there 290 | files := []string{ 291 | filepath.Join(localRepoPath, "root.json"), 292 | filepath.Join(localRepoPath, "timestamp.json"), 293 | filepath.Join(localRepoPath, "snapshot.json"), 294 | filepath.Join(localRepoPath, "targets.json"), 295 | } 296 | 297 | for _, f := range files { 298 | fs, err := os.Stat(f) 299 | require.False(t, os.IsNotExist(err)) 300 | require.NotNil(t, fs) 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /tuf/client.go: -------------------------------------------------------------------------------- 1 | package tuf 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "os" 7 | "path/filepath" 8 | "sync" 9 | "time" 10 | 11 | "github.com/WatchBeam/clock" 12 | "github.com/go-kit/kit/log" 13 | "github.com/go-kit/kit/log/level" 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | // Client is a TUF client. 18 | type Client struct { 19 | // values to autoupdate 20 | checkFrequency time.Duration 21 | backupFileAge time.Duration 22 | watchedTarget string 23 | stagingPath string 24 | notificationHandler NotificationHandler 25 | quit chan struct{} 26 | clock clock.Clock 27 | client *http.Client 28 | maxResponseSize int64 29 | jobs chan func(*repoMan) 30 | wait sync.WaitGroup 31 | logger log.Logger 32 | 33 | // Default true, if true, and autoupdate is enabled check for updates on startup 34 | // instead of waiting until check interval has elapsed. 35 | loadOnStart bool 36 | forceAutoUpdate chan struct{} 37 | } 38 | 39 | const ( 40 | defaultCheckFrequency = 1 * time.Hour 41 | defaultBackupAge = 24 * time.Hour 42 | defaultMaxResponseSize = int64(5 * 1024 * 1024) // 5 Megabytes 43 | ) 44 | 45 | // Option allows customization of the Client. 46 | type Option func(*Client) 47 | 48 | // WithFrequency allows changing the frequency of autoupdate checks. 49 | func WithFrequency(duration time.Duration) Option { 50 | return func(c *Client) { 51 | c.checkFrequency = duration 52 | } 53 | } 54 | 55 | // WithBackupAge changes the amount of time that repository backup files 56 | // are kept before being removed. Current default is one day. 57 | func WithBackupAge(age time.Duration) Option { 58 | return func(c *Client) { 59 | c.backupFileAge = age 60 | } 61 | } 62 | 63 | // WithLogger configures a logger. 64 | func WithLogger(logger log.Logger) Option { 65 | return func(c *Client) { 66 | c.logger = log.With(logger, "library", "TUF") 67 | } 68 | } 69 | 70 | // NotificationHandler gets called when the hosting application has a new version 71 | // of a target that it needs to deal with. The hosting application will need to 72 | // check the err object, if err is nil the stagingPath will point to a validated 73 | // target which is the hosting application's responsibility to deal with. 74 | type NotificationHandler func(stagingPath string, err error) 75 | 76 | // WithAutoUpdate specifies a target which will be auto-downloaded into a staging path by the client. 77 | // WithAutoUpdate requires a NotificationHandler which will be called whenever there is a new upate. 78 | // Use WithFrequency to configure how often the autoupdate goroutine runs. 79 | // There can only be one NotificationHandler per Client. 80 | func WithAutoUpdate(targetName, stagingPath string, onUpdate NotificationHandler) Option { 81 | return func(c *Client) { 82 | c.stagingPath = stagingPath 83 | c.watchedTarget = targetName 84 | c.notificationHandler = onUpdate 85 | } 86 | } 87 | 88 | // WithHTTPClient configures a custom HTTP Client to be used by the Client. 89 | func WithHTTPClient(httpClient *http.Client) Option { 90 | return func(c *Client) { 91 | c.client = httpClient 92 | } 93 | } 94 | 95 | // NewClient creates a TUF Client which can securely download packages from a remote mirror. 96 | // The Client downloads payloads(also called targets) from a remote mirror, validating 97 | // each payload according to the TUF spec. The Client uses a Docker Notary service to 98 | // fetch TUF metadata files stored in the local repository. 99 | // 100 | // You can use one of the provided Options to customize the client configuration. 101 | func NewClient(settings *Settings, opts ...Option) (*Client, error) { 102 | if err := settings.verify(); err != nil { 103 | return nil, err 104 | } 105 | 106 | client := Client{ 107 | maxResponseSize: defaultMaxResponseSize, 108 | client: defaultHttpClient(), 109 | checkFrequency: defaultCheckFrequency, 110 | backupFileAge: defaultBackupAge, 111 | quit: make(chan struct{}), 112 | clock: &clock.DefaultClock{}, 113 | jobs: make(chan func(*repoMan)), 114 | loadOnStart: true, 115 | forceAutoUpdate: make(chan struct{}), 116 | logger: log.NewNopLogger(), 117 | } 118 | for _, opt := range opts { 119 | opt(&client) 120 | } 121 | 122 | level.Debug(client.logger).Log( 123 | "msg", "Client Started", 124 | "GUN", settings.GUN, 125 | ) 126 | 127 | notary, err := newNotaryRepo(settings, client.maxResponseSize, client.client) 128 | if err != nil { 129 | return nil, errors.Wrap(err, "creating notary client") 130 | } 131 | err = notary.ping() 132 | if err != nil { 133 | return nil, errors.Wrap(err, "pinging notary server failed") 134 | } 135 | localRepo, err := newLocalRepo(settings.LocalRepoPath) 136 | if err != nil { 137 | return nil, errors.New("creating local tuf role repo") 138 | } 139 | 140 | rm := newRepoMan(localRepo, notary, settings, notary.client, client.backupFileAge, client.clock) 141 | var autoupdate *autoupdater 142 | if client.watchedTarget != "" { 143 | if client.notificationHandler == nil { 144 | return nil, errors.New("notification handler required for autoupdate") 145 | } 146 | // Initialize with file integrity info on the target we are watching from 147 | // the validated local TUF repository. 148 | validatedTargets, err := localRepo.targets(&localTargetFetcher{localRepo.baseDir()}) 149 | if err != nil { 150 | return nil, errors.Wrap(err, "creating tuf client") 151 | } 152 | fim, ok := validatedTargets.paths[client.watchedTarget] 153 | if !ok { 154 | return nil, errors.Errorf("target %q does not exist", client.watchedTarget) 155 | } 156 | autoupdate = newAutoupdater(&client, fim) 157 | } 158 | ticker := client.clock.NewTicker(client.checkFrequency).Chan() 159 | client.wait.Add(1) 160 | go workerLoop( 161 | ticker, 162 | client.quit, 163 | client.jobs, 164 | &client.wait, 165 | rm, 166 | client.forceAutoUpdate, 167 | autoupdate, 168 | ) 169 | // This will force autoupdate to run as soon as we start instead of waiting 170 | // until checkFrequency has elapsed. 171 | if client.loadOnStart { 172 | client.forceAutoUpdate <- struct{}{} 173 | } 174 | 175 | return &client, nil 176 | } 177 | 178 | // Update updates the local TUF metadata from a remote repository. If the update is successful, 179 | // a list of files that have changed will be returned. 180 | // 181 | // Update gets the current metadata from the notary repository and performs 182 | // requisite checks and validations as specified in the TUF spec section 5.1 'The Client Application'. 183 | // Note that we expect that we do not use consistent snapshots and delegations are 184 | // not supported because for our purposes, both are unnecessary. 185 | // See https://github.com/theupdateframework/tuf/blob/904fa9b8df8ab8c632a210a2b05fd741e366788a/docs/tuf-spec.txt 186 | func (c *Client) Update() (files FimMap, latest bool, err error) { 187 | type resultUpdate struct { 188 | files FimMap 189 | latest bool 190 | err error 191 | } 192 | resultC := make(chan resultUpdate) 193 | c.jobs <- func(rm *repoMan) { 194 | 195 | latest, err := rm.refresh() 196 | if err != nil { 197 | resultC <- resultUpdate{nil, false, err} 198 | return 199 | } 200 | if rm.targets == nil { 201 | resultC <- resultUpdate{nil, false, errors.New("root target not present")} 202 | return 203 | } 204 | resultC <- resultUpdate{rm.targets.paths.clone(), latest, nil} 205 | 206 | } 207 | result := <-resultC 208 | return result.files, result.latest, result.err 209 | } 210 | 211 | // Download downloads a local resource from a remote URL. 212 | // Download will use local TUF metadata, so it's important to call Update before dowloading a new file. 213 | func (c *Client) Download(targetName string, destination io.Writer) error { 214 | resultC := make(chan error) 215 | c.jobs <- func(rm *repoMan) { 216 | 217 | level.Debug(c.logger).Log( 218 | "msg", "TUF downloading", 219 | "targetName", targetName, 220 | "destination", destination, 221 | ) 222 | resultC <- rm.downloadTarget(targetName, destination) 223 | 224 | } 225 | return <-resultC 226 | } 227 | 228 | type autoupdater struct { 229 | watchedTarget string 230 | stagingPath string 231 | notifier NotificationHandler 232 | currentFim FileIntegrityMeta 233 | } 234 | 235 | func newAutoupdater(client *Client, seedFIM FileIntegrityMeta) *autoupdater { 236 | return &autoupdater{ 237 | watchedTarget: client.watchedTarget, 238 | stagingPath: client.stagingPath, 239 | notifier: client.notificationHandler, 240 | currentFim: seedFIM, 241 | } 242 | } 243 | 244 | func (au *autoupdater) update(rm *repoMan) { 245 | _, err := rm.refresh() 246 | if err != nil { 247 | au.notifier("", errors.Wrap(err, "calling update")) 248 | return 249 | } 250 | if rm.targets == nil { 251 | au.notifier("", errors.New("expected root target missing in update")) 252 | return 253 | } 254 | if newFim, ok := rm.targets.paths[au.watchedTarget]; ok { 255 | if !newFim.Equal(au.currentFim) { 256 | if err := downloadAndNotify(rm, au.watchedTarget, au.stagingPath, au.notifier); err != nil { 257 | return 258 | } 259 | au.currentFim = newFim 260 | } 261 | } 262 | } 263 | 264 | // workerLoop is the only method that has a reference to the tuf 265 | // repository manager. It will run as a separate goroutine. Operations 266 | // that interact with the tuf repository will be executed in the 267 | // sequence that jobs are received. 268 | func workerLoop( 269 | ticker <-chan time.Time, 270 | quit <-chan struct{}, 271 | jobs <-chan func(*repoMan), 272 | wait *sync.WaitGroup, 273 | rm *repoMan, 274 | forceAutoUpdate <-chan struct{}, 275 | autoupdate *autoupdater, 276 | ) { 277 | defer wait.Done() 278 | for { 279 | select { 280 | case job := <-jobs: 281 | job(rm) 282 | case <-ticker: 283 | if autoupdate != nil { 284 | autoupdate.update(rm) 285 | } 286 | case <-forceAutoUpdate: 287 | if autoupdate != nil { 288 | autoupdate.update(rm) 289 | } 290 | case <-quit: 291 | return 292 | } 293 | } 294 | } 295 | 296 | func downloadAndNotify(rm *repoMan, watchedTarget, stagingPath string, cb NotificationHandler) error { 297 | dpath := filepath.Join(stagingPath, watchedTarget) 298 | if err := os.MkdirAll(filepath.Dir(dpath), 0755); err != nil { 299 | cb("", err) 300 | return err 301 | } 302 | destination, err := os.Create(dpath) 303 | if err != nil { 304 | cb("", err) 305 | return err 306 | } 307 | if err := rm.downloadTarget(watchedTarget, destination); err != nil { 308 | destination.Close() 309 | os.Remove(dpath) 310 | cb("", err) 311 | return err 312 | } 313 | // the file descriptor must be closed in order to allow the 314 | // notificationHandler to work with the file in the staging path. 315 | destination.Close() 316 | cb(dpath, nil) 317 | return nil 318 | } 319 | 320 | // Stop must be called when done with the updater. 321 | func (c *Client) Stop() { 322 | // cause all goroutines that have the quit channel to exit 323 | close(c.quit) 324 | // wait until they are all done 325 | c.wait.Wait() 326 | } 327 | 328 | func defaultHttpClient() *http.Client { 329 | return &http.Client{ 330 | Transport: &http.Transport{ 331 | TLSHandshakeTimeout: 5 * time.Second, 332 | }, 333 | Timeout: 5 * time.Second, 334 | } 335 | } 336 | --------------------------------------------------------------------------------