From 8d33b02b2c39ede30d3c184b8b00e975ced1898a Mon Sep 17 00:00:00 2001 From: roos Date: Wed, 13 Nov 2019 17:18:36 +0100 Subject: [PATCH] SPKI: Generate certificate chains This PR adds the capability to generate certificate chains. Generated chains are verified before writing to file system. --- .../scion-pki/internal/v2/certs/BUILD.bazel | 2 + go/tools/scion-pki/internal/v2/certs/chain.go | 254 ++++++++++++++++++ .../scion-pki/internal/v2/certs/chain_test.go | 81 ++++++ go/tools/scion-pki/internal/v2/certs/cmd.go | 21 ++ .../internal/v2/certs/issuer_test.go | 8 +- .../scion-pki/internal/v2/certs/loader.go | 23 ++ .../internal/v2/certs/loader_test.go | 25 +- .../testdata/ISD1/ASff00_0_111/as-v1.toml | 12 + .../certs/ISD1-ASff00_0_111-V1.crt | 1 + .../testdata/ISD1/ASff00_0_111/keys.toml | 21 ++ .../ISD1/ASff00_0_111/keys/as-decrypt-v2.key | 11 + .../ASff00_0_111/keys/as-revocation-v2.key | 11 + .../ISD1/ASff00_0_111/keys/as-signing-v1.key | 11 + go/tools/scion-pki/internal/v2/certs/util.go | 5 + .../scion-pki/internal/v2/certs/verify.go | 44 ++- go/tools/scion-pki/internal/v2/conf/as.go | 5 + 16 files changed, 525 insertions(+), 10 deletions(-) create mode 100644 go/tools/scion-pki/internal/v2/certs/chain.go create mode 100644 go/tools/scion-pki/internal/v2/certs/chain_test.go create mode 100644 go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/as-v1.toml create mode 100644 go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/certs/ISD1-ASff00_0_111-V1.crt create mode 100644 go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys.toml create mode 100644 go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys/as-decrypt-v2.key create mode 100644 go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys/as-revocation-v2.key create mode 100644 go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys/as-signing-v1.key diff --git a/go/tools/scion-pki/internal/v2/certs/BUILD.bazel b/go/tools/scion-pki/internal/v2/certs/BUILD.bazel index 33ccdb0fcf..5d60c8085e 100644 --- a/go/tools/scion-pki/internal/v2/certs/BUILD.bazel +++ b/go/tools/scion-pki/internal/v2/certs/BUILD.bazel @@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ + "chain.go", "cmd.go", "human.go", "issuer.go", @@ -30,6 +31,7 @@ go_library( go_test( name = "go_default_test", srcs = [ + "chain_test.go", "issuer_test.go", "loader_test.go", "main_test.go", diff --git a/go/tools/scion-pki/internal/v2/certs/chain.go b/go/tools/scion-pki/internal/v2/certs/chain.go new file mode 100644 index 0000000000..e8f07cbd0f --- /dev/null +++ b/go/tools/scion-pki/internal/v2/certs/chain.go @@ -0,0 +1,254 @@ +// 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" + "path/filepath" + "time" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/keyconf" + "github.com/scionproto/scion/go/lib/scrypto" + "github.com/scionproto/scion/go/lib/scrypto/cert/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" + "github.com/scionproto/scion/go/tools/scion-pki/internal/v2/keys" +) + +type chainMeta struct { + Chain cert.Chain + Version scrypto.Version +} + +type chainGen struct { + Dirs pkicmn.Dirs + Version scrypto.Version +} + +func (g chainGen) Run(asMap pkicmn.ASMap) error { + cfgs, err := loader{Dirs: g.Dirs, Version: g.Version}.LoadASConfigs(asMap) + if err != nil { + return serrors.WrapStr("unable to load AS certificate configs", err) + } + certs, err := g.Generate(cfgs) + if err != nil { + return serrors.WrapStr("unable to generate AS certificates", err) + } + if err := g.Sign(certs, cfgs); err != nil { + return serrors.WrapStr("unable to sign AS certificates", err) + } + if err := g.verify(certs); err != nil { + return serrors.WrapStr("unable to verify AS certificates", err) + } + if err := g.createDirs(certs); err != nil { + return serrors.WrapStr("unable to create output directories", err) + } + if err := g.write(certs); err != nil { + return serrors.WrapStr("unable to write AS certificates", err) + } + return nil +} + +func (g chainGen) Generate(cfgs map[addr.IA]conf.AS) (map[addr.IA]chainMeta, error) { + certs := make(map[addr.IA]chainMeta) + for ia, cfg := range cfgs { + signed, err := g.generate(ia, cfg) + if err != nil { + return nil, serrors.WrapStr("unable to generate issuer certificate", err, + "ia", ia, "version", cfg.Version) + } + certs[ia] = signed + } + return certs, nil +} + +func (g chainGen) generate(ia addr.IA, cfg conf.AS) (chainMeta, error) { + pubKeys, err := g.loadPubKeys(ia, cfg) + if err != nil { + return chainMeta{}, serrors.WrapStr("unable to load all public keys", err) + } + enc, err := cert.EncodeAS(g.newCert(ia, cfg, pubKeys)) + if err != nil { + return chainMeta{}, serrors.WrapStr("unable to encode AS certificate", err) + } + file := IssuerFile(g.Dirs.Out, cfg.IssuerIA, cfg.IssuerCertVersion) + raw, err := ioutil.ReadFile(file) + if err != nil { + return chainMeta{}, serrors.WrapStr("unable to read issuer certificate", err, "file", file) + } + issuer, err := cert.ParseSignedIssuer(raw) + if err != nil { + return chainMeta{}, serrors.WrapStr("unable to parse issuer certificate", err, "file", file) + } + meta := chainMeta{ + Chain: cert.Chain{ + Issuer: issuer, + AS: cert.SignedAS{Encoded: enc}, + }, + Version: cfg.Version, + } + return meta, nil +} + +func (g chainGen) loadPubKeys(ia addr.IA, cfg conf.AS) (map[cert.KeyType]keyconf.Key, error) { + keys := make(map[cert.KeyType]keyconf.Key) + type meta struct { + Usage keyconf.Usage + Version scrypto.KeyVersion + } + load := map[cert.KeyType]keyconf.ID{ + cert.SigningKey: { + IA: ia, + Version: *cfg.SigningKeyVersion, + Usage: keyconf.ASSigningKey, + }, + cert.EncryptionKey: { + IA: ia, + Version: *cfg.EncryptionKeyVersion, + Usage: keyconf.ASDecryptionKey, + }, + } + if cfg.RevocationKeyVersion != nil { + load[cert.RevocationKey] = keyconf.ID{ + IA: ia, + Version: *cfg.RevocationKeyVersion, + Usage: keyconf.ASRevocationKey, + } + } + for keyType, id := range load { + key, err := g.loadPubKey(id) + if err != nil { + return nil, serrors.WrapStr("unable to load key", err, "usage", id.Usage) + } + keys[keyType] = key + } + return keys, nil +} + +func (g chainGen) loadPubKey(id keyconf.ID) (keyconf.Key, error) { + key, fromPriv, err := keys.LoadPublicKey(g.Dirs.Out, id) + if err != nil { + return keyconf.Key{}, err + } + if fromPriv { + pkicmn.QuietPrint("Using private %s key for %s\n", id.Usage, id.IA) + return key, nil + } + pkicmn.QuietPrint("Using public %s key for %s\n", id.Usage, id.IA) + return key, nil +} + +func (g chainGen) newCert(ia addr.IA, cfg conf.AS, pubKeys map[cert.KeyType]keyconf.Key) *cert.AS { + val := cfg.Validity.Eval(time.Now()) + c := &cert.AS{ + Base: cert.Base{ + Subject: ia, + Version: cfg.Version, + FormatVersion: 1, + Description: cfg.Description, + OptionalDistributionPoints: cfg.OptDistPoints, + Validity: &val, + Keys: translateKeys(pubKeys), + }, + Issuer: cert.IssuerCertID{ + IA: cfg.IssuerIA, + CertificateVersion: cfg.IssuerCertVersion, + }, + } + return c +} + +func (g chainGen) Sign(protos map[addr.IA]chainMeta, cfgs map[addr.IA]conf.AS) error { + for ia, meta := range protos { + var err error + if meta.Chain, err = g.sign(cfgs[ia], meta.Chain); err != nil { + return serrors.WrapStr("unable to sign AS certificate", err, "ia", ia) + } + protos[ia] = meta + } + return nil +} + +func (g chainGen) sign(cfg conf.AS, chain cert.Chain) (cert.Chain, error) { + file := conf.IssuerFile(g.Dirs.Root, cfg.IssuerIA, cfg.IssuerCertVersion) + issCfg, err := conf.LoadIssuer(file) + if err != nil { + return cert.Chain{}, serrors.WrapStr("unable to load issuer config", err, "file", file) + } + file = filepath.Join(keys.PrivateDir(g.Dirs.Out, cfg.IssuerIA), + keyconf.PrivateKeyFile(keyconf.IssCertSigningKey, *issCfg.IssuingKeyVersion)) + id := keyconf.ID{ + IA: cfg.IssuerIA, + Usage: keyconf.IssCertSigningKey, + Version: *issCfg.IssuingKeyVersion, + } + key, err := keyconf.LoadKeyFromFile(file, keyconf.PrivateKey, id) + if err != nil { + return cert.Chain{}, serrors.WrapStr("unable to load issuing key", err, "file", file) + } + protected := cert.ProtectedAS{ + Algorithm: key.Algorithm, + IA: cfg.IssuerIA, + CertificateVersion: cfg.IssuerCertVersion, + } + if chain.AS.EncodedProtected, err = cert.EncodeProtectedAS(protected); err != nil { + return cert.Chain{}, serrors.WrapStr("unable to encode protected", err) + } + chain.AS.Signature, err = scrypto.Sign(chain.AS.SigInput(), key.Bytes, key.Algorithm) + if err != nil { + return cert.Chain{}, serrors.WrapStr("unable to sign issuer certificate", err) + } + return chain, nil +} + +func (g chainGen) verify(certs map[addr.IA]chainMeta) error { + v := verifier{Dirs: g.Dirs} + for ia, meta := range certs { + raw, err := meta.Chain.MarshalJSON() + if err != nil { + return serrors.WrapStr("unable to encode certificate chain", err) + } + if err := v.VerifyChain(raw); err != nil { + return serrors.WrapStr("unable to verify certificate chain", err, "ia", ia) + } + } + return nil +} + +func (g chainGen) createDirs(certs map[addr.IA]chainMeta) error { + for ia := range certs { + if err := os.MkdirAll(Dir(g.Dirs.Out, ia), 0755); err != nil { + return serrors.WrapStr("unable to make certs directory", err, "ia", ia) + } + } + return nil +} + +func (g chainGen) write(certs map[addr.IA]chainMeta) error { + for ia, meta := range certs { + raw, err := meta.Chain.MarshalJSON() + if err != nil { + return serrors.WrapStr("unable to encode signed issuer certificate", err) + } + file := ASFile(g.Dirs.Out, ia, meta.Version) + if err := pkicmn.WriteToFile(raw, file, 0644); err != nil { + return serrors.WrapStr("unable to write signed issuer certificate", err, "file", file) + } + } + return nil +} diff --git a/go/tools/scion-pki/internal/v2/certs/chain_test.go b/go/tools/scion-pki/internal/v2/certs/chain_test.go new file mode 100644 index 0000000000..7ec978000d --- /dev/null +++ b/go/tools/scion-pki/internal/v2/certs/chain_test.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 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/xtest" + "github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn" +) + +var ( + ia111 = xtest.MustParseIA("1-ff00:0:111") + chainASMap = pkicmn.ASMap{1: {ia111}} +) + +func TestChainGenRun(t *testing.T) { + tmpDir, cleanF := xtest.MustTempDir("", "test-certs-chain") + defer cleanF() + + isdDir := filepath.Join(tmpDir, "ISD1") + require.NoError(t, os.MkdirAll(isdDir, 0777)) + err := exec.Command("cp", "-r", + "./testdata/ISD1/ASff00_0_110", + "./testdata/ISD1/trcs", + "./testdata/ISD1/trc-v1.toml", + isdDir).Run() + require.NoError(t, err) + + asDir := filepath.Join(isdDir, "ASff00_0_111") + require.NoError(t, os.MkdirAll(asDir, 0777)) + err = exec.Command("cp", "-r", + "./testdata/ISD1/ASff00_0_111/keys", + "./testdata/ISD1/ASff00_0_111/as-v1.toml", + asDir).Run() + require.NoError(t, err) + + g := chainGen{ + Dirs: pkicmn.Dirs{Root: "./testdata", Out: tmpDir}, + } + err = g.Run(chainASMap) + require.NoError(t, err) + + golden, err := ioutil.ReadFile(ASFile("./testdata", ia111, 1)) + require.NoError(t, err) + result, err := ioutil.ReadFile(ASFile(tmpDir, ia111, 1)) + require.NoError(t, err) + assert.Equal(t, golden, result) +} + +// TestUpdateGoldenChain provides an easy way to update the golden file after +// the format has changed. +func TestUpdateGoldenChain(t *testing.T) { + if *update { + force := pkicmn.Force + pkicmn.Force = true + defer func() { pkicmn.Force = force }() + g := chainGen{Dirs: pkicmn.Dirs{Root: "./testdata", Out: "./testdata"}, Version: 1} + err := g.Run(chainASMap) + require.NoError(t, err) + } +} diff --git a/go/tools/scion-pki/internal/v2/certs/cmd.go b/go/tools/scion-pki/internal/v2/certs/cmd.go index c4e4143150..2af252fd66 100644 --- a/go/tools/scion-pki/internal/v2/certs/cmd.go +++ b/go/tools/scion-pki/internal/v2/certs/cmd.go @@ -77,6 +77,26 @@ var genIssuerCmd = &cobra.Command{ }, } +var genChainCmd = &cobra.Command{ + Use: "chain", + Short: "Generate the certificate chain", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + g := chainGen{ + Dirs: pkicmn.GetDirs(), + Version: scrypto.Version(version), + } + asMap, err := pkicmn.ProcessSelector(args[0]) + if err != nil { + return serrors.WrapStr("unable to select target ISDs", err, "selector", args[0]) + } + if err := g.Run(asMap); err != nil { + return serrors.WrapStr("unable to generate certificate chains", err) + } + return nil + }, +} + var humanCmd = &cobra.Command{ Use: "human", Short: "Display human readable issuer certificates and certificate chains", @@ -94,6 +114,7 @@ var humanCmd = &cobra.Command{ } func init() { + Cmd.AddCommand(genChainCmd) Cmd.AddCommand(genIssuerCmd) Cmd.AddCommand(humanCmd) } diff --git a/go/tools/scion-pki/internal/v2/certs/issuer_test.go b/go/tools/scion-pki/internal/v2/certs/issuer_test.go index 66755b6fe7..a3222ccfce 100644 --- a/go/tools/scion-pki/internal/v2/certs/issuer_test.go +++ b/go/tools/scion-pki/internal/v2/certs/issuer_test.go @@ -29,8 +29,8 @@ import ( ) var ( - ia110 = xtest.MustParseIA("1-ff00:0:110") - testASMap = pkicmn.ASMap{1: {ia110}} + ia110 = xtest.MustParseIA("1-ff00:0:110") + issASMap = pkicmn.ASMap{1: {ia110}} ) func TestIssuerGenRun(t *testing.T) { @@ -56,7 +56,7 @@ func TestIssuerGenRun(t *testing.T) { g := issGen{ Dirs: pkicmn.Dirs{Root: "./testdata", Out: tmpDir}, } - err = g.Run(testASMap) + err = g.Run(issASMap) require.NoError(t, err) golden, err := ioutil.ReadFile(IssuerFile("./testdata", ia110, 1)) @@ -74,7 +74,7 @@ func TestUpdateGoldenIssuer(t *testing.T) { pkicmn.Force = true defer func() { pkicmn.Force = force }() g := issGen{Dirs: pkicmn.Dirs{Root: "./testdata", Out: "./testdata"}, Version: 1} - err := g.Run(testASMap) + err := g.Run(issASMap) require.NoError(t, err) } } diff --git a/go/tools/scion-pki/internal/v2/certs/loader.go b/go/tools/scion-pki/internal/v2/certs/loader.go index 3355ef9686..41652a8c06 100644 --- a/go/tools/scion-pki/internal/v2/certs/loader.go +++ b/go/tools/scion-pki/internal/v2/certs/loader.go @@ -54,6 +54,29 @@ func (l loader) LoadIssuerConfigs(asMap pkicmn.ASMap) (map[addr.IA]conf.Issuer, return cfgs, nil } +func (l loader) LoadASConfigs(asMap pkicmn.ASMap) (map[addr.IA]conf.AS, error) { + cfgs := make(map[addr.IA]conf.AS) + for _, ias := range asMap { + for _, ia := range ias { + s := selector{ + File: conf.ASFile, + All: conf.AllASFiles, + Regex: `as-v(\d*)\.toml$`, + } + file, err := l.selectConfig(ia, s) + if err != nil { + return nil, serrors.WrapStr("unable to select config", err, "ia", ia) + } + cfg, err := conf.LoadAS(file) + if err != nil { + return nil, serrors.WithCtx(err, "ia", ia) + } + cfgs[ia] = cfg + } + } + return cfgs, nil +} + func (l loader) selectConfig(ia addr.IA, s selector) (string, error) { if l.Version != scrypto.LatestVer { return s.File(l.Dirs.Root, ia, l.Version), nil diff --git a/go/tools/scion-pki/internal/v2/certs/loader_test.go b/go/tools/scion-pki/internal/v2/certs/loader_test.go index 6c35371a20..c1b6f2f90a 100644 --- a/go/tools/scion-pki/internal/v2/certs/loader_test.go +++ b/go/tools/scion-pki/internal/v2/certs/loader_test.go @@ -40,9 +40,32 @@ func TestLoaderLoadIssuerConfigs(t *testing.T) { Dirs: pkicmn.Dirs{Root: "./testdata", Out: "./testdata"}, Version: test.Version, } - cfgs, err := l.LoadIssuerConfigs(testASMap) + cfgs, err := l.LoadIssuerConfigs(issASMap) require.NoError(t, err) assert.Equal(t, test.Expected, cfgs[ia110].Version) }) } } + +func TestLoaderLoadASConfigs(t *testing.T) { + tests := map[string]struct { + Version scrypto.Version + Expected scrypto.Version + }{ + "v1": {Version: 1, Expected: 1}, + "max": {Version: 0, Expected: 1}, + } + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + t.Parallel() + l := loader{ + Dirs: pkicmn.Dirs{Root: "./testdata", Out: "./testdata"}, + Version: test.Version, + } + cfgs, err := l.LoadASConfigs(chainASMap) + require.NoError(t, err) + assert.Equal(t, test.Expected, cfgs[ia111].Version) + }) + } +} diff --git a/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/as-v1.toml b/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/as-v1.toml new file mode 100644 index 0000000000..1517c0f492 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/as-v1.toml @@ -0,0 +1,12 @@ +description = "AS certificate 1-ff00:0:111" +version = 1 +signing_key_version = 1 +encryption_key_version = 2 +revocation_key_version = 2 +issuer_ia = "1-ff00:0:110" +issuer_cert_version = 1 +optional_distribution_points = [] + +[validity] + not_before = 1573033769 + validity = "1y" diff --git a/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/certs/ISD1-ASff00_0_111-V1.crt b/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/certs/ISD1-ASff00_0_111-V1.crt new file mode 100644 index 0000000000..a54a73a71e --- /dev/null +++ b/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/certs/ISD1-ASff00_0_111-V1.crt @@ -0,0 +1 @@ +[{"payload":"ZXlKemRXSnFaV04wSWpvaU1TMW1aakF3T2pBNk1URXdJaXdpZG1WeWMybHZiaUk2TVN3aVptOXliV0YwWDNabGNuTnBiMjRpT2pFc0ltUmxjMk55YVhCMGFXOXVJam9pU1hOemRXVnlJR05sY25ScFptbGpZWFJsSURFdFptWXdNRG93T2pFeE1DSXNJbTl3ZEdsdmJtRnNYMlJwYzNSeWFXSjFkR2x2Ymw5d2IybHVkSE1pT2x0ZExDSjJZV3hwWkdsMGVTSTZleUp1YjNSZlltVm1iM0psSWpveE5UY3pNRE16TnpZNUxDSnViM1JmWVdaMFpYSWlPakUyTURRMU5qazNOamw5TENKclpYbHpJanA3SW1semMzVnBibWNpT25zaWEyVjVYM1psY25OcGIyNGlPakVzSW1Gc1oyOXlhWFJvYlNJNkltVmtNalUxTVRraUxDSnJaWGtpT2lKVVVXaDVORzVGTkdSalFtNXpUa0prVVVOaVNFNHdOR1Z2ZUZCU1JIWjRkamt4TUVVdlNGRnllaTlqUFNKOUxDSnlaWFp2WTJGMGFXOXVJanA3SW10bGVWOTJaWEp6YVc5dUlqb3lMQ0poYkdkdmNtbDBhRzBpT2lKbFpESTFOVEU1SWl3aWEyVjVJam9pVXpKR2IxcGxLM1JHVXpBM1FsVnNaWEpRWWtKaGJqVlJRVlo2VGswMlJGUTNTVGxNV210clJHRlFPRDBpZlgwc0ltbHpjM1ZsY2lJNmV5SjBjbU5mZG1WeWMybHZiaUk2TVgwc0ltTmxjblJwWm1sallYUmxYM1I1Y0dVaU9pSnBjM04xWlhJaWZR","protected":"ZXlKaGJHY2lPaUpsWkRJMU5URTVJaXdpZEhsd1pTSTZJblJ5WXlJc0luUnlZMTkyWlhKemFXOXVJam94TENKamNtbDBJanBiSW5SNWNHVWlMQ0owY21OZmRtVnljMmx2YmlKZGZR","signature":"k0GLHNQJjByF8fFb+Mscv6bvwLObJSDq5RX3f9u1UFu7m/UMdpbuWRRksQNKWo7WBo7Xtj29WaqXmQBhLQm2AA=="},{"payload":"ZXlKemRXSnFaV04wSWpvaU1TMW1aakF3T2pBNk1URXhJaXdpZG1WeWMybHZiaUk2TVN3aVptOXliV0YwWDNabGNuTnBiMjRpT2pFc0ltUmxjMk55YVhCMGFXOXVJam9pUVZNZ1kyVnlkR2xtYVdOaGRHVWdNUzFtWmpBd09qQTZNVEV4SWl3aWIzQjBhVzl1WVd4ZlpHbHpkSEpwWW5WMGFXOXVYM0J2YVc1MGN5STZXMTBzSW5aaGJHbGthWFI1SWpwN0ltNXZkRjlpWldadmNtVWlPakUxTnpNd016TTNOamtzSW01dmRGOWhablJsY2lJNk1UWXdORFUyT1RjMk9YMHNJbXRsZVhNaU9uc2laVzVqY25sd2RHbHZiaUk2ZXlKclpYbGZkbVZ5YzJsdmJpSTZNaXdpWVd4bmIzSnBkR2h0SWpvaVpXUXlOVFV4T1NJc0ltdGxlU0k2SW5SUk5IZFFhVEF4YzBGdU5tSlZTVlJKY1VGWVMyRnpjakE1U0RsdFZsaHNkRWRFTUhKWE1FSlFNVms5SW4wc0luSmxkbTlqWVhScGIyNGlPbnNpYTJWNVgzWmxjbk5wYjI0aU9qSXNJbUZzWjI5eWFYUm9iU0k2SW1Wa01qVTFNVGtpTENKclpYa2lPaUpZUkdWTmVUWTROMk5HYlRaUVZuRjVPV2hYTVZwUWVGRkZNa0ZEZUVock5rYzVjbXRHTW1aeVV6UTRQU0o5TENKemFXZHVhVzVuSWpwN0ltdGxlVjkyWlhKemFXOXVJam94TENKaGJHZHZjbWwwYUcwaU9pSmxaREkxTlRFNUlpd2lhMlY1SWpvaWVFaGFkUzloV1VSWVRIUjBWM0JYVldsVVZWaHJiRFZKVVZOSGVGcDBkblpDWVU1bmFHcFBOWFJLVVQwaWZYMHNJbWx6YzNWbGNpSTZleUpwWVNJNklqRXRabVl3TURvd09qRXhNQ0lzSW1ObGNuUnBabWxqWVhSbFgzWmxjbk5wYjI0aU9qRjlMQ0pqWlhKMGFXWnBZMkYwWlY5MGVYQmxJam9pWVhNaWZR","protected":"ZXlKaGJHY2lPaUpsWkRJMU5URTVJaXdpWTNKcGRDSTZXeUowZVhCbElpd2lZMlZ5ZEdsbWFXTmhkR1ZmZG1WeWMybHZiaUlzSW1saElsMHNJblI1Y0dVaU9pSmpaWEowYVdacFkyRjBaU0lzSW1ObGNuUnBabWxqWVhSbFgzWmxjbk5wYjI0aU9qRXNJbWxoSWpvaU1TMW1aakF3T2pBNk1URXdJbjA=","signature":"bMPZwb0sLByit5A6KX6GpTCZBoxa4JNULHyboJ1gjyrQtLokG88euwuJaOwdIOozxpaEIWTcuNpIldv4evAUBA=="}] diff --git a/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys.toml b/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys.toml new file mode 100644 index 0000000000..644f870947 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys.toml @@ -0,0 +1,21 @@ +[as_cert] + [as_cert.signing] + [as_cert.signing.1] + algorithm = "ed25519" + [as_cert.signing.1.validity] + not_before = 1573033769 + validity = "1y" + + [as_cert.encryption] + [as_cert.encryption.2] + algorithm = "ed25519" + [as_cert.encryption.2.validity] + not_before = 1573033769 + validity = "1y" + + [as_cert.revocation] + [as_cert.revocation.2] + algorithm = "ed25519" + [as_cert.revocation.2.validity] + not_before = 1573033769 + validity = "1y" diff --git a/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys/as-decrypt-v2.key b/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys/as-decrypt-v2.key new file mode 100644 index 0000000000..f57be0a2c8 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys/as-decrypt-v2.key @@ -0,0 +1,11 @@ +-----BEGIN PRIVATE KEY----- +algorithm: ed25519 +ia: 1-ff00:0:111 +not_after: 2020-11-05 09:49:29+0000 +not_before: 2019-11-06 09:49:29+0000 +usage: as-decrypt +version: 2 + +GZuzjBJFlpI++AFuFTc+5rtvE37ORK8+wEJAQ3V0gj0= +-----END PRIVATE KEY----- + diff --git a/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys/as-revocation-v2.key b/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys/as-revocation-v2.key new file mode 100644 index 0000000000..d27f485dc6 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys/as-revocation-v2.key @@ -0,0 +1,11 @@ +-----BEGIN PRIVATE KEY----- +algorithm: ed25519 +ia: 1-ff00:0:111 +not_after: 2020-11-05 09:49:29+0000 +not_before: 2019-11-06 09:49:29+0000 +usage: as-revocation +version: 2 + +cyxE6qnz5H2EEGNSuZPP+x/HOa9u9IhXZJ71C2FN+Ok= +-----END PRIVATE KEY----- + diff --git a/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys/as-signing-v1.key b/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys/as-signing-v1.key new file mode 100644 index 0000000000..c860844593 --- /dev/null +++ b/go/tools/scion-pki/internal/v2/certs/testdata/ISD1/ASff00_0_111/keys/as-signing-v1.key @@ -0,0 +1,11 @@ +-----BEGIN PRIVATE KEY----- +algorithm: ed25519 +ia: 1-ff00:0:111 +not_after: 2020-11-05 09:49:29+0000 +not_before: 2019-11-06 09:49:29+0000 +usage: as-signing +version: 1 + +dSpcYnKWtVUAv1PPnsFS+XRNnDn04As8WP3/7TFh1to= +-----END PRIVATE KEY----- + diff --git a/go/tools/scion-pki/internal/v2/certs/util.go b/go/tools/scion-pki/internal/v2/certs/util.go index 7189de1831..8a77c57cbc 100644 --- a/go/tools/scion-pki/internal/v2/certs/util.go +++ b/go/tools/scion-pki/internal/v2/certs/util.go @@ -36,6 +36,11 @@ func IssuerFile(dir string, ia addr.IA, ver scrypto.Version) string { return filepath.Join(Dir(dir, ia), fmt.Sprintf(pkicmn.IssuerNameFmt, ia.I, ia.A.FileFmt(), ver)) } +// ASFile returns the file path for the certificate chain. +func ASFile(dir string, ia addr.IA, ver scrypto.Version) string { + return filepath.Join(Dir(dir, ia), fmt.Sprintf(pkicmn.CertNameFmt, ia.I, ia.A.FileFmt(), ver)) +} + // translateKeys translates the keys to certificate key metadata. func translateKeys(keys map[cert.KeyType]keyconf.Key) map[cert.KeyType]scrypto.KeyMeta { m := make(map[cert.KeyType]scrypto.KeyMeta) diff --git a/go/tools/scion-pki/internal/v2/certs/verify.go b/go/tools/scion-pki/internal/v2/certs/verify.go index bfb74cb5cf..5638753490 100644 --- a/go/tools/scion-pki/internal/v2/certs/verify.go +++ b/go/tools/scion-pki/internal/v2/certs/verify.go @@ -37,16 +37,50 @@ func (v verifier) VerifyIssuer(raw []byte) error { if err != nil { return serrors.WrapStr("unable to parse signed issuer certificate", err) } + if _, err = v.verifyIssuer(signed); err != nil { + return err + } + return nil +} + +func (v verifier) VerifyChain(raw []byte) error { + chain, err := cert.ParseChain(raw) + if err != nil { + return serrors.WrapStr("unable to parse signed certificate chain", err) + } + issCert, err := v.verifyIssuer(chain.Issuer) + if err != nil { + return err + } + asCert, err := chain.AS.Encoded.Decode() + if err != nil { + return serrors.WrapStr("unable to parse AS certificate payload", err) + } + if err := asCert.Validate(); err != nil { + return serrors.WrapStr("unable to validate AS certificate", err) + } + asVer := cert.ASVerifier{ + Issuer: issCert, + AS: asCert, + SignedAS: &chain.AS, + } + if err := asVer.Verify(); err != nil { + return serrors.WrapStr("unable to verify AS certificate", err) + } + return nil +} + +func (v verifier) verifyIssuer(signed cert.SignedIssuer) (*cert.Issuer, error) { c, err := signed.Encoded.Decode() if err != nil { - return serrors.WrapStr("unable to parse issuer certificate payload", err) + return nil, serrors.WrapStr("unable to parse issuer certificate payload", err) } if err := c.Validate(); err != nil { - return serrors.WrapStr("unable to validate issuer certificate", err) + return nil, serrors.WrapStr("unable to validate issuer certificate", err) } t, err := v.loadTRC(c.Subject.I, c.Issuer.TRCVersion) if err != nil { - return err + return nil, err } issVer := cert.IssuerVerifier{ Issuer: c, @@ -54,9 +88,9 @@ func (v verifier) VerifyIssuer(raw []byte) error { TRC: t, } if err := issVer.Verify(); err != nil { - return serrors.WrapStr("unable to verify issuer certificate", err) + return nil, serrors.WrapStr("unable to verify issuer certificate", err) } - return nil + return c, nil } func (v verifier) loadTRC(isd addr.ISD, version scrypto.Version) (*trc.TRC, error) { diff --git a/go/tools/scion-pki/internal/v2/conf/as.go b/go/tools/scion-pki/internal/v2/conf/as.go index 9932cbb664..032ddc520c 100644 --- a/go/tools/scion-pki/internal/v2/conf/as.go +++ b/go/tools/scion-pki/internal/v2/conf/as.go @@ -32,6 +32,11 @@ func ASFile(dir string, ia addr.IA, version scrypto.Version) string { return filepath.Join(pkicmn.GetAsPath(dir, ia), fmt.Sprintf("as-v%d.toml", version)) } +// AllASFiles returns a glob string that matches all AS files for the given IA. +func AllASFiles(dir string, ia addr.IA) string { + return filepath.Join(pkicmn.GetAsPath(dir, ia), "as-v*.toml") +} + // AS holds the AS certificate configuration. type AS struct { Description string `toml:"description"`