1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
package generator
import (
"encoding/json"
"fmt"
"io"
"io/fs"
"log"
"os"
"path/filepath"
"time"
tuf "github.com/theupdateframework/go-tuf"
"github.com/theupdateframework/go-tuf/data"
"github.com/theupdateframework/go-tuf/pkg/keys"
)
var expirationDate = time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
type persistedKeys struct {
Encrypted bool `json:"encrypted"`
Data []*data.PrivateKey `json:"data"`
}
func assertNoError(err error) {
if err != nil {
panic(fmt.Sprintf("assertion failed: %s", err))
}
}
// copyRepo recursively copies all regular files and directories under src
// to dst. In the case where any destination file/directory exists
// (including dst itself), an error is returned.
func copyRepo(src string, dst string) error {
copyToDest := func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
rel, err := filepath.Rel(src, path)
if err != nil {
return err
}
target := filepath.Join(dst, rel)
mode := info.Mode()
if mode.IsDir() {
return os.Mkdir(target, mode.Perm())
} else if mode.IsRegular() {
sfile, err := os.Open(path)
if err != nil {
return err
}
defer sfile.Close()
dfile, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode.Perm())
if err != nil {
return err
}
defer dfile.Close()
if _, err := io.Copy(dfile, sfile); err != nil {
return err
}
return nil
}
return fmt.Errorf("unknown mode %v", mode)
}
return filepath.Walk(src, copyToDest)
}
func newRepo(dir string) *tuf.Repo {
repo, err := tuf.NewRepoIndent(tuf.FileSystemStore(dir, nil), "", "\t")
assertNoError(err)
return repo
}
func commit(dir string, repo *tuf.Repo) {
assertNoError(repo.SnapshotWithExpires(expirationDate))
assertNoError(repo.TimestampWithExpires(expirationDate))
assertNoError(repo.Commit())
// Remove the keys directory to make sure we don't accidentally use a key.
assertNoError(os.RemoveAll(filepath.Join(dir, "keys")))
}
func addKeys(repo *tuf.Repo, roleKeys map[string][]*data.PrivateKey) {
for role, keyList := range roleKeys {
for _, key := range keyList {
signer, err := keys.GetSigner(key)
assertNoError(err)
assertNoError(repo.AddPrivateKeyWithExpires(role, signer, expirationDate))
}
}
}
func addTargets(repo *tuf.Repo, dir string, files map[string][]byte) {
paths := []string{}
for file, data := range files {
path := filepath.Join(dir, "staged", "targets", file)
assertNoError(os.MkdirAll(filepath.Dir(path), 0755))
assertNoError(os.WriteFile(path, data, 0644))
paths = append(paths, file)
}
assertNoError(repo.AddTargetsWithExpires(paths, nil, expirationDate))
}
func revokeKeys(repo *tuf.Repo, role string, keyList []*data.PrivateKey) {
for _, key := range keyList {
signer, err := keys.GetSigner(key)
assertNoError(err)
assertNoError(repo.RevokeKeyWithExpires(role, signer.PublicData().IDs()[0], expirationDate))
}
}
func generateRepos(dir string, roleKeys map[string][][]*data.PrivateKey, consistentSnapshot bool) {
// Collect all the initial keys we'll use when creating repositories.
// We'll modify this to reflect rotated keys.
keys := map[string][]*data.PrivateKey{
"root": roleKeys["root"][0],
"targets": roleKeys["targets"][0],
"snapshot": roleKeys["snapshot"][0],
"timestamp": roleKeys["timestamp"][0],
}
// Create the initial repo.
dir0 := filepath.Join(dir, "0")
repo0 := newRepo(dir0)
repo0.Init(consistentSnapshot)
addKeys(repo0, keys)
addTargets(repo0, dir0, map[string][]byte{"0": []byte("0")})
commit(dir0, repo0)
// Rotate all the keys to make sure that works.
oldDir := dir0
i := 1
for _, role := range []string{"root", "targets", "snapshot", "timestamp"} {
// Setup the repo.
stepName := fmt.Sprintf("%d", i)
d := filepath.Join(dir, stepName)
assertNoError(copyRepo(oldDir, d))
repo := newRepo(d)
addKeys(repo, keys)
// Actually rotate the keys
revokeKeys(repo, role, roleKeys[role][0])
addKeys(repo, map[string][]*data.PrivateKey{
role: roleKeys[role][1],
})
keys[role] = roleKeys[role][1]
// Add a target to make sure that works, then commit.
addTargets(repo, d, map[string][]byte{stepName: []byte(stepName)})
commit(d, repo)
i += 1
oldDir = d
}
// Add another target file to make sure the workflow worked.
stepName := fmt.Sprintf("%d", i)
d := filepath.Join(dir, stepName)
assertNoError(copyRepo(oldDir, d))
repo := newRepo(d)
addKeys(repo, keys)
addTargets(repo, d, map[string][]byte{stepName: []byte(stepName)})
commit(d, repo)
}
func Generate(dir string, keysPath string, consistentSnapshot bool) {
f, err := os.Open(keysPath)
assertNoError(err)
var roleKeys map[string][][]*data.PrivateKey
assertNoError(json.NewDecoder(f).Decode(&roleKeys))
log.Printf("generating %s", dir)
generateRepos(dir, roleKeys, consistentSnapshot)
}
|