Skip to content

Commit

Permalink
SPKI: Add key configuration (#3217)
Browse files Browse the repository at this point in the history
Adds:
- support for key configuration parsing.
- key template generation from topo file
  • Loading branch information
oncilla authored Oct 4, 2019
1 parent 4550c28 commit 5640bd4
Show file tree
Hide file tree
Showing 12 changed files with 582 additions and 37 deletions.
15 changes: 6 additions & 9 deletions go/lib/util/duration_wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
)

var _ (toml.TextUnmarshaler) = (*DurWrap)(nil)
var _ (toml.TextMarshaler) = (*DurWrap)(nil)
var _ (toml.TextMarshaler) = DurWrap{}
var _ (flag.Value) = (*DurWrap)(nil)

// DurWrap is a wrapper to enable marshalling and unmarshalling of durations
Expand All @@ -35,19 +35,16 @@ func (d *DurWrap) UnmarshalText(text []byte) error {
return d.Set(string(text))
}

func (d *DurWrap) MarshalText() (text []byte, err error) {
return []byte(FmtDuration(d.Duration)), nil
}

func (d *DurWrap) Set(text string) error {
var err error
d.Duration, err = ParseDuration(text)
return err
}

func (d *DurWrap) String() string {
if d == nil {
return FmtDuration(0)
}
func (d DurWrap) MarshalText() (text []byte, err error) {
return []byte(FmtDuration(d.Duration)), nil
}

func (d DurWrap) String() string {
return FmtDuration(d.Duration)
}
9 changes: 9 additions & 0 deletions go/tools/scion-pki/internal/v2/conf/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@ go_library(
srcs = [
"as.go",
"isd.go",
"key.go",
"validity.go",
],
importpath = "github.com/scionproto/scion/go/tools/scion-pki/internal/v2/conf",
visibility = ["//go/tools/scion-pki:__subpackages__"],
deps = [
"//go/lib/addr:go_default_library",
"//go/lib/common:go_default_library",
"//go/lib/scrypto:go_default_library",
"//go/lib/scrypto/cert/v2: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",
"@com_github_burntsushi_toml//:go_default_library",
"@com_github_go_ini_ini//:go_default_library",
],
)
Expand All @@ -23,12 +29,15 @@ go_test(
srcs = [
"as_test.go",
"isd_test.go",
"key_test.go",
],
data = ["//go/tools/scion-pki/internal/v2/conf/testdata:data"],
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/v2/conf/testdata:go_default_library",
"@com_github_stretchr_testify//assert:go_default_library",
"@com_github_stretchr_testify//require:go_default_library",
],
Expand Down
220 changes: 220 additions & 0 deletions go/tools/scion-pki/internal/v2/conf/key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// 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 conf

import (
"encoding"
"io"
"strconv"

"github.com/BurntSushi/toml"

"github.com/scionproto/scion/go/lib/scrypto"
"github.com/scionproto/scion/go/lib/scrypto/cert/v2"
"github.com/scionproto/scion/go/lib/scrypto/trc/v2"
"github.com/scionproto/scion/go/lib/serrors"
)

// KeysFileName is the file name of the key configuration.
const KeysFileName = "keys.toml"

// Keys holds the key configuration.
type Keys struct {
Primary map[trc.KeyType]map[scrypto.KeyVersion]KeyMeta
Issuer map[cert.KeyType]map[scrypto.KeyVersion]KeyMeta
AS map[cert.KeyType]map[scrypto.KeyVersion]KeyMeta
}

// LoadKeys loads the keys from the provided file. The contents are already
// validated.
func LoadKeys(file string) (Keys, error) {
var m tomlKeys
if _, err := toml.DecodeFile(file, &m); err != nil {
return Keys{}, serrors.WrapStr("unable to load key config from file", err, "file", file)
}
k, err := m.Keys()
if err != nil {
return Keys{}, serrors.WithCtx(err, "file", file)
}
if err := k.Validate(); err != nil {
return Keys{}, serrors.WrapStr("unable to validate key config", err, "file", file)
}
return k, nil
}

// Encode writes the encoded keys config to the writer.
func (k Keys) Encode(w io.Writer) error {
m, err := keyMarshalerFromKeys(k)
if err != nil {
return serrors.WrapStr("unable to convert key config", err)
}
if err := toml.NewEncoder(w).Encode(m); err != nil {
return serrors.WrapStr("unable to encode key config", err)
}
return nil
}

// Validate checks all key metas.
func (k Keys) Validate() error {
for t, metas := range k.Primary {
if err := k.validateKeyMetas(metas); err != nil {
return serrors.WithCtx(err, "type", t)
}
}
for t, metas := range k.Issuer {
if err := k.validateKeyMetas(metas); err != nil {
return serrors.WithCtx(err, "type", t)
}
}
for t, metas := range k.AS {
if err := k.validateKeyMetas(metas); err != nil {
return serrors.WithCtx(err, "type", t)
}
}
return nil
}

func (k Keys) validateKeyMetas(metas map[scrypto.KeyVersion]KeyMeta) error {
for ver, meta := range metas {
if err := meta.Validate(); err != nil {
return serrors.WrapStr("invalid key meta", err, "version", ver)
}
}
return nil
}

// KeyMeta defines the
type KeyMeta struct {
Algorithm string `toml:"algorithm"`
Validity Validity `toml:"validity"`
}

// Validate checks all values.
func (m KeyMeta) Validate() error {
if m.Algorithm == "" {
return serrors.New("algorithm not set")
}
if err := m.Validity.Validate(); err != nil {
return serrors.WrapStr("invalid validity", err)
}
return nil
}

// tomlKeys is used for toml encoding and decoding because the library only
// allows string map keys.
type tomlKeys struct {
Primary map[string]map[string]KeyMeta `toml:"primary"`
Issuer map[string]map[string]KeyMeta `toml:"issuer_cert"`
AS map[string]map[string]KeyMeta `toml:"as_cert"`
}

func (k tomlKeys) Keys() (Keys, error) {
keys := Keys{
Primary: make(map[trc.KeyType]map[scrypto.KeyVersion]KeyMeta),
Issuer: make(map[cert.KeyType]map[scrypto.KeyVersion]KeyMeta),
AS: make(map[cert.KeyType]map[scrypto.KeyVersion]KeyMeta),
}
for raw, metas := range k.Primary {
var keyType trc.KeyType
if err := keyType.UnmarshalText([]byte(raw)); err != nil {
return Keys{}, serrors.WrapStr("unable to parse key type", err,
"input", raw, "section", "primary")
}
parsed, err := k.convertKeyMetas(metas)
if err != nil {
return Keys{}, serrors.WithCtx(err, "key_type", raw)
}
keys.Primary[keyType] = parsed
}
if err := k.convertCertKeys(keys.Issuer, k.Issuer); err != nil {
return Keys{}, serrors.WithCtx(err, "section", "issuer_cert")
}
if err := k.convertCertKeys(keys.AS, k.AS); err != nil {
return Keys{}, serrors.WithCtx(err, "section", "as_cert")
}
return keys, nil
}

func (k tomlKeys) convertCertKeys(dst map[cert.KeyType]map[scrypto.KeyVersion]KeyMeta,
src map[string]map[string]KeyMeta) error {

for raw, metas := range src {
var keyType cert.KeyType
if err := keyType.UnmarshalText([]byte(raw)); err != nil {
return serrors.WrapStr("unable to parse key type", err, "input", raw)
}
parsed, err := k.convertKeyMetas(metas)
if err != nil {
return serrors.WithCtx(err, "key_type", raw)
}
dst[keyType] = parsed
}
return nil
}

func (k tomlKeys) convertKeyMetas(
metas map[string]KeyMeta) (map[scrypto.KeyVersion]KeyMeta, error) {

m := make(map[scrypto.KeyVersion]KeyMeta, len(metas))
for raw, meta := range metas {
ver, err := strconv.ParseUint(raw, 10, 64)
if err != nil {
return nil, serrors.WrapStr("unable to parse key version", err, "input", raw)
}
m[scrypto.KeyVersion(ver)] = meta
}
return m, nil
}

func keyMarshalerFromKeys(k Keys) (tomlKeys, error) {
m := tomlKeys{
Primary: make(map[string]map[string]KeyMeta),
Issuer: make(map[string]map[string]KeyMeta),
AS: make(map[string]map[string]KeyMeta),
}
for keyType, metas := range k.Primary {
if err := marshalKeyMetas(m.Primary, keyType, metas); err != nil {
return tomlKeys{}, serrors.WithCtx(err, "section", "primary")
}
}
for keyType, metas := range k.Issuer {
if err := marshalKeyMetas(m.Issuer, keyType, metas); err != nil {
return tomlKeys{}, serrors.WithCtx(err, "section", "issuer_cert")
}
}
for keyType, metas := range k.AS {
if err := marshalKeyMetas(m.AS, keyType, metas); err != nil {
return tomlKeys{}, serrors.WithCtx(err, "section", "as_cert")
}
}
return m, nil
}

func marshalKeyMetas(dst map[string]map[string]KeyMeta, keyType encoding.TextMarshaler,
metas map[scrypto.KeyVersion]KeyMeta) error {

raw, err := keyType.MarshalText()
if err != nil {
return serrors.WrapStr("unable to marshal key type", err, "key_type", keyType)
}
keyTypeStr := string(raw)
for ver, meta := range metas {
if _, ok := dst[keyTypeStr]; !ok {
dst[keyTypeStr] = make(map[string]KeyMeta)
}
dst[keyTypeStr][strconv.FormatUint(uint64(ver), 10)] = meta
}
return nil
}
53 changes: 53 additions & 0 deletions go/tools/scion-pki/internal/v2/conf/key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// 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 conf_test

import (
"bytes"
"flag"
"io/ioutil"
"testing"

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

"github.com/scionproto/scion/go/tools/scion-pki/internal/v2/conf"
"github.com/scionproto/scion/go/tools/scion-pki/internal/v2/conf/testdata"
)

var update = flag.Bool("update", false, "set to true to regenerate golden files")

func TestKeys(t *testing.T) {
var buf bytes.Buffer
err := testdata.GoldenKeys.Encode(&buf)
require.NoError(t, err)

if *update {
err := ioutil.WriteFile("testdata/keys.toml", buf.Bytes(), 0644)
require.NoError(t, err)
}

t.Run("loaded keys config matches", func(t *testing.T) {
keys, err := conf.LoadKeys("testdata/keys.toml")
require.NoError(t, err)
assert.Equal(t, testdata.GoldenKeys, keys)
})

t.Run("encoded keys config matches", func(t *testing.T) {
raw, err := ioutil.ReadFile("testdata/keys.toml")
require.NoError(t, err)
assert.Equal(t, raw, buf.Bytes())
})
}
21 changes: 21 additions & 0 deletions go/tools/scion-pki/internal/v2/conf/testdata/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")

filegroup(
name = "data",
srcs = glob(["*.toml"]),
visibility = ["//visibility:public"],
)

go_library(
name = "go_default_library",
srcs = ["keys.go"],
importpath = "github.com/scionproto/scion/go/tools/scion-pki/internal/v2/conf/testdata",
visibility = ["//go/tools/scion-pki:__subpackages__"],
deps = [
"//go/lib/scrypto:go_default_library",
"//go/lib/scrypto/cert/v2:go_default_library",
"//go/lib/scrypto/trc/v2:go_default_library",
"//go/lib/util:go_default_library",
"//go/tools/scion-pki/internal/v2/conf:go_default_library",
],
)
Loading

0 comments on commit 5640bd4

Please sign in to comment.