Repository: scionproto/scion
Pull request #3528 (2019-12-16 13:54:21)

keyconf: Add key ring
Add simple key ring that loads the key from the file system on demand.
This implements the trust.KeyRing interface.

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



Patch:
diff --git a/go/lib/keyconf/BUILD.bazel b/go/lib/keyconf/BUILD.bazel
index 21d8750f3..c535ab187 100644
--- a/go/lib/keyconf/BUILD.bazel
+++ b/go/lib/keyconf/BUILD.bazel
@@ -6,6 +6,7 @@ go_library(
         "doc.go",
         "key.go",
         "keyconf.go",
+        "ring.go",
     ],
     importpath = "github.com/scionproto/scion/go/lib/keyconf",
     visibility = ["//visibility:public"],
@@ -25,6 +26,7 @@ go_test(
         "doc_test.go",
         "key_test.go",
         "keyconf_test.go",
+        "ring_test.go",
     ],
     data = glob(["testdata/**"]),
     embed = [":go_default_library"],
diff --git a/go/lib/keyconf/ring.go b/go/lib/keyconf/ring.go
new file mode 100644
index 000000000..e1acab7e7
--- /dev/null
+++ b/go/lib/keyconf/ring.go
@@ -0,0 +1,40 @@
+// 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 keyconf
+
+import (
+	"path/filepath"
+
+	"github.com/scionproto/scion/go/lib/addr"
+	"github.com/scionproto/scion/go/lib/scrypto"
+)
+
+// LoadingRing loads the private keys on-demand from the file system.
+type LoadingRing struct {
+	Dir string
+	IA  addr.IA
+}
+
+// PrivateKey returns the private key for the given usage and version. If it
+// is not in the key ring, an error is returned.
+func (r LoadingRing) PrivateKey(usage Usage, version scrypto.KeyVersion) (Key, error) {
+	id := ID{
+		IA:      r.IA,
+		Usage:   usage,
+		Version: version,
+	}
+	file := filepath.Join(r.Dir, PrivateKeyFile(usage, version))
+	return LoadKeyFromFile(file, PrivateKey, id)
+}


Test patch:
diff --git a/go/lib/keyconf/ring_test.go b/go/lib/keyconf/ring_test.go
new file mode 100644
index 000000000..a61818278
--- /dev/null
+++ b/go/lib/keyconf/ring_test.go
@@ -0,0 +1,79 @@
+// 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 keyconf_test
+
+import (
+	"encoding/pem"
+	"io/ioutil"
+	"path"
+	"strconv"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/scionproto/scion/go/lib/keyconf"
+	"github.com/scionproto/scion/go/lib/util"
+	"github.com/scionproto/scion/go/lib/xtest"
+)
+
+func TestLoadingRingPrivateKey(t *testing.T) {
+	block := pemBlock(t)
+	tmpDir, cleanF := xtest.MustTempDir("", "test-keyconf-ring")
+	defer cleanF()
+	file := path.Join(tmpDir, keyconf.PrivateKeyFile(keyconf.ASSigningKey, 2))
+	err := ioutil.WriteFile(file, pem.EncodeToMemory(&block), 0644)
+	require.NoError(t, err)
+
+	tests := map[string]struct {
+		Ring         keyconf.LoadingRing
+		ErrAssertion assert.ErrorAssertionFunc
+	}{
+		"valid": {
+			Ring: keyconf.LoadingRing{
+				Dir: tmpDir,
+				IA:  xtest.MustParseIA("1-ff00:0:110"),
+			},
+			ErrAssertion: assert.NoError,
+		},
+		"wrong IA": {
+			Ring: keyconf.LoadingRing{
+				Dir: tmpDir,
+				IA:  xtest.MustParseIA("1-ff00:0:111"),
+			},
+			ErrAssertion: assert.Error,
+		},
+	}
+	for name, test := range tests {
+		t.Run(name, func(t *testing.T) {
+			k, err := test.Ring.PrivateKey(keyconf.ASSigningKey, 2)
+			test.ErrAssertion(t, err)
+			if err != nil {
+				return
+			}
+			assert.Equal(t, block.Type, string(k.Type))
+			assert.Equal(t, block.Headers["usage"], string(k.Usage))
+			assert.Equal(t, block.Headers["algorithm"], k.Algorithm)
+			assert.Equal(t, block.Headers["not_after"],
+				util.TimeToCompact(k.Validity.NotAfter.Time))
+			assert.Equal(t, block.Headers["not_before"],
+				util.TimeToCompact(k.Validity.NotBefore.Time))
+			assert.Equal(t, block.Headers["version"], strconv.FormatUint(uint64(k.Version), 10))
+			assert.Equal(t, block.Headers["ia"], k.IA.String())
+			assert.Equal(t, block.Bytes, k.Bytes)
+			assert.Equal(t, block, k.PEM())
+		})
+	}
+}