Merge pull request #47 from docker/cryptoservice-refactor

Refactor crypto service
This commit is contained in:
Diogo Mónica 2015-07-13 15:29:59 -07:00
commit 935b9a9366
52 changed files with 385 additions and 1472 deletions

2
Godeps/Godeps.json generated
View File

@ -52,7 +52,7 @@
},
{
"ImportPath": "github.com/endophage/gotuf",
"Rev": "ab4ba80203ffa5bfd742e6891bd28bfbf43a9453"
"Rev": "82786136d505f582d0f898a2e80c9f6b97b1402c"
},
{
"ImportPath": "github.com/go-sql-driver/mysql",

View File

@ -1,24 +1,36 @@
# GOTUF
This is still a work in progress but will shortly be a fully compliant
Go implementation of [The Update Framework (TUF)](http://theupdateframework.com/).
## Where's the CLI
This repository provides a library only. The [Notary project](https://github.com/docker/notary)
from Docker should be considered the official CLI to be used with this implementation of TUF.
## TODOs:
- [X] Add Targets to existing repo
- [X] Sign metadata files
- [X] Refactor TufRepo to take care of signing ~~and verification~~
- [ ] Ensure consistent capitalization in naming (TUF\_\_\_ vs Tuf\_\_\_)
- [ ] Make caching of metadata files smarter - PR #5
- [ ] ~~Add configuration for CLI commands. Order of configuration priority from most to least: flags, config file, defaults~~ Notary should be the official CLI
- [X] Reasses organization of data types. Possibly consolidate a few things into the data package but break up package into a few more distinct files
- [ ] Comprehensive test cases
- [ ] Delete files no longer in use
- [ ] Fix up errors. Some have to be instantiated, others don't, the inconsistency is annoying.
- [X] Bump version numbers in meta files (could probably be done better)
## Credits
This implementation was originally forked from [flynn/go-tuf](https://github.com/flynn/go-tuf),
however in attempting to add delegations I found I was making such
significant changes that I could not maintain backwards compatibility
without the code becoming overly convoluted.
Some features such as pluggable verifiers have alreayd been merged upstream to flynn/go-tuf
and we are in discussion with [titanous](https://github.com/titanous) about working to merge the 2 implementations.
This implementation retains the same 3 Clause BSD license present on
the original flynn implementation.
TODOs:
- [X] Add Targets to existing repo
- [X] Sign metadata files
- [X] Refactor TufRepo to take care of signing ~~and verification~~
- [ ] Ensure consistent capitalization in naming (TUF\_\_\_ vs Tuf\_\_\_)
- [ ] Make caching of metadata files smarter
- [ ] ~~Add configuration for CLI commands. Order of configuration priority from most to least: flags, config file, defaults~~ Notary should be the official CLI
- [ ] Reasses organization of data types. Possibly consolidate a few things into the data package but break up package into a few more distinct files
- [ ] Comprehensive test cases
- [ ] Delete files no longer in use
- [ ] Fix up errors. Some have to be instantiated, others don't, the inconsistency is annoying.
- [X] Bump version numbers in meta files (could probably be done better)

View File

@ -1,111 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/codegangsta/cli"
"github.com/endophage/gotuf/keys"
"github.com/endophage/gotuf"
"github.com/endophage/gotuf/client"
"github.com/endophage/gotuf/data"
"github.com/endophage/gotuf/store"
)
var commandDownload = cli.Command{
Name: "download",
Usage: "provide the path to a target you wish to download.",
Action: download,
}
func init() {
commandDownload.Flags = []cli.Flag{
cli.StringFlag{
Name: "root, r",
Value: "",
Usage: "The local file path to the current, or immediately previous, root.json for the TUF repo.",
EnvVar: "TUF_ROOT",
},
cli.StringFlag{
Name: "host, h",
Value: "",
Usage: "The scheme and hostname of the TUF repository, e.g. http://example.com/.",
EnvVar: "TUF_HOST",
},
cli.StringFlag{
Name: "meta, m",
Value: "",
Usage: "The path prefix for TUF metadata files.",
EnvVar: "TUF_META_PREFIX",
},
cli.StringFlag{
Name: "ext, e",
Value: "json",
Usage: "The file extension for TUF metadata files.",
EnvVar: "TUF_META_EXT",
},
cli.StringFlag{
Name: "targets, t",
Value: "",
Usage: "The path prefix for target files.",
EnvVar: "TUF_TARGETS_PREFIX",
},
}
}
func download(ctx *cli.Context) {
if len(ctx.Args()) < 1 {
fmt.Println("At least one target name must be provided.")
return
}
var root []byte
r := &data.Signed{}
err := json.Unmarshal(root, r)
if err != nil {
fmt.Println("Could not read initial root.json")
return
}
kdb := keys.NewDB()
repo := tuf.NewTufRepo(kdb, nil)
repo.SetRoot(r)
remote, err := store.NewHTTPStore(
ctx.String("host"),
ctx.String("meta"),
ctx.String("ext"),
ctx.String("targets"),
)
cached := store.NewFileCacheStore(remote, "/tmp/tuf")
if err != nil {
fmt.Println(err)
return
}
client := client.NewClient(repo, cached, kdb)
err = client.Update()
if err != nil {
fmt.Println(err)
return
}
filename := filepath.Base(ctx.Args()[0])
f, err := os.OpenFile(filename, os.O_TRUNC|os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
m := client.TargetMeta(ctx.Args()[0])
if m == nil {
fmt.Println("Requested package not found.")
return
}
err = client.DownloadTarget(f, ctx.Args()[0], m)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Requested pacakge downloaded.")
}

View File

@ -1,27 +0,0 @@
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
app := cli.NewApp()
app.Name = "tufc"
app.Usage = "tuf download <package name>"
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "config",
Value: "config.json",
Usage: "Set the path to a json configuration file for the TUF repo you want to interact with.",
},
}
app.Commands = []cli.Command{
commandDownload,
}
app.Run(os.Args)
}

View File

@ -1,84 +0,0 @@
package main
import (
"fmt"
"log"
"github.com/endophage/gotuf"
"github.com/endophage/gotuf/signed"
"github.com/endophage/gotuf/store"
"github.com/endophage/gotuf/testutils"
"github.com/flynn/go-docopt"
)
func main() {
log.SetFlags(0)
usage := `usage: tuftools [-h|--help] <command> [<args>...]
Options:
-h, --help
Commands:
help Show usage for a specific command
meta Generate metadata from the given file path
See "tuf help <command>" for more information on a specific command
`
args, _ := docopt.Parse(usage, nil, true, "", true)
cmd := args.String["<command>"]
cmdArgs := args.All["<args>"].([]string)
if cmd == "help" {
if len(cmdArgs) == 0 { // `tuf help`
fmt.Println(usage)
return
} else { // `tuf help <command>`
cmd = cmdArgs[0]
cmdArgs = []string{"--help"}
}
}
if err := runCommand(cmd, cmdArgs); err != nil {
log.Fatalln("ERROR:", err)
}
}
type cmdFunc func(*docopt.Args, *tuf.Repo) error
type command struct {
usage string
f cmdFunc
}
var commands = make(map[string]*command)
func register(name string, f cmdFunc, usage string) {
commands[name] = &command{usage: usage, f: f}
}
func runCommand(name string, args []string) error {
argv := make([]string, 1, 1+len(args))
argv[0] = name
argv = append(argv, args...)
cmd, ok := commands[name]
if !ok {
return fmt.Errorf("%s is not a tuf command. See 'tuf help'", name)
}
parsedArgs, err := docopt.Parse(cmd.usage, argv, true, "", true)
if err != nil {
return err
}
db := testutils.GetSqliteDB()
local := store.DBStore(db, "")
signer := signed.Ed25519{}
repo, err := tuf.NewRepo(&signer, local, "sha256")
if err != nil {
return err
}
return cmd.f(parsedArgs, repo)
}

View File

@ -1,39 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"github.com/endophage/gotuf"
"github.com/endophage/gotuf/data"
"github.com/flynn/go-docopt"
)
func init() {
register("meta", cmdMeta, `
usage: tuftools meta [<path>...]
Generate sample metadata for file(s) given by path.
`)
}
func cmdMeta(args *docopt.Args, repo *tuf.Repo) error {
paths := args.All["<path>"].([]string)
for _, file := range paths {
reader, _ := os.Open(file)
meta, _ := data.NewFileMeta(reader, "sha256")
jsonBytes, err := json.Marshal(meta)
if err != nil {
return err
}
filename := fmt.Sprintf("%s.meta.json", file)
err = ioutil.WriteFile(filename, jsonBytes, 0644)
if err != nil {
return err
}
}
return nil
}

View File

@ -1,48 +0,0 @@
# go-tuf client CLI
## Install
```
go get github.com/flynn/go-tuf/cmd/tuf-client
```
## Usage
The CLI provides three commands:
* `tuf-client init` - initialize a local file store using root keys (e.g. from
the output of `tuf root-keys`)
* `tuf-client list` - list available targets and their file sizes
* `tuf-client get` - get a target file and write to STDOUT
All commands require the base URL of the TUF repository as the first non-flag
argument, and accept an optional `--store` flag which is the path to the local
storage.
Run `tuf-client help` from the command line to get more detailed usage
information.
## Examples
```
# init
$ tuf-client init https://example.com/path/to/repo
# init with a custom store path
$ tuf-client init --store /tmp/tuf.db https://example.com/path/to/repo
# list available targets
$ tuf-client list https://example.com/path/to/repo
PATH SIZE
/foo.txt 1.6KB
/bar.txt 336B
/baz.txt 1.5KB
# get a target
$ tuf-client get https://example.com/path/to/repo /foo.txt
the contents of foo.txt
# the prefixed / is optional
$ tuf-client get https://example.com/path/to/repo foo.txt
the contents of foo.txt
```

View File

@ -1,52 +0,0 @@
package main
import (
"io"
"io/ioutil"
"os"
tuf "github.com/endophage/gotuf/client"
"github.com/endophage/gotuf/utils"
"github.com/flynn/go-docopt"
)
func init() {
register("get", cmdGet, `
usage: tuf-client get [-s|--store=<path>] <url> <target>
Options:
-s <path> The path to the local file store [default: tuf.db]
Get a target from the repository.
`)
}
type tmpFile struct {
*os.File
}
func (t *tmpFile) Delete() error {
t.Close()
return os.Remove(t.Name())
}
func cmdGet(args *docopt.Args, client *tuf.Client) error {
if _, err := client.Update(); err != nil && !tuf.IsLatestSnapshot(err) {
return err
}
target := utils.NormalizeTarget(args.String["<target>"])
file, err := ioutil.TempFile("", "gotuf")
if err != nil {
return err
}
tmp := tmpFile{file}
if err := client.Download(target, &tmp); err != nil {
return err
}
defer tmp.Delete()
if _, err := tmp.Seek(0, os.SEEK_SET); err != nil {
return err
}
_, err = io.Copy(os.Stdout, file)
return err
}

View File

@ -1,41 +0,0 @@
package main
import (
"encoding/json"
"io"
"os"
tuf "github.com/endophage/gotuf/client"
"github.com/endophage/gotuf/data"
"github.com/flynn/go-docopt"
)
func init() {
register("init", cmdInit, `
usage: tuf-client init [-s|--store=<path>] <url> [<root-keys-file>]
Options:
-s <path> The path to the local file store [default: tuf.db]
Initialize the local file store with root keys.
`)
}
func cmdInit(args *docopt.Args, client *tuf.Client) error {
file := args.String["<root-keys-file>"]
var in io.Reader
if file == "" || file == "-" {
in = os.Stdin
} else {
var err error
in, err = os.Open(file)
if err != nil {
return err
}
}
var rootKeys []*data.Key
if err := json.NewDecoder(in).Decode(&rootKeys); err != nil {
return err
}
return client.Init(rootKeys, len(rootKeys))
}

View File

@ -1,39 +0,0 @@
package main
import (
"fmt"
"os"
"text/tabwriter"
"github.com/dustin/go-humanize"
tuf "github.com/endophage/gotuf/client"
"github.com/flynn/go-docopt"
)
func init() {
register("list", cmdList, `
usage: tuf-client list [-s|--store=<path>] <url>
Options:
-s <path> The path to the local file store [default: tuf.db]
List available target files.
`)
}
func cmdList(args *docopt.Args, client *tuf.Client) error {
if _, err := client.Update(); err != nil && !tuf.IsLatestSnapshot(err) {
return err
}
targets, err := client.Targets()
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
defer w.Flush()
fmt.Fprintln(w, "PATH\tSIZE")
for path, meta := range targets {
fmt.Fprintf(w, "%s\t%s\n", path, humanize.Bytes(uint64(meta.Length)))
}
return nil
}

View File

@ -1,94 +0,0 @@
package main
import (
"fmt"
"log"
"github.com/endophage/gotuf/client"
"github.com/endophage/gotuf/store"
"github.com/flynn/go-docopt"
)
func main() {
log.SetFlags(0)
usage := `usage: tuf-client [-h|--help] <command> [<args>...]
Options:
-h, --help
Commands:
help Show usage for a specific command
init Initialize with root keys
list List available target files
get Get a target file
See "tuf-client help <command>" for more information on a specific command.
`
args, _ := docopt.Parse(usage, nil, true, "", true)
cmd := args.String["<command>"]
cmdArgs := args.All["<args>"].([]string)
if cmd == "help" {
if len(cmdArgs) == 0 { // `tuf-client help`
fmt.Println(usage)
return
} else { // `tuf-client help <command>`
cmd = cmdArgs[0]
cmdArgs = []string{"--help"}
}
}
if err := runCommand(cmd, cmdArgs); err != nil {
log.Fatalln("ERROR:", err)
}
}
type cmdFunc func(*docopt.Args, *client.Client) error
type command struct {
usage string
f cmdFunc
}
var commands = make(map[string]*command)
func register(name string, f cmdFunc, usage string) {
commands[name] = &command{usage: usage, f: f}
}
func runCommand(name string, args []string) error {
argv := make([]string, 1, 1+len(args))
argv[0] = name
argv = append(argv, args...)
cmd, ok := commands[name]
if !ok {
return fmt.Errorf("%s is not a tuf-client command. See 'tuf-client help'", name)
}
parsedArgs, err := docopt.Parse(cmd.usage, argv, true, "", true)
if err != nil {
return err
}
client, err := tufClient(parsedArgs)
if err != nil {
return err
}
return cmd.f(parsedArgs, client)
}
func tufClient(args *docopt.Args) (*client.Client, error) {
storePath, ok := args.String["--store"]
if !ok {
storePath = args.String["-s"]
}
local := store.FileSystemStore(storePath, nil)
remote, err := client.HTTPRemoteStore(args.String["<url>"], nil)
if err != nil {
return nil, err
}
return client.NewClient(local, remote), nil
}

View File

@ -1,36 +0,0 @@
package main
import (
// "encoding/json"
"github.com/endophage/gotuf"
"github.com/flynn/go-docopt"
)
func init() {
register("add", cmdAdd, `
usage: tuf add [--expires=<days>] [--custom=<data>] [<path>...]
Add target file(s).
Options:
--expires=<days> Set the targets manifest to expire <days> days from now.
--custom=<data> Set custom JSON data for the target(s).
`)
}
func cmdAdd(args *docopt.Args, repo *tuf.Repo) error {
// var custom json.RawMessage
// if c := args.String["--custom"]; c != "" {
// custom = json.RawMessage(c)
// }
paths := args.All["<path>"].([]string)
if arg := args.String["--expires"]; arg != "" {
expires, err := parseExpires(arg)
if err != nil {
return err
}
return repo.AddTargetsWithExpires(nil, expires, paths...)
}
return repo.AddTargets(nil, paths...)
}

View File

@ -1,18 +0,0 @@
package main
import (
"github.com/endophage/gotuf"
"github.com/flynn/go-docopt"
)
func init() {
register("clean", cmdClean, `
usage: tuf clean
Remove all staged manifests.
`)
}
func cmdClean(args *docopt.Args, repo *tuf.Repo) error {
return repo.Clean()
}

View File

@ -1,18 +0,0 @@
package main
import (
"github.com/endophage/gotuf"
"github.com/flynn/go-docopt"
)
func init() {
register("commit", cmdCommit, `
usage: tuf commit
Commit staged files to the repository.
`)
}
func cmdCommit(args *docopt.Args, repo *tuf.Repo) error {
return repo.Commit()
}

View File

@ -1,43 +0,0 @@
package main
import (
"fmt"
"github.com/endophage/gotuf"
"github.com/flynn/go-docopt"
)
func init() {
register("gen-key", cmdGenKey, `
usage: tuf gen-key [--expires=<days>] <role>
Generate a new signing key for the given role.
The key will be serialized to JSON and written to the "keys" directory with
filename pattern "ROLE-KEYID.json". The root manifest will also be staged
with the addition of the key's ID to the role's list of key IDs.
Options:
--expires=<days> Set the root manifest to expire <days> days from now.
`)
}
func cmdGenKey(args *docopt.Args, repo *tuf.Repo) error {
role := args.String["<role>"]
var id string
var err error
if arg := args.String["--expires"]; arg != "" {
expires, err := parseExpires(arg)
if err != nil {
return err
}
id, err = repo.GenKeyWithExpires(role, expires)
} else {
id, err = repo.GenKey(role)
}
if err != nil {
return err
}
fmt.Println("Generated", role, "key with ID", id)
return nil
}

View File

@ -1,23 +0,0 @@
package main
import (
"github.com/endophage/gotuf"
"github.com/flynn/go-docopt"
)
func init() {
register("init", cmdInit, `
usage: tuf init [--consistent-snapshot=false]
Initialize a new repository.
This is only required if the repository should not generate consistent
snapshots (i.e. by passing "--consistent-snapshot=false"). If consistent
snapshots should be generated, the repository will be implicitly
initialized to do so when generating keys.
`)
}
func cmdInit(args *docopt.Args, repo *tuf.Repo) error {
return repo.Init(args.String["--consistent-snapshot"] != "false")
}

View File

@ -1,167 +0,0 @@
package main
import (
"bufio"
"bytes"
"errors"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"
"github.com/docker/docker/pkg/term"
"github.com/flynn/go-docopt"
"github.com/endophage/gotuf"
"github.com/endophage/gotuf/signed"
"github.com/endophage/gotuf/store"
"github.com/endophage/gotuf/utils"
)
func main() {
log.SetFlags(0)
usage := `usage: tuf [-h|--help] [-d|--dir=<dir>] [--insecure-plaintext] <command> [<args>...]
Options:
-h, --help
-d <dir> The path to the repository (defaults to the current working directory)
--insecure-plaintext Don't encrypt signing keys
Commands:
help Show usage for a specific command
gen-key Generate a new signing key for a specific manifest
revoke-key Revoke a signing key
add Add target file(s)
remove Remove a target file
snapshot Update the snapshot manifest
timestamp Update the timestamp manifest
sign Sign a manifest
commit Commit staged files to the repository
regenerate Recreate the targets manifest
clean Remove all staged manifests
root-keys Output a JSON serialized array of root keys to STDOUT
See "tuf help <command>" for more information on a specific command
`
args, _ := docopt.Parse(usage, nil, true, "", true)
cmd := args.String["<command>"]
cmdArgs := args.All["<args>"].([]string)
if cmd == "help" {
if len(cmdArgs) == 0 { // `tuf help`
fmt.Println(usage)
return
} else { // `tuf help <command>`
cmd = cmdArgs[0]
cmdArgs = []string{"--help"}
}
}
dir, ok := args.String["-d"]
if !ok {
dir = args.String["--dir"]
}
if dir == "" {
var err error
dir, err = os.Getwd()
if err != nil {
log.Fatal(err)
}
}
if err := runCommand(cmd, cmdArgs, dir, args.Bool["--insecure-plaintext"]); err != nil {
log.Fatalln("ERROR:", err)
}
}
type cmdFunc func(*docopt.Args, *tuf.Repo) error
type command struct {
usage string
f cmdFunc
}
var commands = make(map[string]*command)
func register(name string, f cmdFunc, usage string) {
commands[name] = &command{usage: usage, f: f}
}
func runCommand(name string, args []string, dir string, insecure bool) error {
argv := make([]string, 1, 1+len(args))
argv[0] = name
argv = append(argv, args...)
cmd, ok := commands[name]
if !ok {
return fmt.Errorf("%s is not a tuf command. See 'tuf help'", name)
}
parsedArgs, err := docopt.Parse(cmd.usage, argv, true, "", true)
if err != nil {
return err
}
var p utils.PassphraseFunc
if !insecure {
p = getPassphrase
}
signer := signed.Ed25519{}
repo, err := tuf.NewRepo(&signer, store.FileSystemStore(dir, p), "sha256")
if err != nil {
return err
}
return cmd.f(parsedArgs, repo)
}
func parseExpires(arg string) (time.Time, error) {
days, err := strconv.Atoi(arg)
if err != nil {
return time.Time{}, fmt.Errorf("failed to parse --expires arg: %s", err)
}
return time.Now().AddDate(0, 0, days).UTC(), nil
}
func getPassphrase(role string, confirm bool) ([]byte, error) {
if pass := os.Getenv(fmt.Sprintf("TUF_%s_PASSPHRASE", strings.ToUpper(role))); pass != "" {
return []byte(pass), nil
}
state, err := term.SaveState(0)
if err != nil {
return nil, err
}
term.DisableEcho(0, state)
defer term.RestoreTerminal(0, state)
stdin := bufio.NewReader(os.Stdin)
fmt.Printf("Enter %s keys passphrase: ", role)
passphrase, err := stdin.ReadBytes('\n')
fmt.Println()
if err != nil {
return nil, err
}
passphrase = passphrase[0 : len(passphrase)-1]
if !confirm {
return passphrase, nil
}
fmt.Printf("Repeat %s keys passphrase: ", role)
confirmation, err := stdin.ReadBytes('\n')
fmt.Println()
if err != nil {
return nil, err
}
confirmation = confirmation[0 : len(confirmation)-1]
if !bytes.Equal(passphrase, confirmation) {
return nil, errors.New("The entered passphrases do not match")
}
return passphrase, nil
}

View File

@ -1,22 +0,0 @@
package main
import (
"log"
"github.com/endophage/gotuf"
"github.com/flynn/go-docopt"
)
func init() {
register("regenerate", cmdRegenerate, `
usage: tuf regenerate [--consistent-snapshot=false]
Recreate the targets manifest.
`)
}
func cmdRegenerate(args *docopt.Args, repo *tuf.Repo) error {
// TODO: implement this
log.Println("not implemented")
return nil
}

View File

@ -1,35 +0,0 @@
package main
import (
"errors"
"github.com/endophage/gotuf"
"github.com/flynn/go-docopt"
)
func init() {
register("remove", cmdRemove, `
usage: tuf remove [--expires=<days>] [--all] [<path>...]
Remove target file(s).
Options:
--all Remove all target files.
--expires=<days> Set the targets manifest to expire <days> days from now.
`)
}
func cmdRemove(args *docopt.Args, repo *tuf.Repo) error {
paths := args.All["<path>"].([]string)
if len(paths) == 0 && !args.Bool["--all"] {
return errors.New("either specify some paths or set the --all flag to remove all targets")
}
if arg := args.String["--expires"]; arg != "" {
expires, err := parseExpires(arg)
if err != nil {
return err
}
return repo.RemoveTargetsWithExpires(expires, paths...)
}
return repo.RemoveTargets(paths)
}

View File

@ -1,31 +0,0 @@
package main
import (
"github.com/endophage/gotuf"
"github.com/flynn/go-docopt"
)
func init() {
register("revoke-key", cmdRevokeKey, `
usage: tuf revoke-key [--expires=<days>] <role> <id>
Revoke a signing key
The key will be removed from the root manifest, but the key will remain in the
"keys" directory if present.
Options:
--expires=<days> Set the root manifest to expire <days> days from now.
`)
}
func cmdRevokeKey(args *docopt.Args, repo *tuf.Repo) error {
if arg := args.String["--expires"]; arg != "" {
expires, err := parseExpires(arg)
if err != nil {
return err
}
return repo.RevokeKeyWithExpires(args.String["<role>"], args.String["<id>"], expires)
}
return repo.RevokeKey(args.String["<role>"], args.String["<id>"])
}

View File

@ -1,27 +0,0 @@
package main
import (
"encoding/json"
"os"
"github.com/endophage/gotuf"
"github.com/flynn/go-docopt"
)
func init() {
register("root-keys", cmdRootKeys, `
usage: tuf root-keys
Outputs a JSON serialized array of root keys to STDOUT.
The resulting JSON should be distributed to clients for performing initial updates.
`)
}
func cmdRootKeys(args *docopt.Args, repo *tuf.Repo) error {
keys, err := repo.RootKeys()
if err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(keys)
}

View File

@ -1,18 +0,0 @@
package main
import (
"github.com/endophage/gotuf"
"github.com/flynn/go-docopt"
)
func init() {
register("sign", cmdSign, `
usage: tuf sign <manifest>
Sign a manifest.
`)
}
func cmdSign(args *docopt.Args, repo *tuf.Repo) error {
return repo.Sign(args.String["<manifest>"])
}

View File

@ -1,29 +0,0 @@
package main
import (
"github.com/endophage/gotuf"
"github.com/flynn/go-docopt"
)
func init() {
register("snapshot", cmdSnapshot, `
usage: tuf snapshot [--expires=<days>] [--compression=<format>]
Update the snapshot manifest.
Options:
--expires=<days> Set the snapshot manifest to expire <days> days from now.
`)
}
func cmdSnapshot(args *docopt.Args, repo *tuf.Repo) error {
// TODO: parse --compression
if arg := args.String["--expires"]; arg != "" {
expires, err := parseExpires(arg)
if err != nil {
return err
}
return repo.SnapshotWithExpires(tuf.CompressionTypeNone, expires)
}
return repo.Snapshot(tuf.CompressionTypeNone)
}

View File

@ -1,28 +0,0 @@
package main
import (
"github.com/endophage/gotuf"
"github.com/flynn/go-docopt"
)
func init() {
register("timestamp", cmdTimestamp, `
usage: tuf timestamp [--expires=<days>]
Update the timestamp manifest.
Options:
--expires=<days> Set the timestamp manifest to expire <days> days from now.
`)
}
func cmdTimestamp(args *docopt.Args, repo *tuf.Repo) error {
if arg := args.String["--expires"]; arg != "" {
expires, err := parseExpires(arg)
if err != nil {
return err
}
return repo.TimestampWithExpires(expires)
}
return repo.Timestamp()
}

View File

@ -10,7 +10,7 @@ import (
type Key interface {
ID() string
Cipher() string
Algorithm() KeyAlgorithm
Public() []byte
Private() []byte
}
@ -21,14 +21,14 @@ type KeyPair struct {
}
type TUFKey struct {
id string `json:"-"`
Type string `json:"keytype"`
Value KeyPair `json:"keyval"`
id string `json:"-"`
Type KeyAlgorithm `json:"keytype"`
Value KeyPair `json:"keyval"`
}
func NewTUFKey(cipher string, public, private []byte) *TUFKey {
func NewTUFKey(algorithm KeyAlgorithm, public, private []byte) *TUFKey {
return &TUFKey{
Type: cipher,
Type: algorithm,
Value: KeyPair{
Public: public,
Private: private,
@ -36,13 +36,13 @@ func NewTUFKey(cipher string, public, private []byte) *TUFKey {
}
}
func (k TUFKey) Cipher() string {
func (k TUFKey) Algorithm() KeyAlgorithm {
return k.Type
}
func (k *TUFKey) ID() string {
if k.id == "" {
pubK := NewTUFKey(k.Cipher(), k.Public(), nil)
pubK := NewTUFKey(k.Algorithm(), k.Public(), nil)
data, err := cjson.Marshal(&pubK)
if err != nil {
logrus.Error("Error generating key ID:", err)
@ -65,10 +65,10 @@ func (k PublicKey) Private() []byte {
return nil
}
func NewPublicKey(cipher string, public []byte) *PublicKey {
func NewPublicKey(algorithm KeyAlgorithm, public []byte) *PublicKey {
return &PublicKey{
TUFKey{
Type: cipher,
Type: algorithm,
Value: KeyPair{
Public: public,
Private: nil,
@ -87,10 +87,10 @@ type PrivateKey struct {
TUFKey
}
func NewPrivateKey(cipher string, public, private []byte) *PrivateKey {
func NewPrivateKey(algorithm KeyAlgorithm, public, private []byte) *PrivateKey {
return &PrivateKey{
TUFKey{
Type: cipher,
Type: algorithm,
Value: KeyPair{
Public: []byte(public),
Private: []byte(private),

View File

@ -8,21 +8,37 @@ import (
"hash"
"io"
"io/ioutil"
"strings"
"time"
"github.com/Sirupsen/logrus"
)
type KeyAlgorithm string
func (k KeyAlgorithm) String() string {
return string(k)
}
type SigAlgorithm string
func (k SigAlgorithm) String() string {
return string(k)
}
const (
defaultHashAlgorithm = "sha256"
EDDSASignature = "eddsa"
RSAPSSSignature = "rsapss"
ECDSASignature = "ecdsa"
RSAKey = "rsa"
RSAx509Key = "rsa-x509"
ECDSAKey = "ecdsa"
ECDSAx509Key = "ecdsa-x509"
PyCryptoSignature = "pycrypto-pkcs#1 pss"
EDDSASignature SigAlgorithm = "eddsa"
RSAPSSSignature SigAlgorithm = "rsapss"
ECDSASignature SigAlgorithm = "ecdsa"
PyCryptoSignature SigAlgorithm = "pycrypto-pkcs#1 pss"
ED25519Key KeyAlgorithm = "ed25519"
RSAKey KeyAlgorithm = "rsa"
RSAx509Key KeyAlgorithm = "rsa-x509"
ECDSAKey KeyAlgorithm = "ecdsa"
ECDSAx509Key KeyAlgorithm = "ecdsa-x509"
)
var TUFTypes = map[string]string{
@ -63,9 +79,9 @@ type Signed struct {
}
type Signature struct {
KeyID string `json:"keyid"`
Method string `json:"method"`
Signature HexBytes `json:"sig"`
KeyID string `json:"keyid"`
Method SigAlgorithm `json:"method"`
Signature HexBytes `json:"sig"`
}
type Files map[string]FileMeta
@ -144,3 +160,16 @@ func DefaultExpires(role string) time.Time {
}
return t.UTC().Round(time.Second)
}
type unmarshalledSignature Signature
func (s *Signature) UnmarshalJSON(data []byte) error {
uSignature := unmarshalledSignature{}
err := json.Unmarshal(data, &uSignature)
if err != nil {
return err
}
uSignature.Method = SigAlgorithm(strings.ToLower(string(uSignature.Method)))
*s = Signature(uSignature)
return nil
}

View File

@ -2,6 +2,7 @@ package data
import (
"bytes"
"encoding/json"
. "gopkg.in/check.v1"
)
@ -43,3 +44,14 @@ func (TypesSuite) TestGenerateFileMetaExplicit(c *C) {
c.Assert(hash.String(), DeepEquals, val)
}
}
func (TypesSuite) TestSignatureUnmarshalJSON(c *C) {
signatureJSON := `{"keyid":"97e8e1b51b6e7cf8720a56b5334bd8692ac5b28233c590b89fab0b0cd93eeedc","method":"RSA","sig":"2230cba525e4f5f8fc744f234221ca9a92924da4cc5faf69a778848882fcf7a20dbb57296add87f600891f2569a9c36706314c240f9361c60fd36f5a915a0e9712fc437b761e8f480868d7a4444724daa0d29a2669c0edbd4046046649a506b3d711d0aa5e70cb9d09dec7381e7de27a3168e77731e08f6ed56fcce2478855e837816fb69aff53412477748cd198dce783850080d37aeb929ad0f81460ebd31e61b772b6c7aa56977c787d4281fa45dbdefbb38d449eb5bccb2702964a52c78811545939712c8280dee0b23b2fa9fbbdd6a0c42476689ace655eba0745b4a21ba108bcd03ad00fdefff416dc74e08486a0538f8fd24989e1b9fc89e675141b7c"}`
var sig Signature
err := json.Unmarshal([]byte(signatureJSON), &sig)
c.Assert(err, IsNil)
// Check that the method string is lowercased
c.Assert(sig.Method.String(), Equals, "rsa")
}

View File

@ -2,6 +2,7 @@ package signed
import (
"crypto/rand"
"errors"
"github.com/agl/ed25519"
"github.com/endophage/gotuf/data"
@ -37,7 +38,7 @@ func (trust *Ed25519) Sign(keyIDs []string, toSign []byte) ([]data.Signature, er
sig := ed25519.Sign(&priv, toSign)
signatures = append(signatures, data.Signature{
KeyID: kID,
Method: "ED25519",
Method: data.EDDSASignature,
Signature: sig[:],
})
}
@ -45,13 +46,17 @@ func (trust *Ed25519) Sign(keyIDs []string, toSign []byte) ([]data.Signature, er
}
func (trust *Ed25519) Create(role string) (*data.PublicKey, error) {
func (trust *Ed25519) Create(role string, algorithm data.KeyAlgorithm) (*data.PublicKey, error) {
if algorithm != data.ED25519Key {
return nil, errors.New("only ED25519 supported by this cryptoservice")
}
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
public := data.NewPublicKey("ED25519", pub[:])
private := data.NewPrivateKey("ED25519", pub[:], priv[:])
public := data.NewPublicKey(data.ED25519Key, pub[:])
private := data.NewPrivateKey(data.ED25519Key, pub[:], priv[:])
trust.addKey(private)
return public, nil
}

View File

@ -18,10 +18,9 @@ type SigningService interface {
type KeyService interface {
// Create issues a new key pair and is responsible for loading
// the private key into the appropriate signing service.
Create(role string) (*data.PublicKey, error)
// PublicKeys return the PublicKey instances for the given KeyIDs
// PublicKeys(keyIDs ...string) (map[string]*data.PublicKey, error)
// The role isn't currently used for anything, but it's here to support
// future features
Create(role string, algorithm data.KeyAlgorithm) (*data.PublicKey, error)
}
// CryptoService defines a unified Signing and Key Service as this

View File

@ -5,19 +5,9 @@ import (
"github.com/endophage/gotuf/data"
)
// Signer encapsulates a signing service with some convenience methods to
// interface between TUF keys and the generic service interface
type Signer struct {
service CryptoService
}
func NewSigner(service CryptoService) *Signer {
return &Signer{service}
}
// Sign takes a data.Signed and a key, calculated and adds the signature
// to the data.Signed
func (signer *Signer) Sign(s *data.Signed, keys ...*data.PublicKey) error {
func Sign(service CryptoService, s *data.Signed, keys ...*data.PublicKey) error {
logrus.Debugf("sign called with %d keys", len(keys))
signatures := make([]data.Signature, 0, len(s.Signatures)+1)
keyIDMemb := make(map[string]struct{})
@ -34,7 +24,7 @@ func (signer *Signer) Sign(s *data.Signed, keys ...*data.PublicKey) error {
}
signatures = append(signatures, sig)
}
newSigs, err := signer.service.Sign(keyIDs, s.Signed)
newSigs, err := service.Sign(keyIDs, s.Signed)
if err != nil {
return err
}
@ -42,12 +32,3 @@ func (signer *Signer) Sign(s *data.Signed, keys ...*data.PublicKey) error {
s.Signatures = append(signatures, newSigs...)
return nil
}
func (signer *Signer) Create(role string) (*data.PublicKey, error) {
key, err := signer.service.Create(role)
return key, err
}
//func (signer *Signer) PublicKeys(keyIDs ...string) (map[string]*data.PublicKey, error) {
// return signer.service.PublicKeys(keyIDs...)
//}

View File

@ -27,7 +27,7 @@ func (mts *MockCryptoService) Sign(keyIDs []string, _ []byte) ([]data.Signature,
return sigs, nil
}
func (mts *MockCryptoService) Create(_ string) (*data.PublicKey, error) {
func (mts *MockCryptoService) Create(_ string, _ data.KeyAlgorithm) (*data.PublicKey, error) {
return &mts.testKey, nil
}
@ -42,16 +42,14 @@ var _ CryptoService = &MockCryptoService{}
func TestBasicSign(t *testing.T) {
testKey, _ := pem.Decode([]byte(testKeyPEM1))
k := data.NewPublicKey(data.RSAKey, testKey.Bytes)
signer := Signer{&MockCryptoService{
testKey: *k,
}}
key, err := signer.Create("root")
mockCryptoService := &MockCryptoService{testKey: *k}
key, err := mockCryptoService.Create("root", data.ED25519Key)
if err != nil {
t.Fatal(err)
}
testData := data.Signed{}
signer.Sign(&testData, key)
Sign(mockCryptoService, &testData, key)
if len(testData.Signatures) != 1 {
t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures))
@ -69,13 +67,11 @@ func TestBasicSign(t *testing.T) {
func TestReSign(t *testing.T) {
testKey, _ := pem.Decode([]byte(testKeyPEM1))
k := data.NewPublicKey(data.RSAKey, testKey.Bytes)
signer := Signer{&MockCryptoService{
testKey: *k,
}}
mockCryptoService := &MockCryptoService{testKey: *k}
testData := data.Signed{}
signer.Sign(&testData, k)
signer.Sign(&testData, k)
Sign(mockCryptoService, &testData, k)
Sign(mockCryptoService, &testData, k)
if len(testData.Signatures) != 1 {
t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures))
@ -88,16 +84,16 @@ func TestReSign(t *testing.T) {
}
func TestMultiSign(t *testing.T) {
signer := Signer{&MockCryptoService{}}
mockCryptoService := &MockCryptoService{}
testData := data.Signed{}
testKey, _ := pem.Decode([]byte(testKeyPEM1))
key := data.NewPublicKey(data.RSAKey, testKey.Bytes)
signer.Sign(&testData, key)
Sign(mockCryptoService, &testData, key)
testKey, _ = pem.Decode([]byte(testKeyPEM2))
key = data.NewPublicKey(data.RSAKey, testKey.Bytes)
signer.Sign(&testData, key)
Sign(mockCryptoService, &testData, key)
if len(testData.Signatures) != 2 {
t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures))
@ -115,11 +111,9 @@ func TestMultiSign(t *testing.T) {
func TestCreate(t *testing.T) {
testKey, _ := pem.Decode([]byte(testKeyPEM1))
k := data.NewPublicKey(data.RSAKey, testKey.Bytes)
signer := Signer{&MockCryptoService{
testKey: *k,
}}
mockCryptoService := &MockCryptoService{testKey: *k}
key, err := signer.Create("root")
key, err := mockCryptoService.Create("root", data.ED25519Key)
if err != nil {
t.Fatal(err)

View File

@ -18,7 +18,7 @@ import (
// Verifiers serves as a map of all verifiers available on the system and
// can be injected into a verificationService. For testing and configuration
// purposes, it will not be used by default.
var Verifiers = map[string]Verifier{
var Verifiers = map[data.SigAlgorithm]Verifier{
data.RSAPSSSignature: RSAPSSVerifier{},
data.PyCryptoSignature: RSAPyCryptoVerifier{},
data.ECDSASignature: ECDSAVerifier{},
@ -27,8 +27,8 @@ var Verifiers = map[string]Verifier{
// RegisterVerifier provides a convenience function for init() functions
// to register additional verifiers or replace existing ones.
func RegisterVerifier(name string, v Verifier) {
curr, ok := Verifiers[name]
func RegisterVerifier(algorithm data.SigAlgorithm, v Verifier) {
curr, ok := Verifiers[algorithm]
if ok {
typOld := reflect.TypeOf(curr)
typNew := reflect.TypeOf(v)
@ -38,9 +38,9 @@ func RegisterVerifier(name string, v Verifier) {
typNew.PkgPath(), typNew.Name(),
)
} else {
logrus.Debug("adding verifier for: ", name)
logrus.Debug("adding verifier for: ", algorithm)
}
Verifiers[name] = v
Verifiers[algorithm] = v
}
type Ed25519Verifier struct{}
@ -83,10 +83,10 @@ type RSAPSSVerifier struct{}
// Verify does the actual check.
func (v RSAPSSVerifier) Verify(key data.Key, sig []byte, msg []byte) error {
cipher := key.Cipher()
algorithm := key.Algorithm()
var pubKey crypto.PublicKey
switch cipher {
switch algorithm {
case data.RSAx509Key:
pemCert, _ := pem.Decode([]byte(key.Public()))
if pemCert == nil {
@ -107,7 +107,7 @@ func (v RSAPSSVerifier) Verify(key data.Key, sig []byte, msg []byte) error {
return ErrInvalid
}
default:
logrus.Infof("invalid key type for RSAPSS verifier: %s", cipher)
logrus.Infof("invalid key type for RSAPSS verifier: %s", algorithm)
return ErrInvalid
}
@ -145,10 +145,10 @@ type ECDSAVerifier struct{}
// Verify does the actual check.
func (v ECDSAVerifier) Verify(key data.Key, sig []byte, msg []byte) error {
cipher := key.Cipher()
algorithm := key.Algorithm()
var pubKey crypto.PublicKey
switch cipher {
switch algorithm {
case data.ECDSAx509Key:
pemCert, _ := pem.Decode([]byte(key.Public()))
if pemCert == nil {
@ -170,7 +170,7 @@ func (v ECDSAVerifier) Verify(key data.Key, sig []byte, msg []byte) error {
return ErrInvalid
}
default:
logrus.Infof("invalid key type for ECDSA verifier: %s", cipher)
logrus.Infof("invalid key type for ECDSA verifier: %s", algorithm)
return ErrInvalid
}

View File

@ -19,7 +19,7 @@ import (
)
type KeyTemplate struct {
KeyType string
KeyType data.KeyAlgorithm
}
const baseRSAKey = `{"keytype":"{{.KeyType}}","keyval":{"public":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyyvBtTg2xzYS+MTTIBqSpI4V78tt8Yzqi7Jki/Z6NqjiDvcnbgcTqNR2t6B2W5NjGdp/hSaT2jyHM+kdmEGaPxg/zIuHbL3NIp4e0qwovWiEgACPIaELdn8O/kt5swsSKl1KMvLCH1sM86qMibNMAZ/hXOwd90TcHXCgZ91wHEAmsdjDC3dB0TT+FBgOac8RM01Y196QrZoOaDMTWh0EQfw7YbXAElhFVDFxBzDdYWbcIHSIogXQmq0CP+zaL/1WgcZZIClt2M6WCaxxF1S34wNn45gCvVZiZQ/iKWHerSr/2dGQeGo+7ezMSutRzvJ+01fInD86RS/CEtBCFZ1VyQIDAQAB","private":"MIIEpAIBAAKCAQEAyyvBtTg2xzYS+MTTIBqSpI4V78tt8Yzqi7Jki/Z6NqjiDvcnbgcTqNR2t6B2W5NjGdp/hSaT2jyHM+kdmEGaPxg/zIuHbL3NIp4e0qwovWiEgACPIaELdn8O/kt5swsSKl1KMvLCH1sM86qMibNMAZ/hXOwd90TcHXCgZ91wHEAmsdjDC3dB0TT+FBgOac8RM01Y196QrZoOaDMTWh0EQfw7YbXAElhFVDFxBzDdYWbcIHSIogXQmq0CP+zaL/1WgcZZIClt2M6WCaxxF1S34wNn45gCvVZiZQ/iKWHerSr/2dGQeGo+7ezMSutRzvJ+01fInD86RS/CEtBCFZ1VyQIDAQABAoIBAHar8FFxrE1gAGTeUpOF8fG8LIQMRwO4U6eVY7V9GpWiv6gOJTHXYFxU/aL0Ty3eQRxwy9tyVRo8EJz5pRex+e6ws1M+jLOviYqW4VocxQ8dZYd+zBvQfWmRfah7XXJ/HPUx2I05zrmR7VbGX6Bu4g5w3KnyIO61gfyQNKF2bm2Q3yblfupx3URvX0bl180R/+QN2Aslr4zxULFE6b+qJqBydrztq+AAP3WmskRxGa6irFnKxkspJqUpQN1mFselj6iQrzAcwkRPoCw0RwCCMq1/OOYvQtgxTJcO4zDVlbw54PvnxPZtcCWw7fO8oZ2Fvo2SDo75CDOATOGaT4Y9iqECgYEAzWZSpFbN9ZHmvq1lJQg//jFAyjsXRNn/nSvyLQILXltz6EHatImnXo3v+SivG91tfzBI1GfDvGUGaJpvKHoomB+qmhd8KIQhO5MBdAKZMf9fZqZofOPTD9xRXECCwdi+XqHBmL+l1OWz+O9Bh+Qobs2as/hQVgHaoXhQpE0NkTcCgYEA/Tjf6JBGl1+WxQDoGZDJrXoejzG9OFW19RjMdmPrg3t4fnbDtqTpZtCzXxPTCSeMrvplKbqAqZglWyq227ksKw4p7O6YfyhdtvC58oJmivlLr6sFaTsER7mDcYce8sQpqm+XQ8IPbnOk0Z1l6g56euTwTnew49uy25M6U1xL0P8CgYEAxEXv2Kw+OVhHV5PX4BBHHj6we88FiDyMfwM8cvfOJ0datekf9X7ImZkmZEAVPJpWBMD+B0J0jzU2b4SLjfFVkzBHVOH2Ob0xCH2MWPAWtekin7OKizUlPbW5ZV8b0+Kq30DQ/4a7D3rEhK8UPqeuX1tHZox1MAqrgbq3zJj4yvcCgYEAktYPKPm4pYCdmgFrlZ+bA0iEPf7Wvbsd91F5BtHsOOM5PQQ7e0bnvWIaEXEad/2CG9lBHlBy2WVLjDEZthILpa/h6e11ao8KwNGY0iKBuebT17rxOVMqqTjPGt8CuD2994IcEgOPFTpkAdUmyvG4XlkxbB8F6St17NPUB5DGuhsCgYA//Lfytk0FflXEeRQ16LT1YXgV7pcR2jsha4+4O5pxSFw/kTsOfJaYHg8StmROoyFnyE3sg76dCgLn0LENRCe5BvDhJnp5bMpQldG3XwcAxH8FGFNY4LtV/2ZKnJhxcONkfmzQPOmTyedOzrKQ+bNURsqLukCypP7/by6afBY4dA=="}}`
@ -248,8 +248,8 @@ func TestECDSAVerifierWithInvalidSignature(t *testing.T) {
}
func rsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
if privKey.Cipher() != data.RSAKey {
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
if privKey.Algorithm() != data.RSAKey {
return nil, fmt.Errorf("private key type not supported: %s", privKey.Algorithm())
}
// Create an rsa.PrivateKey out of the private key bytes
@ -268,8 +268,8 @@ func rsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte,
}
func ecdsaSign(privKey *data.PrivateKey, hashed []byte) ([]byte, error) {
if privKey.Cipher() != data.ECDSAKey {
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
if privKey.Algorithm() != data.ECDSAKey {
return nil, fmt.Errorf("private key type not supported: %s", privKey.Algorithm())
}
// Create an ecdsa.PrivateKey out of the private key bytes

View File

@ -3,7 +3,6 @@ package signed
import (
"encoding/json"
"errors"
"strings"
"time"
"github.com/Sirupsen/logrus"
@ -45,8 +44,8 @@ func VerifyRoot(s *data.Signed, minVersion int, keys map[string]*data.PublicKey,
valid := make(map[string]struct{})
for _, sig := range s.Signatures {
// make method lookup consistent with case uniformity.
method := strings.ToLower(sig.Method)
// method lookup is consistent due to Unmarshal JSON doing lower case for us.
method := sig.Method
verifier, ok := Verifiers[method]
if !ok {
logrus.Debugf("continuing b/c signing method is not supported for verify root: %s\n", sig.Method)
@ -133,8 +132,8 @@ func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error {
logrus.Debugf("continuing b/c keyid lookup was nil: %s\n", sig.KeyID)
continue
}
// make method lookup consistent with case uniformity.
method := strings.ToLower(sig.Method)
// method lookup is consistent due to Unmarshal JSON doing lower case for us.
method := sig.Method
verifier, ok := Verifiers[method]
if !ok {
logrus.Debugf("continuing b/c signing method is not supported: %s\n", sig.Method)

View File

@ -20,7 +20,6 @@ var _ = Suite(&VerifySuite{})
func (VerifySuite) Test(c *C) {
trust := NewEd25519()
signer := NewSigner(trust)
type test struct {
name string
keys []*data.PublicKey
@ -78,8 +77,8 @@ func (VerifySuite) Test(c *C) {
{
name: "more than enough signatures",
mut: func(t *test) {
k, _ := signer.Create("root")
signer.Sign(t.s, k)
k, _ := trust.Create("root", data.ED25519Key)
Sign(trust, t.s, k)
t.keys = append(t.keys, k)
t.roles["root"].KeyIDs = append(t.roles["root"].KeyIDs, k.ID())
},
@ -95,15 +94,15 @@ func (VerifySuite) Test(c *C) {
{
name: "unknown key",
mut: func(t *test) {
k, _ := signer.Create("root")
signer.Sign(t.s, k)
k, _ := trust.Create("root", data.ED25519Key)
Sign(trust, t.s, k)
},
},
{
name: "unknown key below threshold",
mut: func(t *test) {
k, _ := signer.Create("root")
signer.Sign(t.s, k)
k, _ := trust.Create("root", data.ED25519Key)
Sign(trust, t.s, k)
t.roles["root"].Threshold = 2
},
err: ErrRoleThreshold,
@ -111,16 +110,16 @@ func (VerifySuite) Test(c *C) {
{
name: "unknown keys in db",
mut: func(t *test) {
k, _ := signer.Create("root")
signer.Sign(t.s, k)
k, _ := trust.Create("root", data.ED25519Key)
Sign(trust, t.s, k)
t.keys = append(t.keys, k)
},
},
{
name: "unknown keys in db below threshold",
mut: func(t *test) {
k, _ := signer.Create("root")
signer.Sign(t.s, k)
k, _ := trust.Create("root", data.ED25519Key)
Sign(trust, t.s, k)
t.keys = append(t.keys, k)
t.roles["root"].Threshold = 2
},
@ -157,13 +156,13 @@ func (VerifySuite) Test(c *C) {
t.typ = data.TUFTypes[t.role]
}
if t.keys == nil && t.s == nil {
k, _ := signer.Create("root")
k, _ := trust.Create("root", data.ED25519Key)
meta := &signedMeta{Type: t.typ, Version: t.ver, Expires: t.exp.Format("2006-01-02 15:04:05 MST")}
b, err := cjson.Marshal(meta)
c.Assert(err, IsNil)
s := &data.Signed{Signed: b}
signer.Sign(s, k)
Sign(trust, s, k)
t.s = s
t.keys = []*data.PublicKey{k}
}

View File

@ -4,7 +4,6 @@ import (
"encoding/hex"
"encoding/json"
"net/http"
"strings"
"testing"
"github.com/tent/canonical-json-go"
@ -55,7 +54,7 @@ func TestGetMeta(t *testing.T) {
if err != nil {
t.Fatal(err)
}
method := strings.ToLower(p.Signatures[0].Method)
method := p.Signatures[0].Method
err = signed.Verifiers[method].Verify(k, sigBytes, msg)
if err != nil {
t.Fatal(err)
@ -68,13 +67,13 @@ func TestPyCryptoRSAPSSCompat(t *testing.T) {
//privPem := "-----BEGIN RSA PRIVATE KEY-----\nMIIG4wIBAAKCAYEAnKuXZeefa2LmgxaL5NsMzKOHNe+x/nL6ik+lDBCTV6OdcwAh\nHQS+PONGhrChIUVR6Vth3hUCrreLzPO73Oo5VSCuRJ53UronENl6lsa5mFKP8StY\nLvIDITNvkoT3j52BJIjyNUK9UKY9As2TNqDfBEPIRp28ev/NViwGOEkBu2UAbwCI\ndnDXm8JQErCZA0Ydm7PKGgjLbFsFGrVzqXHK6pdzJXlhr9yap3UpgQ/iO9JtoEYB\n2EXsnSrPc9JRjR30bNHHtnVql3fvinXrAEwq3xmN4p+R4VGzfdQN+8Kl/IPjqWB5\n35twhFYEG/B7Ze8IwbygBjK3co/KnOPqMUrMBI8ztvPiogz+MvXb8WvarZ6TMTh8\nifZI96r7zzqyzjR1hJulEy3IsMGvz8XS2J0X7sXoaqszEtXdq5ef5zKVxkiyIQZc\nbPgmpHLq4MgfdryuVVc/RPASoRIXG4lKaTJj1ANMFPxDQpHudCLxwCzjCb+sVa20\nHBRPTnzo8LSZkI6jAgMBAAECggGAdzyI7z/HLt2IfoAsXDLynNRgVYZluzgawiU3\ngeUjnnGhpSKWERXJC2IWDPBk0YOGgcnQxErNTdfXiFZ/xfRlSgqjVwob2lRe4w4B\npLr+CZXcgznv1VrPUvdolOSp3R2Mahfn7u0qVDUQ/g8jWVI6KW7FACmQhzQkPM8o\ntLGrpcmK+PA465uaHKtYccEB02ILqrK8v++tknv7eIZczrsSKlS1h/HHjSaidYxP\n2DAUiF7wnChrwwQEvuEUHhwVgQcoDMBoow0zwHdbFiFO2ZT54H2oiJWLhpR/x6RK\ngM1seqoPH2sYErPJACMcYsMtF4Tx7b5c4WSj3vDCGb+jeqnNS6nFC3aMnv75mUS2\nYDPU1heJFd8pNHVf0RDejLZZUiJSnXf3vpOxt9Xv2+4He0jeMfLV7zX0mO2Ni3MJ\nx6PiVy4xerHImOuuHzSla5crOq2ECiAxd1wEOFDRD2LRHzfhpk1ghiA5xA1qwc7Z\neRnkVfoy6PPZ4lZakZTm0p8YCQURAoHBAMUIC/7vnayLae7POmgy+np/ty7iMfyd\nV1eO6LTO21KAaGGlhaY26WD/5LcG2FUgc5jKKahprGrmiNLzLUeQPckJmuijSEVM\nl/4DlRvCo867l7fLaVqYzsQBBdeGIFNiT+FBOd8atff87ZBEfH/rXbDi7METD/VR\n4TdblnCsKYAXEJUdkw3IK7SUGERiQZIwKXrH/Map4ibDrljJ71iCgEureU0DBwcg\nwLftmjGMISoLscdRxeubX5uf/yxtHBJeRwKBwQDLjzHhb4gNGdBHUl4hZPAGCq1V\nLX/GpfoOVObW64Lud+tI6N9GNua5/vWduL7MWWOzDTMZysganhKwsJCY5SqAA9p0\nb6ohusf9i1nUnOa2F2j+weuYPXrTYm+ZrESBBdaEJPuj3R5YHVujrBA9Xe0kVOe3\nne151A+0xJOI3tX9CttIaQAsXR7cMDinkDITw6i7X4olRMPCSixHLW97cDsVDRGt\necO1d4dP3OGscN+vKCoL6tDKDotzWHYPwjH47sUCgcEAoVI8WCiipbKkMnaTsNsE\ngKXvO0DSgq3k5HjLCbdQldUzIbgfnH7bSKNcBYtiNxjR7OihgRW8qO5GWsnmafCs\n1dy6a/2835id3cnbHRaZflvUFhVDFn2E1bCsstFLyFn3Y0w/cO9yzC/X5sZcVXRF\nit3R0Selakv3JZckru4XMJwx5JWJYMBjIIAc+miknWg3niL+UT6pPun65xG3mXWI\nS+yC7c4rw+dKQ44UMLs2MDHRBoxqi8T0W/x9NkfDszpjAoHAclH7S4ZdvC3RIR0L\nLGoJuvroGbwx1JiGdOINuooNwGuswge2zTIsJi0gN/H3hcB2E6rIFiYid4BrMrwW\nmSeq1LZVS6siu0qw4p4OVy+/CmjfWKQD8j4k6u6PipiK6IMk1JYIlSCr2AS04JjT\njgNgGVVtxVt2cUM9huIXkXjEaRZdzK7boA60NCkIyGJdHWh3LLQdW4zg/A64C0lj\nIMoJBGuQkAKgfRuh7KI6Q6Qom7BM3OCFXdUJUEBQHc2MTyeZAoHAJdBQGBn1RFZ+\nn75AnbTMZJ6Twp2fVjzWUz/+rnXFlo87ynA18MR2BzaDST4Bvda29UBFGb32Mux9\nOHukqLgIE5jDuqWjy4B5eCoxZf/OvwlgXkX9+gprGR3axn/PZBFPbFB4ZmjbWLzn\nbocn7FJCXf+Cm0cMmv1jIIxej19MUU/duq9iq4RkHY2LG+KrSEQIUVmImCftXdN3\n/qNP5JetY0eH6C+KRc8JqDB0nvbqZNOgYXOfYXo/5Gk8XIHTFihm\n-----END RSA PRIVATE KEY-----"
testStr := "The quick brown fox jumps over the lazy dog."
sigHex := "4e05ee9e435653549ac4eddbc43e1a6868636e8ea6dbec2564435afcb0de47e0824cddbd88776ddb20728c53ecc90b5d543d5c37575fda8bd0317025fc07de62ee8084b1a75203b1a23d1ef4ac285da3d1fc63317d5b2cf1aafa3e522acedd366ccd5fe4a7f02a42922237426ca3dc154c57408638b9bfaf0d0213855d4e9ee621db204151bcb13d4dbb18f930ec601469c992c84b14e9e0b6f91ac9517bb3b749dd117e1cbac2e4acb0e549f44558a2005898a226d5b6c8b9291d7abae0d9e0a16858b89662a085f74a202deb867acab792bdbd2c36731217caea8b17bd210c29b890472f11e5afdd1dd7b69004db070e04201778f2c49f5758643881403d45a58d08f51b5c63910c6185892f0b590f191d760b669eff2464456f130239bba94acf54a0cb98f6939ff84ae26a37f9b890be259d9b5d636f6eb367b53e895227d7d79a3a88afd6d28c198ee80f6527437c5fbf63accb81709925c4e03d1c9eaee86f58e4bd1c669d6af042dbd412de0d13b98b1111e2fadbe34b45de52125e9a"
k := data.NewPublicKey("RSA", []byte(pubPem))
k := data.NewPublicKey(data.RSAKey, []byte(pubPem))
sigBytes, err := hex.DecodeString(sigHex)
if err != nil {
t.Fatal(err)
}
v := signed.RSAPSSVerifier{}
v := signed.RSAPyCryptoVerifier{}
err = v.Verify(k, sigBytes, []byte(testStr))
if err != nil {
t.Fatal(err)
@ -88,11 +87,11 @@ func TestPyNaCled25519Compat(t *testing.T) {
sigHex := "166e7013e48f26dccb4e68fe4cf558d1cd3af902f8395534336a7f8b4c56588694aa3ac671767246298a59d5ef4224f02c854f41bfcfe70241db4be1546d6a00"
pub, _ := hex.DecodeString(pubHex)
k := data.NewPublicKey("ED25519", pub)
k := data.NewPublicKey(data.ED25519Key, pub)
sigBytes, _ := hex.DecodeString(sigHex)
err := signed.Verifiers["ED25519"].Verify(k, sigBytes, []byte(testStr))
err := signed.Verifiers[data.EDDSASignature].Verify(k, sigBytes, []byte(testStr))
if err != nil {
t.Fatal(err)
}

View File

@ -50,21 +50,21 @@ func (err *ErrNotLoaded) Error() string {
// fetching raw JSON and using the Set* functions to populate
// the TufRepo instance.
type TufRepo struct {
Root *data.SignedRoot
Targets map[string]*data.SignedTargets
Snapshot *data.SignedSnapshot
Timestamp *data.SignedTimestamp
keysDB *keys.KeyDB
signer *signed.Signer
Root *data.SignedRoot
Targets map[string]*data.SignedTargets
Snapshot *data.SignedSnapshot
Timestamp *data.SignedTimestamp
keysDB *keys.KeyDB
cryptoService signed.CryptoService
}
// NewTufRepo initializes a TufRepo instance with a keysDB and a signer.
// If the TufRepo will only be used for reading, the signer should be nil.
func NewTufRepo(keysDB *keys.KeyDB, signer *signed.Signer) *TufRepo {
func NewTufRepo(keysDB *keys.KeyDB, cryptoService signed.CryptoService) *TufRepo {
repo := &TufRepo{
Targets: make(map[string]*data.SignedTargets),
keysDB: keysDB,
signer: signer,
Targets: make(map[string]*data.SignedTargets),
keysDB: keysDB,
cryptoService: cryptoService,
}
return repo
}
@ -75,7 +75,7 @@ func (tr *TufRepo) AddBaseKeys(role string, keys ...data.Key) error {
return &ErrNotLoaded{role: "root"}
}
for _, k := range keys {
key := data.NewPublicKey(k.Cipher(), k.Public())
key := data.NewPublicKey(k.Algorithm(), k.Public())
tr.Root.Signed.Keys[key.ID()] = key
tr.keysDB.AddKey(key)
tr.Root.Signed.Roles[role].KeyIDs = append(tr.Root.Signed.Roles[role].KeyIDs, key.ID())
@ -142,7 +142,7 @@ func (tr *TufRepo) UpdateDelegations(role *data.Role, keys []data.Key, before st
return errors.ErrInvalidRole{}
}
for _, k := range keys {
key := data.NewPublicKey(k.Cipher(), k.Public())
key := data.NewPublicKey(k.Algorithm(), k.Public())
if !utils.StrSliceContains(role.KeyIDs, key.ID()) {
role.KeyIDs = append(role.KeyIDs, key.ID())
}
@ -203,7 +203,7 @@ func (tr *TufRepo) InitRoot(consistent bool) error {
// checked by KeyDB when role was added.
key := tr.keysDB.GetKey(kid)
// Create new key object to doubly ensure private key is excluded
k := data.NewPublicKey(key.Cipher(), key.Public())
k := data.NewPublicKey(key.Algorithm(), key.Public())
rootKeys[kid] = k
}
}
@ -449,7 +449,7 @@ func (tr *TufRepo) UpdateTimestamp(s *data.Signed) error {
return nil
}
func (tr *TufRepo) SignRoot(expires time.Time, signer *signed.Signer) (*data.Signed, error) {
func (tr *TufRepo) SignRoot(expires time.Time, cryptoService signed.CryptoService) (*data.Signed, error) {
logrus.Debug("signing root...")
if tr.Root.Dirty {
tr.Root.Signed.Version++
@ -459,7 +459,7 @@ func (tr *TufRepo) SignRoot(expires time.Time, signer *signed.Signer) (*data.Sig
if err != nil {
return nil, err
}
signed, err = tr.sign(signed, *root, signer)
signed, err = tr.sign(signed, *root, cryptoService)
if err != nil {
return nil, err
}
@ -467,7 +467,7 @@ func (tr *TufRepo) SignRoot(expires time.Time, signer *signed.Signer) (*data.Sig
return signed, nil
}
func (tr *TufRepo) SignTargets(role string, expires time.Time, signer *signed.Signer) (*data.Signed, error) {
func (tr *TufRepo) SignTargets(role string, expires time.Time, cryptoService signed.CryptoService) (*data.Signed, error) {
logrus.Debugf("sign targets called for role %s", role)
if tr.Targets[role].Dirty {
tr.Targets[role].Signed.Version++
@ -477,7 +477,7 @@ func (tr *TufRepo) SignTargets(role string, expires time.Time, signer *signed.Si
return nil, err
}
targets := tr.keysDB.GetRole(role)
signed, err = tr.sign(signed, *targets, signer)
signed, err = tr.sign(signed, *targets, cryptoService)
if err != nil {
logrus.Debug("errored signing ", role)
return nil, err
@ -494,10 +494,10 @@ func (tr *TufRepo) SignTargets(role string, expires time.Time, signer *signed.Si
}
}
func (tr *TufRepo) SignSnapshot(expires time.Time, signer *signed.Signer) (*data.Signed, error) {
func (tr *TufRepo) SignSnapshot(expires time.Time, cryptoService signed.CryptoService) (*data.Signed, error) {
logrus.Debug("signing snapshot...")
if tr.Root.Dirty {
signedRoot, err := tr.SignRoot(data.DefaultExpires("root"), signer)
signedRoot, err := tr.SignRoot(data.DefaultExpires("root"), cryptoService)
if err != nil {
return nil, err
}
@ -511,7 +511,7 @@ func (tr *TufRepo) SignSnapshot(expires time.Time, signer *signed.Signer) (*data
if !targets.Dirty {
continue
}
signedTargets, err := tr.SignTargets(role, data.DefaultExpires("targets"), signer)
signedTargets, err := tr.SignTargets(role, data.DefaultExpires("targets"), cryptoService)
if err != nil {
return nil, err
}
@ -528,7 +528,7 @@ func (tr *TufRepo) SignSnapshot(expires time.Time, signer *signed.Signer) (*data
return nil, err
}
snapshot := tr.keysDB.GetRole(data.ValidRoles["snapshot"])
signed, err = tr.sign(signed, *snapshot, signer)
signed, err = tr.sign(signed, *snapshot, cryptoService)
if err != nil {
return nil, err
}
@ -543,10 +543,10 @@ func (tr *TufRepo) SignSnapshot(expires time.Time, signer *signed.Signer) (*data
}
}
func (tr *TufRepo) SignTimestamp(expires time.Time, signer *signed.Signer) (*data.Signed, error) {
func (tr *TufRepo) SignTimestamp(expires time.Time, cryptoService signed.CryptoService) (*data.Signed, error) {
logrus.Debug("SignTimestamp")
if tr.Snapshot.Dirty {
signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot"), signer)
signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot"), cryptoService)
if err != nil {
return nil, err
}
@ -562,7 +562,7 @@ func (tr *TufRepo) SignTimestamp(expires time.Time, signer *signed.Signer) (*dat
return nil, err
}
timestamp := tr.keysDB.GetRole(data.ValidRoles["timestamp"])
signed, err = tr.sign(signed, *timestamp, signer)
signed, err = tr.sign(signed, *timestamp, cryptoService)
if err != nil {
return nil, err
}
@ -578,7 +578,7 @@ func (tr *TufRepo) SignTimestamp(expires time.Time, signer *signed.Signer) (*dat
}
}
func (tr TufRepo) sign(signed *data.Signed, role data.Role, signer *signed.Signer) (*data.Signed, error) {
func (tr TufRepo) sign(signedData *data.Signed, role data.Role, cryptoService signed.CryptoService) (*data.Signed, error) {
ks := make([]*data.PublicKey, 0, len(role.KeyIDs))
for _, kid := range role.KeyIDs {
k := tr.keysDB.GetKey(kid)
@ -590,16 +590,12 @@ func (tr TufRepo) sign(signed *data.Signed, role data.Role, signer *signed.Signe
if len(ks) < 1 {
return nil, keys.ErrInvalidKey
}
if signer != nil {
err := signer.Sign(signed, ks...)
if err != nil {
return nil, err
}
} else {
err := tr.signer.Sign(signed, ks...)
if err != nil {
return nil, err
}
if cryptoService == nil {
cryptoService = tr.cryptoService
}
return signed, nil
err := signed.Sign(cryptoService, signedData, ks...)
if err != nil {
return nil, err
}
return signedData, nil
}

View File

@ -13,21 +13,20 @@ import (
"github.com/endophage/gotuf/signed"
)
func initRepo(t *testing.T, signer *signed.Signer, keyDB *keys.KeyDB) *TufRepo {
rootKey, err := signer.Create("root")
func initRepo(t *testing.T, cryptoService signed.CryptoService, keyDB *keys.KeyDB) *TufRepo {
rootKey, err := cryptoService.Create("root", data.ED25519Key)
if err != nil {
t.Fatal(err)
}
targetsKey, err := signer.Create("targets")
targetsKey, err := cryptoService.Create("targets", data.ED25519Key)
if err != nil {
t.Fatal(err)
}
snapshotKey, err := signer.Create("snapshot")
snapshotKey, err := cryptoService.Create("snapshot", data.ED25519Key)
if err != nil {
t.Fatal(err)
}
timestampKey, err := signer.Create("timestamp")
timestampKey, err := cryptoService.Create("timestamp", data.ED25519Key)
if err != nil {
t.Fatal(err)
}
@ -71,7 +70,7 @@ func initRepo(t *testing.T, signer *signed.Signer, keyDB *keys.KeyDB) *TufRepo {
keyDB.AddRole(snapshotRole)
keyDB.AddRole(timestampRole)
repo := NewTufRepo(keyDB, signer)
repo := NewTufRepo(keyDB, cryptoService)
err = repo.InitRepo(false)
if err != nil {
t.Fatal(err)
@ -124,19 +123,17 @@ func writeRepo(t *testing.T, dir string, repo *TufRepo) {
func TestInitRepo(t *testing.T) {
ed25519 := signed.NewEd25519()
signer := signed.NewSigner(ed25519)
keyDB := keys.NewDB()
repo := initRepo(t, signer, keyDB)
repo := initRepo(t, ed25519, keyDB)
writeRepo(t, "/tmp/tufrepo", repo)
}
func TestUpdateDelegations(t *testing.T) {
ed25519 := signed.NewEd25519()
signer := signed.NewSigner(ed25519)
keyDB := keys.NewDB()
repo := initRepo(t, signer, keyDB)
repo := initRepo(t, ed25519, keyDB)
testKey, err := signer.Create("targets/test")
testKey, err := ed25519.Create("targets/test", data.ED25519Key)
if err != nil {
t.Fatal(err)
}
@ -150,7 +147,7 @@ func TestUpdateDelegations(t *testing.T) {
t.Fatal(err)
}
testDeepKey, err := signer.Create("targets/test/deep")
testDeepKey, err := ed25519.Create("targets/test/deep", data.ED25519Key)
if err != nil {
t.Fatal(err)
}

View File

@ -15,51 +15,52 @@ import (
"github.com/endophage/gotuf/data"
)
type genericCryptoService struct {
// CryptoService implements Sign and Create, holding a specific GUN and keystore to
// operate on
type CryptoService struct {
gun string
passphrase string
keyStore *trustmanager.KeyFileStore
}
// RSACryptoService implements Sign and Create, holding a specific GUN and keystore to
// operate on
type RSACryptoService struct {
genericCryptoService
}
// ECDSACryptoService implements Sign and Create, holding a specific GUN and keystore to
// operate on
type ECDSACryptoService struct {
genericCryptoService
}
// NewRSACryptoService returns an instance of CryptoService
func NewRSACryptoService(gun string, keyStore *trustmanager.KeyFileStore, passphrase string) *RSACryptoService {
return &RSACryptoService{genericCryptoService{gun: gun, keyStore: keyStore, passphrase: passphrase}}
// NewCryptoService returns an instance of CryptoService
func NewCryptoService(gun string, keyStore *trustmanager.KeyFileStore, passphrase string) *CryptoService {
return &CryptoService{gun: gun, keyStore: keyStore, passphrase: passphrase}
}
// Create is used to generate keys for targets, snapshots and timestamps
func (ccs *RSACryptoService) Create(role string) (*data.PublicKey, error) {
privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize)
if err != nil {
return nil, fmt.Errorf("failed to generate RSA key: %v", err)
func (ccs *CryptoService) Create(role string, algorithm data.KeyAlgorithm) (*data.PublicKey, error) {
var privKey *data.PrivateKey
var err error
switch algorithm {
case data.RSAKey:
privKey, err = trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize)
if err != nil {
return nil, fmt.Errorf("failed to generate RSA key: %v", err)
}
case data.ECDSAKey:
privKey, err = trustmanager.GenerateECDSAKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate EC key: %v", err)
}
default:
return nil, fmt.Errorf("private key type not supported for key generation: %s", algorithm)
}
logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role, privKey.ID())
// Store the private key into our keystore with the name being: /GUN/ID.key
err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey)
if err != nil {
return nil, fmt.Errorf("failed to add key to filestore: %v", err)
}
logrus.Debugf("generated new RSA key for role: %s and keyID: %s", role, privKey.ID())
return data.PublicKeyFromPrivate(*privKey), nil
}
// Sign returns the signatures for the payload with a set of keyIDs. It ignores
// errors to sign and expects the called to validate if the number of returned
// signatures is adequate.
func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
// Create hasher and hash data
hash := crypto.SHA256
hashed := sha256.Sum256(payload)
@ -72,11 +73,8 @@ func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa
var privKey *data.PrivateKey
var err error
// Read PrivateKey from file.
// TODO(diogo): This assumes both that only root keys are encrypted and
// that encrypted keys are always X509 encoded
// Read PrivateKey from file and decrypt it if there is a passphrase.
if ccs.passphrase != "" {
// This is a root key
privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase)
} else {
privKey, err = ccs.keyStore.GetKey(keyName)
@ -86,27 +84,33 @@ func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa
// InitRepo gets a signer that doesn't have access to
// the root keys. Continuing here is safe because we
// end up not returning any signatures.
logrus.Debugf("ignoring error attempting to retrieve RSA key ID: %s, %v", keyid, err)
logrus.Debugf("ignoring error attempting to retrieve key ID: %s, %v", keyid, err)
continue
}
sig, err := rsaSign(privKey, hash, hashed[:])
algorithm := privKey.Algorithm()
var sigAlgorithm data.SigAlgorithm
var sig []byte
switch algorithm {
case data.RSAKey:
sig, err = rsaSign(privKey, hash, hashed[:])
sigAlgorithm = data.RSAPSSSignature
case data.ECDSAKey:
sig, err = ecdsaSign(privKey, hashed[:])
sigAlgorithm = data.ECDSASignature
}
if err != nil {
// If the rsaSign method got called with a non RSA private key,
// we ignore this call.
// This might happen when root is ECDSA, targets and snapshots RSA, and
// gotuf still attempts to sign root with this cryptoserver
// return nil, err
logrus.Debugf("ignoring error attempting to RSA sign with keyID: %s, %v", keyid, err)
logrus.Debugf("ignoring error attempting to %s sign with keyID: %s, %v", algorithm, keyid, err)
continue
}
logrus.Debugf("appending RSA signature with Key ID: %s", privKey.ID())
logrus.Debugf("appending %s signature with Key ID: %s", algorithm, keyid)
// Append signatures to result array
signatures = append(signatures, data.Signature{
KeyID: keyid,
Method: data.RSAPSSSignature,
Method: sigAlgorithm,
Signature: sig[:],
})
}
@ -115,8 +119,8 @@ func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa
}
func rsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
if privKey.Cipher() != data.RSAKey {
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
if privKey.Algorithm() != data.RSAKey {
return nil, fmt.Errorf("private key type not supported: %s", privKey.Algorithm())
}
// Create an rsa.PrivateKey out of the private key bytes
@ -134,94 +138,9 @@ func rsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte,
return sig, nil
}
// NewECDSACryptoService returns an instance of CryptoService
func NewECDSACryptoService(gun string, keyStore *trustmanager.KeyFileStore, passphrase string) *ECDSACryptoService {
return &ECDSACryptoService{genericCryptoService{gun: gun, keyStore: keyStore, passphrase: passphrase}}
}
// Create is used to generate keys for targets, snapshots and timestamps
func (ccs *ECDSACryptoService) Create(role string) (*data.PublicKey, error) {
privKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate EC key: %v", err)
}
// Store the private key into our keystore with the name being: /GUN/ID.key
err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey)
if err != nil {
return nil, fmt.Errorf("failed to add key to filestore: %v", err)
}
logrus.Debugf("generated new ECDSA key for role %s with keyID: %s", role, privKey.ID())
return data.PublicKeyFromPrivate(*privKey), nil
}
// Sign returns the signatures for the payload with a set of keyIDs. It ignores
// errors to sign and expects the called to validate if the number of returned
// signatures is adequate.
func (ccs *ECDSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
// Create hasher and hash data
hashed := sha256.Sum256(payload)
signatures := make([]data.Signature, 0, len(keyIDs))
for _, keyid := range keyIDs {
// ccs.gun will be empty if this is the root key
keyName := filepath.Join(ccs.gun, keyid)
var privKey *data.PrivateKey
var err error
// Read PrivateKey from file
// TODO(diogo): This assumes both that only root keys are encrypted and
// that encrypted keys are always X509 encoded
if ccs.passphrase != "" {
// This is a root key
privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase)
} else {
privKey, err = ccs.keyStore.GetKey(keyName)
}
if err != nil {
// Note that GetDecryptedKey always fails on InitRepo.
// InitRepo gets a signer that doesn't have access to
// the root keys. Continuing here is safe because we
// end up not returning any signatures.
// TODO(diogo): figure out if there are any specific error types to
// check. We're swallowing all errors.
logrus.Debugf("Ignoring error attempting to retrieve ECDSA key ID: %s, %v", keyid, err)
continue
}
if err != nil {
fmt.Println("ERROR: ", err.Error())
}
sig, err := ecdsaSign(privKey, hashed[:])
if err != nil {
// If the ecdsaSign method got called with a non ECDSA private key,
// we ignore this call.
// This might happen when root is RSA, targets and snapshots ECDSA, and
// gotuf still attempts to sign root with this cryptoserver
// return nil, err
logrus.Debugf("ignoring error attempting to ECDSA sign with keyID: %s, %v", privKey.ID(), err)
continue
}
logrus.Debugf("appending ECDSA signature with Key ID: %s", privKey.ID())
// Append signatures to result array
signatures = append(signatures, data.Signature{
KeyID: keyid,
Method: data.ECDSASignature,
Signature: sig[:],
})
}
return signatures, nil
}
func ecdsaSign(privKey *data.PrivateKey, hashed []byte) ([]byte, error) {
if privKey.Cipher() != data.ECDSAKey {
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
if privKey.Algorithm() != data.ECDSAKey {
return nil, fmt.Errorf("private key type not supported: %s", privKey.Algorithm())
}
// Create an ecdsa.PrivateKey out of the private key bytes

View File

@ -55,11 +55,12 @@ const (
/// that doesn't exist.
var ErrRepositoryNotExist = errors.New("repository does not exist")
// UnlockedSigner encapsulates a private key and a signer that uses that private key,
// providing convinience methods for generation of certificates.
type UnlockedSigner struct {
privKey *data.PrivateKey
signer *signed.Signer
// UnlockedCryptoService encapsulates a private key and a cryptoservice that
// uses that private key, providing convinience methods for generation of
// certificates.
type UnlockedCryptoService struct {
privKey *data.PrivateKey
cryptoService signed.CryptoService
}
// NotaryRepository stores all the information needed to operate on a notary
@ -72,11 +73,10 @@ type NotaryRepository struct {
caStore trustmanager.X509Store
certificateStore trustmanager.X509Store
fileStore store.MetadataStore
signer *signed.Signer
cryptoService signed.CryptoService
tufRepo *tuf.TufRepo
privKeyStore *trustmanager.KeyFileStore
rootKeyStore *trustmanager.KeyFileStore
rootSigner *UnlockedSigner
roundTrip http.RoundTripper
}
@ -115,17 +115,16 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper) (*N
return nil, err
}
// TODO(diogo): This hardcodes snapshots and targets to using EC. Change it.
signer := signed.NewSigner(NewECDSACryptoService(gun, privKeyStore, ""))
cryptoService := NewCryptoService(gun, privKeyStore, "")
nRepo := &NotaryRepository{
gun: gun,
baseDir: baseDir,
baseURL: baseURL,
tufRepoPath: filepath.Join(baseDir, tufDir, gun),
signer: signer,
privKeyStore: privKeyStore,
roundTrip: rt,
gun: gun,
baseDir: baseDir,
baseURL: baseURL,
tufRepoPath: filepath.Join(baseDir, tufDir, gun),
cryptoService: cryptoService,
privKeyStore: privKeyStore,
roundTrip: rt,
}
if err := nRepo.loadKeys(trustDir, rootKeysDir); err != nil {
@ -137,8 +136,8 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper) (*N
// Initialize creates a new repository by using rootKey as the root Key for the
// TUF repository.
func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error {
rootCert, err := uSigner.GenerateCertificate(r.gun)
func (r *NotaryRepository) Initialize(uCryptoService *UnlockedCryptoService) error {
rootCert, err := uCryptoService.GenerateCertificate(r.gun)
if err != nil {
return err
}
@ -149,25 +148,25 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error {
// If the key is RSA, we store it as type RSAx509, if it is ECDSA we store it
// as ECDSAx509 to allow the gotuf verifiers to correctly decode the
// key on verification of signatures.
var cipherType string
cipher := uSigner.privKey.Cipher()
switch cipher {
var algorithmType data.KeyAlgorithm
algorithm := uCryptoService.privKey.Algorithm()
switch algorithm {
case data.RSAKey:
cipherType = data.RSAx509Key
algorithmType = data.RSAx509Key
case data.ECDSAKey:
cipherType = data.ECDSAx509Key
algorithmType = data.ECDSAx509Key
default:
return fmt.Errorf("invalid format for root key: %s", cipher)
return fmt.Errorf("invalid format for root key: %s", algorithm)
}
// Generate a x509Key using the rootCert as the public key
rootKey := data.NewPublicKey(cipherType, trustmanager.CertToPEM(rootCert))
rootKey := data.NewPublicKey(algorithmType, trustmanager.CertToPEM(rootCert))
// Creates a symlink between the certificate ID and the real public key it
// is associated with. This is used to be able to retrieve the root private key
// associated with a particular certificate
logrus.Debugf("Linking %s to %s.", rootKey.ID(), uSigner.ID())
err = r.rootKeyStore.Link(uSigner.ID(), rootKey.ID())
logrus.Debugf("Linking %s to %s.", rootKey.ID(), uCryptoService.ID())
err = r.rootKeyStore.Link(uCryptoService.ID(), rootKey.ID())
if err != nil {
return err
}
@ -186,15 +185,16 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error {
}
// Turn the JSON timestamp key from the remote server into a TUFKey
timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public())
logrus.Debugf("got remote %s timestamp key with keyID: %s", parsedKey.Cipher(), timestampKey.ID())
timestampKey := data.NewPublicKey(parsedKey.Algorithm(), parsedKey.Public())
logrus.Debugf("got remote %s timestamp key with keyID: %s", parsedKey.Algorithm(), timestampKey.ID())
// This is currently hardcoding the targets and snapshots keys to ECDSA
// Targets and snapshot keys are always generated locally.
targetsKey, err := r.signer.Create("targets")
targetsKey, err := r.cryptoService.Create("targets", data.ECDSAKey)
if err != nil {
return err
}
snapshotKey, err := r.signer.Create("snapshot")
snapshotKey, err := r.cryptoService.Create("snapshot", data.ECDSAKey)
if err != nil {
return err
}
@ -236,7 +236,7 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error {
return err
}
r.tufRepo = tuf.NewTufRepo(kdb, r.signer)
r.tufRepo = tuf.NewTufRepo(kdb, r.cryptoService)
r.fileStore, err = store.NewFilesystemStore(
r.tufRepoPath,
@ -252,7 +252,7 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error {
return err
}
if err := r.saveMetadata(uSigner.signer); err != nil {
if err := r.saveMetadata(uCryptoService.cryptoService); err != nil {
return err
}
@ -389,11 +389,11 @@ func (r *NotaryRepository) Publish(getPass passwordRetriever) error {
return err
}
rootKeyID := r.tufRepo.Root.Signed.Roles["root"].KeyIDs[0]
rootSigner, err := r.GetRootSigner(rootKeyID, passphrase)
rootCryptoService, err := r.GetRootCryptoService(rootKeyID, passphrase)
if err != nil {
return err
}
root, err = r.tufRepo.SignRoot(data.DefaultExpires("root"), rootSigner.signer)
root, err = r.tufRepo.SignRoot(data.DefaultExpires("root"), rootCryptoService.cryptoService)
if err != nil {
return err
}
@ -459,7 +459,7 @@ func (r *NotaryRepository) bootstrapRepo() error {
}
kdb := keys.NewDB()
tufRepo := tuf.NewTufRepo(kdb, r.signer)
tufRepo := tuf.NewTufRepo(kdb, r.cryptoService)
logrus.Debugf("Loading trusted collection.")
rootJSON, err := fileStore.GetMeta("root", 0)
@ -499,8 +499,8 @@ func (r *NotaryRepository) bootstrapRepo() error {
return nil
}
func (r *NotaryRepository) saveMetadata(rootSigner *signed.Signer) error {
signedRoot, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), rootSigner)
func (r *NotaryRepository) saveMetadata(rootCryptoService signed.CryptoService) error {
signedRoot, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), rootCryptoService)
if err != nil {
return err
}
@ -632,7 +632,7 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) {
}
kdb := keys.NewDB()
r.tufRepo = tuf.NewTufRepo(kdb, r.signer)
r.tufRepo = tuf.NewTufRepo(kdb, r.cryptoService)
err = r.tufRepo.SetRoot(root)
if err != nil {
@ -653,11 +653,15 @@ func (r *NotaryRepository) ListRootKeys() []string {
}
// GenRootKey generates a new root key protected by a given passphrase
// TODO(diogo): show not create keys manually, should use a cryptoservice instead
func (r *NotaryRepository) GenRootKey(algorithm, passphrase string) (string, error) {
var err error
var privKey *data.PrivateKey
switch strings.ToLower(algorithm) {
// We don't want external API callers to rely on internal TUF data types, so
// the API here should continue to receive a string algorithm, and ensure
// that it is downcased
switch data.KeyAlgorithm(strings.ToLower(algorithm)) {
case data.RSAKey:
privKey, err = trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize)
case data.ECDSAKey:
@ -676,28 +680,18 @@ func (r *NotaryRepository) GenRootKey(algorithm, passphrase string) (string, err
return privKey.ID(), nil
}
// GetRootSigner retreives a root key that includes the ID and a signer
func (r *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) {
// GetRootCryptoService retreives a root key and a cryptoservice to use with it
func (r *NotaryRepository) GetRootCryptoService(rootKeyID, passphrase string) (*UnlockedCryptoService, error) {
privKey, err := r.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase)
if err != nil {
return nil, fmt.Errorf("could not get decrypted root key with keyID: %s, %v", rootKeyID, err)
}
var signer *signed.Signer
cipher := privKey.Cipher()
// Passing an empty GUN because root keys aren't associated with a GUN.
switch strings.ToLower(cipher) {
case data.RSAKey:
signer = signed.NewSigner(NewRSACryptoService("", r.rootKeyStore, passphrase))
case data.ECDSAKey:
signer = signed.NewSigner(NewECDSACryptoService("", r.rootKeyStore, passphrase))
default:
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher)
}
cryptoService := NewCryptoService("", r.rootKeyStore, passphrase)
return &UnlockedSigner{
privKey: privKey,
signer: signer}, nil
return &UnlockedCryptoService{
privKey: privKey,
cryptoService: cryptoService}, nil
}
func (r *NotaryRepository) loadKeys(trustDir, rootKeysDir string) error {
@ -738,35 +732,35 @@ func (r *NotaryRepository) loadKeys(trustDir, rootKeysDir string) error {
return nil
}
// ID gets a consistent ID based on the PrivateKey bytes and cipher type
func (uk *UnlockedSigner) ID() string {
return uk.PublicKey().ID()
// ID gets a consistent ID based on the PrivateKey bytes and algorithm type
func (ucs *UnlockedCryptoService) ID() string {
return ucs.PublicKey().ID()
}
// PublicKey Returns the public key associated with the Private Key within the Signer
func (uk *UnlockedSigner) PublicKey() *data.PublicKey {
return data.PublicKeyFromPrivate(*uk.privKey)
// PublicKey Returns the public key associated with the private key
func (ucs *UnlockedCryptoService) PublicKey() *data.PublicKey {
return data.PublicKeyFromPrivate(*ucs.privKey)
}
// GenerateCertificate generates an X509 Certificate from a template, given a GUN
func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, error) {
cipher := uk.privKey.Cipher()
func (ucs *UnlockedCryptoService) GenerateCertificate(gun string) (*x509.Certificate, error) {
algorithm := ucs.privKey.Algorithm()
var publicKey crypto.PublicKey
var privateKey crypto.PrivateKey
var err error
switch cipher {
switch algorithm {
case data.RSAKey:
var rsaPrivateKey *rsa.PrivateKey
rsaPrivateKey, err = x509.ParsePKCS1PrivateKey(uk.privKey.Private())
rsaPrivateKey, err = x509.ParsePKCS1PrivateKey(ucs.privKey.Private())
privateKey = rsaPrivateKey
publicKey = rsaPrivateKey.Public()
case data.ECDSAKey:
var ecdsaPrivateKey *ecdsa.PrivateKey
ecdsaPrivateKey, err = x509.ParseECPrivateKey(uk.privKey.Private())
ecdsaPrivateKey, err = x509.ParseECPrivateKey(ucs.privKey.Private())
privateKey = ecdsaPrivateKey
publicKey = ecdsaPrivateKey.Public()
default:
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher)
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm)
}
if err != nil {
return nil, fmt.Errorf("failed to parse root key: %s (%v)", gun, err)

View File

@ -43,11 +43,13 @@ func createTestServer(t *testing.T) (*httptest.Server, *http.ServeMux) {
// sure the repository looks correct on disk.
// We test this with both an RSA and ECDSA root key
func TestInitRepo(t *testing.T) {
testInitRepo(t, data.RSAKey)
testInitRepo(t, data.ECDSAKey)
if !testing.Short() {
testInitRepo(t, data.RSAKey)
}
}
func testInitRepo(t *testing.T, rootType string) {
func testInitRepo(t *testing.T, rootType data.KeyAlgorithm) {
gun := "docker.com/notary"
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
@ -61,13 +63,13 @@ func testInitRepo(t *testing.T, rootType string) {
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
assert.NoError(t, err, "error creating repo: %s", err)
rootKeyID, err := repo.GenRootKey(rootType, "passphrase")
rootKeyID, err := repo.GenRootKey(rootType.String(), "passphrase")
assert.NoError(t, err, "error generating root key: %s", err)
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
rootCryptoService, err := repo.GetRootCryptoService(rootKeyID, "passphrase")
assert.NoError(t, err, "error retrieving root key: %s", err)
err = repo.Initialize(rootSigner)
err = repo.Initialize(rootCryptoService)
assert.NoError(t, err, "error creating repository: %s", err)
// Inspect contents of the temporary directory
@ -98,7 +100,7 @@ func testInitRepo(t *testing.T, rootType string) {
// Look for keys in root_keys
// There should be a file named after the key ID of the root key we
// passed in.
rootKeyFilename := rootSigner.ID() + ".key"
rootKeyFilename := rootCryptoService.ID() + ".key"
_, err = os.Stat(filepath.Join(tempBaseDir, "private", "root_keys", rootKeyFilename))
assert.NoError(t, err, "missing root key")
@ -184,11 +186,13 @@ type tufChange struct {
// internal HTTP server.
// We test this with both an RSA and ECDSA root key
func TestAddListTarget(t *testing.T) {
testAddListTarget(t, data.RSAKey)
testAddListTarget(t, data.ECDSAKey)
if !testing.Short() {
testInitRepo(t, data.RSAKey)
}
}
func testAddListTarget(t *testing.T, rootType string) {
func testAddListTarget(t *testing.T, rootType data.KeyAlgorithm) {
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
defer os.RemoveAll(tempBaseDir)
@ -203,13 +207,13 @@ func testAddListTarget(t *testing.T, rootType string) {
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
assert.NoError(t, err, "error creating repository: %s", err)
rootKeyID, err := repo.GenRootKey(rootType, "passphrase")
rootKeyID, err := repo.GenRootKey(rootType.String(), "passphrase")
assert.NoError(t, err, "error generating root key: %s", err)
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
rootCryptoService, err := repo.GetRootCryptoService(rootKeyID, "passphrase")
assert.NoError(t, err, "error retreiving root key: %s", err)
err = repo.Initialize(rootSigner)
err = repo.Initialize(rootCryptoService)
assert.NoError(t, err, "error creating repository: %s", err)
// Add fixtures/ca.cert as a target. There's no particular reason
@ -367,11 +371,13 @@ func testAddListTarget(t *testing.T, rootType string) {
// TestValidateRootKey verifies that the public data in root.json for the root
// key is a valid x509 certificate.
func TestValidateRootKey(t *testing.T) {
testValidateRootKey(t, data.RSAKey)
testValidateRootKey(t, data.ECDSAKey)
if !testing.Short() {
testInitRepo(t, data.RSAKey)
}
}
func testValidateRootKey(t *testing.T, rootType string) {
func testValidateRootKey(t *testing.T, rootType data.KeyAlgorithm) {
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
defer os.RemoveAll(tempBaseDir)
@ -386,13 +392,13 @@ func testValidateRootKey(t *testing.T, rootType string) {
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
assert.NoError(t, err, "error creating repository: %s", err)
rootKeyID, err := repo.GenRootKey(rootType, "passphrase")
rootKeyID, err := repo.GenRootKey(rootType.String(), "passphrase")
assert.NoError(t, err, "error generating root key: %s", err)
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
rootCryptoService, err := repo.GetRootCryptoService(rootKeyID, "passphrase")
assert.NoError(t, err, "error retreiving root key: %s", err)
err = repo.Initialize(rootSigner)
err = repo.Initialize(rootCryptoService)
assert.NoError(t, err, "error creating repository: %s", err)
rootJSONFile := filepath.Join(tempBaseDir, "tuf", gun, "metadata", "root.json")

View File

@ -13,7 +13,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/term"
notaryclient "github.com/docker/notary/client"
"github.com/endophage/gotuf/data"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@ -120,7 +119,7 @@ func tufInit(cmd *cobra.Command, args []string) {
if err != nil {
fatalf(err.Error())
}
rootKeyID, err = nRepo.GenRootKey(data.ECDSAKey, passphrase)
rootKeyID, err = nRepo.GenRootKey("ECDSA", passphrase)
if err != nil {
fatalf(err.Error())
}
@ -134,12 +133,12 @@ func tufInit(cmd *cobra.Command, args []string) {
}
}
rootSigner, err := nRepo.GetRootSigner(rootKeyID, passphrase)
rootCryptoService, err := nRepo.GetRootCryptoService(rootKeyID, passphrase)
if err != nil {
fatalf(err.Error())
}
nRepo.Initialize(rootSigner)
nRepo.Initialize(rootCryptoService)
if err != nil {
fatalf(err.Error())
}

View File

@ -165,20 +165,20 @@ func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Req
Err: fmt.Errorf("Version store not configured"),
}
}
sign := ctx.Value("signer")
signer, ok := sign.(*signed.Signer)
cryptoServiceVal := ctx.Value("cryptoService")
cryptoService, ok := cryptoServiceVal.(signed.CryptoService)
if !ok {
return &errors.HTTPError{
HTTPStatus: http.StatusInternalServerError,
Code: 9999,
Err: fmt.Errorf("Signer not configured"),
Err: fmt.Errorf("CryptoService not configured"),
}
}
vars := mux.Vars(r)
gun := vars["imageName"]
out, err := timestamp.GetOrCreateTimestamp(gun, store, signer)
out, err := timestamp.GetOrCreateTimestamp(gun, store, cryptoService)
if err != nil {
if _, ok := err.(*storage.ErrNoKey); ok {
return &errors.HTTPError{
@ -224,7 +224,7 @@ func GetTimestampKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.
vars := mux.Vars(r)
gun := vars["imageName"]
key, err := timestamp.GetOrCreateTimestampKey(gun, store, crypto)
key, err := timestamp.GetOrCreateTimestampKey(gun, store, crypto, data.ECDSAKey)
if err != nil {
return &errors.HTTPError{
HTTPStatus: http.StatusInternalServerError,

View File

@ -3,6 +3,7 @@ package storage
import (
"database/sql"
"github.com/endophage/gotuf/data"
"github.com/go-sql-driver/mysql"
)
@ -91,10 +92,11 @@ func (db *MySQLStorage) Delete(gun string) error {
}
// GetTimestampKey returns the timestamps Public Key data
func (db *MySQLStorage) GetTimestampKey(gun string) (cipher string, public []byte, err error) {
func (db *MySQLStorage) GetTimestampKey(gun string) (algorithm data.KeyAlgorithm, public []byte, err error) {
stmt := "SELECT `cipher`, `public` FROM `timestamp_keys` WHERE `gun`=?;"
row := db.QueryRow(stmt, gun)
var cipher string
err = row.Scan(&cipher, &public)
if err == sql.ErrNoRows {
return "", nil, ErrNoKey{gun: gun}
@ -102,13 +104,13 @@ func (db *MySQLStorage) GetTimestampKey(gun string) (cipher string, public []byt
return "", nil, err
}
return cipher, public, err
return data.KeyAlgorithm(cipher), public, err
}
// SetTimestampKey attempts to write a TimeStamp key and returns an error if it already exists
func (db *MySQLStorage) SetTimestampKey(gun, cipher string, public []byte) error {
func (db *MySQLStorage) SetTimestampKey(gun string, algorithm data.KeyAlgorithm, public []byte) error {
stmt := "INSERT INTO `timestamp_keys` (`gun`, `cipher`, `public`) VALUES (?,?,?);"
_, err := db.Exec(stmt, gun, cipher, public)
_, err := db.Exec(stmt, gun, string(algorithm), public)
if err, ok := err.(*mysql.MySQLError); ok {
if err.Number == 1022 { // duplicate key error
return &ErrTimestampKeyExists{gun: gun}

View File

@ -1,10 +1,12 @@
package storage
import "github.com/endophage/gotuf/data"
// MetaStore holds the methods that are used for a Metadata Store
type MetaStore interface {
UpdateCurrent(gun, role string, version int, data []byte) error
GetCurrent(gun, tufRole string) (data []byte, err error)
Delete(gun string) error
GetTimestampKey(gun string) (cipher string, public []byte, err error)
SetTimestampKey(gun, cipher string, public []byte) error
GetTimestampKey(gun string) (algorithm data.KeyAlgorithm, public []byte, err error)
SetTimestampKey(gun string, algorithm data.KeyAlgorithm, public []byte) error
}

View File

@ -4,11 +4,13 @@ import (
"fmt"
"strings"
"sync"
"github.com/endophage/gotuf/data"
)
type key struct {
cipher string
public []byte
algorithm data.KeyAlgorithm
public []byte
}
type ver struct {
@ -73,7 +75,7 @@ func (st *MemStorage) Delete(gun string) error {
}
// GetTimestampKey returns the public key material of the timestamp key of a given gun
func (st *MemStorage) GetTimestampKey(gun string) (cipher string, public []byte, err error) {
func (st *MemStorage) GetTimestampKey(gun string) (algorithm data.KeyAlgorithm, public []byte, err error) {
// no need for lock. It's ok to return nil if an update
// wasn't observed
k, ok := st.tsKeys[gun]
@ -81,12 +83,12 @@ func (st *MemStorage) GetTimestampKey(gun string) (cipher string, public []byte,
return "", nil, &ErrNoKey{gun: gun}
}
return k.cipher, k.public, nil
return k.algorithm, k.public, nil
}
// SetTimestampKey sets a Timestamp key under a gun
func (st *MemStorage) SetTimestampKey(gun, cipher string, public []byte) error {
k := &key{cipher: cipher, public: public}
func (st *MemStorage) SetTimestampKey(gun string, algorithm data.KeyAlgorithm, public []byte) error {
k := &key{algorithm: algorithm, public: public}
st.lock.Lock()
defer st.lock.Unlock()
if _, ok := st.tsKeys[gun]; ok {

View File

@ -51,7 +51,7 @@ func TestGetTimestampKey(t *testing.T) {
c, k, err := s.GetTimestampKey("gun")
assert.Nil(t, err, "Expected error to be nil")
assert.Equal(t, data.RSAKey, c, "Expected cipher rsa, received %s", c)
assert.Equal(t, data.RSAKey, c, "Expected algorithm rsa, received %s", c)
assert.Equal(t, []byte("test"), k, "Key data was wrong")
}
@ -63,7 +63,7 @@ func TestSetTimestampKey(t *testing.T) {
assert.IsType(t, &ErrTimestampKeyExists{}, err, "Expected err to be ErrTimestampKeyExists")
k := s.tsKeys["gun"]
assert.Equal(t, data.RSAKey, k.cipher, "Expected cipher to be rsa, received %s", k.cipher)
assert.Equal(t, data.RSAKey, k.algorithm, "Expected algorithm to be rsa, received %s", k.algorithm)
assert.Equal(t, []byte("test"), k.public, "Public key did not match expected")
}

View File

@ -17,28 +17,28 @@ import (
// found. It attempts to handle the race condition that may occur if 2 servers try to
// create the key at the same time by simply querying the store a second time if it
// receives a conflict when writing.
func GetOrCreateTimestampKey(gun string, store storage.MetaStore, crypto signed.CryptoService) (*data.TUFKey, error) {
cipher, public, err := store.GetTimestampKey(gun)
func GetOrCreateTimestampKey(gun string, store storage.MetaStore, crypto signed.CryptoService, fallBackAlgorithm data.KeyAlgorithm) (*data.TUFKey, error) {
keyAlgorithm, public, err := store.GetTimestampKey(gun)
if err == nil {
return data.NewTUFKey(cipher, public, nil), nil
return data.NewTUFKey(keyAlgorithm, public, nil), nil
}
if _, ok := err.(*storage.ErrNoKey); ok {
key, err := crypto.Create("timestamp")
key, err := crypto.Create("timestamp", fallBackAlgorithm)
if err != nil {
return nil, err
}
err = store.SetTimestampKey(gun, key.Cipher(), key.Public())
err = store.SetTimestampKey(gun, key.Algorithm(), key.Public())
if err == nil {
return &key.TUFKey, nil
}
if _, ok := err.(*storage.ErrTimestampKeyExists); ok {
cipher, public, err = store.GetTimestampKey(gun)
keyAlgorithm, public, err = store.GetTimestampKey(gun)
if err != nil {
return nil, err
}
return data.NewTUFKey(cipher, public, nil), nil
return data.NewTUFKey(keyAlgorithm, public, nil), nil
}
return nil, err
}
@ -48,7 +48,7 @@ func GetOrCreateTimestampKey(gun string, store storage.MetaStore, crypto signed.
// GetOrCreateTimestamp returns the current timestamp for the gun. This may mean
// a new timestamp is generated either because none exists, or because the current
// one has expired. Once generated, the timestamp is saved in the store.
func GetOrCreateTimestamp(gun string, store storage.MetaStore, signer *signed.Signer) ([]byte, error) {
func GetOrCreateTimestamp(gun string, store storage.MetaStore, cryptoService signed.CryptoService) ([]byte, error) {
d, err := store.GetCurrent(gun, "timestamp")
if err != nil {
if _, ok := err.(*storage.ErrNotFound); !ok {
@ -69,7 +69,7 @@ func GetOrCreateTimestamp(gun string, store storage.MetaStore, signer *signed.Si
return d, nil
}
}
sgnd, version, err := createTimestamp(gun, ts, store, signer)
sgnd, version, err := createTimestamp(gun, ts, store, cryptoService)
if err != nil {
logrus.Error("Failed to create a new timestamp")
return nil, err
@ -95,14 +95,14 @@ func timestampExpired(ts *data.SignedTimestamp) bool {
// is assumed this is the immediately previous one, and the new one will have a
// version number one higher than prev. The store is used to lookup the current
// snapshot, this function does not save the newly generated timestamp.
func createTimestamp(gun string, prev *data.SignedTimestamp, store storage.MetaStore, signer *signed.Signer) (*data.Signed, int, error) {
cipher, public, err := store.GetTimestampKey(gun)
func createTimestamp(gun string, prev *data.SignedTimestamp, store storage.MetaStore, cryptoService signed.CryptoService) (*data.Signed, int, error) {
algorithm, public, err := store.GetTimestampKey(gun)
if err != nil {
// owner of gun must have generated a timestamp key otherwise
// we won't proceed with generating everything.
return nil, 0, err
}
key := data.NewPublicKey(cipher, public)
key := data.NewPublicKey(algorithm, public)
snapshot, err := store.GetCurrent(gun, "snapshot")
if err != nil {
return nil, 0, err
@ -128,7 +128,7 @@ func createTimestamp(gun string, prev *data.SignedTimestamp, store storage.MetaS
Signatures: ts.Signatures,
Signed: sgndTs,
}
err = signer.Sign(out, key)
err = signed.Sign(cryptoService, out, key)
if err != nil {
return nil, 0, err
}

View File

@ -32,11 +32,11 @@ func TestTimestampExpired(t *testing.T) {
func TestGetTimestampKey(t *testing.T) {
store := storage.NewMemStorage()
crypto := signed.NewEd25519()
k, err := GetOrCreateTimestampKey("gun", store, crypto)
k, err := GetOrCreateTimestampKey("gun", store, crypto, data.ED25519Key)
assert.Nil(t, err, "Expected nil error")
assert.NotNil(t, k, "Key should not be nil")
k2, err := GetOrCreateTimestampKey("gun", store, crypto)
k2, err := GetOrCreateTimestampKey("gun", store, crypto, data.ED25519Key)
assert.Nil(t, err, "Expected nil error")
@ -48,16 +48,15 @@ func TestGetTimestampKey(t *testing.T) {
func TestGetTimestamp(t *testing.T) {
store := storage.NewMemStorage()
crypto := signed.NewEd25519()
signer := signed.NewSigner(crypto)
snapshot := &data.SignedSnapshot{}
snapJSON, _ := json.Marshal(snapshot)
store.UpdateCurrent("gun", "snapshot", 0, snapJSON)
// create a key to be used by GetTimestamp
_, err := GetOrCreateTimestampKey("gun", store, crypto)
_, err := GetOrCreateTimestampKey("gun", store, crypto, data.ED25519Key)
assert.Nil(t, err, "GetTimestampKey errored")
_, err = GetOrCreateTimestamp("gun", store, signer)
_, err = GetOrCreateTimestamp("gun", store, crypto)
assert.Nil(t, err, "GetTimestamp errored")
}

View File

@ -54,7 +54,7 @@ func (trust *RufusSigner) Sign(keyIDs []string, toSign []byte) ([]data.Signature
}
signatures = append(signatures, data.Signature{
KeyID: sig.KeyID.ID,
Method: sig.Algorithm.Algorithm,
Method: data.SigAlgorithm(sig.Algorithm.Algorithm),
Signature: sig.Content,
})
}
@ -62,13 +62,14 @@ func (trust *RufusSigner) Sign(keyIDs []string, toSign []byte) ([]data.Signature
}
// Create creates a remote key and returns the PublicKey associated with the remote private key
func (trust *RufusSigner) Create(role string) (*data.PublicKey, error) {
// TODO(diogo): Ignoring algorithm for now until rufus supports it
func (trust *RufusSigner) Create(role string, _ data.KeyAlgorithm) (*data.PublicKey, error) {
publicKey, err := trust.kmClient.CreateKey(context.Background(), &pb.Void{})
if err != nil {
return nil, err
}
//TODO(mccauley): Update API to return algorithm and/or take it as a param
public := data.NewPublicKey(publicKey.Algorithm.Algorithm, publicKey.PublicKey)
public := data.NewPublicKey(data.KeyAlgorithm(publicKey.Algorithm.Algorithm), publicKey.PublicKey)
return public, nil
}
@ -82,7 +83,7 @@ func (trust *RufusSigner) PublicKeys(keyIDs ...string) (map[string]*data.PublicK
return nil, err
}
publicKeys[public.KeyID.ID] =
data.NewPublicKey(public.Algorithm.Algorithm, public.PublicKey)
data.NewPublicKey(data.KeyAlgorithm(public.Algorithm.Algorithm), public.PublicKey)
}
return publicKeys, nil
}

View File

@ -100,7 +100,7 @@ func fingerprintCert(cert *x509.Certificate) (CertID, error) {
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
pemdata := pem.EncodeToMemory(&block)
keyType := ""
var keyType data.KeyAlgorithm
switch cert.PublicKeyAlgorithm {
case x509.RSA:
keyType = data.RSAx509Key
@ -228,7 +228,7 @@ func GenerateRSAKey(random io.Reader, bits int) (*data.PrivateKey, error) {
}
// RSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type
func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey, keyType string) (*data.PrivateKey, error) {
func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey, keyType data.KeyAlgorithm) (*data.PrivateKey, error) {
// Get a DER-encoded representation of the PublicKey
rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey)
if err != nil {
@ -261,7 +261,7 @@ func GenerateECDSAKey(random io.Reader) (*data.PrivateKey, error) {
}
// ECDSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type
func ECDSAToPrivateKey(ecdsaPrivKey *ecdsa.PrivateKey, keyType string) (*data.PrivateKey, error) {
func ECDSAToPrivateKey(ecdsaPrivKey *ecdsa.PrivateKey, keyType data.KeyAlgorithm) (*data.PrivateKey, error) {
// Get a DER-encoded representation of the PublicKey
ecdsaPubBytes, err := x509.MarshalPKIXPublicKey(&ecdsaPrivKey.PublicKey)
if err != nil {
@ -280,15 +280,15 @@ func ECDSAToPrivateKey(ecdsaPrivKey *ecdsa.PrivateKey, keyType string) (*data.Pr
// KeyToPEM returns a PEM encoded key from a Private Key
func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) {
var pemType string
cipher := privKey.Cipher()
algorithm := privKey.Algorithm()
switch cipher {
switch algorithm {
case data.RSAKey:
pemType = "RSA PRIVATE KEY"
case data.ECDSAKey:
pemType = "EC PRIVATE KEY"
default:
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher)
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm)
}
return pem.EncodeToMemory(&pem.Block{Type: pemType, Bytes: privKey.Private()}), nil
@ -298,15 +298,15 @@ func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) {
// and a passphrase
func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) {
var blockType string
cipher := key.Cipher()
algorithm := key.Algorithm()
switch cipher {
switch algorithm {
case data.RSAKey:
blockType = "RSA PRIVATE KEY"
case data.ECDSAKey:
blockType = "EC PRIVATE KEY"
default:
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher)
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm)
}
password := []byte(passphrase)

View File

@ -48,9 +48,6 @@ func (root *rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
ctx := context.WithValue(root.context, "repo", vars["imageName"])
// endophage: I don't guarantee that a signer will always be stateless but a CryptoService
// is expected to be. Create a new Signer for each request.
ctx = context.WithValue(ctx, "signer", signed.NewSigner(root.trust))
ctx = context.WithValue(ctx, "cryptoService", root.trust)
ctx = context.WithValue(ctx, "http.request", r)