blob: 68bcec3aa007ec7f07141ee2932ff63d84163314 [file] [log] [blame]
package generator
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"time"
tuf "github.com/flynn/go-tuf"
"github.com/flynn/go-tuf/sign"
)
var expirationDate = time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
type persistedKeys struct {
Encrypted bool `json:"encrypted"`
Data []*sign.PrivateKey `json:"data"`
}
func assertNotNil(err error) {
if err != nil {
panic(fmt.Sprintf("assertion failed: %s", err))
}
}
func copyRepo(src string, dst string) {
cmd := exec.Command("cp", "-r", src, dst)
assertNotNil(cmd.Run())
}
func newRepo(dir string) *tuf.Repo {
repo, err := tuf.NewRepoIndent(tuf.FileSystemStore(dir, nil), "", "\t")
assertNotNil(err)
return repo
}
func commit(dir string, repo *tuf.Repo) {
assertNotNil(repo.SnapshotWithExpires(tuf.CompressionTypeNone, expirationDate))
assertNotNil(repo.TimestampWithExpires(expirationDate))
assertNotNil(repo.Commit())
// Remove the keys directory to make sure we don't accidentally use a key.
assertNotNil(os.RemoveAll(filepath.Join(dir, "keys")))
}
func addKeys(repo *tuf.Repo, roleKeys map[string][]*sign.PrivateKey) {
for role, keys := range roleKeys {
for _, key := range keys {
assertNotNil(repo.AddPrivateKeyWithExpires(role, key, 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)
assertNotNil(os.MkdirAll(filepath.Dir(path), 0755))
assertNotNil(ioutil.WriteFile(path, data, 0644))
paths = append(paths, file)
}
assertNotNil(repo.AddTargetsWithExpires(paths, nil, expirationDate))
}
func revokeKeys(repo *tuf.Repo, role string, keys []*sign.PrivateKey) {
for _, key := range keys {
assertNotNil(repo.RevokeKeyWithExpires(role, key.PublicData().IDs()[0], expirationDate))
}
}
func generateRepos(dir string, roleKeys map[string][][]*sign.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][]*sign.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)
copyRepo(oldDir, d)
repo := newRepo(d)
addKeys(repo, keys)
// Actually rotate the keys
revokeKeys(repo, role, roleKeys[role][0])
addKeys(repo, map[string][]*sign.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)
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)
assertNotNil(err)
var roleKeys map[string][][]*sign.PrivateKey
assertNotNil(json.NewDecoder(f).Decode(&roleKeys))
log.Printf("generating %s", dir)
generateRepos(dir, roleKeys, consistentSnapshot)
}