Repository: scionproto/scion
Pull request #3567 (2020-01-03 14:26:59)

SPKI: Improve help messages
This change improves the help messages of the scion pki tool.
Also, the usage messages are now printed for the correct sub command,
instead of the root command.

Additionally, rename TRC2 to TRC.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/scionproto/scion/3567)
<!-- Reviewable:end -->



Patch:
diff --git a/go/tools/scion-pki/internal/cmd/BUILD.bazel b/go/tools/scion-pki/internal/cmd/BUILD.bazel
index 6e31ac588..61a398d70 100644
--- a/go/tools/scion-pki/internal/cmd/BUILD.bazel
+++ b/go/tools/scion-pki/internal/cmd/BUILD.bazel
@@ -6,7 +6,7 @@ go_library(
     importpath = "github.com/scionproto/scion/go/tools/scion-pki/internal/cmd",
     visibility = ["//go/tools/scion-pki:__subpackages__"],
     deps = [
-        "//go/lib/common:go_default_library",
+        "//go/lib/serrors:go_default_library",
         "//go/tools/scion-pki/internal/certs:go_default_library",
         "//go/tools/scion-pki/internal/keys:go_default_library",
         "//go/tools/scion-pki/internal/pkicmn:go_default_library",
diff --git a/go/tools/scion-pki/internal/cmd/cmd.go b/go/tools/scion-pki/internal/cmd/cmd.go
index d862ecf86..cfd58ec96 100644
--- a/go/tools/scion-pki/internal/cmd/cmd.go
+++ b/go/tools/scion-pki/internal/cmd/cmd.go
@@ -21,7 +21,7 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/serrors"
 	"github.com/scionproto/scion/go/tools/scion-pki/internal/certs"
 	"github.com/scionproto/scion/go/tools/scion-pki/internal/keys"
 	"github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn"
@@ -43,7 +43,6 @@ root configuration files used in the SCION control plane PKI.`,
 		}
 	},
 	SilenceErrors: true,
-	SilenceUsage:  true,
 }
 
 const (
@@ -87,12 +86,11 @@ var autoCompleteCmd = &cobra.Command{
 func Execute() {
 	err := RootCmd.Execute()
 	switch err.(type) {
-	case common.BasicError:
+	case serrors.Wrapper:
 		fmt.Fprintf(os.Stderr, "Error: %s\n", err)
 		os.Exit(2)
 	case error:
 		fmt.Fprintf(os.Stderr, "Error: %s\n", err)
-		RootCmd.Usage()
 		os.Exit(1)
 	}
 }
diff --git a/go/tools/scion-pki/internal/v2/certs/BUILD.bazel b/go/tools/scion-pki/internal/v2/certs/BUILD.bazel
index 5d60c8085..0eb05f178 100644
--- a/go/tools/scion-pki/internal/v2/certs/BUILD.bazel
+++ b/go/tools/scion-pki/internal/v2/certs/BUILD.bazel
@@ -5,6 +5,7 @@ go_library(
     srcs = [
         "chain.go",
         "cmd.go",
+        "doc.go",
         "human.go",
         "issuer.go",
         "loader.go",
diff --git a/go/tools/scion-pki/internal/v2/certs/cmd.go b/go/tools/scion-pki/internal/v2/certs/cmd.go
index e8122c911..ab193b2d6 100644
--- a/go/tools/scion-pki/internal/v2/certs/cmd.go
+++ b/go/tools/scion-pki/internal/v2/certs/cmd.go
@@ -28,42 +28,33 @@ var version uint64
 var Cmd = &cobra.Command{
 	Use:   "certs",
 	Short: "Interact with certificates for the SCION control plane PKI.",
-	Long: `
-'certs' can be used to generate and verify certificates for the SCION control plane PKI.
+	Long: `'certs' can be used to generate and verify certificates for the SCION control plane PKI.
 
 Selector:
-	*-*
-		All ISDs and ASes under the root directory.
-	X-*
-		All ASes in ISD X.
-	X-Y
-		A specific AS X-Y, e.g. AS 1-ff00:0:300
+    *-*: All ISDs and ASes under the root directory.
+    X-*: All ASes in ISD X.
+    X-Y: A specific AS X-Y, e.g. AS 1-ff00:0:110.
 
-'certs' needs to be pointed to the root directory where all keys and certificates are
-stored on disk (-d flag). It expects the contents of the root directory to follow
-a predefined structure:
-	<root>/
-		ISD1/
-			trc-v1.toml
-			ASff00_0_110/
-				as-v1.toml
-				issuer-v1.toml
-				keys.toml
-				certs/
-				keys/
-			ASff00_0_120/
-			...
-		ISD2/
-			ASff00_0_210/
-			...
-		...
+The subcommands expect the contents of the root directory to follow a predefined
+file structure. See 'scion-pki help v2' for more information.
 `,
 }
 
 var genIssuerCmd = &cobra.Command{
 	Use:   "issuer",
 	Short: "Generate the issuer certificate",
-	Args:  cobra.ExactArgs(1),
+	Example: `  scion-pki v2 certs issuer 1-ff00:0:110
+  scion-pki v2 certs issuer '*'
+  scion-pki v2 certs issuer 1-ff00:0:110 -d $SPKI_ROOT_DIR
+  scion-pki v2 certs issuer 1-ff00:0:110 --version 42`,
+	Long: `'issuer' generates the issuer certificate based on the selector.
+
+This command requires a valid issuer configuration file. Further, the referenced
+TRC and its configuration file must be present.
+
+See 'scion-pki help v2 certs' for information on the selector.
+`,
+	Args: cobra.ExactArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		g := issGen{
 			Dirs:    pkicmn.GetDirs(),
@@ -80,7 +71,19 @@ var genIssuerCmd = &cobra.Command{
 var genChainCmd = &cobra.Command{
 	Use:   "chain",
 	Short: "Generate the certificate chain",
-	Args:  cobra.ExactArgs(1),
+	Example: `  scion-pki v2 certs chain 1-ff00:0:110
+  scion-pki v2 certs chain '*'
+  scion-pki v2 certs chain 1-ff00:0:110 -d $SPKI_ROOT_DIR
+  scion-pki v2 certs chain 1-ff00:0:110 --version 42`,
+	Long: `'chain' generates the AS certificate and the resulting chain based on the selector.
+
+This command requires a valid AS configuration file. Further, the referenced
+issuer certificate and its configuration file must be present. For verification,
+the TRC referenced by the issuer certificate must also be present.
+
+See 'scion-pki help v2 certs' for information on the selector.
+`,
+	Args: cobra.ExactArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		g := chainGen{
 			Dirs:    pkicmn.GetDirs(),
@@ -97,9 +100,11 @@ var genChainCmd = &cobra.Command{
 var humanCmd = &cobra.Command{
 	Use:   "human",
 	Short: "Display human readable issuer certificates and certificate chains",
-	Long: `
-	'human' parses the provided issuer certificate and certificate chain files
-	and displays them in a human readable format.
+	Example: `  scion-pki v2 certs human ISD1/ASff00_0_110/certs/ISD1-ASff00_0_110.crt
+  scion-pki v2 certs human ISD1/ASff00_0_110/certs/ISD1-ASff00_0_110.issuer
+  scion-pki v2 certs human ISD1/ASff00_0_110/certs/*`,
+	Long: `'human' parses the provided issuer certificate and certificate chain files
+and displays them in a human readable format.
 `,
 	Args: cobra.MinimumNArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
diff --git a/go/tools/scion-pki/internal/v2/certs/doc.go b/go/tools/scion-pki/internal/v2/certs/doc.go
new file mode 100644
index 000000000..e72bac2ee
--- /dev/null
+++ b/go/tools/scion-pki/internal/v2/certs/doc.go
@@ -0,0 +1,17 @@
+// Copyright 2019 Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package certs defines the commands to generate issuer and AS certificates
+// from the configuration files.
+package certs
diff --git a/go/tools/scion-pki/internal/v2/cmd/cmd.go b/go/tools/scion-pki/internal/v2/cmd/cmd.go
index ee2be8dbe..df9508f53 100644
--- a/go/tools/scion-pki/internal/v2/cmd/cmd.go
+++ b/go/tools/scion-pki/internal/v2/cmd/cmd.go
@@ -25,7 +25,32 @@ import (
 
 var Cmd = &cobra.Command{
 	Use:   "v2",
-	Short: "Use the new format",
+	Short: "Scion Public Key Infrastructure Management Tool",
+	Long: `scion-pki is a tool to generate keys, certificates, and trust
+root configuration files used in the SCION control plane PKI.
+
+The subcommands expect the contents of the root directory to follow a rigid and
+predefined file structure:
+
+<root>
+├── ISD1
+│   ├── ASff00_0_c
+│   │   ├── as-v1.toml      # AS certificate configuration (versioned)
+│   │   ├── certs           # Dir containing issuer certificates and certificate chains
+│   │   ├── issuer-v1.toml  # Issuer certificate configuration (versioned)
+│   │   ├── keys            # Dir containing private keys
+│   │   ├── keys.toml       # Keys configuration file
+│   │   └── pub             # Dir containing public keys
+│   ├── trcs                # Dir containing partial and signed TRCs
+│   │   └── ISD1-V1.parts   # Dir containing partially signed TRC for specific version
+│   └── trc-v1.toml         # TRC configuration (versioned)
+
+A sample file structure can be generated in 'DIR' by running:
+  scion-pki v2 tmpl sample > $DIR/sample.topo
+  scion-pki v2 tmpl topo -d $DIR sample.topo
+
+The 'certs', 'keys', 'trcs' directories are created on demand by the tool.
+`,
 }
 
 func init() {
diff --git a/go/tools/scion-pki/internal/v2/conf/BUILD.bazel b/go/tools/scion-pki/internal/v2/conf/BUILD.bazel
index 8ade0bebb..df696690f 100644
--- a/go/tools/scion-pki/internal/v2/conf/BUILD.bazel
+++ b/go/tools/scion-pki/internal/v2/conf/BUILD.bazel
@@ -4,6 +4,7 @@ go_library(
     name = "go_default_library",
     srcs = [
         "as.go",
+        "doc.go",
         "issuer.go",
         "key.go",
         "trc.go",
diff --git a/go/tools/scion-pki/internal/v2/conf/doc.go b/go/tools/scion-pki/internal/v2/conf/doc.go
new file mode 100644
index 000000000..85421a9de
--- /dev/null
+++ b/go/tools/scion-pki/internal/v2/conf/doc.go
@@ -0,0 +1,62 @@
+// Copyright 2020 Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package conf defines the configuration files for the scion-pki tool.
+//
+// Config files
+//
+// The way scion-pki generates trust material, such as TRCs or certificates, can
+// be configured through configuration files. There are four configuration file
+// types:
+//
+// TRC: For each TRC version, there is exactly one configuration file. The
+// config specifies the contents of the TRC (e.g. the validity period, the
+// voting ASes and which keys to use).
+//
+// Keys: Each AS has a keys configuration file that specifies all keys with
+// their version, validity and algorithm.
+//
+// AS certificate: For each AS certificate version, there is exactly one
+// configuration file. The config specifies the contents of the certificate
+// (e.g. the validity period, the issuing AS, and which keys to use).
+//
+// Issuer certificate: For each Issuer certificate version, there is exactly one
+// configuration file. The config specifies the contents of the certificate
+// (e.g. the validity period, the issuing AS, and which keys to use).
+//
+// Directory Structure
+//
+// The directory structure how config files are arranged and the file naming is
+// rigid. A sample tree is shown below. This package exposes a set of helper
+// functions to determine the correct file names.
+//
+//    <root>/
+//    ├── ISD1
+//    │   ├── ASff00_0_110
+//    │   │   ├── as-v1.toml
+//    │   │   ├── iss-v1.toml
+//    │   │   ├── keys.toml
+//    │   │   ...
+//    │   ├── ASff00_0_120
+//    │   │   ├── as-v1.toml
+//    │   │   ├── as-v2.toml
+//    │   │   ├── keys.toml
+//    │   │   ...
+//    │   ├── ASff00_0_130
+//    │   │    ...
+//    │   ├── trc-v1.toml
+//    │   ...
+//    ├── ISD2
+//        ...
+package conf
diff --git a/go/tools/scion-pki/internal/v2/conf/trc.go b/go/tools/scion-pki/internal/v2/conf/trc.go
index 459bb15a6..44bf090c0 100644
--- a/go/tools/scion-pki/internal/v2/conf/trc.go
+++ b/go/tools/scion-pki/internal/v2/conf/trc.go
@@ -39,9 +39,8 @@ func AllTRCFiles(dir string, isd addr.ISD) string {
 	return filepath.Join(pkicmn.GetIsdPath(dir, isd), "trc-v*.toml")
 }
 
-// TRC2 holds the TRC configuration.
-// TODO(roosd): rename to TRC.
-type TRC2 struct {
+// TRC holds the TRC configuration.
+type TRC struct {
 	Description       string
 	Version           scrypto.Version
 	BaseVersion       scrypto.Version
@@ -54,23 +53,23 @@ type TRC2 struct {
 }
 
 // LoadTRC loads the TRC configuration from the provided file. The contents are already validated.
-func LoadTRC(file string) (TRC2, error) {
+func LoadTRC(file string) (TRC, error) {
 	var t tomlTRC
 	if _, err := toml.DecodeFile(file, &t); err != nil {
-		return TRC2{}, serrors.WrapStr("unable to load TRC config from file", err, "file", file)
+		return TRC{}, serrors.WrapStr("unable to load TRC config from file", err, "file", file)
 	}
 	cfg, err := t.TRC()
 	if err != nil {
-		return TRC2{}, serrors.WithCtx(err, "file", file)
+		return TRC{}, serrors.WithCtx(err, "file", file)
 	}
 	if err := cfg.Validate(); err != nil {
-		return TRC2{}, serrors.WrapStr("unable to validate TRC config", err, "file", file)
+		return TRC{}, serrors.WrapStr("unable to validate TRC config", err, "file", file)
 	}
 	return cfg, nil
 }
 
 // Encode writes the encoded TRC config to the writer.
-func (cfg TRC2) Encode(w io.Writer) error {
+func (cfg TRC) Encode(w io.Writer) error {
 	t := tomlTRCFromTRC(cfg)
 	if err := toml.NewEncoder(w).Encode(t); err != nil {
 		return serrors.WrapStr("unable to encode TRC config", err)
@@ -79,7 +78,7 @@ func (cfg TRC2) Encode(w io.Writer) error {
 }
 
 // Validate checks all values are set.
-func (cfg TRC2) Validate() error {
+func (cfg TRC) Validate() error {
 	switch {
 	case cfg.Description == "":
 		return serrors.New("description is not set")
@@ -155,8 +154,8 @@ type tomlTRC struct {
 	PrimaryASes       map[string]Primary `toml:"primary_ases"`
 }
 
-func (t tomlTRC) TRC() (TRC2, error) {
-	cfg := TRC2{
+func (t tomlTRC) TRC() (TRC, error) {
+	cfg := TRC{
 		Description:       t.Description,
 		Version:           t.Version,
 		BaseVersion:       t.BaseVersion,
@@ -170,14 +169,14 @@ func (t tomlTRC) TRC() (TRC2, error) {
 	for raw, primary := range t.PrimaryASes {
 		as, err := addr.ASFromString(raw)
 		if err != nil {
-			return TRC2{}, serrors.WrapStr("unable to parse AS number", err, "input", raw)
+			return TRC{}, serrors.WrapStr("unable to parse AS number", err, "input", raw)
 		}
 		cfg.PrimaryASes[as] = primary
 	}
 	return cfg, nil
 }
 
-func tomlTRCFromTRC(cfg TRC2) tomlTRC {
+func tomlTRCFromTRC(cfg TRC) tomlTRC {
 	t := tomlTRC{
 		Description:       cfg.Description,
 		Version:           cfg.Version,
diff --git a/go/tools/scion-pki/internal/v2/keys/BUILD.bazel b/go/tools/scion-pki/internal/v2/keys/BUILD.bazel
index 50b38117f..d4736d571 100644
--- a/go/tools/scion-pki/internal/v2/keys/BUILD.bazel
+++ b/go/tools/scion-pki/internal/v2/keys/BUILD.bazel
@@ -4,6 +4,7 @@ go_library(
     name = "go_default_library",
     srcs = [
         "cmd.go",
+        "doc.go",
         "priv.go",
         "pub.go",
         "util.go",
diff --git a/go/tools/scion-pki/internal/v2/keys/cmd.go b/go/tools/scion-pki/internal/v2/keys/cmd.go
index a9b427a7b..88bb17ac2 100644
--- a/go/tools/scion-pki/internal/v2/keys/cmd.go
+++ b/go/tools/scion-pki/internal/v2/keys/cmd.go
@@ -25,23 +25,31 @@ var Cmd = &cobra.Command{
 	Use:   "keys",
 	Short: "Generate keys for the SCION control plane PKI.",
 	Long: `
-'keys' can be used to generate all the necessary keys used in the SCION control plane PKI as well
-as the AS master key.
+'keys' can be used to generate all the necessary keys used in the SCION control plane PKI.
 
 Selector:
-	*-*
-		All ISDs and ASes under the root directory.
-	X-*
-		All ASes in ISD X.
-	X-Y
-		A specific AS X-Y, e.g. AS 1-ff00:0:300
+    *-*: All ISDs and ASes under the root directory.
+    X-*: All ASes in ISD X.
+    X-Y: A specific AS X-Y, e.g. AS 1-ff00:0:110.
+
+The subcommands expect the contents of the root directory to follow a predefined
+file structure. See 'scion-pki help v2' for more information.
 `,
 }
 
 var privateCmd = &cobra.Command{
 	Use:   "private",
 	Short: "Generate private keys",
-	Args:  cobra.ExactArgs(1),
+	Example: `  scion-pki v2 keys private 1-ff00:0:110
+  scion-pki v2 keys private '*'
+  scion-pki v2 keys private 1-ff00:0:110 -d $SPKI_ROOT_DIR`,
+	Long: `'private' generates the private keys based on the selector.
+Already existing keys are not overwritten, unless the force flag is enabled.
+This command requires a valid keys.toml.
+
+See 'scion-pki help v2 keys' for information on the selector.
+`,
+	Args: cobra.ExactArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		g := privGen{Dirs: pkicmn.GetDirs()}
 		asMap, err := pkicmn.ProcessSelector(args[0])
@@ -58,7 +66,16 @@ var privateCmd = &cobra.Command{
 var publicCmd = &cobra.Command{
 	Use:   "public",
 	Short: "Generate public keys",
-	Args:  cobra.ExactArgs(1),
+	Example: `  scion-pki v2 keys public 1-ff00:0:110
+  scion-pki v2 keys public '*'
+  scion-pki v2 keys public 1-ff00:0:110 -d $SPKI_ROOT_DIR`,
+	Long: `'public' generates the public keys based on the selector.
+For all ASes covered by the selector, the public keys are derived from the existing
+private keys in the 'keys' directory. Non-existent private keys are not generated.
+
+See 'scion-pki help v2 keys' for information on the selector.
+`,
+	Args: cobra.ExactArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		g := pubGen{Dirs: pkicmn.GetDirs()}
 		asMap, err := pkicmn.ProcessSelector(args[0])
diff --git a/go/tools/scion-pki/internal/v2/keys/doc.go b/go/tools/scion-pki/internal/v2/keys/doc.go
new file mode 100644
index 000000000..689e75f09
--- /dev/null
+++ b/go/tools/scion-pki/internal/v2/keys/doc.go
@@ -0,0 +1,17 @@
+// Copyright 2019 Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package keys defines the commands to generate the private and public keys
+// from the key configuration files.
+package keys
diff --git a/go/tools/scion-pki/internal/v2/keys/pub-pub b/go/tools/scion-pki/internal/v2/keys/pub-pub
deleted file mode 100644
index 11360c7e7..000000000
--- a/go/tools/scion-pki/internal/v2/keys/pub-pub
+++ /dev/null
@@ -1,9 +0,0 @@
------BEGIN PUBLIC KEY-----
-algorithm: 
-ia: 1-ff00:0:110
-not_after: 0001-01-01 00:00:00+0000
-not_before: 0001-01-01 00:00:00+0000
-usage: as-signing
-version: 1
-
------END PUBLIC KEY-----
diff --git a/go/tools/scion-pki/internal/v2/tmpl/cmd.go b/go/tools/scion-pki/internal/v2/tmpl/cmd.go
index 6104c795a..1d00c7b26 100644
--- a/go/tools/scion-pki/internal/v2/tmpl/cmd.go
+++ b/go/tools/scion-pki/internal/v2/tmpl/cmd.go
@@ -31,14 +31,21 @@ var Cmd = &cobra.Command{
 	Use:   "tmpl",
 	Short: "Generate configuration templates for ISDs and ASes.",
 	Long: `
-'tmpl' can be used to generate configuration file templates for ISDs and ASes.
+'tmpl' generates configuration file templates of ISDs and ASes.
 `,
 }
 
 var topo = &cobra.Command{
 	Use:   "topo",
-	Short: "Generate isd.ini and as.ini templates for the provided topo",
-	Args:  cobra.ExactArgs(1),
+	Short: "Generate configuration files for the provided topo",
+	Long: `
+'topo' generates the necessary configuration files for the provided topology.
+
+Keys, TRCs and certificates are all configured to be version '1'. Based on these
+configuration files, the 'keys', 'trcs' and 'certs' commands can be run to
+generated the respective trust material.
+`,
+	Args: cobra.ExactArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		val, err := validityFromFlags()
 		if err != nil {
diff --git a/go/tools/scion-pki/internal/v2/tmpl/topo.go b/go/tools/scion-pki/internal/v2/tmpl/topo.go
index 4ceac08b3..29f3762b2 100644
--- a/go/tools/scion-pki/internal/v2/tmpl/topo.go
+++ b/go/tools/scion-pki/internal/v2/tmpl/topo.go
@@ -72,8 +72,8 @@ func (g topoGen) setupDirs(topo topoFile) error {
 	return nil
 }
 
-func (g topoGen) genTRCs(topo topoFile) (map[addr.ISD]conf.TRC2, error) {
-	trcs := make(map[addr.ISD]conf.TRC2)
+func (g topoGen) genTRCs(topo topoFile) (map[addr.ISD]conf.TRC, error) {
+	trcs := make(map[addr.ISD]conf.TRC)
 	for isd := range topo.ISDs() {
 		trcs[isd] = g.genTRC(isd, topo)
 		var buf bytes.Buffer
@@ -88,10 +88,10 @@ func (g topoGen) genTRCs(topo topoFile) (map[addr.ISD]conf.TRC2, error) {
 	return trcs, nil
 }
 
-func (g topoGen) genTRC(isd addr.ISD, topo topoFile) conf.TRC2 {
+func (g topoGen) genTRC(isd addr.ISD, topo topoFile) conf.TRC {
 	primaries := topo.Primaries(isd)
 	reset := true
-	cfg := conf.TRC2{
+	cfg := conf.TRC{
 		Description: fmt.Sprintf("ISD %d", isd),
 		Version:     1,
 		BaseVersion: 1,
@@ -109,7 +109,7 @@ func (g topoGen) genTRC(isd addr.ISD, topo topoFile) conf.TRC2 {
 	return cfg
 }
 
-func (g topoGen) genKeys(topo topoFile, cfg map[addr.ISD]conf.TRC2) error {
+func (g topoGen) genKeys(topo topoFile, cfg map[addr.ISD]conf.TRC) error {
 	for ia := range topo.ASes {
 		keys := g.genASKeys(ia.A, cfg[ia.I])
 		var buf bytes.Buffer
@@ -124,7 +124,7 @@ func (g topoGen) genKeys(topo topoFile, cfg map[addr.ISD]conf.TRC2) error {
 	return nil
 }
 
-func (g topoGen) genASKeys(as addr.AS, cfg conf.TRC2) conf.Keys {
+func (g topoGen) genASKeys(as addr.AS, cfg conf.TRC) conf.Keys {
 	keys := conf.Keys{
 		Primary: make(map[trc.KeyType]map[scrypto.KeyVersion]conf.KeyMeta),
 		Issuer:  make(map[cert.KeyType]map[scrypto.KeyVersion]conf.KeyMeta),
diff --git a/go/tools/scion-pki/internal/v2/trcs/BUILD.bazel b/go/tools/scion-pki/internal/v2/trcs/BUILD.bazel
index 876a20e66..355151c4d 100644
--- a/go/tools/scion-pki/internal/v2/trcs/BUILD.bazel
+++ b/go/tools/scion-pki/internal/v2/trcs/BUILD.bazel
@@ -5,6 +5,7 @@ go_library(
     srcs = [
         "cmd.go",
         "combine.go",
+        "doc.go",
         "gen.go",
         "human.go",
         "loader.go",
diff --git a/go/tools/scion-pki/internal/v2/trcs/cmd.go b/go/tools/scion-pki/internal/v2/trcs/cmd.go
index d85a3c113..2d795543e 100644
--- a/go/tools/scion-pki/internal/v2/trcs/cmd.go
+++ b/go/tools/scion-pki/internal/v2/trcs/cmd.go
@@ -31,30 +31,43 @@ var version uint64
 var Cmd = &cobra.Command{
 	Use:   "trcs",
 	Short: "Generate TRCs for the SCION control plane PKI",
-	Long: `
-'trc' can be used to generate Trust Root Configuration (TRC) files used in the SCION control
-plane PKI.
+	Long: `'trc' can be used to generate Trust Root Configuration (TRC) files used in the
+SCION control plane PKI.
 
 Generating a TRC can be split into three phases:
 1. 'proto': Generate the prototype TRC that contains the payload part of the signed TRC.
 2. 'sign': Sign the payload with the respective private keys.
 3. 'combine': Combine the signatures and the payload to a fully signed TRC.
 
-In case the caller has access to all private keys, the caller can use a short-cut command
-that generates the signed TRC in one call: 'gen'.
+In case the caller has access to all private keys, the caller can use a
+short-cut command that generates the signed TRC in one call: 'gen'.
 
 Selector:
-	*
-		All ISDs under the root directory.
-	X
-		ISD X.
+    *: All ISDs under the root directory.
+    X: ISD X.
+
+The subcommands expect the contents of the root directory to follow a predefined
+file structure. See 'scion-pki help v2' for more information.
 `,
 }
 
 var gen = &cobra.Command{
 	Use:   "gen",
 	Short: "Generate new TRCs",
-	Args:  cobra.ExactArgs(1),
+	Example: `  scion-pki v2 trcs gen 1
+  scion-pki v2 trcs gen '*' -d $SPKI_ROOT_DIR
+  scion-pki v2 trcs gen '*' --version 42`,
+	Long: `'gen' generates the TRCS based on the selector.
+
+This command coalesces the prototype, sign and combine step of the trc generation.
+
+This command requires a valid TRC configuration file. Further, all private keys
+for the signing ASes must be present. For non-base TRCs, the previous TRC must
+be present.
+
+See 'scion-pki help v2 trcs' for information on the selector.
+`,
+	Args: cobra.ExactArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		g := fullGen{
 			Dirs:    pkicmn.GetDirs(),
@@ -74,9 +87,18 @@ var gen = &cobra.Command{
 var proto = &cobra.Command{
 	Use:   "proto",
 	Short: "Generate new proto TRCs",
-	Long: `
-	'proto' generates new prototype TRCs from the ISD configs. They need to be signed
-	using the sign command.
+	Example: `  scion-pki v2 trcs proto 1
+  scion-pki v2 trcs proto '*' -d $SPKI_ROOT_DIR
+  scion-pki v2 trcs proto '*' --version 42`,
+	Long: `'proto' generates the prototype TRCs based on the selector.
+
+This command requires a valid TRC configuration file. Further, the private or
+public key for each key referenced in the TRC must be present. For non-base TRCs,
+the previous TRC must be present.
+
+The prototype TRC is stored in a sub-directory of 'trcs'.
+
+See 'scion-pki help v2 trcs' for information on the selector.
 `,
 	Args: cobra.ExactArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
@@ -98,8 +120,19 @@ var proto = &cobra.Command{
 var sign = &cobra.Command{
 	Use:   "sign",
 	Short: "Sign the proto TRCs",
-	Long: `
-	'sign' generates new signatures for the proto TRCs.
+	Example: `  scion-pki v2 trcs sign 1-ff00:0:110
+  scion-pki v2 trcs sign '1-*' -d $SPKI_ROOT_DIR
+  scion-pki v2 trcs sign '*' --version 42`,
+	Long: `'sign' generates the partial signatures for TRCs based on the selector.
+
+This command requires a valid TRC configuration file. Further, the private keys
+that issue the votes and/or proof of possessions are required. For non-base TRCs,
+the previous TRC must be present.
+
+Selector:
+    *-*: All ASes under the root directory.
+    X-*: All ASes in ISD X.
+    X-Y: A specific AS X-Y, e.g. AS 1-ff00:0:110.
 `,
 	Args: cobra.ExactArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
@@ -121,8 +154,16 @@ var sign = &cobra.Command{
 var combine = &cobra.Command{
 	Use:   "combine",
 	Short: "Combine the proto TRCs with their signatures",
-	Long: `
-	'combine' generates a new signed TRC from the prototype TRC and the signatures.
+	Example: `  scion-pki v2 trcs combine 1
+  scion-pki v2 trcs combine '*' -d $SPKI_ROOT_DIR
+  scion-pki v2 trcs combine '*' --version 42`,
+	Long: `'combine' generates a new signed TRC from the prototype TRC and the signatures
+based on the selector.
+
+This command requires the prototype TRC and all partial signatures. For non-base
+TRCs, the previous TRC must be present.
+
+See 'scion-pki help v2 trcs' for information on the selector.
 `,
 	Args: cobra.ExactArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
@@ -144,8 +185,9 @@ var combine = &cobra.Command{
 var human = &cobra.Command{
 	Use:   "human",
 	Short: "Display human readable",
-	Long: `
-	'human' parses the provided TRCs and displays them in a human readable format.
+	Example: `  scion-pki v2 trcs human ISD1/trcs/ISD1-V1.trc
+  scion-pki v2 trcs human ISD1/trcs/*`,
+	Long: `'human' parses the provided TRCs and displays them in a human readable format.
 `,
 	Args: cobra.MinimumNArgs(1),
 	RunE: func(cmd *cobra.Command, args []string) error {
diff --git a/go/tools/scion-pki/internal/v2/trcs/doc.go b/go/tools/scion-pki/internal/v2/trcs/doc.go
new file mode 100644
index 000000000..f082fc6c6
--- /dev/null
+++ b/go/tools/scion-pki/internal/v2/trcs/doc.go
@@ -0,0 +1,17 @@
+// Copyright 2019 Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package trcs defines the commands to generate base TRC and TRC updates from
+// the configuration files.
+package trcs
diff --git a/go/tools/scion-pki/internal/v2/trcs/loader.go b/go/tools/scion-pki/internal/v2/trcs/loader.go
index a08d72506..81e50e498 100644
--- a/go/tools/scion-pki/internal/v2/trcs/loader.go
+++ b/go/tools/scion-pki/internal/v2/trcs/loader.go
@@ -33,8 +33,8 @@ type loader struct {
 	Version scrypto.Version
 }
 
-func (l loader) LoadConfigs(isds []addr.ISD) (map[addr.ISD]conf.TRC2, error) {
-	cfgs := make(map[addr.ISD]conf.TRC2)
+func (l loader) LoadConfigs(isds []addr.ISD) (map[addr.ISD]conf.TRC, error) {
+	cfgs := make(map[addr.ISD]conf.TRC)
 	for _, isd := range isds {
 		file, err := l.selectConfig(isd)
 		if err != nil {
@@ -82,7 +82,7 @@ func (l loader) findMaxVersion(files []string) (scrypto.Version, error) {
 	return scrypto.Version(max), nil
 }
 
-func (l loader) LoadProtos(cfgs map[addr.ISD]conf.TRC2) (map[addr.ISD]signedMeta, error) {
+func (l loader) LoadProtos(cfgs map[addr.ISD]conf.TRC) (map[addr.ISD]signedMeta, error) {
 	protos := make(map[addr.ISD]signedMeta)
 	for isd, cfg := range cfgs {
 		file := ProtoFile(l.Dirs.Out, isd, cfg.Version)
diff --git a/go/tools/scion-pki/internal/v2/trcs/prototype.go b/go/tools/scion-pki/internal/v2/trcs/prototype.go
index 857cefee0..f1620758c 100644
--- a/go/tools/scion-pki/internal/v2/trcs/prototype.go
+++ b/go/tools/scion-pki/internal/v2/trcs/prototype.go
@@ -66,7 +66,7 @@ func (g protoGen) Run(asMap pkicmn.ASMap) error {
 }
 
 // Generate generates the prototype TRCs for all provided configurations.
-func (g protoGen) Generate(cfgs map[addr.ISD]conf.TRC2) (map[addr.ISD]signedMeta, error) {
+func (g protoGen) Generate(cfgs map[addr.ISD]conf.TRC) (map[addr.ISD]signedMeta, error) {
 	trcs := make(map[addr.ISD]signedMeta)
 	for isd, cfg := range cfgs {
 		signed, err := g.generate(isd, cfg)
@@ -80,7 +80,7 @@ func (g protoGen) Generate(cfgs map[addr.ISD]conf.TRC2) (map[addr.ISD]signedMeta
 }
 
 // generate generates the prototype TRC for a specific configuration.
-func (g protoGen) generate(isd addr.ISD, cfg conf.TRC2) (signedMeta, error) {
+func (g protoGen) generate(isd addr.ISD, cfg conf.TRC) (signedMeta, error) {
 	pubKeys, err := g.loadPubKeys(isd, cfg)
 	if err != nil {
 		return signedMeta{}, serrors.WrapStr("unable to load all public keys", err)
@@ -101,7 +101,7 @@ func (g protoGen) generate(isd addr.ISD, cfg conf.TRC2) (signedMeta, error) {
 }
 
 // loadPubKeys loads all public keys necessary for the given configuration.
-func (g protoGen) loadPubKeys(isd addr.ISD, cfg conf.TRC2) (map[addr.AS]pubKeys, error) {
+func (g protoGen) loadPubKeys(isd addr.ISD, cfg conf.TRC) (map[addr.AS]pubKeys, error) {
 	all := make(map[addr.AS]pubKeys)
 	for as, primary := range cfg.PrimaryASes {
 		ia := addr.IA{I: isd, A: as}
@@ -178,7 +178,7 @@ func (g protoGen) loadPubKey(id keyconf.ID) (keyconf.Key, error) {
 	return key, nil
 }
 
-func (g protoGen) newTRC(isd addr.ISD, cfg conf.TRC2, keys map[addr.AS]pubKeys) (*trc.TRC, error) {
+func (g protoGen) newTRC(isd addr.ISD, cfg conf.TRC, keys map[addr.AS]pubKeys) (*trc.TRC, error) {
 	quorum := uint8(cfg.VotingQuorum)
 	reset := *cfg.TrustResetAllowed
 	val := cfg.Validity.Eval(time.Now())
diff --git a/go/tools/scion-pki/internal/v2/trcs/sign.go b/go/tools/scion-pki/internal/v2/trcs/sign.go
index 9d65a2bcc..d0f05b1d4 100644
--- a/go/tools/scion-pki/internal/v2/trcs/sign.go
+++ b/go/tools/scion-pki/internal/v2/trcs/sign.go
@@ -53,7 +53,7 @@ func (g signatureGen) Run(asMap pkicmn.ASMap) error {
 	return nil
 }
 
-func (g signatureGen) Generate(asMap pkicmn.ASMap, cfgs map[addr.ISD]conf.TRC2,
+func (g signatureGen) Generate(asMap pkicmn.ASMap, cfgs map[addr.ISD]conf.TRC,
 	protos map[addr.ISD]signedMeta) (map[addr.ISD]trcParts, error) {
 
 	parts := make(map[addr.ISD]trcParts)
@@ -83,7 +83,7 @@ func (g signatureGen) Generate(asMap pkicmn.ASMap, cfgs map[addr.ISD]conf.TRC2,
 	return parts, nil
 }
 
-func (g signatureGen) generate(ia addr.IA, cfg conf.TRC2, signed trc.Signed,
+func (g signatureGen) generate(ia addr.IA, cfg conf.TRC, signed trc.Signed,
 	t *trc.TRC) (trc.Signed, error) {
 
 	if err := g.sanityChecks(ia.I, cfg, t); err != nil {
@@ -104,7 +104,7 @@ func (g signatureGen) generate(ia addr.IA, cfg conf.TRC2, signed trc.Signed,
 }
 
 // sanityChecks does some small sanity checks to ensure the right TRC is signed.
-func (g signatureGen) sanityChecks(isd addr.ISD, cfg conf.TRC2, t *trc.TRC) error {
+func (g signatureGen) sanityChecks(isd addr.ISD, cfg conf.TRC, t *trc.TRC) error {
 	if isd != t.ISD {
 		return serrors.New("ISD does not match", "proto", t.ISD, "cfg", isd)
 	}
@@ -119,7 +119,7 @@ func (g signatureGen) sanityChecks(isd addr.ISD, cfg conf.TRC2, t *trc.TRC) erro
 }
 
 func (g signatureGen) castVote(signatures map[trc.Protected]trc.Signature, ia addr.IA,
-	cfg conf.TRC2, signed trc.Signed, t *trc.TRC) error {
+	cfg conf.TRC, signed trc.Signed, t *trc.TRC) error {
 
 	vote, ok := t.Votes[ia.A]
 	if !ok {
@@ -155,7 +155,7 @@ func (g signatureGen) castVote(signatures map[trc.Protected]trc.Signature, ia ad
 }
 
 func (g signatureGen) showPOP(signatures map[trc.Protected]trc.Signature, ia addr.IA,
-	cfg conf.TRC2, signed trc.Signed, t *trc.TRC) error {
+	cfg conf.TRC, signed trc.Signed, t *trc.TRC) error {
 
 	for _, keyType := range t.ProofOfPossession[ia.A] {
 		id := keyconf.ID{
diff --git a/go/tools/scion-pki/main.go b/go/tools/scion-pki/main.go
index b9a0b2f80..94581861a 100644
--- a/go/tools/scion-pki/main.go
+++ b/go/tools/scion-pki/main.go
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Tool to generate and verify control-plane PKI primitives.
 package main
 
 import (


Test patch:
diff --git a/go/tools/scion-pki/internal/v2/conf/trc_test.go b/go/tools/scion-pki/internal/v2/conf/trc_test.go
index 65186a145..41d81d603 100644
--- a/go/tools/scion-pki/internal/v2/conf/trc_test.go
+++ b/go/tools/scion-pki/internal/v2/conf/trc_test.go
@@ -34,7 +34,7 @@ import (
 func TestTRCEncode(t *testing.T) {
 	tests := map[string]struct {
 		File   string
-		Config conf.TRC2
+		Config conf.TRC
 	}{
 		"v1": {
 			File:   "testdata/trc-v1.toml",
@@ -61,7 +61,7 @@ func TestTRCEncode(t *testing.T) {
 func TestLoadTRC(t *testing.T) {
 	tests := map[string]struct {
 		File   string
-		Config conf.TRC2
+		Config conf.TRC
 	}{
 		"v1": {
 			File:   "testdata/trc-v1.toml",
@@ -85,7 +85,7 @@ func TestLoadTRC(t *testing.T) {
 // the format has changed.
 func TestUpdateGoldenTRC(t *testing.T) {
 	if *update {
-		cfgs := map[string]conf.TRC2{
+		cfgs := map[string]conf.TRC{
 			"testdata/trc-v1.toml": TRCv1(),
 			"testdata/trc-v2.toml": TRCv2(),
 		}
@@ -100,10 +100,10 @@ func TestUpdateGoldenTRC(t *testing.T) {
 }
 
 // TRCv1 generates a TRC configuration for testing.
-func TRCv1() conf.TRC2 {
+func TRCv1() conf.TRC {
 	t := true
 	v1 := scrypto.KeyVersion(1)
-	return conf.TRC2{
+	return conf.TRC{
 		Description:       "Testing TRC",
 		Version:           1,
 		BaseVersion:       1,
@@ -128,11 +128,11 @@ func TRCv1() conf.TRC2 {
 }
 
 // TRCv2 generates a TRC configuration for testing.
-func TRCv2() conf.TRC2 {
+func TRCv2() conf.TRC {
 	t := true
 	v1 := scrypto.KeyVersion(1)
 	v2 := scrypto.KeyVersion(2)
-	return conf.TRC2{
+	return conf.TRC{
 		Description:       "Testing TRC",
 		Version:           2,
 		BaseVersion:       1,