diff --git a/go/tools/scion-pki/internal/v2/certs/BUILD.bazel b/go/tools/scion-pki/internal/v2/certs/BUILD.bazel index dceeb13d05..33ccdb0fcf 100644 --- a/go/tools/scion-pki/internal/v2/certs/BUILD.bazel +++ b/go/tools/scion-pki/internal/v2/certs/BUILD.bazel @@ -4,6 +4,7 @@ go_library( name = "go_default_library", srcs = [ "cmd.go", + "human.go", "issuer.go", "loader.go", "util.go", diff --git a/go/tools/scion-pki/internal/v2/certs/cmd.go b/go/tools/scion-pki/internal/v2/certs/cmd.go index dffd38d88b..c4e4143150 100644 --- a/go/tools/scion-pki/internal/v2/certs/cmd.go +++ b/go/tools/scion-pki/internal/v2/certs/cmd.go @@ -77,6 +77,23 @@ var genIssuerCmd = &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. +`, + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if err := runHuman(args); err != nil { + return err + } + return nil + }, +} + func init() { Cmd.AddCommand(genIssuerCmd) + Cmd.AddCommand(humanCmd) } diff --git a/go/tools/scion-pki/internal/v2/certs/human.go b/go/tools/scion-pki/internal/v2/certs/human.go new file mode 100644 index 0000000000..96fb20e892 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/certs/human.go @@ -0,0 +1,117 @@ +// 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 + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + + "github.com/scionproto/scion/go/lib/scrypto/cert/v2" + "github.com/scionproto/scion/go/lib/serrors" +) + +func runHuman(files []string) error { + issuers, chains := MatchFiles(files) + for _, file := range issuers { + if err := genHumanIssuer(file); err != nil { + return serrors.WrapStr("unable to display issuer certificate", err, "file", file) + } + } + for _, file := range chains { + if err := genHumanChain(file); err != nil { + return serrors.WrapStr("unable to display certificate chain", err, "file", file) + } + } + return nil +} + +func genHumanIssuer(file string) error { + raw, err := ioutil.ReadFile(file) + if err != nil { + return err + } + signed, err := cert.ParseSignedIssuer(raw) + if err != nil { + return serrors.WrapStr("unable to parse signed issuer certificate", err) + } + d, err := decodeIssuer(&signed) + if err != nil { + return serrors.WrapStr("unable to decode issuer certificate", err) + } + if raw, err = json.MarshalIndent(d, "", " "); err != nil { + return serrors.WrapStr("unable to write human readable issuer certificate", err) + } + _, err = fmt.Fprintln(os.Stdout, string(raw)) + return err +} + +func genHumanChain(file string) error { + raw, err := ioutil.ReadFile(file) + if err != nil { + return err + } + signed, err := cert.ParseChain(raw) + if err != nil { + return serrors.WrapStr("unable to parse signed certificate chain", err) + } + humanReadable := make([]decodedCert, 2) + humanReadable[0], err = decodeIssuer(&signed.Issuer) + if err != nil { + return serrors.WrapStr("unable to decode issuer certificate", err) + } + humanReadable[1], err = decodeAS(&signed.AS) + if err != nil { + return serrors.WrapStr("unable to decode AS certificate", err) + } + if raw, err = json.MarshalIndent(humanReadable, "", " "); err != nil { + return serrors.WrapStr("unable to write human readable certificate chain", err) + } + _, err = fmt.Fprintln(os.Stdout, string(raw)) + return err +} + +type decodedCert struct { + Payload interface{} `json:"payload"` + Protected interface{} `json:"protected"` + Signature []byte `json:"signature"` +} + +func decodeAS(c *cert.SignedAS) (decodedCert, error) { + var err error + var d decodedCert + if d.Payload, err = c.Encoded.Decode(); err != nil { + return decodedCert{}, err + } + if d.Protected, err = c.EncodedProtected.Decode(); err != nil { + return decodedCert{}, err + } + d.Signature = c.Signature + return d, nil +} + +func decodeIssuer(c *cert.SignedIssuer) (decodedCert, error) { + var err error + var d decodedCert + if d.Payload, err = c.Encoded.Decode(); err != nil { + return decodedCert{}, err + } + if d.Protected, err = c.EncodedProtected.Decode(); err != nil { + return decodedCert{}, err + } + d.Signature = c.Signature + return d, nil +} diff --git a/go/tools/scion-pki/internal/v2/certs/util.go b/go/tools/scion-pki/internal/v2/certs/util.go index b66e48d84f..7189de1831 100644 --- a/go/tools/scion-pki/internal/v2/certs/util.go +++ b/go/tools/scion-pki/internal/v2/certs/util.go @@ -17,6 +17,7 @@ package certs import ( "fmt" "path/filepath" + "strings" "github.com/scionproto/scion/go/lib/addr" "github.com/scionproto/scion/go/lib/keyconf" @@ -47,3 +48,25 @@ func translateKeys(keys map[cert.KeyType]keyconf.Key) map[cert.KeyType]scrypto.K } return m } + +// MatchFiles matches all issuer certificate and certificate chain file names. +func MatchFiles(files []string) ([]string, []string) { + var issuers, chains []string + for _, file := range files { + _, name := filepath.Split(file) + switch { + case match(pkicmn.IssuerNameFmt, name): + issuers = append(issuers, file) + case match(pkicmn.CertNameFmt, name): + chains = append(chains, file) + default: + pkicmn.QuietPrint("Skipping non-certificate file: %s\n", file) + } + } + return issuers, chains +} + +func match(fmtString, name string) bool { + matched, _ := filepath.Match(strings.NewReplacer("%d", "*", "%s", "*").Replace(fmtString), name) + return matched +}