diff --git a/go/lib/scrypto/asym.go b/go/lib/scrypto/asym.go index 88dc44e474..2f1ffbfbf2 100644 --- a/go/lib/scrypto/asym.go +++ b/go/lib/scrypto/asym.go @@ -100,9 +100,13 @@ func GetPubKey(privKey []byte, algo string) ([]byte, error) { func Sign(sigInput, signKey common.RawBytes, signAlgo string) (common.RawBytes, error) { switch strings.ToLower(signAlgo) { case Ed25519: - if len(signKey) != ed25519.PrivateKeySize { - return nil, common.NewBasicError(ErrInvalidPrivKeySize, nil, "expected", - ed25519.PrivateKeySize, "actual", len(signKey)) + switch len(signKey) { + case ed25519.PrivateKeySize: + case ed25519.SeedSize: + signKey = common.RawBytes(ed25519.NewKeyFromSeed(signKey)) + default: + return nil, common.NewBasicError(ErrInvalidPrivKeySize, nil, + "expected", ed25519.PrivateKeySize, "actual", len(signKey)) } return ed25519.Sign(ed25519.PrivateKey(signKey), sigInput), nil default: diff --git a/go/lib/scrypto/trc/v2/signed.go b/go/lib/scrypto/trc/v2/signed.go index 6a280d05a4..4b8c2922c1 100644 --- a/go/lib/scrypto/trc/v2/signed.go +++ b/go/lib/scrypto/trc/v2/signed.go @@ -78,8 +78,8 @@ func Encode(trc *TRC) (Encoded, error) { } // Decode returns the decoded Decode. -func (p *Encoded) Decode() (*TRC, error) { - b, err := scrypto.Base64.DecodeString(string(*p)) +func (p Encoded) Decode() (*TRC, error) { + b, err := scrypto.Base64.DecodeString(string(p)) if err != nil { return nil, err } @@ -110,8 +110,8 @@ func EncodeProtected(p Protected) (EncodedProtected, error) { } // Decode decodes and return the protected header. -func (h *EncodedProtected) Decode() (Protected, error) { - b, err := scrypto.Base64.DecodeString(string(*h)) +func (h EncodedProtected) Decode() (Protected, error) { + b, err := scrypto.Base64.DecodeString(string(h)) if err != nil { return Protected{}, err } diff --git a/go/lib/scrypto/trc/v2/update.go b/go/lib/scrypto/trc/v2/update.go index b98bca7102..c3e7732d23 100644 --- a/go/lib/scrypto/trc/v2/update.go +++ b/go/lib/scrypto/trc/v2/update.go @@ -86,17 +86,11 @@ func (v *UpdateValidator) Validate() (UpdateInfo, error) { if err := v.sanity(); err != nil { return UpdateInfo{}, common.NewBasicError(ErrSanityCheck, err) } - keyChanges, err := v.keyChanges() + info, err := v.UpdateInfo() if err != nil { return UpdateInfo{}, err } - attrChanges := v.attrChanges() - info := UpdateInfo{ - Type: v.updateType(keyChanges, attrChanges), - KeyChanges: keyChanges, - AttributeChanges: attrChanges, - } - if err := v.checkProofOfPossesion(keyChanges); err != nil { + if err := v.checkProofOfPossesion(info.KeyChanges); err != nil { return info, err } if err := v.checkVotes(info); err != nil { @@ -135,6 +129,21 @@ func (v *UpdateValidator) sanity() error { return nil } +// UpdateInfo returns information about the TRC update. +func (v *UpdateValidator) UpdateInfo() (UpdateInfo, error) { + keyChanges, err := v.keyChanges() + if err != nil { + return UpdateInfo{}, err + } + attrChanges := v.attrChanges() + info := UpdateInfo{ + Type: v.updateType(keyChanges, attrChanges), + KeyChanges: keyChanges, + AttributeChanges: attrChanges, + } + return info, nil +} + func (v *UpdateValidator) keyChanges() (*KeyChanges, error) { c := newKeyChanges() for as, primary := range v.Next.PrimaryASes { diff --git a/go/tools/scion-pki/internal/pkicmn/pkicmn.go b/go/tools/scion-pki/internal/pkicmn/pkicmn.go index c7b6a1269e..62751c72eb 100644 --- a/go/tools/scion-pki/internal/pkicmn/pkicmn.go +++ b/go/tools/scion-pki/internal/pkicmn/pkicmn.go @@ -33,7 +33,7 @@ const ( TrcNameFmt = "ISD%d-V%d.trc" TRCPartsDirFmt = "ISD%d-V%d.parts" TRCSigPartFmt = "ISD%d-V%d.sig.%s" - TRCProtoNameFmt = "ISD%d-V%d.proto" + TRCProtoNameFmt = "ISD%d-V%d.prototype" TRCsDir = "trcs" CertsDir = "certs" KeysDir = "keys" @@ -67,6 +67,18 @@ func GetDirs() Dirs { } } +// ASMap contains all ASes matched by the selector. +type ASMap map[addr.ISD][]addr.IA + +// ISDs returns all ISDs in the mapping. +func (m ASMap) ISDs() []addr.ISD { + list := make([]addr.ISD, 0, len(m)) + for isd := range m { + list = append(list, isd) + } + return list +} + // ParseSelector parses the given selector. The returned strings are in file format. func ParseSelector(selector string) (string, string, error) { toks := strings.Split(selector, "-") @@ -100,7 +112,7 @@ func ParseSelector(selector string) (string, string, error) { // ProcessSelector processes the given selector and returns a mapping from ISD id to ASes // of that ISD. In case of an ISD-only selector, i.e., a '*' or any number the lists of // ASes will be empty. -func ProcessSelector(selector string) (map[addr.ISD][]addr.IA, error) { +func ProcessSelector(selector string) (ASMap, error) { isdTok, asTok, err := ParseSelector(selector) if err != nil { return nil, err diff --git a/go/tools/scion-pki/internal/pkicmn/pkicmn_test.go b/go/tools/scion-pki/internal/pkicmn/pkicmn_test.go index 972a04ce3c..905e065e81 100644 --- a/go/tools/scion-pki/internal/pkicmn/pkicmn_test.go +++ b/go/tools/scion-pki/internal/pkicmn/pkicmn_test.go @@ -64,7 +64,7 @@ func TestProcessSelector(t *testing.T) { setupTest(t) tests := map[string]struct { selector string - isdAsMap map[addr.ISD][]addr.IA + isdAsMap ASMap err error }{ "Empty selector string": { @@ -72,7 +72,7 @@ func TestProcessSelector(t *testing.T) { }, "ISD only selector with empty AS selector": { selector: "1", - isdAsMap: map[addr.ISD][]addr.IA{ + isdAsMap: ASMap{ addr.ISD(1): getIAFromASes(addr.ISD(1), ases), }, }, @@ -82,7 +82,7 @@ func TestProcessSelector(t *testing.T) { }, "Wildcard ISD selector with empty AS selector": { selector: "*", - isdAsMap: map[addr.ISD][]addr.IA{ + isdAsMap: ASMap{ addr.ISD(1): getIAFromASes(addr.ISD(1), ases), }, }, @@ -92,13 +92,13 @@ func TestProcessSelector(t *testing.T) { }, "Wildcard AS selector with fixed ISD selector": { selector: "1-*", - isdAsMap: map[addr.ISD][]addr.IA{ + isdAsMap: ASMap{ addr.ISD(1): getIAFromASes(addr.ISD(1), ases), }, }, "Fixed ISD-AS selector": { selector: "1-ff00:0:10", - isdAsMap: map[addr.ISD][]addr.IA{ + isdAsMap: ASMap{ addr.ISD(1): getIAFromASes(addr.ISD(1), ases[:1]), }, }, diff --git a/go/tools/scion-pki/internal/v2/conf/trc.go b/go/tools/scion-pki/internal/v2/conf/trc.go index fb1f676570..459bb15a65 100644 --- a/go/tools/scion-pki/internal/v2/conf/trc.go +++ b/go/tools/scion-pki/internal/v2/conf/trc.go @@ -34,6 +34,11 @@ func TRCFile(dir string, isd addr.ISD, version scrypto.Version) string { return filepath.Join(pkicmn.GetIsdPath(dir, isd), fmt.Sprintf("trc-v%d.toml", version)) } +// AllTRCFiles returns a glob string that matches all TRC files for the given isd. +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 { diff --git a/go/tools/scion-pki/internal/v2/keys/priv.go b/go/tools/scion-pki/internal/v2/keys/priv.go index 03b675797c..8ea52c4e73 100644 --- a/go/tools/scion-pki/internal/v2/keys/priv.go +++ b/go/tools/scion-pki/internal/v2/keys/priv.go @@ -36,7 +36,7 @@ type privGen struct { Dirs pkicmn.Dirs } -func (g privGen) Run(asMap map[addr.ISD][]addr.IA) error { +func (g privGen) Run(asMap pkicmn.ASMap) error { cfgs, err := g.loadConfigs(asMap) if err != nil { return err @@ -54,7 +54,7 @@ func (g privGen) Run(asMap map[addr.ISD][]addr.IA) error { return nil } -func (g privGen) loadConfigs(asMap map[addr.ISD][]addr.IA) (map[addr.IA]conf.Keys, error) { +func (g privGen) loadConfigs(asMap pkicmn.ASMap) (map[addr.IA]conf.Keys, error) { cfgs := make(map[addr.IA]conf.Keys) for _, ases := range asMap { for _, ia := range ases { diff --git a/go/tools/scion-pki/internal/v2/keys/pub.go b/go/tools/scion-pki/internal/v2/keys/pub.go index e91919c50d..e4a88b84e0 100644 --- a/go/tools/scion-pki/internal/v2/keys/pub.go +++ b/go/tools/scion-pki/internal/v2/keys/pub.go @@ -32,7 +32,7 @@ type pubGen struct { Dirs pkicmn.Dirs } -func (g pubGen) Run(asMap map[addr.ISD][]addr.IA) error { +func (g pubGen) Run(asMap pkicmn.ASMap) error { privKeys, err := g.loadPrivateKeys(asMap) if err != nil { return err @@ -50,7 +50,7 @@ func (g pubGen) Run(asMap map[addr.ISD][]addr.IA) error { return nil } -func (g pubGen) loadPrivateKeys(asMap map[addr.ISD][]addr.IA) (map[addr.IA][]keyconf.Key, error) { +func (g pubGen) loadPrivateKeys(asMap pkicmn.ASMap) (map[addr.IA][]keyconf.Key, error) { priv := make(map[addr.IA][]keyconf.Key) for _, ases := range asMap { for _, ia := range ases { diff --git a/go/tools/scion-pki/internal/v2/trcs/BUILD.bazel b/go/tools/scion-pki/internal/v2/trcs/BUILD.bazel index 494d8c4330..40ccc534f9 100644 --- a/go/tools/scion-pki/internal/v2/trcs/BUILD.bazel +++ b/go/tools/scion-pki/internal/v2/trcs/BUILD.bazel @@ -1,15 +1,12 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ - "ases.go", "cmd.go", - "combine.go", - "gen.go", "human.go", + "loader.go", "prototype.go", - "sign.go", "util.go", ], importpath = "github.com/scionproto/scion/go/tools/scion-pki/internal/v2/trcs", @@ -21,9 +18,25 @@ go_library( "//go/lib/scrypto:go_default_library", "//go/lib/scrypto/trc/v2:go_default_library", "//go/lib/serrors:go_default_library", - "//go/lib/util:go_default_library", "//go/tools/scion-pki/internal/pkicmn:go_default_library", "//go/tools/scion-pki/internal/v2/conf:go_default_library", + "//go/tools/scion-pki/internal/v2/keys:go_default_library", "@com_github_spf13_cobra//:go_default_library", + "@org_golang_x_xerrors//:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["prototype_test.go"], + data = glob(["testdata/**"]), + embed = [":go_default_library"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/scrypto: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", + "@com_github_stretchr_testify//require:go_default_library", ], ) diff --git a/go/tools/scion-pki/internal/v2/trcs/ases.go b/go/tools/scion-pki/internal/v2/trcs/ases.go deleted file mode 100644 index 2fa30fadef..0000000000 --- a/go/tools/scion-pki/internal/v2/trcs/ases.go +++ /dev/null @@ -1,112 +0,0 @@ -// 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 - -import ( - "path/filepath" - - "github.com/scionproto/scion/go/lib/addr" - "github.com/scionproto/scion/go/lib/common" - "github.com/scionproto/scion/go/lib/keyconf" - "github.com/scionproto/scion/go/lib/scrypto/trc/v2" - "github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn" - "github.com/scionproto/scion/go/tools/scion-pki/internal/v2/conf" -) - -// asCfg holds the AS configuration including the private keys loaded from file system. -type asCfg struct { - ASCfg *conf.ASCfg - Keys map[trc.KeyType][]byte -} - -// KeyTypeToAlgo determines the algorithm for the key type. -func (cfg *asCfg) KeyTypeToAlgo(keyType trc.KeyType) string { - switch keyType { - case trc.OnlineKey: - return cfg.ASCfg.Online - case trc.OfflineKey: - return cfg.ASCfg.Offline - case trc.IssuingKey: - return cfg.ASCfg.Issuing - } - return "none" -} - -// loadPrimaryASes loads the primary ASes with their keys for the ASes in the -// whitelist. If the whitelist is empty, all primary ASes are loaded. -func loadPrimaryASes(isd addr.ISD, isdCfg *conf.ISDCfg, wl []addr.IA) (map[addr.AS]*asCfg, error) { - ases := make(map[addr.AS][]trc.KeyType) - for _, as := range isdCfg.VotingASes { - ases[as] = []trc.KeyType{trc.OnlineKey, trc.OfflineKey} - } - for _, as := range isdCfg.IssuingASes { - ases[as] = append(ases[as], trc.IssuingKey) - } - filter(isd, ases, wl) - primaryASes := make(map[addr.AS]*asCfg) - for as, keys := range ases { - ia := addr.IA{I: isd, A: as} - cfg, err := loadASCfg(ia) - if err != nil { - return nil, err - } - for _, keyType := range keys { - if cfg.Keys[keyType], err = loadKey(ia, keyType, cfg); err != nil { - return nil, err - } - } - primaryASes[as] = cfg - } - return primaryASes, nil -} - -// filter deletes all entries that are not on the whitelist. -func filter(isd addr.ISD, ases map[addr.AS][]trc.KeyType, wl []addr.IA) { - if len(wl) == 0 { - return - } - for as := range ases { - if !pkicmn.Contains(wl, addr.IA{I: isd, A: as}) { - delete(ases, as) - } - } -} - -func loadASCfg(ia addr.IA) (*asCfg, error) { - cfgPath := filepath.Join(pkicmn.GetAsPath(pkicmn.RootDir, ia), conf.ASConfFileName) - cfg, err := conf.LoadASCfg(filepath.Dir(cfgPath)) - if err != nil { - return nil, err - } - return &asCfg{ASCfg: cfg, Keys: make(map[trc.KeyType][]byte)}, nil -} - -func loadKey(ia addr.IA, keyType trc.KeyType, cfg *asCfg) ([]byte, error) { - var file string - switch keyType { - case trc.OnlineKey: - file = keyconf.TRCOnlineKeyFile - case trc.OfflineKey: - file = keyconf.TRCOfflineKeyFile - case trc.IssuingKey: - file = keyconf.TRCIssuingKeyFile - } - path := filepath.Join(pkicmn.GetAsPath(pkicmn.OutDir, ia), pkicmn.KeysDir, file) - key, err := keyconf.LoadKey(path, cfg.KeyTypeToAlgo(keyType)) - if err != nil { - return nil, common.NewBasicError("unable to load key", err, "type", keyType, "file", path) - } - return key, nil -} diff --git a/go/tools/scion-pki/internal/v2/trcs/cmd.go b/go/tools/scion-pki/internal/v2/trcs/cmd.go index 208288da82..18747e634b 100644 --- a/go/tools/scion-pki/internal/v2/trcs/cmd.go +++ b/go/tools/scion-pki/internal/v2/trcs/cmd.go @@ -19,8 +19,15 @@ 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" ) +var version uint64 + +// TODO(roosd): Expand help text with the new TRC configuration format. + var Cmd = &cobra.Command{ Use: "trcs", Short: "Generate TRCs for the SCION control plane PKI", @@ -41,69 +48,9 @@ Selector: All ISDs under the root directory. X ISD X. -'trc' 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: - / - ISD1/ - isd.ini - AS1/ - AS2/ - ... - ISD2/ - isd.ini - AS1/ - ... - ... -isd.ini contains the preconfigured parameters according to which 'trc' generates -the TRCs. It follows the ini format and can contain only the default section with -the following values: - Description [required] - arbitrary non-empty string used to describe the ISD/TRC -and a section 'TRC' with the following values: - Version [required] - integer representing the version of the TRC - BaseVersion [required] - integer representing the base version of the TRC - VotingQuorum [required] - integer representing the number of voting ASes needed to sign an updated TRC. - GracePeriod [required] - duration string indicating how long the previous TRC is still valid. - Must be 0s for base TRC. - TrustResetAllowed [required] - boolean indicating whether trust resets are allowed for this ISD. - NotBefore [optional] - integer representing the not_before time in the TRC represented as seconds - since UNIX epoch. If 0 will be set to now. - Validity [required] - duration string determining the validity of the TRC, e.g., 180d or 36h. - AuthoritativeASes [required] - comma-separated list of AS identifiers representing the authoritative - ASes of the ISD, e.g., ff00:0:110,ff00:0:120. - CoreASes [required] - comma-separated list of AS identifiers representing the core - ASes of the ISD, e.g., ff00:0:110,ff00:0:120. - IssuingASes [required] - comma-separated list of AS identifiers representing the issuing - ASes of the ISD, e.g., ff00:0:110,ff00:0:120. - VotingASes [required] - comma-separated list of AS identifiers representing the voting - ASes of the ISD, e.g., ff00:0:110,ff00:0:120. `, } -var gen = &cobra.Command{ - Use: "gen", - Short: "Generate new TRCs", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - if err := runGenTrc(args[0]); err != nil { - return common.NewBasicError("unable to generate TRCs", err) - } - return nil - }, -} - var proto = &cobra.Command{ Use: "proto", Short: "Generate new proto TRCs", @@ -113,38 +60,16 @@ var proto = &cobra.Command{ `, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - if err := runProto(args[0]); err != nil { - return common.NewBasicError("unable to generate prototype TRCs", err) + g := protoGen{ + Dirs: pkicmn.GetDirs(), + Version: scrypto.Version(version), } - return nil - }, -} - -var sign = &cobra.Command{ - Use: "sign", - Short: "Sign the proto TRCs", - Long: ` - 'sign' generates new signatures for the proto TRCs. -`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - if err := runSign(args[0]); err != nil { - return common.NewBasicError("unable to sign TRCs", err) + asMap, err := pkicmn.ProcessSelector(args[0]) + if err != nil { + return serrors.WrapStr("unable to select target ISDs", err, "selector", args[0]) } - return nil - }, -} - -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. -`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - if err := runCombine(args[0]); err != nil { - return common.NewBasicError("unable to combine TRCs", err) + if err := g.Run(asMap); err != nil { + return serrors.WrapStr("unable to generate prototype TRCs", err) } return nil }, @@ -166,9 +91,7 @@ var human = &cobra.Command{ } func init() { - Cmd.AddCommand(gen) + Cmd.PersistentFlags().Uint64Var(&version, "version", 0, "TRC version (0 indicates newest)") Cmd.AddCommand(proto) - Cmd.AddCommand(sign) - Cmd.AddCommand(combine) Cmd.AddCommand(human) } diff --git a/go/tools/scion-pki/internal/v2/trcs/combine.go b/go/tools/scion-pki/internal/v2/trcs/combine.go deleted file mode 100644 index 719ba69c0f..0000000000 --- a/go/tools/scion-pki/internal/v2/trcs/combine.go +++ /dev/null @@ -1,96 +0,0 @@ -// 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 - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "path/filepath" - - "github.com/scionproto/scion/go/lib/addr" - "github.com/scionproto/scion/go/lib/common" - "github.com/scionproto/scion/go/lib/scrypto" - "github.com/scionproto/scion/go/lib/scrypto/trc/v2" - "github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn" - "github.com/scionproto/scion/go/tools/scion-pki/internal/v2/conf" -) - -func runCombine(selector string) error { - asMap, err := pkicmn.ProcessSelector(selector) - if err != nil { - return err - } - for isd := range asMap { - if err = combineAndWrite(isd); err != nil { - return common.NewBasicError("unable to combine TRC", err, "isd", isd) - } - } - return nil -} - -func combineAndWrite(isd addr.ISD) error { - isdCfg, err := conf.LoadISDCfg(pkicmn.GetIsdPath(pkicmn.RootDir, isd)) - if err != nil { - return common.NewBasicError("error loading ISD config", err) - } - t, encoded, err := loadProtoTRC(isd, isdCfg.Version) - if err != nil { - return common.NewBasicError("unable to load prototype TRC", err) - } - signatures, err := loadUniqueSignatures(isd, t.Version, encoded) - if err != nil { - return common.NewBasicError("unable to load signatures", err) - } - signed := &trc.Signed{ - EncodedTRC: encoded, - Signatures: signatures, - } - if err := validateAndWrite(t, signed); err != nil { - return err - } - return nil -} - -func loadUniqueSignatures(isd addr.ISD, ver scrypto.Version, - encoded trc.Encoded) ([]trc.Signature, error) { - - fnames, err := filepath.Glob(PartsFile(isd, uint64(ver), "*")) - if err != nil { - return nil, common.NewBasicError("unable to list all signatures", err) - } - signatures := make(map[trc.Protected]trc.Signature) - for _, fname := range fnames { - raw, err := ioutil.ReadFile(fname) - if err != nil { - return nil, common.NewBasicError("unable to read file", err, "file", fname) - } - var signed trc.Signed - if err := json.Unmarshal(raw, &signed); err != nil { - return nil, common.NewBasicError("unable to parse file", err, "file", fname) - } - if !bytes.Equal(encoded, signed.EncodedTRC) { - pkicmn.QuietPrint("Ignoring signed in %s. Payload is different", fname) - } - for _, sign := range signed.Signatures { - protected, err := sign.EncodedProtected.Decode() - if err != nil { - return nil, common.NewBasicError("unable to parse protected", err, "file", fname) - } - signatures[protected] = sign - } - } - return sortSignatures(signatures), nil -} diff --git a/go/tools/scion-pki/internal/v2/trcs/gen.go b/go/tools/scion-pki/internal/v2/trcs/gen.go deleted file mode 100644 index c58e07d6e1..0000000000 --- a/go/tools/scion-pki/internal/v2/trcs/gen.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, 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 - -import ( - "github.com/scionproto/scion/go/lib/addr" - "github.com/scionproto/scion/go/lib/common" - "github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn" - "github.com/scionproto/scion/go/tools/scion-pki/internal/v2/conf" -) - -func runGenTrc(selector string) error { - asMap, err := pkicmn.ProcessSelector(selector) - if err != nil { - return err - } - for isd := range asMap { - if err = genAndWriteSignedTRC(isd); err != nil { - return common.NewBasicError("unable to generate TRC", err, "isd", isd) - } - } - return nil -} - -func genAndWriteSignedTRC(isd addr.ISD) error { - isdCfg, err := conf.LoadISDCfg(pkicmn.GetIsdPath(pkicmn.RootDir, isd)) - if err != nil { - return common.NewBasicError("error loading ISD config", err) - } - t, encoded, err := genProto(isd, isdCfg) - if err != nil { - return common.NewBasicError("unable to generate TRC", err) - } - primaryASes, err := loadPrimaryASes(isd, isdCfg, nil) - if err != nil { - return common.NewBasicError("error loading AS configs", err) - } - signed, err := signTRC(t, encoded, primaryASes) - if err != nil { - return common.NewBasicError("unable to partially sign TRC", err) - } - if err := validateAndWrite(t, signed); err != nil { - return err - } - return nil -} diff --git a/go/tools/scion-pki/internal/v2/trcs/human.go b/go/tools/scion-pki/internal/v2/trcs/human.go index fd688c2577..b95de3d3bb 100644 --- a/go/tools/scion-pki/internal/v2/trcs/human.go +++ b/go/tools/scion-pki/internal/v2/trcs/human.go @@ -20,14 +20,14 @@ import ( "io/ioutil" "os" - "github.com/scionproto/scion/go/lib/common" "github.com/scionproto/scion/go/lib/scrypto/trc/v2" + "github.com/scionproto/scion/go/lib/serrors" ) func runHuman(files []string) error { for _, file := range files { if err := genHuman(file); err != nil { - return common.NewBasicError("unable to generate human output", err, "file", file) + return serrors.WrapStr("unable to generate human output", err, "file", file) } } return nil @@ -40,15 +40,15 @@ func genHuman(file string) error { } var signed trc.Signed if err := json.Unmarshal(raw, &signed); err != nil { - return common.NewBasicError("unable to parse signed TRC", err, "file", file) + return serrors.WrapStr("unable to parse signed TRC", err, "file", file) } t, err := signed.EncodedTRC.Decode() if err != nil { - return common.NewBasicError("unable to parse TRC payload", err, "file", file) + return serrors.WrapStr("unable to parse TRC payload", err, "file", file) } signatures, err := parseSignatures(signed.Signatures) if err != nil { - return common.NewBasicError("unable to parse signatures", err, "file", file) + return serrors.WrapStr("unable to parse signatures", err, "file", file) } humanReadable := struct { Payload *trc.TRC `json:"payload"` @@ -58,7 +58,7 @@ func genHuman(file string) error { Signatures: signatures, } if raw, err = json.MarshalIndent(humanReadable, "", " "); err != nil { - return common.NewBasicError("unable to write human readable trc", err, "file", file) + return serrors.WrapStr("unable to write human readable trc", err, "file", file) } _, err = fmt.Fprintln(os.Stdout, string(raw)) return err @@ -69,7 +69,7 @@ func parseSignatures(packed []trc.Signature) ([]signature, error) { for i, s := range packed { p, err := s.EncodedProtected.Decode() if err != nil { - return nil, common.NewBasicError("unable to parse protected meta", err, "idx", i) + return nil, serrors.WrapStr("unable to parse protected meta", err, "idx", i) } signatures = append(signatures, signature{Protected: p, Signature: s.Signature}) } diff --git a/go/tools/scion-pki/internal/v2/trcs/loader.go b/go/tools/scion-pki/internal/v2/trcs/loader.go new file mode 100644 index 0000000000..55e9d75721 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/loader.go @@ -0,0 +1,81 @@ +// 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 + +import ( + "path/filepath" + "regexp" + "strconv" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/scrypto" + "github.com/scionproto/scion/go/lib/serrors" + "github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn" + "github.com/scionproto/scion/go/tools/scion-pki/internal/v2/conf" +) + +type loader struct { + Dirs pkicmn.Dirs + Version scrypto.Version +} + +func (l loader) LoadConfigs(isds []addr.ISD) (map[addr.ISD]conf.TRC2, error) { + cfgs := make(map[addr.ISD]conf.TRC2) + for _, isd := range isds { + file, err := l.selectConfig(isd) + if err != nil { + return nil, serrors.WrapStr("unable to select config", err, "isd", isd) + } + cfg, err := conf.LoadTRC(file) + if err != nil { + return nil, serrors.WrapStr("unable to load TRC config", err, "isd", isd) + } + cfgs[isd] = cfg + } + return cfgs, nil +} + +func (l loader) selectConfig(isd addr.ISD) (string, error) { + if l.Version != scrypto.LatestVer { + return conf.TRCFile(l.Dirs.Root, isd, l.Version), nil + } + files, err := filepath.Glob(conf.AllTRCFiles(l.Dirs.Root, isd)) + if err != nil { + return "", serrors.WrapStr("unable to search all available versions", err) + } + if len(files) == 0 { + return "", serrors.WrapStr("no TRC config files found", err) + } + max, err := l.findMaxVersion(files) + if err != nil { + return "", serrors.WrapStr("unable to find max version", err) + } + return conf.TRCFile(l.Dirs.Root, isd, max), nil +} + +func (l loader) findMaxVersion(files []string) (scrypto.Version, error) { + re := regexp.MustCompile(`trc-v(\d*)\.toml$`) + var max uint64 + for _, file := range files { + ver, err := strconv.ParseUint(re.FindStringSubmatch(file)[1], 10, 64) + if err != nil { + return 0, serrors.WrapStr("unable to parse version", err, "file", file) + } + if ver > max { + max = ver + } + } + return scrypto.Version(max), nil +} diff --git a/go/tools/scion-pki/internal/v2/trcs/prototype.go b/go/tools/scion-pki/internal/v2/trcs/prototype.go index 6596ec10d5..dacbb3d8f3 100644 --- a/go/tools/scion-pki/internal/v2/trcs/prototype.go +++ b/go/tools/scion-pki/internal/v2/trcs/prototype.go @@ -15,155 +15,313 @@ package trcs import ( - "encoding/json" "os" + "path/filepath" "sort" "time" + "golang.org/x/xerrors" + "github.com/scionproto/scion/go/lib/addr" - "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/keyconf" "github.com/scionproto/scion/go/lib/scrypto" "github.com/scionproto/scion/go/lib/scrypto/trc/v2" - "github.com/scionproto/scion/go/lib/util" + "github.com/scionproto/scion/go/lib/serrors" "github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn" "github.com/scionproto/scion/go/tools/scion-pki/internal/v2/conf" + "github.com/scionproto/scion/go/tools/scion-pki/internal/v2/keys" ) -func runProto(selector string) error { - asMap, err := pkicmn.ProcessSelector(selector) +// signedMeta keeps track of the TRC version. +type signedMeta struct { + Signed trc.Signed + Version scrypto.Version +} + +// pubKeys holds the public keys for a primary AS. +type pubKeys map[trc.KeyType]keyconf.Key + +type protoGen struct { + Dirs pkicmn.Dirs + Version scrypto.Version +} + +// Run generates the prototype TRCs for all ISDs in the provided mapping. If no +// version is specified, the TRC configuration file with the highest version is +// chosen for each ISD. The generated TRCs are then written to disk. +func (g protoGen) Run(asMap pkicmn.ASMap) error { + cfgs, err := loader{Dirs: g.Dirs, Version: g.Version}.LoadConfigs(asMap.ISDs()) if err != nil { - return err + return serrors.WrapStr("unable to load TRC configs", err) } - for isd := range asMap { - if err = genAndWriteProto(isd); err != nil { - return common.NewBasicError("unable to generating prototype TRC", err, "isd", isd) - } + protos, err := g.Generate(cfgs) + if err != nil { + return serrors.WrapStr("unable to generate prototype TRCs", err) + } + if err := g.createDirs(protos); err != nil { + return serrors.WrapStr("unable to create output directories", err) + } + if err := g.writeTRCs(protos); err != nil { + return serrors.WrapStr("unable to write prototype TRCs", err) } return nil } -func genAndWriteProto(isd addr.ISD) error { - isdCfg, err := conf.LoadISDCfg(pkicmn.GetIsdPath(pkicmn.RootDir, isd)) +// Generate generates the prototype TRCs for all provided configurations. +func (g protoGen) Generate(cfgs map[addr.ISD]conf.TRC2) (map[addr.ISD]signedMeta, error) { + trcs := make(map[addr.ISD]signedMeta) + for isd, cfg := range cfgs { + signed, err := g.generate(isd, cfg) + if err != nil { + return nil, serrors.WrapStr("unable to generate TRC", err, + "isd", isd, "version", cfg.Version) + } + trcs[isd] = signed + } + return trcs, nil +} + +// generate generates the prototype TRC for a specific configuration. +func (g protoGen) generate(isd addr.ISD, cfg conf.TRC2) (signedMeta, error) { + pubKeys, err := g.loadPubKeys(isd, cfg) if err != nil { - return common.NewBasicError("error loading ISD config", err) + return signedMeta{}, serrors.WrapStr("unable to load all public keys", err) } - t, encoded, err := genProto(isd, isdCfg) + t, err := g.newTRC(isd, cfg, pubKeys) if err != nil { - return common.NewBasicError("unable to generate TRC", err) + return signedMeta{}, serrors.WrapStr("unable to create prototype TRC", err) } - signed := &trc.Signed{EncodedTRC: encoded} - raw, err := json.Marshal(signed) + enc, err := trc.Encode(t) if err != nil { - return common.NewBasicError("unable to marshal", err) + return signedMeta{}, serrors.WrapStr("unable to encode TRC payload", err) } - if err := os.MkdirAll(PartsDir(isd, uint64(t.Version)), 0755); err != nil { - return err + meta := signedMeta{ + Signed: trc.Signed{EncodedTRC: enc}, + Version: t.Version, } - return pkicmn.WriteToFile(raw, ProtoFile(isd, uint64(t.Version)), 0644) + return meta, nil } -func genProto(isd addr.ISD, isdCfg *conf.ISDCfg) (*trc.TRC, trc.Encoded, error) { - pkicmn.QuietPrint("Generating prototype TRC for ISD %d\n", isd) - primaryASes, err := loadPrimaryASes(isd, isdCfg, nil) +// 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) { + all := make(map[addr.AS]pubKeys) + for as, primary := range cfg.PrimaryASes { + ia := addr.IA{I: isd, A: as} + keys := make(pubKeys) + if err := g.insertIssuingKey(keys, ia, primary); err != nil { + return nil, serrors.WithCtx(err, "as", as) + } + if err := g.insertVotingKeys(keys, ia, primary); err != nil { + return nil, serrors.WithCtx(err, "as", as) + } + all[as] = keys + } + return all, nil +} + +// insertIssuingKey inserts the issuing key if the primary is an issuing AS. +func (g protoGen) insertIssuingKey(dst pubKeys, ia addr.IA, primary conf.Primary) error { + if !primary.Attributes.Contains(trc.Issuing) { + return nil + } + key, err := g.loadPubKey(ia, keyconf.TRCIssuingKey, *primary.IssuingKeyVersion) if err != nil { - return nil, nil, common.NewBasicError("error loading primary ASes configs", err) + return serrors.WrapStr("unable to load issuing key", err) } - t, err := newTRC(isd, isdCfg, primaryASes) + dst[trc.IssuingKey] = key + return nil +} + +// insertVotingKeys inserts the online and offline voting keys if the primary is +// a voting AS. +func (g protoGen) insertVotingKeys(dst pubKeys, ia addr.IA, primary conf.Primary) error { + if !primary.Attributes.Contains(trc.Voting) { + return nil + } + online, err := g.loadPubKey(ia, keyconf.TRCVotingOnlineKey, *primary.VotingOnlineKeyVersion) + if err != nil { + return serrors.WrapStr("unable to load voting online key", err) + } + dst[trc.OnlineKey] = online + offline, err := g.loadPubKey(ia, keyconf.TRCVotingOfflineKey, *primary.VotingOfflineKeyVersion) if err != nil { - return nil, nil, err + return serrors.WrapStr("unable to load voting offline key", err) + } + dst[trc.OfflineKey] = offline + return nil +} + +// loadPubKey attempts to load the private key and use it to generate the public +// key. If that fails, loadPubKey attempts to load the public key directly. +func (g protoGen) loadPubKey(ia addr.IA, usage keyconf.Usage, + version scrypto.KeyVersion) (keyconf.Key, error) { + + file := filepath.Join(keys.PrivateDir(g.Dirs.Out, ia), keyconf.PrivateKeyFile(usage, version)) + priv, err := loadKey(file, ia, usage, version) + if err == nil { + pkicmn.QuietPrint("Using private key %s\n", file) + return keys.PublicKey(priv) + } + if !xerrors.Is(err, errReadFile) { + return keyconf.Key{}, err } - encoded, err := trc.Encode(t) + file = filepath.Join(keys.PublicDir(g.Dirs.Out, ia), keyconf.PublicKeyFile(usage, ia, version)) + pub, err := loadKey(file, ia, usage, version) if err != nil { - return nil, nil, common.NewBasicError("unable to encode TRC", err) + return keyconf.Key{}, serrors.WrapStr("unable to load public key", err, "file", file) } - return t, encoded, nil + pkicmn.QuietPrint("Using public key %s\n", file) + return pub, nil } -func newTRC(isd addr.ISD, isdCfg *conf.ISDCfg, primaryASes map[addr.AS]*asCfg) (*trc.TRC, error) { - quorum := uint8(isdCfg.TRC.VotingQuorum) - reset := isdCfg.TRC.TrustResetAllowed +func (g protoGen) newTRC(isd addr.ISD, cfg conf.TRC2, keys map[addr.AS]pubKeys) (*trc.TRC, error) { + quorum := uint8(cfg.VotingQuorum) + reset := *cfg.TrustResetAllowed + val := cfg.Validity.Eval(time.Now()) t := &trc.TRC{ ISD: isd, - Version: scrypto.Version(isdCfg.TRC.Version), - BaseVersion: scrypto.Version(isdCfg.TRC.BaseVersion), - Description: isdCfg.Desc, + Version: cfg.Version, + BaseVersion: cfg.BaseVersion, + Description: cfg.Description, VotingQuorumPtr: &quorum, FormatVersion: 1, - GracePeriod: &trc.Period{Duration: isdCfg.TRC.GracePeriod}, + GracePeriod: &trc.Period{Duration: cfg.GracePeriod.Duration}, TrustResetAllowedPtr: &reset, - Validity: createValidity(isdCfg.TRC.NotBefore, isdCfg.TRC.Validity), + Validity: &val, PrimaryASes: make(trc.PrimaryASes), Votes: make(map[addr.AS]trc.Vote), ProofOfPossession: make(map[addr.AS][]trc.KeyType), } - if !t.Base() { - return nil, common.NewBasicError("TRC updates not supported yet", nil, - "version", t.Version, "base", t.BaseVersion) - } - for as, cfg := range primaryASes { + for as, primary := range cfg.PrimaryASes { t.PrimaryASes[as] = trc.PrimaryAS{ - Attributes: getAttributes(isdCfg, as), - Keys: getKeys(cfg), + Attributes: sortAttributes(primary.Attributes), + Keys: getKeys(keys[as]), + } + } + var prev *trc.TRC + if !t.Base() { + var err error + file := SignedFile(g.Dirs.Out, isd, t.Version-1) + if prev, _, err = loadTRC(file); err != nil { + return nil, serrors.WrapStr("unable to load previous TRC", err, "file", file) } - t.ProofOfPossession[as] = getKeyTypes(cfg) + } + if err := g.attachVotes(t, prev, cfg.Votes); err != nil { + return nil, serrors.WrapStr("unable to attach votes", err) + } + if err := g.attachPOPs(t, prev); err != nil { + return nil, serrors.WrapStr("unable to attach proof of possessions", err) } if err := t.ValidateInvariant(); err != nil { - return nil, common.NewBasicError("invariant violated", err) + return nil, serrors.WrapStr("invariant violated", err) + } + if !t.Base() { + if _, err := (&trc.UpdateValidator{Next: t, Prev: prev}).Validate(); err != nil { + return nil, serrors.WrapStr("invalid update", err) + } } return t, nil } -func createValidity(notBefore uint32, validity time.Duration) *scrypto.Validity { - val := &scrypto.Validity{ - NotBefore: util.UnixTime{Time: util.SecsToTime(notBefore)}, +func (g protoGen) attachVotes(next, prev *trc.TRC, voters []addr.AS) error { + if next.Base() { + return nil + } + info, err := (&trc.UpdateValidator{Next: next, Prev: prev}).UpdateInfo() + if err != nil { + return serrors.WrapStr("unable to get update info", err) } - if notBefore == 0 { - val.NotBefore.Time = time.Now() + for _, voter := range voters { + prevPrimary, ok := prev.PrimaryASes[voter] + if !ok || !prevPrimary.Is(trc.Voting) { + return serrors.New("non-voting AS cannot cast vote", "as", voter) + } + _, modifiedOnline := info.KeyChanges.Modified[trc.OnlineKey][voter] + if info.Type != trc.RegularUpdate || modifiedOnline { + next.Votes[voter] = trc.Vote{ + KeyType: trc.OfflineKey, + KeyVersion: prevPrimary.Keys[trc.OfflineKey].KeyVersion, + } + } else { + next.Votes[voter] = trc.Vote{ + KeyType: trc.OnlineKey, + KeyVersion: prevPrimary.Keys[trc.OnlineKey].KeyVersion, + } + } } - val.NotAfter = util.UnixTime{Time: val.NotBefore.Add(validity)} - return val + return nil } -func getAttributes(isdCfg *conf.ISDCfg, as addr.AS) []trc.Attribute { - var a []trc.Attribute - m := map[trc.Attribute][]addr.AS{ - trc.Authoritative: isdCfg.AuthoritativeASes, - trc.Core: isdCfg.CoreASes, - trc.Issuing: isdCfg.IssuingASes, - trc.Voting: isdCfg.VotingASes, +func (g protoGen) attachPOPs(next, prev *trc.TRC) error { + if next.Base() { + for as, primary := range next.PrimaryASes { + keyTypes := make([]trc.KeyType, 0, len(primary.Keys)) + for keyType := range primary.Keys { + keyTypes = append(keyTypes, keyType) + } + sort.Slice(keyTypes, func(i, j int) bool { return keyTypes[i] < keyTypes[j] }) + next.ProofOfPossession[as] = keyTypes + } + return nil } - for attr, list := range m { - if pkicmn.ContainsAS(list, as) { - a = append(a, attr) + info, err := (&trc.UpdateValidator{Next: next, Prev: prev}).UpdateInfo() + if err != nil { + return serrors.WrapStr("unable to get update info", err) + } + for keyType, metas := range info.KeyChanges.Fresh { + for as := range metas { + next.ProofOfPossession[as] = append(next.ProofOfPossession[as], keyType) } } - sort.Slice(a, func(i, j int) bool { return a[i] < a[j] }) - return a + for keyType, metas := range info.KeyChanges.Modified { + for as := range metas { + next.ProofOfPossession[as] = append(next.ProofOfPossession[as], keyType) + } + } + for _, keyTypes := range next.ProofOfPossession { + sort.Slice(keyTypes, func(i, j int) bool { return keyTypes[i] < keyTypes[j] }) + } + return nil } -func getKeys(cfg *asCfg) map[trc.KeyType]scrypto.KeyMeta { - // FIXME(roosd): allow for different key versions. - m := make(map[trc.KeyType]scrypto.KeyMeta) - for keyType, key := range cfg.Keys { - algo := cfg.KeyTypeToAlgo(keyType) - pubKey, err := scrypto.GetPubKey(key, algo) +func (g protoGen) createDirs(trcs map[addr.ISD]signedMeta) error { + for isd, meta := range trcs { + dir := filepath.Dir(ProtoFile(g.Dirs.Out, isd, meta.Version)) + if err := os.MkdirAll(dir, 0755); err != nil { + return serrors.WrapStr("unable to make TRC parts directory", err, "dir", dir) + } + } + return nil +} + +func (g protoGen) writeTRCs(trcs map[addr.ISD]signedMeta) error { + for isd, meta := range trcs { + raw, err := trc.EncodeSigned(meta.Signed) if err != nil { - pkicmn.ErrorAndExit("unsupported algorithm passed validation algo=%s", algo) + return serrors.WrapStr("unable to marshal prototype TRC", err, "isd", isd) } - m[keyType] = scrypto.KeyMeta{ - Algorithm: algo, - Key: pubKey, - KeyVersion: 1, + file := ProtoFile(g.Dirs.Out, isd, meta.Version) + if err := pkicmn.WriteToFile(raw, file, 0644); err != nil { + return serrors.WrapStr("unable to write prototype TRC", err, "file", file) } } - return m + return nil +} + +func sortAttributes(attrs []trc.Attribute) []trc.Attribute { + a := append([]trc.Attribute{}, attrs...) + sort.Slice(a, func(i, j int) bool { return a[i] < a[j] }) + return a } -func getKeyTypes(cfg *asCfg) []trc.KeyType { - keyTypes := make([]trc.KeyType, 0, len(cfg.Keys)) - for keyType := range cfg.Keys { - keyTypes = append(keyTypes, keyType) +func getKeys(keys map[trc.KeyType]keyconf.Key) map[trc.KeyType]scrypto.KeyMeta { + m := make(map[trc.KeyType]scrypto.KeyMeta) + for keyType, key := range keys { + m[keyType] = scrypto.KeyMeta{ + Algorithm: key.Algorithm, + Key: append([]byte{}, key.Bytes...), + KeyVersion: key.Version, + } } - sort.Slice(keyTypes, func(i, j int) bool { return keyTypes[i] < keyTypes[j] }) - return keyTypes + return m } diff --git a/go/tools/scion-pki/internal/v2/trcs/prototype_test.go b/go/tools/scion-pki/internal/v2/trcs/prototype_test.go new file mode 100644 index 0000000000..a1f4f91732 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/prototype_test.go @@ -0,0 +1,103 @@ +// 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 + +import ( + "flag" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/scrypto" + "github.com/scionproto/scion/go/lib/xtest" + "github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn" +) + +var update = flag.Bool("update", false, "set to true to regenerate golden files") + +var ( + ia110 = xtest.MustParseIA("1-ff00:0:110") + ia120 = xtest.MustParseIA("1-ff00:0:120") + ia130 = xtest.MustParseIA("1-ff00:0:130") + + testASMap = map[addr.ISD][]addr.IA{1: {ia110, ia120, ia130}} +) + +func TestProtoGen(t *testing.T) { + if *update { + create := func(v scrypto.Version) error { + force := pkicmn.Force + pkicmn.Force = true + defer func() { pkicmn.Force = force }() + g := protoGen{Dirs: pkicmn.Dirs{Root: "./testdata", Out: "./testdata"}, Version: v} + return g.Run(testASMap) + } + require.NoError(t, create(1)) + require.NoError(t, create(2)) + require.NoError(t, create(3)) + } + tests := map[string]struct { + Version scrypto.Version + }{ + "v1": {Version: 1}, + "v2": {Version: 2}, + "v3": {Version: 3}, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + t.Parallel() + tmpDir, cleanF := xtest.MustTempDir("", "test-trcs-proto") + defer cleanF() + + // Setup file structure in temporary directory. + isdDir := filepath.Join(tmpDir, "ISD1") + require.NoError(t, os.MkdirAll(isdDir, 0777)) + err := exec.Command("cp", "-r", + "./testdata/ISD1/ASff00_0_110", + "./testdata/ISD1/ASff00_0_120", + "./testdata/ISD1/ASff00_0_130", + isdDir).Run() + require.NoError(t, err) + if test.Version > 1 { + require.NoError(t, os.MkdirAll(Dir(tmpDir, 1), 0777)) + err = exec.Command("cp", ProtoFile("./testdata", 1, test.Version-1), + SignedFile(tmpDir, 1, test.Version-1)).Run() + require.NoError(t, err) + } + + // Run protoGen generator and compare golden files. + g := protoGen{ + Dirs: pkicmn.Dirs{Root: "./testdata", Out: tmpDir}, + Version: test.Version, + } + err = g.Run(testASMap) + require.NoError(t, err) + + golden, err := ioutil.ReadFile(ProtoFile("./testdata", 1, test.Version)) + require.NoError(t, err) + result, err := ioutil.ReadFile(ProtoFile(tmpDir, 1, test.Version)) + require.NoError(t, err) + assert.Equal(t, golden, result) + }) + } +} diff --git a/go/tools/scion-pki/internal/v2/trcs/sign.go b/go/tools/scion-pki/internal/v2/trcs/sign.go deleted file mode 100644 index 596f1ddfd4..0000000000 --- a/go/tools/scion-pki/internal/v2/trcs/sign.go +++ /dev/null @@ -1,151 +0,0 @@ -// 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 - -import ( - "encoding/json" - "io/ioutil" - "os" - - "github.com/scionproto/scion/go/lib/addr" - "github.com/scionproto/scion/go/lib/common" - "github.com/scionproto/scion/go/lib/scrypto" - "github.com/scionproto/scion/go/lib/scrypto/trc/v2" - "github.com/scionproto/scion/go/lib/serrors" - "github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn" - "github.com/scionproto/scion/go/tools/scion-pki/internal/v2/conf" -) - -func runSign(selector string) error { - _, asSelector, err := pkicmn.ParseSelector(selector) - if err != nil { - return err - } - asMap, err := pkicmn.ProcessSelector(selector) - if err != nil { - return err - } - for isd, ases := range asMap { - if err = genAndWriteSignatures(isd, ases, asSelector); err != nil { - return common.NewBasicError("unable to sign TRC", err, "isd", isd) - } - } - return nil -} - -func genAndWriteSignatures(isd addr.ISD, ases []addr.IA, selector string) error { - isdCfg, err := conf.LoadISDCfg(pkicmn.GetIsdPath(pkicmn.RootDir, isd)) - if err != nil { - return common.NewBasicError("error loading ISD config", err) - } - primaryASes, err := loadPrimaryASes(isd, isdCfg, ases) - if err != nil { - return common.NewBasicError("error loading AS configs", err) - } - t, encoded, err := loadProtoTRC(isd, isdCfg.Version) - if err != nil { - return common.NewBasicError("unable to load prototype TRC", err) - } - if err := sanityChecks(isd, isdCfg, t); err != nil { - return common.NewBasicError("invalid prototype TRC", err) - } - signed, err := signTRC(t, encoded, primaryASes) - if err != nil { - return common.NewBasicError("unable to partially sign TRC", err) - } - raw, err := json.Marshal(signed) - if err != nil { - return common.NewBasicError("error json-encoding partially signed TRC", err) - } - if err := os.MkdirAll(PartsDir(isd, uint64(t.Version)), 0755); err != nil { - return err - } - return pkicmn.WriteToFile(raw, PartsFile(isd, uint64(t.Version), selector), 0644) -} - -func loadProtoTRC(isd addr.ISD, ver uint64) (*trc.TRC, trc.Encoded, error) { - raw, err := ioutil.ReadFile(ProtoFile(isd, ver)) - if err != nil { - return nil, nil, err - } - var signed trc.Signed - if err := json.Unmarshal(raw, &signed); err != nil { - return nil, nil, err - } - t, err := signed.EncodedTRC.Decode() - if err != nil { - return nil, nil, err - } - return t, signed.EncodedTRC, nil -} - -// sanityChecks does some small sanity checks to ensure the right TRC is signed. -func sanityChecks(isd addr.ISD, isdCfg *conf.ISDCfg, t *trc.TRC) error { - if isd != t.ISD { - return common.NewBasicError("ISD does not match", nil, "proto", t.ISD, "cfg", isd) - } - if isdCfg.Version != uint64(t.Version) { - return common.NewBasicError("version does not match", nil, "proto", t.Version, - "cfg", isdCfg.Version) - } - if isdCfg.BaseVersion != uint64(t.BaseVersion) { - return common.NewBasicError("base_version does not match", nil, "proto", t.BaseVersion, - "cfg", isdCfg.BaseVersion) - } - return nil -} - -func signTRC(t *trc.TRC, encoded trc.Encoded, primaryASes map[addr.AS]*asCfg) ( - *trc.Signed, error) { - - signatures := make(map[trc.Protected]trc.Signature) - // FIXME(roosd): Here votes should be cast in updates. - for as, keyTypes := range t.ProofOfPossession { - // Skip ASes that are not selected. - if _, ok := primaryASes[as]; !ok { - continue - } - for _, keyType := range keyTypes { - protected := trc.Protected{ - AS: as, - Algorithm: t.PrimaryASes[as].Keys[keyType].Algorithm, - KeyType: keyType, - KeyVersion: t.PrimaryASes[as].Keys[keyType].KeyVersion, - Type: trc.POPSignature, - } - encProtected, err := trc.EncodeProtected(protected) - if err != nil { - return nil, err - } - signature, err := scrypto.Sign(trc.SigInput(encProtected, encoded), - primaryASes[as].Keys[keyType], primaryASes[as].KeyTypeToAlgo(keyType)) - if err != nil { - return nil, err - } - signatures[protected] = trc.Signature{ - EncodedProtected: encProtected, - Signature: signature, - } - } - } - if len(signatures) == 0 { - return nil, serrors.New("no signature generated") - } - signed := &trc.Signed{ - EncodedTRC: encoded, - Signatures: sortSignatures(signatures), - } - return signed, nil -} diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys.toml b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys.toml new file mode 100644 index 0000000000..c65dcdf553 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys.toml @@ -0,0 +1,24 @@ +[primary] + [primary.issuing] + [primary.issuing.1] + algorithm = "ed25519" + [primary.issuing.1.validity] + not_before = 1573033769 + validity = "1y" + [primary.offline] + [primary.offline.1] + algorithm = "ed25519" + [primary.offline.1.validity] + not_before = 1573033769 + validity = "1y" + [primary.online] + [primary.online.1] + algorithm = "ed25519" + [primary.online.1.validity] + not_before = 1573033769 + validity = "1y" + [primary.online.2] + algorithm = "ed25519" + [primary.online.2.validity] + not_before = 1573033769 + validity = "1y" diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys/trc-issuing-v1.key b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys/trc-issuing-v1.key new file mode 100644 index 0000000000..af7cba0f34 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys/trc-issuing-v1.key @@ -0,0 +1,11 @@ +-----BEGIN PRIVATE KEY----- +algorithm: ed25519 +ia: 1-ff00:0:110 +not_after: 2020-11-05 09:49:29+0000 +not_before: 2019-11-06 09:49:29+0000 +usage: trc-issuing +version: 1 + +/iU+76cLc9A74+cJ5hk9aWiljUlr8iN5luB2fHBcIuQ= +-----END PRIVATE KEY----- + diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys/trc-voting-offline-v1.key b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys/trc-voting-offline-v1.key new file mode 100644 index 0000000000..64b8e8caed --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys/trc-voting-offline-v1.key @@ -0,0 +1,11 @@ +-----BEGIN PRIVATE KEY----- +algorithm: ed25519 +ia: 1-ff00:0:110 +not_after: 2020-11-05 09:49:29+0000 +not_before: 2019-11-06 09:49:29+0000 +usage: trc-voting-offline +version: 1 + +TwI/A/i8ffcEllMB0f+DY3yk8su0SVqQQjvq8QGCZPM= +-----END PRIVATE KEY----- + diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys/trc-voting-online-v1.key b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys/trc-voting-online-v1.key new file mode 100644 index 0000000000..fb4d150e60 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys/trc-voting-online-v1.key @@ -0,0 +1,11 @@ +-----BEGIN PRIVATE KEY----- +algorithm: ed25519 +ia: 1-ff00:0:110 +not_after: 2020-11-05 09:49:29+0000 +not_before: 2019-11-06 09:49:29+0000 +usage: trc-voting-online +version: 1 + +rEaWCh72Y5ng8rGOrTh72umopkO0HoCpgeAx8e5ZxGY= +-----END PRIVATE KEY----- + diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys/trc-voting-online-v2.key b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys/trc-voting-online-v2.key new file mode 100644 index 0000000000..e859779958 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_110/keys/trc-voting-online-v2.key @@ -0,0 +1,11 @@ +-----BEGIN PRIVATE KEY----- +algorithm: ed25519 +ia: 1-ff00:0:110 +not_after: 2020-11-05 09:49:29+0000 +not_before: 2019-11-06 09:49:29+0000 +usage: trc-voting-online +version: 2 + +lfHPd7d8CybImT7JYqy9ZQHvnvl9B5l9rfelOFO7xZM= +-----END PRIVATE KEY----- + diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_120/keys.toml b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_120/keys.toml new file mode 100644 index 0000000000..62af9b766e --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_120/keys.toml @@ -0,0 +1,13 @@ +[primary] + [primary.offline] + [primary.offline.1] + algorithm = "ed25519" + [primary.offline.1.validity] + not_before = 1573033769 + validity = "1y" + [primary.online] + [primary.online.1] + algorithm = "ed25519" + [primary.online.1.validity] + not_before = 1573033769 + validity = "1y" diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_120/keys/trc-voting-offline-v1.key b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_120/keys/trc-voting-offline-v1.key new file mode 100644 index 0000000000..a0de13490d --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_120/keys/trc-voting-offline-v1.key @@ -0,0 +1,11 @@ +-----BEGIN PRIVATE KEY----- +algorithm: ed25519 +ia: 1-ff00:0:120 +not_after: 2020-11-05 09:49:29+0000 +not_before: 2019-11-06 09:49:29+0000 +usage: trc-voting-offline +version: 1 + +FUvx7rvUvUWEGSgoV8aeieB0su7+7zdpk+GB/TddTjI= +-----END PRIVATE KEY----- + diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_120/keys/trc-voting-online-v1.key b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_120/keys/trc-voting-online-v1.key new file mode 100644 index 0000000000..c13943caa8 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_120/keys/trc-voting-online-v1.key @@ -0,0 +1,11 @@ +-----BEGIN PRIVATE KEY----- +algorithm: ed25519 +ia: 1-ff00:0:120 +not_after: 2020-11-05 09:49:29+0000 +not_before: 2019-11-06 09:49:29+0000 +usage: trc-voting-online +version: 1 + +mBvyZI4Vht1P5DMvVf6gRyekeuY2iYcYDd0fVu8rhBI= +-----END PRIVATE KEY----- + diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_130/keys.toml b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_130/keys.toml new file mode 100644 index 0000000000..8be1b6a29f --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_130/keys.toml @@ -0,0 +1,19 @@ +[primary] + [primary.issuing] + [primary.issuing.1] + algorithm = "ed25519" + [primary.issuing.1.validity] + not_before = 1573033769 + validity = "1y" + [primary.offline] + [primary.offline.1] + algorithm = "ed25519" + [primary.offline.1.validity] + not_before = 1573033769 + validity = "1y" + [primary.online] + [primary.online.1] + algorithm = "ed25519" + [primary.online.1.validity] + not_before = 1573033769 + validity = "1y" diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_130/keys/trc-issuing-v1.key b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_130/keys/trc-issuing-v1.key new file mode 100644 index 0000000000..cb56e14258 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_130/keys/trc-issuing-v1.key @@ -0,0 +1,11 @@ +-----BEGIN PRIVATE KEY----- +algorithm: ed25519 +ia: 1-ff00:0:130 +not_after: 2020-11-05 09:49:29+0000 +not_before: 2019-11-06 09:49:29+0000 +usage: trc-issuing +version: 1 + +xUracxyjavZ+5nMPXWK9tMgtDU/bxix7Qo/ah/ktyCM= +-----END PRIVATE KEY----- + diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_130/keys/trc-voting-offline-v1.key b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_130/keys/trc-voting-offline-v1.key new file mode 100644 index 0000000000..f84041158a --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_130/keys/trc-voting-offline-v1.key @@ -0,0 +1,11 @@ +-----BEGIN PRIVATE KEY----- +algorithm: ed25519 +ia: 1-ff00:0:130 +not_after: 2020-11-05 09:49:29+0000 +not_before: 2019-11-06 09:49:29+0000 +usage: trc-voting-offline +version: 1 + +jdfjxGlb3PkxygQJYtdTWxgr0PTi+Ob71vWJgyJ15NU= +-----END PRIVATE KEY----- + diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_130/keys/trc-voting-online-v1.key b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_130/keys/trc-voting-online-v1.key new file mode 100644 index 0000000000..533dd587ea --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/ASff00_0_130/keys/trc-voting-online-v1.key @@ -0,0 +1,11 @@ +-----BEGIN PRIVATE KEY----- +algorithm: ed25519 +ia: 1-ff00:0:130 +not_after: 2020-11-05 09:49:29+0000 +not_before: 2019-11-06 09:49:29+0000 +usage: trc-voting-online +version: 1 + +El1/wX04H7YivIHH3M2yOY2GuEl3m1l0blrty2xiPGo= +-----END PRIVATE KEY----- + diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trc-v1.toml b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trc-v1.toml new file mode 100644 index 0000000000..d727d71b1d --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trc-v1.toml @@ -0,0 +1,29 @@ +description = "ISD 1" +version = 1 +base_version = 1 +voting_quorum = 2 +grace_period = "0s" +trust_reset_allowed = true +votes = [] + +[validity] + not_before = 1573033769 + validity = "1y" + +[primary_ases] + [primary_ases."ff00:0:110"] + attributes = ["issuing", "voting"] + issuing_key_version = 1 + voting_online_key_version = 1 + voting_offline_key_version = 1 + [primary_ases."ff00:0:120"] + attributes = ["authoritative", "core", "voting"] + issuing_key_version = 1 + voting_online_key_version = 1 + voting_offline_key_version = 1 + [primary_ases."ff00:0:130"] + attributes = ["authoritative", "core", "issuing", "voting"] + issuing_key_version = 1 + voting_online_key_version = 1 + voting_offline_key_version = 1 + diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trc-v2.toml b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trc-v2.toml new file mode 100644 index 0000000000..da9fe3c506 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trc-v2.toml @@ -0,0 +1,29 @@ +description = "ISD 1" +version = 2 +base_version = 1 +voting_quorum = 2 +grace_period = "10h" +trust_reset_allowed = true +votes = ["ff00:0:110", "ff00:0:120"] + +[validity] + not_before = 1573033869 + validity = "1y" + +[primary_ases] + [primary_ases."ff00:0:110"] + attributes = ["issuing", "voting"] + issuing_key_version = 1 + voting_online_key_version = 2 + voting_offline_key_version = 1 + [primary_ases."ff00:0:120"] + attributes = ["authoritative", "core", "voting"] + issuing_key_version = 1 + voting_online_key_version = 1 + voting_offline_key_version = 1 + [primary_ases."ff00:0:130"] + attributes = ["authoritative", "core", "issuing", "voting"] + issuing_key_version = 1 + voting_online_key_version = 1 + voting_offline_key_version = 1 + diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trc-v3.toml b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trc-v3.toml new file mode 100644 index 0000000000..6cfb0ff97f --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trc-v3.toml @@ -0,0 +1,29 @@ +description = "ISD 1" +version = 3 +base_version = 1 +voting_quorum = 1 +grace_period = "2h" +trust_reset_allowed = true +votes = ["ff00:0:120", "ff00:0:130"] + +[validity] + not_before = 1573033969 + validity = "1y" + +[primary_ases] + [primary_ases."ff00:0:110"] + attributes = ["issuing", "voting"] + issuing_key_version = 1 + voting_online_key_version = 2 + voting_offline_key_version = 1 + [primary_ases."ff00:0:120"] + attributes = ["authoritative", "core", "voting"] + issuing_key_version = 1 + voting_online_key_version = 1 + voting_offline_key_version = 1 + [primary_ases."ff00:0:130"] + attributes = ["authoritative", "core", "issuing", "voting"] + issuing_key_version = 1 + voting_online_key_version = 1 + voting_offline_key_version = 1 + diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trcs/ISD1-V1.parts/ISD1-V1.prototype b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trcs/ISD1-V1.parts/ISD1-V1.prototype new file mode 100644 index 0000000000..c8607dbe2a --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trcs/ISD1-V1.parts/ISD1-V1.prototype @@ -0,0 +1 @@ +{"payload":"ZXlKcGMyUWlPakVzSW5SeVkxOTJaWEp6YVc5dUlqb3hMQ0ppWVhObFgzWmxjbk5wYjI0aU9qRXNJbVJsYzJOeWFYQjBhVzl1SWpvaVNWTkVJREVpTENKMmIzUnBibWRmY1hWdmNuVnRJam95TENKbWIzSnRZWFJmZG1WeWMybHZiaUk2TVN3aVozSmhZMlZmY0dWeWFXOWtJam93TENKMGNuVnpkRjl5WlhObGRGOWhiR3h2ZDJWa0lqcDBjblZsTENKMllXeHBaR2wwZVNJNmV5SnViM1JmWW1WbWIzSmxJam94TlRjek1ETXpOelk1TENKdWIzUmZZV1owWlhJaU9qRTJNRFExTmprM05qbDlMQ0p3Y21sdFlYSjVYMkZ6WlhNaU9uc2labVl3TURvd09qRXhNQ0k2ZXlKaGRIUnlhV0oxZEdWeklqcGJJbWx6YzNWcGJtY2lMQ0oyYjNScGJtY2lYU3dpYTJWNWN5STZleUpwYzNOMWFXNW5JanA3SW10bGVWOTJaWEp6YVc5dUlqb3hMQ0poYkdkdmNtbDBhRzBpT2lKbFpESTFOVEU1SWl3aWEyVjVJam9pYXk5dVNrRlhPVFYzZVRWRmExZGtlbTFIYUVWR1UweG5TRUpKTm5WQmRuSkRSMGs1VjAxM1VVUTBNRDBpZlN3aWIyWm1iR2x1WlNJNmV5SnJaWGxmZG1WeWMybHZiaUk2TVN3aVlXeG5iM0pwZEdodElqb2laV1F5TlRVeE9TSXNJbXRsZVNJNkluVkNSMHRSVTNCM1ZtNVNObTAxTlhsblJ6aEtNVll6VVhwcE1tTjJkbGh5VWtwSGRreHdjMlpOUTAwOUluMHNJbTl1YkdsdVpTSTZleUpyWlhsZmRtVnljMmx2YmlJNk1Td2lZV3huYjNKcGRHaHRJam9pWldReU5UVXhPU0lzSW10bGVTSTZJbWxQVXk4MmVIUXZSemQyVG5CNVdGQXlibnB1Y3pGcFJsbDFValoyUVRoeGFXRXhWWFo2ZFRKelNsVTlJbjE5ZlN3aVptWXdNRG93T2pFeU1DSTZleUpoZEhSeWFXSjFkR1Z6SWpwYkltRjFkR2h2Y21sMFlYUnBkbVVpTENKamIzSmxJaXdpZG05MGFXNW5JbDBzSW10bGVYTWlPbnNpYjJabWJHbHVaU0k2ZXlKclpYbGZkbVZ5YzJsdmJpSTZNU3dpWVd4bmIzSnBkR2h0SWpvaVpXUXlOVFV4T1NJc0ltdGxlU0k2SWxWUGIwdGtVa2t2YmtKRVNIbElOSGR6YmpoMmF6Rk1TbkZ6TjFkcFkyb3pia2xpZGk5VGEySklMMDA5SW4wc0ltOXViR2x1WlNJNmV5SnJaWGxmZG1WeWMybHZiaUk2TVN3aVlXeG5iM0pwZEdodElqb2laV1F5TlRVeE9TSXNJbXRsZVNJNkluVkVhM0pOWW5OMlkyTTBjR3REZEZncmJXSkJSbGcxVXpNeFMwdDRaMUZaWldwNldIQnVZbXhCWVVVOUluMTlmU3dpWm1Zd01Eb3dPakV6TUNJNmV5SmhkSFJ5YVdKMWRHVnpJanBiSW1GMWRHaHZjbWwwWVhScGRtVWlMQ0pqYjNKbElpd2lhWE56ZFdsdVp5SXNJblp2ZEdsdVp5SmRMQ0pyWlhseklqcDdJbWx6YzNWcGJtY2lPbnNpYTJWNVgzWmxjbk5wYjI0aU9qRXNJbUZzWjI5eWFYUm9iU0k2SW1Wa01qVTFNVGtpTENKclpYa2lPaUpzWjNwQ2FteFZRWEV2TjFBNVVsQjJkRTVsUVRSek1qYzROMVV5TUc5MU0xSnNNRlp1ZGxZMlVYRnpQU0o5TENKdlptWnNhVzVsSWpwN0ltdGxlVjkyWlhKemFXOXVJam94TENKaGJHZHZjbWwwYUcwaU9pSmxaREkxTlRFNUlpd2lhMlY1SWpvaWFucDFjMXB5V1dOWU1VOUZUWE01VTJsVmJUQlJUREJHTDJkaVpXNVBhSFIyTXpGQlNVdDBaakIyVlQwaWZTd2liMjVzYVc1bElqcDdJbXRsZVY5MlpYSnphVzl1SWpveExDSmhiR2R2Y21sMGFHMGlPaUpsWkRJMU5URTVJaXdpYTJWNUlqb2lhME53WWtOeGVscExOR3hhWkdWTFp6ZEhNRE5PWjA5S2VYZGpNRkpHY25neU5EaENWa0ZHWjNoall6MGlmWDE5ZlN3aWRtOTBaWE1pT250OUxDSndjbTl2Wmw5dlpsOXdiM056WlhOemFXOXVJanA3SW1abU1EQTZNRG94TVRBaU9sc2lhWE56ZFdsdVp5SXNJbTl1YkdsdVpTSXNJbTltWm14cGJtVWlYU3dpWm1Zd01Eb3dPakV5TUNJNld5SnZibXhwYm1VaUxDSnZabVpzYVc1bElsMHNJbVptTURBNk1Eb3hNekFpT2xzaWFYTnpkV2x1WnlJc0ltOXViR2x1WlNJc0ltOW1abXhwYm1VaVhYMTk=","signatures":null} diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trcs/ISD1-V2.parts/ISD1-V2.prototype b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trcs/ISD1-V2.parts/ISD1-V2.prototype new file mode 100644 index 0000000000..3eea2eb621 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trcs/ISD1-V2.parts/ISD1-V2.prototype @@ -0,0 +1 @@ +{"payload":"ZXlKcGMyUWlPakVzSW5SeVkxOTJaWEp6YVc5dUlqb3lMQ0ppWVhObFgzWmxjbk5wYjI0aU9qRXNJbVJsYzJOeWFYQjBhVzl1SWpvaVNWTkVJREVpTENKMmIzUnBibWRmY1hWdmNuVnRJam95TENKbWIzSnRZWFJmZG1WeWMybHZiaUk2TVN3aVozSmhZMlZmY0dWeWFXOWtJam96TmpBd01Dd2lkSEoxYzNSZmNtVnpaWFJmWVd4c2IzZGxaQ0k2ZEhKMVpTd2lkbUZzYVdScGRIa2lPbnNpYm05MFgySmxabTl5WlNJNk1UVTNNekF6TXpnMk9Td2libTkwWDJGbWRHVnlJam94TmpBME5UWTVPRFk1ZlN3aWNISnBiV0Z5ZVY5aGMyVnpJanA3SW1abU1EQTZNRG94TVRBaU9uc2lZWFIwY21saWRYUmxjeUk2V3lKcGMzTjFhVzVuSWl3aWRtOTBhVzVuSWwwc0ltdGxlWE1pT25zaWFYTnpkV2x1WnlJNmV5SnJaWGxmZG1WeWMybHZiaUk2TVN3aVlXeG5iM0pwZEdodElqb2laV1F5TlRVeE9TSXNJbXRsZVNJNkltc3Zia3BCVnprMWQzazFSV3RYWkhwdFIyaEZSbE5NWjBoQ1NUWjFRWFp5UTBkSk9WZE5kMUZFTkRBOUluMHNJbTltWm14cGJtVWlPbnNpYTJWNVgzWmxjbk5wYjI0aU9qRXNJbUZzWjI5eWFYUm9iU0k2SW1Wa01qVTFNVGtpTENKclpYa2lPaUoxUWtkTFVWTndkMVp1VWpadE5UVjVaMGM0U2pGV00xRjZhVEpqZG5aWWNsSktSM1pNY0hObVRVTk5QU0o5TENKdmJteHBibVVpT25zaWEyVjVYM1psY25OcGIyNGlPaklzSW1Gc1oyOXlhWFJvYlNJNkltVmtNalUxTVRraUxDSnJaWGtpT2lKU1YyOTFia0l4ZVZkbE1rWmtVVmxSUjFRMWFrdFBWMU5hTDFCR2RWcFlSRU5PZVhWblNUWmxNbVU0UFNKOWZYMHNJbVptTURBNk1Eb3hNakFpT25zaVlYUjBjbWxpZFhSbGN5STZXeUpoZFhSb2IzSnBkR0YwYVhabElpd2lZMjl5WlNJc0luWnZkR2x1WnlKZExDSnJaWGx6SWpwN0ltOW1abXhwYm1VaU9uc2lhMlY1WDNabGNuTnBiMjRpT2pFc0ltRnNaMjl5YVhSb2JTSTZJbVZrTWpVMU1Ua2lMQ0pyWlhraU9pSlZUMjlMWkZKSkwyNUNSRWg1U0RSM2MyNDRkbXN4VEVweGN6ZFhhV05xTTI1SlluWXZVMnRpU0M5TlBTSjlMQ0p2Ym14cGJtVWlPbnNpYTJWNVgzWmxjbk5wYjI0aU9qRXNJbUZzWjI5eWFYUm9iU0k2SW1Wa01qVTFNVGtpTENKclpYa2lPaUoxUkd0eVRXSnpkbU5qTkhCclEzUllLMjFpUVVaWU5WTXpNVXRMZUdkUldXVnFlbGh3Ym1Kc1FXRkZQU0o5Zlgwc0ltWm1NREE2TURveE16QWlPbnNpWVhSMGNtbGlkWFJsY3lJNld5SmhkWFJvYjNKcGRHRjBhWFpsSWl3aVkyOXlaU0lzSW1semMzVnBibWNpTENKMmIzUnBibWNpWFN3aWEyVjVjeUk2ZXlKcGMzTjFhVzVuSWpwN0ltdGxlVjkyWlhKemFXOXVJam94TENKaGJHZHZjbWwwYUcwaU9pSmxaREkxTlRFNUlpd2lhMlY1SWpvaWJHZDZRbXBzVlVGeEx6ZFFPVkpRZG5ST1pVRTBjekkzT0RkVk1qQnZkVE5TYkRCV2JuWldObEZ4Y3owaWZTd2liMlptYkdsdVpTSTZleUpyWlhsZmRtVnljMmx2YmlJNk1Td2lZV3huYjNKcGRHaHRJam9pWldReU5UVXhPU0lzSW10bGVTSTZJbXA2ZFhOYWNsbGpXREZQUlUxek9WTnBWVzB3VVV3d1JpOW5ZbVZ1VDJoMGRqTXhRVWxMZEdZd2RsVTlJbjBzSW05dWJHbHVaU0k2ZXlKclpYbGZkbVZ5YzJsdmJpSTZNU3dpWVd4bmIzSnBkR2h0SWpvaVpXUXlOVFV4T1NJc0ltdGxlU0k2SW10RGNHSkRjWHBhU3pSc1dtUmxTMmMzUnpBelRtZFBTbmwzWXpCU1JuSjRNalE0UWxaQlJtZDRZMk05SW4xOWZYMHNJblp2ZEdWeklqcDdJbVptTURBNk1Eb3hNVEFpT25zaWEyVjVYM1I1Y0dVaU9pSnZabVpzYVc1bElpd2lhMlY1WDNabGNuTnBiMjRpT2pGOUxDSm1aakF3T2pBNk1USXdJanA3SW10bGVWOTBlWEJsSWpvaWIyNXNhVzVsSWl3aWEyVjVYM1psY25OcGIyNGlPakY5ZlN3aWNISnZiMlpmYjJaZmNHOXpjMlZ6YzJsdmJpSTZleUptWmpBd09qQTZNVEV3SWpwYkltOXViR2x1WlNKZGZYMA==","signatures":null} diff --git a/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trcs/ISD1-V3.parts/ISD1-V3.prototype b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trcs/ISD1-V3.parts/ISD1-V3.prototype new file mode 100644 index 0000000000..0ea3ed2d28 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/trcs/testdata/ISD1/trcs/ISD1-V3.parts/ISD1-V3.prototype @@ -0,0 +1 @@ +{"payload":"ZXlKcGMyUWlPakVzSW5SeVkxOTJaWEp6YVc5dUlqb3pMQ0ppWVhObFgzWmxjbk5wYjI0aU9qRXNJbVJsYzJOeWFYQjBhVzl1SWpvaVNWTkVJREVpTENKMmIzUnBibWRmY1hWdmNuVnRJam94TENKbWIzSnRZWFJmZG1WeWMybHZiaUk2TVN3aVozSmhZMlZmY0dWeWFXOWtJam8zTWpBd0xDSjBjblZ6ZEY5eVpYTmxkRjloYkd4dmQyVmtJanAwY25WbExDSjJZV3hwWkdsMGVTSTZleUp1YjNSZlltVm1iM0psSWpveE5UY3pNRE16T1RZNUxDSnViM1JmWVdaMFpYSWlPakUyTURRMU5qazVOamw5TENKd2NtbHRZWEo1WDJGelpYTWlPbnNpWm1Zd01Eb3dPakV4TUNJNmV5SmhkSFJ5YVdKMWRHVnpJanBiSW1semMzVnBibWNpTENKMmIzUnBibWNpWFN3aWEyVjVjeUk2ZXlKcGMzTjFhVzVuSWpwN0ltdGxlVjkyWlhKemFXOXVJam94TENKaGJHZHZjbWwwYUcwaU9pSmxaREkxTlRFNUlpd2lhMlY1SWpvaWF5OXVTa0ZYT1RWM2VUVkZhMWRrZW0xSGFFVkdVMHhuU0VKSk5uVkJkbkpEUjBrNVYwMTNVVVEwTUQwaWZTd2liMlptYkdsdVpTSTZleUpyWlhsZmRtVnljMmx2YmlJNk1Td2lZV3huYjNKcGRHaHRJam9pWldReU5UVXhPU0lzSW10bGVTSTZJblZDUjB0UlUzQjNWbTVTTm0wMU5YbG5SemhLTVZZelVYcHBNbU4yZGxoeVVrcEhka3h3YzJaTlEwMDlJbjBzSW05dWJHbHVaU0k2ZXlKclpYbGZkbVZ5YzJsdmJpSTZNaXdpWVd4bmIzSnBkR2h0SWpvaVpXUXlOVFV4T1NJc0ltdGxlU0k2SWxKWGIzVnVRakY1VjJVeVJtUlJXVkZIVkRWcVMwOVhVMW92VUVaMVdsaEVRMDU1ZFdkSk5tVXlaVGc5SW4xOWZTd2labVl3TURvd09qRXlNQ0k2ZXlKaGRIUnlhV0oxZEdWeklqcGJJbUYxZEdodmNtbDBZWFJwZG1VaUxDSmpiM0psSWl3aWRtOTBhVzVuSWwwc0ltdGxlWE1pT25zaWIyWm1iR2x1WlNJNmV5SnJaWGxmZG1WeWMybHZiaUk2TVN3aVlXeG5iM0pwZEdodElqb2laV1F5TlRVeE9TSXNJbXRsZVNJNklsVlBiMHRrVWtrdmJrSkVTSGxJTkhkemJqaDJhekZNU25Gek4xZHBZMm96YmtsaWRpOVRhMkpJTDAwOUluMHNJbTl1YkdsdVpTSTZleUpyWlhsZmRtVnljMmx2YmlJNk1Td2lZV3huYjNKcGRHaHRJam9pWldReU5UVXhPU0lzSW10bGVTSTZJblZFYTNKTlluTjJZMk0wY0d0RGRGZ3JiV0pCUmxnMVV6TXhTMHQ0WjFGWlpXcDZXSEJ1WW14QllVVTlJbjE5ZlN3aVptWXdNRG93T2pFek1DSTZleUpoZEhSeWFXSjFkR1Z6SWpwYkltRjFkR2h2Y21sMFlYUnBkbVVpTENKamIzSmxJaXdpYVhOemRXbHVaeUlzSW5admRHbHVaeUpkTENKclpYbHpJanA3SW1semMzVnBibWNpT25zaWEyVjVYM1psY25OcGIyNGlPakVzSW1Gc1oyOXlhWFJvYlNJNkltVmtNalUxTVRraUxDSnJaWGtpT2lKc1ozcENhbXhWUVhFdk4xQTVVbEIyZEU1bFFUUnpNamM0TjFVeU1HOTFNMUpzTUZadWRsWTJVWEZ6UFNKOUxDSnZabVpzYVc1bElqcDdJbXRsZVY5MlpYSnphVzl1SWpveExDSmhiR2R2Y21sMGFHMGlPaUpsWkRJMU5URTVJaXdpYTJWNUlqb2lhbnAxYzFweVdXTllNVTlGVFhNNVUybFZiVEJSVERCR0wyZGlaVzVQYUhSMk16RkJTVXQwWmpCMlZUMGlmU3dpYjI1c2FXNWxJanA3SW10bGVWOTJaWEp6YVc5dUlqb3hMQ0poYkdkdmNtbDBhRzBpT2lKbFpESTFOVEU1SWl3aWEyVjVJam9pYTBOd1lrTnhlbHBMTkd4YVpHVkxaemRITUROT1owOUtlWGRqTUZKR2NuZ3lORGhDVmtGR1ozaGpZejBpZlgxOWZTd2lkbTkwWlhNaU9uc2labVl3TURvd09qRXlNQ0k2ZXlKclpYbGZkSGx3WlNJNkltOW1abXhwYm1VaUxDSnJaWGxmZG1WeWMybHZiaUk2TVgwc0ltWm1NREE2TURveE16QWlPbnNpYTJWNVgzUjVjR1VpT2lKdlptWnNhVzVsSWl3aWEyVjVYM1psY25OcGIyNGlPakY5ZlN3aWNISnZiMlpmYjJaZmNHOXpjMlZ6YzJsdmJpSTZlMzE5","signatures":null} diff --git a/go/tools/scion-pki/internal/v2/trcs/util.go b/go/tools/scion-pki/internal/v2/trcs/util.go index a478e4be84..a67ec76038 100644 --- a/go/tools/scion-pki/internal/v2/trcs/util.go +++ b/go/tools/scion-pki/internal/v2/trcs/util.go @@ -15,101 +15,82 @@ package trcs import ( - "encoding/json" + "encoding/pem" "fmt" - "os" + "io/ioutil" "path/filepath" - "sort" "github.com/scionproto/scion/go/lib/addr" - "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/keyconf" + "github.com/scionproto/scion/go/lib/scrypto" "github.com/scionproto/scion/go/lib/scrypto/trc/v2" + "github.com/scionproto/scion/go/lib/serrors" "github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn" ) +var errReadFile = serrors.New("error reading file") + // Dir returns the directory where TRCs are written to. -func Dir(isd addr.ISD) string { - return filepath.Join(pkicmn.GetIsdPath(pkicmn.OutDir, isd), pkicmn.TRCsDir) +func Dir(dir string, isd addr.ISD) string { + return filepath.Join(pkicmn.GetIsdPath(dir, isd), pkicmn.TRCsDir) } // PartsDir returns the directory where the partially signed TRC is written to. -func PartsDir(isd addr.ISD, ver uint64) string { - return filepath.Join(Dir(isd), fmt.Sprintf(pkicmn.TRCPartsDirFmt, isd, ver)) +func PartsDir(dir string, isd addr.ISD, ver scrypto.Version) string { + return filepath.Join(Dir(dir, isd), fmt.Sprintf(pkicmn.TRCPartsDirFmt, isd, ver)) } // ProtoFile returns the file path for the prototype TRC. -func ProtoFile(isd addr.ISD, ver uint64) string { - return filepath.Join(PartsDir(isd, ver), fmt.Sprintf(pkicmn.TRCProtoNameFmt, isd, ver)) -} - -// PartsFile returns the file path for the partially signed TRC with the selector. -func PartsFile(isd addr.ISD, ver uint64, selector string) string { - return filepath.Join(PartsDir(isd, ver), - fmt.Sprintf(pkicmn.TRCSigPartFmt, isd, ver, selector)) +func ProtoFile(dir string, isd addr.ISD, ver scrypto.Version) string { + return filepath.Join(PartsDir(dir, isd, ver), fmt.Sprintf(pkicmn.TRCProtoNameFmt, isd, ver)) } // SignedFile returns the file path for the signed TRC. -func SignedFile(isd addr.ISD, ver uint64) string { - return filepath.Join(Dir(isd), fmt.Sprintf(pkicmn.TrcNameFmt, isd, ver)) +func SignedFile(dir string, isd addr.ISD, ver scrypto.Version) string { + return filepath.Join(Dir(dir, isd), fmt.Sprintf(pkicmn.TrcNameFmt, isd, ver)) } -func validateAndWrite(t *trc.TRC, signed *trc.Signed) error { - raw, err := json.Marshal(signed) +func loadTRC(file string) (*trc.TRC, trc.Encoded, error) { + raw, err := ioutil.ReadFile(file) if err != nil { - return common.NewBasicError("unable to marshal signed TRC", err) + return nil, nil, err } - if err := validateResult(raw); err != nil { - return common.NewBasicError("unable to validate signed TRC", err) + signed, err := trc.ParseSigned(raw) + if err != nil { + return nil, nil, err } - if err := os.MkdirAll(Dir(t.ISD), 0755); err != nil { - return common.NewBasicError("unable to create TRC dir", err) + t, err := signed.EncodedTRC.Decode() + if err != nil { + return nil, nil, err } - return pkicmn.WriteToFile(raw, SignedFile(t.ISD, uint64(t.Version)), 0644) + return t, signed.EncodedTRC, nil } -func validateResult(raw []byte) error { - var signed trc.Signed - if err := json.Unmarshal(raw, &signed); err != nil { - return common.NewBasicError("invalid signed TRC", err) - } - t, err := signed.EncodedTRC.Decode() +func loadKey(file string, ia addr.IA, usage keyconf.Usage, + version scrypto.KeyVersion) (keyconf.Key, error) { + + raw, err := ioutil.ReadFile(file) if err != nil { - return common.NewBasicError("invalid TRC payload", err) + return keyconf.Key{}, serrors.Wrap(errReadFile, err) } - if err := t.ValidateInvariant(); err != nil { - return common.NewBasicError("violated TRC invariant", err) + block, _ := pem.Decode(raw) + if block == nil { + return keyconf.Key{}, serrors.New("unable to parse PEM") } - // FIXME(roosd): Should also verify votes when supported. - v := trc.POPVerifier{ - TRC: t, - Encoded: signed.EncodedTRC, - Signatures: signed.Signatures, + key, err := keyconf.KeyFromPEM(block) + if err != nil { + return keyconf.Key{}, serrors.WrapStr("unable to decode key", err) } - if err := v.Verify(); err != nil { - return common.NewBasicError("proof of possesions fail to verify", err) + if !key.IA.Equal(ia) { + return keyconf.Key{}, serrors.New("IA does not match", "expected", ia, "actual", key.IA) } - return nil -} - -func sortSignatures(signatures map[trc.Protected]trc.Signature) []trc.Signature { - keys := make([]trc.Protected, 0, len(signatures)) - for key := range signatures { - keys = append(keys, key) + if key.Usage != usage { + return keyconf.Key{}, serrors.New("usage does not match", + "expected", usage, "actual", key.Usage) } - sort.Slice(keys, func(i, j int) bool { - switch { - case keys[i].AS != keys[j].AS: - return keys[i].AS < keys[j].AS - case keys[i].Type != keys[j].Type: - return keys[i].Type < keys[j].Type - case keys[i].KeyType != keys[j].KeyType: - return keys[i].KeyType < keys[j].KeyType - } - return false - }) - sigs := make([]trc.Signature, 0, len(keys)) - for _, key := range keys { - sigs = append(sigs, signatures[key]) + if key.Version != version { + return keyconf.Key{}, serrors.New("version does not match", + "expected", version, "actual", key.Version) } - return sigs + return key, nil }