Skip to content

Commit

Permalink
SPKI: Add verify commands (scionproto#3672)
Browse files Browse the repository at this point in the history
This PR adds verify commands to the crts and trcs command.
  • Loading branch information
oncilla authored and stygerma committed Mar 18, 2020
1 parent 43a978d commit 9a446fe
Show file tree
Hide file tree
Showing 11 changed files with 301 additions and 20 deletions.
2 changes: 2 additions & 0 deletions go/tools/scion-pki/internal/certs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ go_test(
"issuer_test.go",
"loader_test.go",
"main_test.go",
"verify_test.go",
],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
deps = [
"//go/lib/scrypto:go_default_library",
"//go/lib/scrypto/cert:go_default_library",
"//go/lib/xtest:go_default_library",
"//go/tools/scion-pki/internal/pkicmn:go_default_library",
"@com_github_stretchr_testify//assert:go_default_library",
Expand Down
20 changes: 20 additions & 0 deletions go/tools/scion-pki/internal/certs/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,30 @@ and displays them in a human readable format.
},
}

var verifyCmd = &cobra.Command{
Use: "verify",
Short: "Verify all provided issuer certificates and certificate chains",
Example: ` scion-pki certs verify ISD1/ASff00_0_110/certs/ISD1-ASff00_0_110.crt
scion-pki certs verify ISD1/ASff00_0_110/certs/ISD1-ASff00_0_110.issuer
scion-pki certs verify ISD1/ASff00_0_110/certs/*`,
Long: `
'verify' validates and verifies all the issuer certificates and certificate chains.
The TRC that is used to authenticate the issuer certificate must be present.
Whether the file is an issuer certificate or a certificate chain is decided
based on the file ending.
`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
v := verifier{Dirs: pkicmn.GetDirs()}
return v.Run(args)
},
}

func init() {
Cmd.PersistentFlags().Uint64Var(&version, "version", 0,
"certificate version (0 indicates newest)")
Cmd.AddCommand(genChainCmd)
Cmd.AddCommand(genIssuerCmd)
Cmd.AddCommand(humanCmd)
Cmd.AddCommand(verifyCmd)
}
46 changes: 46 additions & 0 deletions go/tools/scion-pki/internal/certs/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,52 @@ type verifier struct {
Dirs pkicmn.Dirs
}

// Run validates and verifies all issuer certificates and certificate chains in
// the file list.
func (v verifier) Run(files []string) error {
var errs serrors.List
issuers, chains := MatchFiles(files)
if err := v.verifyIssuers(issuers); err != nil {
errs = append(errs, err)
}
if err := v.verifyChains(chains); err != nil {
errs = append(errs, err)
}
return errs.ToError()
}

func (v verifier) verifyIssuers(files []string) error {
var errs serrors.List
for _, file := range files {
raw, err := ioutil.ReadFile(file)
if err != nil {
errs = append(errs, serrors.WrapStr("unable to load issuer certificate", err,
"file", file))
continue
}
if err := v.VerifyIssuer(raw); err != nil {
errs = append(errs, serrors.WithCtx(err, "file", file))
}
}
return errs.ToError()
}

func (v verifier) verifyChains(files []string) error {
var errs serrors.List
for _, file := range files {
raw, err := ioutil.ReadFile(file)
if err != nil {
errs = append(errs, serrors.WrapStr("unable to load certificate chain", err,
"file", file))
continue
}
if err := v.VerifyChain(raw); err != nil {
errs = append(errs, serrors.WithCtx(err, "file", file))
}
}
return errs.ToError()
}

// VerifyIssuer validates and verifies a raw signed issuer certificate. For
// verification, the issuing TRC is loaded from the file system.
func (v verifier) VerifyIssuer(raw []byte) error {
Expand Down
94 changes: 94 additions & 0 deletions go/tools/scion-pki/internal/certs/verify_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// 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 (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/scionproto/scion/go/lib/scrypto/cert"
"github.com/scionproto/scion/go/lib/xtest"
"github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn"
)

func TestVerifierRun(t *testing.T) {
tmpDir, cleanF := xtest.MustTempDir("", "test-certs-verifier")
defer cleanF()
isdDir := filepath.Join(tmpDir, "ISD1")
err := exec.Command("cp", "-r", "./testdata/ISD1", isdDir).Run()
require.NoError(t, err)

forged := filepath.Join(tmpDir, "forged")
err = os.MkdirAll(Dir(forged, ia110), 0777)
require.NoError(t, err)

raw, err := ioutil.ReadFile(IssuerFile("./testdata", ia110, 1))
require.NoError(t, err)
iss, err := cert.ParseSignedIssuer(raw)
require.NoError(t, err)
iss.Signature[0] ^= 0xFF
raw, err = cert.EncodeSignedIssuer(iss)
require.NoError(t, err)
forgedIss := IssuerFile(forged, ia110, 1)
err = ioutil.WriteFile(forgedIss, raw, 0644)
require.NoError(t, err)

raw, err = ioutil.ReadFile(ASFile("./testdata", ia111, 1))
require.NoError(t, err)
chain, err := cert.ParseChain(raw)
require.NoError(t, err)
chain.AS.Signature[0] ^= 0xFF
raw, err = chain.MarshalJSON()
require.NoError(t, err)
forgedAS := ASFile(forged, ia110, 1)
err = ioutil.WriteFile(forgedAS, raw, 0644)
require.NoError(t, err)

tests := map[string]struct {
Files []string
Assertion assert.ErrorAssertionFunc
}{
"issuer": {
Files: []string{IssuerFile(tmpDir, ia110, 1)},
Assertion: assert.NoError,
},
"chain": {
Files: []string{ASFile(tmpDir, ia111, 1)},
Assertion: assert.NoError,
},
"all": {
Files: []string{IssuerFile(tmpDir, ia110, 1), ASFile(tmpDir, ia111, 1)},
Assertion: assert.NoError,
},
"forged": {
Files: []string{IssuerFile("fake", ia110, 1), ASFile("fake", ia111, 1),
forgedAS, forgedIss},
Assertion: assert.Error,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
v := verifier{Dirs: pkicmn.Dirs{Root: "./testdata", Out: tmpDir}}
err := v.Run(test.Files)
test.Assertion(t, err)
})
}
}
3 changes: 2 additions & 1 deletion go/tools/scion-pki/internal/trcs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ go_library(
visibility = ["//go/tools/scion-pki:__subpackages__"],
deps = [
"//go/lib/addr:go_default_library",
"//go/lib/common:go_default_library",
"//go/lib/keyconf:go_default_library",
"//go/lib/scrypto:go_default_library",
"//go/lib/scrypto/trc:go_default_library",
Expand All @@ -38,12 +37,14 @@ go_test(
"loader_test.go",
"prototype_test.go",
"sign_test.go",
"validator_test.go",
],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
deps = [
"//go/lib/addr:go_default_library",
"//go/lib/scrypto:go_default_library",
"//go/lib/scrypto/trc:go_default_library",
"//go/lib/xtest:go_default_library",
"//go/tools/scion-pki/internal/pkicmn:go_default_library",
"@com_github_stretchr_testify//assert:go_default_library",
Expand Down
20 changes: 18 additions & 2 deletions go/tools/scion-pki/internal/trcs/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package trcs
import (
"github.com/spf13/cobra"

"github.com/scionproto/scion/go/lib/common"
"github.com/scionproto/scion/go/lib/scrypto"
"github.com/scionproto/scion/go/lib/serrors"
"github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn"
Expand Down Expand Up @@ -182,6 +181,22 @@ See 'scion-pki help trcs' for information on the selector.
},
}

var verify = &cobra.Command{
Use: "verify",
Short: "Verify all provided TRCs",
Example: ` scion-pki trcs verify ISD1/trcs/ISD1-V1.trc
scion-pki trcs verify ISD1/trcs/*`,
Long: `
'verify' validates and verifies all the provided TRCs. In order to verify
non-base TRCs, the predecessor TRC must be available.
`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
v := validator{Dirs: pkicmn.GetDirs()}
return v.Run(args)
},
}

var human = &cobra.Command{
Use: "human",
Short: "Display human readable",
Expand All @@ -192,7 +207,7 @@ var human = &cobra.Command{
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if err := runHuman(args); err != nil {
return common.NewBasicError("unable to display human TRCs", err)
return serrors.WrapStr("unable to display human TRCs", err)
}
return nil
},
Expand All @@ -204,5 +219,6 @@ func init() {
Cmd.AddCommand(proto)
Cmd.AddCommand(sign)
Cmd.AddCommand(combine)
Cmd.AddCommand(verify)
Cmd.AddCommand(human)
}
4 changes: 3 additions & 1 deletion go/tools/scion-pki/internal/trcs/prototype.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,11 @@ func (g protoGen) newTRC(isd addr.ISD, cfg conf.TRC, keys map[addr.AS]pubKeys) (
if !t.Base() {
var err error
file := SignedFile(g.Dirs.Out, isd, t.Version-1)
if prev, _, err = loadTRC(file); err != nil {
dec, err := loadTRC(file)
if err != nil {
return nil, serrors.WrapStr("unable to load previous TRC", err, "file", file)
}
prev = dec.TRC
}
if err := g.attachVotes(t, prev, cfg.Votes); err != nil {
return nil, serrors.WrapStr("unable to attach votes", err)
Expand Down
4 changes: 2 additions & 2 deletions go/tools/scion-pki/internal/trcs/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,13 @@ func (g signatureGen) castVote(signatures map[trc.Protected]trc.Signature, ia ad
if !ok {
return nil
}
prev, _, err := loadTRC(SignedFile(g.Dirs.Out, t.ISD, t.Version-1))
dec, err := loadTRC(SignedFile(g.Dirs.Out, t.ISD, t.Version-1))
if err != nil {
return err
}
id := keyconf.ID{
IA: ia,
Version: prev.PrimaryASes[ia.A].Keys[keyType].KeyVersion,
Version: dec.TRC.PrimaryASes[ia.A].Keys[keyType].KeyVersion,
}
id.Usage, err = keys.UsageFromTRCKeyType(keyType)
if err != nil {
Expand Down
22 changes: 13 additions & 9 deletions go/tools/scion-pki/internal/trcs/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,22 @@ func sortSignatures(signatures map[trc.Protected]trc.Signature) []trc.Signature
return sigs
}

func loadTRC(file string) (*trc.TRC, trc.Encoded, error) {
type decoded struct {
Signed trc.Signed
TRC *trc.TRC
}

func loadTRC(file string) (decoded, error) {
raw, err := ioutil.ReadFile(file)
if err != nil {
return nil, "", err
return decoded{}, err
}
signed, err := trc.ParseSigned(raw)
if err != nil {
return nil, "", err
var dec decoded
if dec.Signed, err = trc.ParseSigned(raw); err != nil {
return decoded{}, err
}
t, err := signed.EncodedTRC.Decode()
if err != nil {
return nil, "", err
if dec.TRC, err = dec.Signed.EncodedTRC.Decode(); err != nil {
return decoded{}, err
}
return t, signed.EncodedTRC, nil
return dec, nil
}
30 changes: 25 additions & 5 deletions go/tools/scion-pki/internal/trcs/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,36 @@ type validator struct {
Dirs pkicmn.Dirs
}

// Run checks that all TRCs provided in the file list are valid and verifiable.
func (v validator) Run(files []string) error {
var errs serrors.List
trcs := make(map[addr.ISD]signedMeta)
for _, file := range files {
dec, err := loadTRC(file)
if err != nil {
errs = append(errs, serrors.WrapStr("unable to load TRC", err, "file", file))
continue
}
trcs[dec.TRC.ISD] = signedMeta{Signed: dec.Signed, Version: dec.TRC.Version}
}
if err := v.Validate(trcs); err != nil {
errs = append(errs, err)
}
return errs.ToError()
}

// Validate checks that all TRCs in the map are valid and verifiable. For base
// TRCs, the TRC invariants are validated and all proof of possessions are
// verified. TRC updates are validated and verified based on the previous TRC.
func (v validator) Validate(combined map[addr.ISD]signedMeta) error {
var errs serrors.List
for isd, meta := range combined {
if err := v.validate(isd, meta); err != nil {
return serrors.WrapStr("TRC cannot be validated/verified", err, "isd", isd)
errs = append(errs, serrors.WrapStr("TRC cannot be validated/verified", err,
"isd", isd, "version", meta.Version))
}
}
return nil
return errs.ToError()
}

func (v validator) validate(isd addr.ISD, meta signedMeta) error {
Expand All @@ -56,13 +76,13 @@ func (v validator) validate(isd addr.ISD, meta signedMeta) error {
if t.Base() {
return nil
}
prev, _, err := loadTRC(SignedFile(v.Dirs.Out, isd, meta.Version-1))
dec, err := loadTRC(SignedFile(v.Dirs.Out, isd, meta.Version-1))
if err != nil {
return serrors.WrapStr("unable to load previous TRC", err, "version", meta.Version-1)
}
val := trc.UpdateValidator{
Next: t,
Prev: prev,
Prev: dec.TRC,
}
if _, err := val.Validate(); err != nil {
return serrors.WrapStr("unable to validate TRC update", err)
Expand All @@ -71,7 +91,7 @@ func (v validator) validate(isd addr.ISD, meta signedMeta) error {
Next: t,
NextEncoded: meta.Signed.EncodedTRC,
Signatures: meta.Signed.Signatures,
Prev: prev,
Prev: dec.TRC,
}
if err := ver.Verify(); err != nil {
return serrors.WrapStr("unable to verify TRC update", err)
Expand Down
Loading

0 comments on commit 9a446fe

Please sign in to comment.