diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a9d2132..c0925a22 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: with: go-version: ${{ steps.go-version.outputs.minimal }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@5fdedb94abba051217030cc86d4523cf3f02243d + uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 with: distribution: goreleaser version: "v1.7.0" diff --git a/README.md b/README.md index 217bcf28..45cbf9b6 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,15 @@ 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. +#### `tuf add-key [--scheme=] [--expires=] [--public-key=] ` + +Adds a new signing key for the given role. + +The root metadata file will be staged +with the addition of the key's ID to the role's list of key IDs. + +The public value can be specified as a path or passed in via stdin. + #### `tuf gen-key [--expires=] ` Prompts the user for an encryption passphrase (unless the diff --git a/cmd/tuf/add_key.go b/cmd/tuf/add_key.go new file mode 100644 index 00000000..e88e25d8 --- /dev/null +++ b/cmd/tuf/add_key.go @@ -0,0 +1,77 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/flynn/go-docopt" + "github.com/theupdateframework/go-tuf" + "github.com/theupdateframework/go-tuf/data" +) + +func init() { + register("add-key", cmdAddKey, ` +usage: tuf add-key [--scheme=] [--expires=] [--public-key=] + +Adds a new signing key for the given role. + +The root metadata file will be staged +with the addition of the key's ID to the role's list of key IDs. + +Options: + --public-key= The Path to the file containing value of the public key. If absent, will be read from stdin. + --expires= Set the metadata file to expire days from now. + --scheme= Set the key scheme to use [default: ed25519]. +`) +} + +func cmdAddKey(args *docopt.Args, repo *tuf.Repo) error { + role := args.String[""] + var keyids []string + + var keyScheme data.KeyScheme + switch t := args.String["--scheme"]; t { + case string(data.KeySchemeEd25519), + string(data.KeySchemeECDSA_SHA2_P256), + string(data.KeySchemeRSASSA_PSS_SHA256): + keyScheme = data.KeyScheme(t) + default: + fmt.Fprintf(os.Stderr, "tuf: key schema %s not recognised\n", t) + return nil + } + f := args.String["--public-key"] + var publicValue string + if f != "" { + bytes, err := os.ReadFile(f) + if err != nil { + return err + } + publicValue = string(bytes) + } else { + var input string + _, err := fmt.Scan(&input) + if err != nil { + return err + } + publicValue = input + } + var err error + var expires time.Time + if arg := args.String["--expires"]; arg != "" { + expires, err = parseExpires(arg) + if err != nil { + return err + } + } else { + expires = data.DefaultExpires(role) + } + keyids, err = repo.AddKeyWithSchemeAndExpires(role, expires, keyScheme, publicValue) + if err != nil { + return err + } + for _, id := range keyids { + fmt.Fprintf(os.Stdout, "Add key with ID %s\n", id) + } + return nil +} diff --git a/cmd/tuf/main.go b/cmd/tuf/main.go index dc6a256c..1c9439ea 100644 --- a/cmd/tuf/main.go +++ b/cmd/tuf/main.go @@ -30,6 +30,7 @@ Options: Commands: help Show usage for a specific command init Initialize a new repository + add-key Adds a new signing key for a specific role gen-key Generate a new signing key for a specific metadata file revoke-key Revoke a signing key add Add target file(s) diff --git a/pkg/keys/rsa.go b/pkg/keys/rsa.go index 618f104e..07a4cd6b 100644 --- a/pkg/keys/rsa.go +++ b/pkg/keys/rsa.go @@ -17,11 +17,11 @@ import ( ) func init() { - VerifierMap.Store(data.KeyTypeRSASSA_PSS_SHA256, newRsaVerifier) + VerifierMap.Store(data.KeyTypeRSASSA_PSS_SHA256, NewRsaVerifier) SignerMap.Store(data.KeyTypeRSASSA_PSS_SHA256, newRsaSigner) } -func newRsaVerifier() Verifier { +func NewRsaVerifier() Verifier { return &rsaVerifier{} } diff --git a/pkg/keys/rsa_test.go b/pkg/keys/rsa_test.go index 73520003..e5406c1a 100644 --- a/pkg/keys/rsa_test.go +++ b/pkg/keys/rsa_test.go @@ -97,7 +97,7 @@ func (ECDSASuite) TestUnmarshalRSAPublicKey(c *C) { signer := &rsaSigner{priv.PrivateKey} goodKey := signer.PublicData() - verifier := newRsaVerifier() + verifier := NewRsaVerifier() c.Assert(verifier.UnmarshalPublicKey(goodKey), IsNil) } @@ -119,7 +119,7 @@ func (ECDSASuite) TestUnmarshalRSA_TooLongContent(c *C) { Algorithms: data.HashAlgorithms, Value: tooLongPayload, } - verifier := newRsaVerifier() + verifier := NewRsaVerifier() err = verifier.UnmarshalPublicKey(badKey) c.Assert(errors.Is(err, io.ErrUnexpectedEOF), Equals, true) } diff --git a/repo.go b/repo.go index db2ac663..e9382ddb 100644 --- a/repo.go +++ b/repo.go @@ -379,6 +379,49 @@ func (r *Repo) GenKeyWithSchemeAndExpires(role string, expires time.Time, keySch return signer.PublicData().IDs(), nil } +func (r *Repo) AddKeyWithSchemeAndExpires(role string, expires time.Time, keyScheme data.KeyScheme, publicValue string) ([]string, error) { + var verifier keys.Verifier + var keyType data.KeyType + switch keyScheme { + case data.KeySchemeEd25519: + verifier = keys.NewEd25519Verifier() + keyType = data.KeyTypeEd25519 + case data.KeySchemeECDSA_SHA2_P256: + verifier = keys.NewEcdsaVerifier() + keyType = data.KeyTypeECDSA_SHA2_P256 + case data.KeySchemeRSASSA_PSS_SHA256: + verifier = keys.NewRsaVerifier() + keyType = data.KeyTypeRSASSA_PSS_SHA256 + default: + return nil, errors.New("unknown key type") + } + + publicValueData, err := json.Marshal(map[string]string{ + "public": publicValue, + }) + if err != nil { + return nil, err + } + + if err := verifier.UnmarshalPublicKey(&data.PublicKey{ + Type: keyType, + Scheme: keyScheme, + Algorithms: data.HashAlgorithms, + Value: publicValueData, + }); err != nil { + return nil, err + } + + publicKey := verifier.MarshalPublicKey() + + // Not compatible with delegated targets roles, since delegated targets keys + // are associated with a delegation (edge), not a role (node). + if err := r.AddVerificationKeyWithExpiration(role, publicKey, expires); err != nil { + return nil, err + } + return publicKey.IDs(), nil +} + func (r *Repo) AddPrivateKey(role string, signer keys.Signer) error { // Not compatible with delegated targets roles, since delegated targets keys // are associated with a delegation (edge), not a role (node).