From f84248d8476322eb7bde697533fe18608ff7bf9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20A=2E=20Garc=C3=ADa=20Pardo=20Gim=C3=A9nez=20de=20lo?= =?UTF-8?q?s=20Galanes?= Date: Thu, 17 Oct 2019 10:05:59 +0200 Subject: [PATCH] Drkey (#63), rebased * DRKey implementation (#51) * Mapping of FirstOrder Messages of Capnp to go/lib * Second level drkey message representation * InputType for PRF added * Add DRKeyLvl2 representation * Second order rep. and key derivation suite * Add enc/dec of DRKeyLvl1 * add keystore * remove AS2HostPair * fix keystore tests * update req & rep according to spec * Finalize msg representation * Extend infra module to handle DRKey requests and replies * Moved keystore * DRKey Handler skeleton for CSGO * Finalized DRKey messaging in infra * Introduce epoch type * Add nonce for encryption as payload * Change date for first level derivation to epoch number * Implement DRKeyLvl1 Request & Reply Handler for cert_srv * minor fixes * Fixed after rebase * Fix build. Minimize changes to setup. Fix configuration use on drkey handler. Remove main package usage in drkey.tasks. * Add UT covering level1 drkey exchange * Return the reply object and UT it * Refactor a bit the level 1 reply handler * Reintroduce the drkey keystore * Refactor the keystore * Configuration refactor * Rebased on master. Coming from 2fed00a89a941db85157cb693fbf80f1f87a793f pre-scionlab to current master. Many changes regarding Messenger and ResponseWriter. Pending code marked with TODO drkeytest . Not passing yet UTs nor they build. * Update the mocks * sciond DRkey level 2 API function skeleton * Continue with the sciond DRKey API. Relay on the messenger methods to obtain the level 2 keys, just synchronously wait for the reply and send it back. * Fix some issues with sciond capnp structures, refactor * Send something from the handler to test sciond. Finish writing the reply back from the response writer to sciond. Fix mapping name of field in lvl2 reply to capnp. * Handle request for level 2. Skeleton for srcAS being this AS. UTs. * WIP Level 2 exchange. Added beginning of a UT covering the case of relaying the query to another CS. WIP relay the query to another CS (level 2 only for now). * WIP Level 2 exchange. Some refactors. drkey.keystore now has an interface. * Mock drkeystore in handler UT * Storing level 2 key in DB. Missing UT. * Complete the data-flow sciond-CS * Fix getting the cert. chain from remote AS. The L2 request handler now derives the key from L1 from another CS, instead of requesting the L2 key itself. Changed signature of DRKeyStore.GetDRKeyLvl1. * Cleanup handler UT * Add a DRKey Store clean task * Simplify DRKeyStore, start drkey.Requester * Add new fcn to drkey store * Add yet another fcn to keystore * Update pending list in requester * Requester setup & UT * Requester processes pending L1 keys * Requester UT * SecretValue function added * DRKey L1 pending never has itself * Cleanup * Obtain SV with the correct timestamp. Also cache the SVs until expiration time. * Refactoring, mainly naming Symbols under go/lib/ctrl/drkey_mgmt . * Refactor, rename. Some symbols in go/lib/drkey . * New drkey types * Refactoring. Creating packages like in the beacon service: - "drkeystorage" with an interface of what a Store is . - "drkey", with implementations of the SV, DRkey level 1 and 2, and also a concrete Store; it also defines the interface for the DB. - "drkeydbsqlite" implementing the DB defined in drkey. Some UTs brought back to life. Still pending the derivation interface, the protocol mapping and cleaning everywhere. * Correct package name to drkeystorage * Introduction of Protocol. There is no configuration counterpart yet. * Minor refactoring * Rollback changes to python * Use Lvl1, Lvl2 and not L1 or Level1 * replace TODO drkeytest w/ TODO(juagargi) * DRKey own configuration. Allow DRKey not to be configured, and run an empty DRKey store. Rename Store methods. * Fix drkeydbsqlite UT * Reintroduce the drkey Store mock. Also build UTs again; some of them fail now. The level 2 handler does not read a level 2 key from the store, but always derives it from the level 1 now. * Simplified configuration. Added config option to map protocols to drkey derivations. * Fixes to the drkey configuration samples * Update signer and verifier in the CS * Split handler file * Remove unused function * Rename some methods from the handlers * Refactor, better func names, added comments * Better names in drkey_mgmt * drkeystorage Store interface change * The level 1 reply contains a timestamp. Also use its CertVerDst field to retrieve the correct certificate when decrypting the reply. Add a config. value holding the age tolerance of the reply. * rename handler files * Also bazel build file, because renaming * Comments and some refactoring * make the set with the correct size already * Display time values in drkey_ctrl with the compact format. Also call util.SecsToTime instead of calling time.Unix with the value casted to int64. * missing bazel build file * Refactor. String methods for keys back. The definition of DRKey is modified to just embed RawBytes. * Remove GetMasterKey from Store * Refactor the drkey types. The secret value and keys are composed of the meta info and a DRKey. A DRKey is just a RawBytes. Leave DRKey.String but remove all other String methods. * Refactor: rename * Split the SV from the Store interface * Rename import package * SecretValueFactory: don't set duration or master key * No default derivation. Removed the default derivation. When a request with a protocol is not mapped to a specific derivation, it fails. * Refactor: split protocol into 2 files * Move Store implementation to cert_srv * New level 1 store * Handlers in ServiceStore * Modified protocol interfaces. Added interface DelegationDerivation, with a method to derive from DS. Modified the delegated implementation. Added default implementations for scmp and piskes. Added default initialization of the registry, with scmp and piskes in it. * Move DeriveLvl1 to the protocol pkg * Clean up store, Lvl1DB interface * refactor: split DB in two structs * Base Backend * Changes in level 2 derivation. Use mac.Sum(nil) to obtain the key value. For level 2 standard derivation, write 1 byte with the length of protocol. Don't write the length otherwise. * Follow the docs deriving SV and lvl2 keys * Fix delegated derivation to follow the docs * Adapt cert drkey UTs * further UT cleaning in cert_srv/internal/config * comment out requester UT as requester is commented out * EpochToSV to the drkey store prior to renaming * rename EpochToSV to SecretValueStore * Fix more go/lib/drkey UTs * fix drkeydbsqlite/db_test UT * removed convey * simplify protocol registry * Simplify registry again * remove until properly defined * Slightly modify DRKeyGetLvl2Key in sciond. Also remove drkeystorage.Store as mock class. * Check DRKey config values also in the CS config UT * WIP: replace custom ia(string) function with xtest.MustParseIA * Removed configuration for protocols. Protocols are not configurable anymore. There is a list of preconfigured protocols. For now it contains only SCMP (direct standard level 2 derivation) and PISKES (delegated derivation through delegation secret). Refactor. Adapt UTs. * WIP prepare for DRKey client store * split implementation of store in several files * WIP: client store * WIP: DRKey store for sciond * DRKey store for sciond * Remove SetMessenger from the client store * cleanup * CS config sample in one file * Add delegation configuration list * Configure which end hosts get DSs * Fix bug in delegated derivation. And add UT. * drkey store cleaner runs also in sciond * Level 1 key prefetcher * fix: quic message handler okay with not handled message types (#3236) * Clearer messages * Allow sciond to work without DRKey * Fixes after rebasing DRKey --- docker/perapp/BUILD.bazel | 66 +- go/cert_srv/BUILD.bazel | 2 + go/cert_srv/internal/config/BUILD.bazel | 17 +- go/cert_srv/internal/config/config.go | 10 + go/cert_srv/internal/config/config_test.go | 11 + go/cert_srv/internal/config/drkey.go | 104 +++ go/cert_srv/internal/config/drkey_test.go | 83 ++ go/cert_srv/internal/config/sample.go | 8 + go/cert_srv/internal/config/state.go | 128 +++ go/cert_srv/internal/config/state_test.go | 59 ++ go/cert_srv/internal/drkey/BUILD.bazel | 48 ++ go/cert_srv/internal/drkey/prefetcher.go | 71 ++ go/cert_srv/internal/drkey/prefetcher_test.go | 177 ++++ .../internal/drkey/secret_value_store.go | 147 ++++ go/cert_srv/internal/drkey/service_store.go | 454 ++++++++++ go/cert_srv/internal/drkey/store_test.go | 224 +++++ go/cert_srv/internal/drkey/testcommon.go | 69 ++ .../as111/certs/ISD1-ASff00_0_111-V1.crt | 32 + .../drkey/testdata/as111/certs/ISD1-V1.trc | 26 + .../drkey/testdata/as111/keys/as-decrypt.key | 1 + .../drkey/testdata/as111/keys/as-sig.seed | 1 + .../drkey/testdata/as111/keys/master0.key | 1 + .../drkey/testdata/as111/keys/master1.key | 1 + .../as112/certs/ISD1-ASff00_0_112-V1.crt | 32 + .../drkey/testdata/as112/certs/ISD1-V1.trc | 26 + .../drkey/testdata/as112/keys/as-decrypt.key | 1 + .../drkey/testdata/as112/keys/as-sig.seed | 1 + .../drkey/testdata/as112/keys/master0.key | 1 + .../drkey/testdata/as112/keys/master1.key | 1 + go/cert_srv/main.go | 63 +- go/lib/ctrl/BUILD.bazel | 1 + go/lib/ctrl/ctrl.go | 26 + go/lib/ctrl/drkey_mgmt/BUILD.bazel | 22 + go/lib/ctrl/drkey_mgmt/drkey_lvl1_rep.go | 67 ++ go/lib/ctrl/drkey_mgmt/drkey_lvl1_req.go | 69 ++ go/lib/ctrl/drkey_mgmt/drkey_lvl2_rep.go | 85 ++ go/lib/ctrl/drkey_mgmt/drkey_lvl2_req.go | 119 +++ go/lib/ctrl/drkey_mgmt/drkey_mgmt.go | 103 +++ go/lib/ctrl/union.go | 10 +- go/lib/drkey/BUILD.bazel | 24 + go/lib/drkey/db.go | 46 + go/lib/drkey/drkey.go | 26 + go/lib/drkey/drkeydbsqlite/BUILD.bazel | 31 + go/lib/drkey/drkeydbsqlite/common.go | 120 +++ go/lib/drkey/drkeydbsqlite/db_test.go | 293 +++++++ go/lib/drkey/drkeydbsqlite/lvl1db.go | 181 ++++ go/lib/drkey/drkeydbsqlite/lvl2db.go | 127 +++ go/lib/drkey/epoch.go | 46 + go/lib/drkey/level1.go | 44 + go/lib/drkey/level2.go | 68 ++ go/lib/drkey/protocol/BUILD.bazel | 31 + go/lib/drkey/protocol/delegated.go | 99 +++ go/lib/drkey/protocol/piskes.go | 45 + go/lib/drkey/protocol/protocol.go | 52 ++ go/lib/drkey/protocol/protocol_test.go | 185 ++++ go/lib/drkey/protocol/scmp.go | 39 + go/lib/drkey/protocol/standard.go | 87 ++ go/lib/drkey/secret_value.go | 64 ++ go/lib/drkey/suite.go | 55 ++ go/lib/drkeystorage/BUILD.bazel | 31 + go/lib/drkeystorage/config.go | 240 ++++++ go/lib/drkeystorage/config_test.go | 158 ++++ go/lib/drkeystorage/sample.go | 36 + go/lib/drkeystorage/store.go | 62 ++ go/lib/infra/BUILD.bazel | 1 + go/lib/infra/common.go | 24 + go/lib/infra/messenger/BUILD.bazel | 1 + go/lib/infra/messenger/messenger.go | 96 +++ .../infra/messenger/quic_response_writer.go | 27 + go/lib/infra/messenger/udp_response_writer.go | 8 + go/lib/infra/mock_infra/BUILD.bazel | 1 + go/lib/infra/mock_infra/infra.go | 87 ++ go/lib/log/BUILD.bazel | 1 - go/lib/sciond/BUILD.bazel | 2 + go/lib/sciond/fake/BUILD.bazel | 1 + go/lib/sciond/fake/fake.go | 7 + go/lib/sciond/mock_sciond/BUILD.bazel | 1 + go/lib/sciond/mock_sciond/sciond.go | 16 + go/lib/sciond/sciond.go | 40 + go/lib/sciond/types.go | 7 + go/lib/util/time.go | 9 + go/proto/drkey_mgmt.capnp.go | 788 +++++++++++++----- go/proto/sciond.capnp.go | 337 +++++--- go/sciond/BUILD.bazel | 2 + go/sciond/internal/config/BUILD.bazel | 1 + go/sciond/internal/config/config.go | 8 +- go/sciond/internal/drkey/BUILD.bazel | 19 + go/sciond/internal/drkey/client_store.go | 87 ++ go/sciond/internal/servers/BUILD.bazel | 3 + go/sciond/internal/servers/handlers.go | 34 + go/sciond/main.go | 20 + proto/ctrl_pld.capnp | 1 + proto/drkey_mgmt.capnp | 58 +- proto/sciond.capnp | 3 + 94 files changed, 5880 insertions(+), 367 deletions(-) create mode 100644 go/cert_srv/internal/config/drkey.go create mode 100644 go/cert_srv/internal/config/drkey_test.go create mode 100644 go/cert_srv/internal/config/state.go create mode 100644 go/cert_srv/internal/config/state_test.go create mode 100644 go/cert_srv/internal/drkey/BUILD.bazel create mode 100644 go/cert_srv/internal/drkey/prefetcher.go create mode 100644 go/cert_srv/internal/drkey/prefetcher_test.go create mode 100644 go/cert_srv/internal/drkey/secret_value_store.go create mode 100644 go/cert_srv/internal/drkey/service_store.go create mode 100644 go/cert_srv/internal/drkey/store_test.go create mode 100644 go/cert_srv/internal/drkey/testcommon.go create mode 100644 go/cert_srv/internal/drkey/testdata/as111/certs/ISD1-ASff00_0_111-V1.crt create mode 100644 go/cert_srv/internal/drkey/testdata/as111/certs/ISD1-V1.trc create mode 100644 go/cert_srv/internal/drkey/testdata/as111/keys/as-decrypt.key create mode 100644 go/cert_srv/internal/drkey/testdata/as111/keys/as-sig.seed create mode 100644 go/cert_srv/internal/drkey/testdata/as111/keys/master0.key create mode 100644 go/cert_srv/internal/drkey/testdata/as111/keys/master1.key create mode 100644 go/cert_srv/internal/drkey/testdata/as112/certs/ISD1-ASff00_0_112-V1.crt create mode 100644 go/cert_srv/internal/drkey/testdata/as112/certs/ISD1-V1.trc create mode 100644 go/cert_srv/internal/drkey/testdata/as112/keys/as-decrypt.key create mode 100644 go/cert_srv/internal/drkey/testdata/as112/keys/as-sig.seed create mode 100644 go/cert_srv/internal/drkey/testdata/as112/keys/master0.key create mode 100644 go/cert_srv/internal/drkey/testdata/as112/keys/master1.key create mode 100644 go/lib/ctrl/drkey_mgmt/BUILD.bazel create mode 100644 go/lib/ctrl/drkey_mgmt/drkey_lvl1_rep.go create mode 100644 go/lib/ctrl/drkey_mgmt/drkey_lvl1_req.go create mode 100644 go/lib/ctrl/drkey_mgmt/drkey_lvl2_rep.go create mode 100644 go/lib/ctrl/drkey_mgmt/drkey_lvl2_req.go create mode 100644 go/lib/ctrl/drkey_mgmt/drkey_mgmt.go create mode 100644 go/lib/drkey/BUILD.bazel create mode 100644 go/lib/drkey/db.go create mode 100644 go/lib/drkey/drkey.go create mode 100644 go/lib/drkey/drkeydbsqlite/BUILD.bazel create mode 100644 go/lib/drkey/drkeydbsqlite/common.go create mode 100644 go/lib/drkey/drkeydbsqlite/db_test.go create mode 100644 go/lib/drkey/drkeydbsqlite/lvl1db.go create mode 100644 go/lib/drkey/drkeydbsqlite/lvl2db.go create mode 100644 go/lib/drkey/epoch.go create mode 100644 go/lib/drkey/level1.go create mode 100644 go/lib/drkey/level2.go create mode 100644 go/lib/drkey/protocol/BUILD.bazel create mode 100644 go/lib/drkey/protocol/delegated.go create mode 100644 go/lib/drkey/protocol/piskes.go create mode 100644 go/lib/drkey/protocol/protocol.go create mode 100644 go/lib/drkey/protocol/protocol_test.go create mode 100644 go/lib/drkey/protocol/scmp.go create mode 100644 go/lib/drkey/protocol/standard.go create mode 100644 go/lib/drkey/secret_value.go create mode 100644 go/lib/drkey/suite.go create mode 100644 go/lib/drkeystorage/BUILD.bazel create mode 100644 go/lib/drkeystorage/config.go create mode 100644 go/lib/drkeystorage/config_test.go create mode 100644 go/lib/drkeystorage/sample.go create mode 100644 go/lib/drkeystorage/store.go create mode 100644 go/sciond/internal/drkey/BUILD.bazel create mode 100644 go/sciond/internal/drkey/client_store.go diff --git a/docker/perapp/BUILD.bazel b/docker/perapp/BUILD.bazel index e3f97089b1..d3e27c734e 100644 --- a/docker/perapp/BUILD.bazel +++ b/docker/perapp/BUILD.bazel @@ -8,7 +8,11 @@ scion_app_images( binary = "//go/border:border", appdir = "/app", workdir = "/share", - entrypoint = ["/app/border", "-config", "/share/conf/br.toml"], + entrypoint = [ + "/app/border", + "-config", + "/share/conf/br.toml", + ], ) scion_app_images( @@ -16,7 +20,11 @@ scion_app_images( binary = "//go/beacon_srv:beacon_srv", appdir = "/app", workdir = "/share", - entrypoint = ["/app/beacon_srv", "-config", "/share/conf/bs.toml"], + entrypoint = [ + "/app/beacon_srv", + "-config", + "/share/conf/bs.toml", + ], ) scion_app_images( @@ -24,7 +32,11 @@ scion_app_images( binary = "//go/cert_srv:cert_srv", appdir = "/app", workdir = "/share", - entrypoint = ["/app/cert_srv", "-config", "/share/conf/cs.toml"], + entrypoint = [ + "/app/cert_srv", + "-config", + "/share/conf/cs.toml", + ], ) scion_app_images( @@ -32,7 +44,11 @@ scion_app_images( binary = "//go/godispatcher:godispatcher", appdir = "/app", workdir = "/share", - entrypoint = ["/app/godispatcher", "-config", "/share/conf/disp.toml"], + entrypoint = [ + "/app/godispatcher", + "-config", + "/share/conf/disp.toml", + ], ) scion_app_images( @@ -40,7 +56,11 @@ scion_app_images( binary = "//go/path_srv:path_srv", appdir = "/app", workdir = "/share", - entrypoint = ["/app/path_srv", "-config", "/share/conf/ps.toml"], + entrypoint = [ + "/app/path_srv", + "-config", + "/share/conf/ps.toml", + ], ) scion_app_images( @@ -48,7 +68,11 @@ scion_app_images( binary = "//go/sciond:sciond", appdir = "/app", workdir = "/share", - entrypoint = ["/app/sciond", "-config", "/share/conf/sd.toml"], + entrypoint = [ + "/app/sciond", + "-config", + "/share/conf/sd.toml", + ], ) scion_app_images( @@ -56,32 +80,36 @@ scion_app_images( binary = "//go/sig:sig", appdir = "/app", workdir = "/share", - entrypoint = ["/app/sig", "-config", "/share/conf/sig.toml"], + entrypoint = [ + "/app/sig", + "-config", + "/share/conf/sig.toml", + ], ) # To generate the images: bazel run //docker/perapp:prod container_bundle( name = "prod", images = { - "scion_border:latest": ":border_prod", - "scion_beacon:latest": ":beacon_srv_prod", - "scion_cert:latest": ":cert_srv_prod", + "scion_border:latest": ":border_prod", + "scion_beacon:latest": ":beacon_srv_prod", + "scion_cert:latest": ":cert_srv_prod", "scion_dispatcher_go:latest": ":dispatcher_go_prod", - "scion_path:latest": ":path_srv_prod", - "scion_sciond:latest": ":sciond_prod", - "scion_sig_nocap:latest": ":sig_prod", + "scion_path:latest": ":path_srv_prod", + "scion_sciond:latest": ":sciond_prod", + "scion_sig_nocap:latest": ":sig_prod", }, ) container_bundle( name = "debug", images = { - "scion_border_debug:latest": ":border_debug", - "scion_beacon_debug:latest": ":beacon_srv_debug", - "scion_cert_debug:latest": ":cert_srv_debug", + "scion_border_debug:latest": ":border_debug", + "scion_beacon_debug:latest": ":beacon_srv_debug", + "scion_cert_debug:latest": ":cert_srv_debug", "scion_dispatcher_go_debug:latest": ":dispatcher_go_debug", - "scion_path_debug:latest": ":path_srv_debug", - "scion_sciond_debug:latest": ":sciond_debug", - "scion_sig_nocap_debug:latest": ":sig_debug", + "scion_path_debug:latest": ":path_srv_debug", + "scion_sciond_debug:latest": ":sciond_debug", + "scion_sig_nocap_debug:latest": ":sig_debug", }, ) diff --git a/go/cert_srv/BUILD.bazel b/go/cert_srv/BUILD.bazel index 3df37bdd21..2b28656c31 100644 --- a/go/cert_srv/BUILD.bazel +++ b/go/cert_srv/BUILD.bazel @@ -8,9 +8,11 @@ go_library( visibility = ["//visibility:private"], deps = [ "//go/cert_srv/internal/config:go_default_library", + "//go/cert_srv/internal/drkey:go_default_library", "//go/lib/addr:go_default_library", "//go/lib/common:go_default_library", "//go/lib/discovery:go_default_library", + "//go/lib/drkeystorage:go_default_library", "//go/lib/env:go_default_library", "//go/lib/fatal:go_default_library", "//go/lib/infra:go_default_library", diff --git a/go/cert_srv/internal/config/BUILD.bazel b/go/cert_srv/internal/config/BUILD.bazel index 3818ff97e7..d273dc9a68 100644 --- a/go/cert_srv/internal/config/BUILD.bazel +++ b/go/cert_srv/internal/config/BUILD.bazel @@ -4,15 +4,23 @@ go_library( name = "go_default_library", srcs = [ "config.go", + "drkey.go", "sample.go", + "state.go", ], importpath = "github.com/scionproto/scion/go/cert_srv/internal/config", visibility = ["//go/cert_srv:__subpackages__"], deps = [ "//go/lib/common:go_default_library", "//go/lib/config:go_default_library", + "//go/lib/drkey:go_default_library", + "//go/lib/drkeystorage:go_default_library", "//go/lib/env:go_default_library", + "//go/lib/infra:go_default_library", "//go/lib/infra/modules/idiscovery:go_default_library", + "//go/lib/infra/modules/trust:go_default_library", + "//go/lib/infra/modules/trust/trustdb:go_default_library", + "//go/lib/keyconf:go_default_library", "//go/lib/scrypto/cert:go_default_library", "//go/lib/serrors:go_default_library", "//go/lib/truststorage:go_default_library", @@ -22,14 +30,21 @@ go_library( go_test( name = "go_default_test", - srcs = ["config_test.go"], + srcs = [ + "config_test.go", + "drkey_test.go", + "state_test.go", + ], data = glob(["testdata/**"]), embed = [":go_default_library"], deps = [ "//go/lib/env/envtest:go_default_library", "//go/lib/infra/modules/idiscovery/idiscoverytest:go_default_library", + "//go/lib/keyconf:go_default_library", + "//go/lib/scrypto:go_default_library", "//go/lib/truststorage/truststoragetest:go_default_library", "@com_github_burntsushi_toml//:go_default_library", + "@com_github_smartystreets_goconvey//convey:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", ], ) diff --git a/go/cert_srv/internal/config/config.go b/go/cert_srv/internal/config/config.go index f6d8821ccb..0b082da89c 100644 --- a/go/cert_srv/internal/config/config.go +++ b/go/cert_srv/internal/config/config.go @@ -49,6 +49,7 @@ const ( var _ config.Config = (*Config)(nil) +// Config that contains all necessary parameters to run a CS. type Config struct { General env.General Features env.Features @@ -59,9 +60,11 @@ type Config struct { Sciond env.SciondClient `toml:"sd_client"` TrustDB truststorage.TrustDBConf Discovery idiscovery.Config + DRKey DRKeyConfig CS CSConfig } +// InitDefaults will initialize the configuration variables with their default values. func (cfg *Config) InitDefaults() { config.InitAll( &cfg.General, @@ -72,10 +75,12 @@ func (cfg *Config) InitDefaults() { &cfg.Sciond, &cfg.TrustDB, &cfg.Discovery, + &cfg.DRKey, &cfg.CS, ) } +// Validate will validate all configuration variables. func (cfg *Config) Validate() error { return config.ValidateAll( &cfg.General, @@ -85,10 +90,12 @@ func (cfg *Config) Validate() error { &cfg.Sciond, &cfg.TrustDB, &cfg.Discovery, + &cfg.DRKey, &cfg.CS, ) } +// Sample writes a sample of the configuration. func (cfg *Config) Sample(dst io.Writer, path config.Path, _ config.CtxMap) { config.WriteSample(dst, path, config.CtxMap{config.ID: idSample}, &cfg.General, @@ -100,16 +107,19 @@ func (cfg *Config) Sample(dst io.Writer, path config.Path, _ config.CtxMap) { &cfg.QUIC, &cfg.TrustDB, &cfg.Discovery, + &cfg.DRKey, &cfg.CS, ) } +// ConfigName returns the configuration name for this block. func (cfg *Config) ConfigName() string { return "cs_config" } var _ config.Config = (*CSConfig)(nil) +// CSConfig holds the configuration values for a CS that don't have their own block. type CSConfig struct { // LeafReissueLeadTime indicates how long in advance of leaf cert expiration // the reissuance process starts. diff --git a/go/cert_srv/internal/config/config_test.go b/go/cert_srv/internal/config/config_test.go index a7111f49fd..b04478d032 100644 --- a/go/cert_srv/internal/config/config_test.go +++ b/go/cert_srv/internal/config/config_test.go @@ -75,6 +75,7 @@ func InitTestConfig(cfg *Config) { truststoragetest.InitTestConfig(&cfg.TrustDB) idiscoverytest.InitTestConfig(&cfg.Discovery) InitTestCSConfig(&cfg.CS) + InitTestDRKeyConfig(&cfg.DRKey) } func InitTestCSConfig(cfg *CSConfig) { @@ -82,11 +83,16 @@ func InitTestCSConfig(cfg *CSConfig) { cfg.DisableCorePush = true } +func InitTestDRKeyConfig(cfg *DRKeyConfig) { + cfg.EpochDuration.Duration = 23 * time.Hour +} + func CheckTestConfig(t *testing.T, cfg *Config, id string) { envtest.CheckTest(t, &cfg.General, &cfg.Logging, &cfg.Metrics, &cfg.Tracing, &cfg.Sciond, id) truststoragetest.CheckTestConfig(t, &cfg.TrustDB, id) idiscoverytest.CheckTestConfig(t, &cfg.Discovery) CheckTestCSConfig(t, &cfg.CS) + CheckTestDRKeyConfig(t, &cfg.DRKey) } func CheckTestCSConfig(t *testing.T, cfg *CSConfig) { @@ -97,3 +103,8 @@ func CheckTestCSConfig(t *testing.T, cfg *CSConfig) { assert.Equal(t, IssuerReissTime, cfg.IssuerReissueLeadTime.Duration) assert.False(t, cfg.DisableCorePush) } + +func CheckTestDRKeyConfig(t *testing.T, cfg *DRKeyConfig) { + assert.Equal(t, cfg.EpochDuration.Duration, DefaultEpochDuration) + assert.Equal(t, cfg.MaxReplyAge.Duration, DefaultMaxReplyAge) +} diff --git a/go/cert_srv/internal/config/drkey.go b/go/cert_srv/internal/config/drkey.go new file mode 100644 index 0000000000..8195171f23 --- /dev/null +++ b/go/cert_srv/internal/config/drkey.go @@ -0,0 +1,104 @@ +// Copyright 2019 ETH Zurich +// +// 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 config + +import ( + "io" + "time" + + "github.com/scionproto/scion/go/lib/config" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/drkeystorage" + "github.com/scionproto/scion/go/lib/util" +) + +const ( + // DefaultEpochDuration is the default duration for the drkey SV and derived keys + DefaultEpochDuration = 24 * time.Hour + // DefaultMaxReplyAge is the default allowed age for replies. + DefaultMaxReplyAge = 2 * time.Second +) + +var _ (config.Config) = (*DRKeyConfig)(nil) + +// DRKeyConfig is the configuration for the connection to the trust database. +type DRKeyConfig struct { + // enabled is set to true if we find all the required fields in the configuration. + enabled bool + // DRKeyDB contains the DRKey DB configuration. + DRKeyDB drkeystorage.DRKeyDBConf + // EpochDuration is the duration of the keys in this CS. + EpochDuration util.DurWrap + // MaxReplyAge is the age limit for a level 1 reply to be accepted. Older are rejected. + MaxReplyAge util.DurWrap + // AuthorizedDelegations is the DelegationList for this CS. + Delegation drkeystorage.DelegationList +} + +// NewDRKeyConfig returns a pointer to a valid, empty configuration. +func NewDRKeyConfig() *DRKeyConfig { + c := DRKeyConfig{ + DRKeyDB: drkeystorage.DRKeyDBConf{}, + Delegation: drkeystorage.DelegationList{}, + } + return &c +} + +// InitDefaults initializes values of unset keys and determines if the configuration enables DRKey. +func (cfg *DRKeyConfig) InitDefaults() { + cfg.enabled = true + if cfg.EpochDuration.Duration == 0 { + cfg.enabled = false + cfg.EpochDuration.Duration = DefaultEpochDuration + } + if cfg.MaxReplyAge.Duration == 0 { + cfg.MaxReplyAge.Duration = DefaultMaxReplyAge + } + config.InitAll(&cfg.DRKeyDB, &cfg.Delegation) + if cfg.DRKeyDB.Connection() == "" { + cfg.enabled = false + } +} + +// Enabled returns true if DRKey is configured. False otherwise. +func (cfg *DRKeyConfig) Enabled() bool { + // TODO(juagargi): check that disabled CSs can receive DRKey queries from sciond (mine crashes) + return cfg.enabled +} + +// Validate validates that all values are parsable. +func (cfg *DRKeyConfig) Validate() error { + if !cfg.Enabled() { + return nil + } + return config.ValidateAll(&cfg.DRKeyDB, &cfg.Delegation) +} + +// Sample writes a config sample to the writer. +func (cfg *DRKeyConfig) Sample(dst io.Writer, path config.Path, ctx config.CtxMap) { + config.WriteString(dst, drkeySample) + config.WriteSample(dst, path, config.CtxMap{config.ID: idSample}, + &cfg.DRKeyDB, &cfg.Delegation) +} + +// ConfigName is the key in the toml file. +func (cfg *DRKeyConfig) ConfigName() string { + return "drkey" +} + +// NewDB creates a drkey.DB from the config. +func (cfg *DRKeyConfig) NewDB() (drkey.Lvl1DB, error) { + return cfg.DRKeyDB.NewLvl1DB() +} diff --git a/go/cert_srv/internal/config/drkey_test.go b/go/cert_srv/internal/config/drkey_test.go new file mode 100644 index 0000000000..582ad1118e --- /dev/null +++ b/go/cert_srv/internal/config/drkey_test.go @@ -0,0 +1,83 @@ +// Copyright 2019 ETH Zurich +// +// 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 config + +import ( + "bytes" + "testing" + "time" + + "github.com/BurntSushi/toml" +) + +func TestInitDefaults(t *testing.T) { + var cfg DRKeyConfig + cfg.InitDefaults() + if cfg.EpochDuration.Duration != 24*time.Hour { + t.Errorf("Unexpected configuration value: %v", cfg.EpochDuration) + } + if cfg.MaxReplyAge.Duration != 2*time.Second { + t.Errorf("Unexpected configuration value: %v", cfg.EpochDuration) + } +} + +func TestDRKeyConfigSample(t *testing.T) { + var sample bytes.Buffer + var cfg DRKeyConfig + cfg.Sample(&sample, nil, nil) + meta, err := toml.Decode(sample.String(), &cfg) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if len(meta.Undecoded()) != 0 { + t.Errorf("Meta should be empty: %v", meta.Undecoded()) + } + if err := cfg.Validate(); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if cfg.EpochDuration.Duration != DefaultEpochDuration { + t.Errorf("Unexpected config value: %v", cfg.EpochDuration) + } + if cfg.MaxReplyAge.Duration != DefaultMaxReplyAge { + t.Errorf("Unexpected config value: %v", cfg.MaxReplyAge) + } +} + +func TestDisable(t *testing.T) { + var cfg = NewDRKeyConfig() + if cfg.Enabled() == true { + t.Error("Unexpected enabled set") + } + if err := cfg.Validate(); err != nil { + t.Errorf("Unexpected error: %v", err) + } + cfg.EpochDuration.Duration = 10 * time.Hour + cfg.MaxReplyAge.Duration = 10 * time.Hour + cfg.DRKeyDB["connection"] = "a" + cfg.DRKeyDB["backend"] = "sqlite" + cfg.InitDefaults() + if cfg.Enabled() != true { + t.Error("Unexpected enabled unset") + } + if err := cfg.Validate(); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if cfg.EpochDuration.Duration != 10*time.Hour { + t.Errorf("Unexpected config value: %v", cfg.EpochDuration) + } + if cfg.MaxReplyAge.Duration != 10*time.Hour { + t.Errorf("Unexpected config value: %v", cfg.MaxReplyAge) + } +} diff --git a/go/cert_srv/internal/config/sample.go b/go/cert_srv/internal/config/sample.go index e60738d964..3b0c1bf635 100644 --- a/go/cert_srv/internal/config/sample.go +++ b/go/cert_srv/internal/config/sample.go @@ -38,3 +38,11 @@ AutomaticRenewal = false # Disable the core pushing. (default false) DisableCorePush = false ` + +const drkeySample = ` +# EpochDuration of the DRKey secret value and of all derived keys. (default "24h") +EpochDuration = "24h" + +# MaxReplyAge is the age limit for a lvl 1 reply to be accepted. Older are rejected. (default "2s") +MaxReplyAge = "2s" +` diff --git a/go/cert_srv/internal/config/state.go b/go/cert_srv/internal/config/state.go new file mode 100644 index 0000000000..3d195b96fa --- /dev/null +++ b/go/cert_srv/internal/config/state.go @@ -0,0 +1,128 @@ +// Copyright 2018 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 config + +import ( + "path/filepath" + "sync" + + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkeystorage" + "github.com/scionproto/scion/go/lib/infra" + "github.com/scionproto/scion/go/lib/infra/modules/trust" + "github.com/scionproto/scion/go/lib/infra/modules/trust/trustdb" + "github.com/scionproto/scion/go/lib/keyconf" +) + +type State struct { + // Store is the trust store. + Store *trust.Store + // TrustDB is the trust DB. + TrustDB trustdb.TrustDB + // keyConf contains the AS level keys. + keyConf *keyconf.Conf + // keyConfLock guards KeyConf. + keyConfLock sync.RWMutex + // signer is used to sign ctrl payloads. + signer infra.Signer + // signerLock guards signer. + signerLock sync.RWMutex + // verifier is used to verify ctrl payloads. + verifier infra.Verifier + // verifierLock guards verifier. + verifierLock sync.RWMutex + // drkey secret value factory. + SecretValues drkeystorage.SecretValueFactory + // DRKeyStore is the drykey store with level 1 keys. + DRKeyStore drkeystorage.ServiceStore +} + +func NewState(keyConf *keyconf.Conf, trustDB trustdb.TrustDB, trustStore *trust.Store, + svFact drkeystorage.SecretValueFactory, drkeyStore drkeystorage.ServiceStore) *State { + + return &State{ + keyConf: keyConf, + Store: trustStore, + TrustDB: trustDB, + SecretValues: svFact, + DRKeyStore: drkeyStore, + } +} + +// LoadKeyConf loads the key configuration. +func LoadKeyConf(confDir string, isCore bool) (*keyconf.Conf, error) { + + keyConf, err := keyconf.Load(filepath.Join(confDir, "keys"), isCore, isCore, false, true) + if err != nil { + return nil, common.NewBasicError(ErrKeyConf, err) + } + return keyConf, nil +} + +// GetSigningKey returns the signing key of the current key configuration. +func (s *State) GetSigningKey() common.RawBytes { + s.keyConfLock.RLock() + defer s.keyConfLock.RUnlock() + return s.keyConf.SignKey +} + +// GetIssSigningKey returns the issuer signing key of the current key configuration. +func (s *State) GetIssSigningKey() common.RawBytes { + s.keyConfLock.RLock() + defer s.keyConfLock.RUnlock() + return s.keyConf.IssSigKey +} + +// GetDecryptKey returns the decryption key of the current key configuration. +func (s *State) GetDecryptKey() common.RawBytes { + s.keyConfLock.RLock() + defer s.keyConfLock.RUnlock() + return s.keyConf.DecryptKey +} + +// GetOnRootKey returns the online root key of the current key configuration. +func (s *State) GetOnRootKey() common.RawBytes { + s.keyConfLock.RLock() + defer s.keyConfLock.RUnlock() + return s.keyConf.OnRootKey +} + +// GetSigner returns the signer of the current configuration. +func (s *State) GetSigner() infra.Signer { + s.signerLock.RLock() + defer s.signerLock.RUnlock() + return s.signer +} + +// SetSigner sets the signer of the current configuration. +func (s *State) SetSigner(signer infra.Signer) { + s.signerLock.Lock() + defer s.signerLock.Unlock() + s.signer = signer +} + +// GetVerifier returns the verifier of the current configuration. +func (s *State) GetVerifier() infra.Verifier { + s.verifierLock.RLock() + defer s.verifierLock.RUnlock() + return s.verifier +} + +// SetVerifier sets the verifier of the current configuration. +func (s *State) SetVerifier(verifier infra.Verifier) { + s.verifierLock.Lock() + defer s.verifierLock.Unlock() + s.verifier = verifier +} diff --git a/go/cert_srv/internal/config/state_test.go b/go/cert_srv/internal/config/state_test.go new file mode 100644 index 0000000000..ccfba779df --- /dev/null +++ b/go/cert_srv/internal/config/state_test.go @@ -0,0 +1,59 @@ +// Copyright 2018 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 config + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + + "github.com/scionproto/scion/go/lib/keyconf" + "github.com/scionproto/scion/go/lib/scrypto" +) + +func TestNewState(t *testing.T) { + mstr0, _ := keyconf.LoadKey("testdata/keys/master0.key", keyconf.RawKey) + mstr1, _ := keyconf.LoadKey("testdata/keys/master1.key", keyconf.RawKey) + dcrpt, _ := keyconf.LoadKey("testdata/keys/as-decrypt.key", + scrypto.Curve25519xSalsa20Poly1305) + asSig, _ := keyconf.LoadKey("testdata/keys/as-sig.seed", scrypto.Ed25519) + issSig, _ := keyconf.LoadKey("testdata/keys/core-sig.seed", scrypto.Ed25519) + online, _ := keyconf.LoadKey("testdata/keys/online-root.seed", scrypto.Ed25519) + Convey("Load core state", t, func() { + keyConf, err := LoadKeyConf("testdata", true) + SoMsg("err", err, ShouldBeNil) + state := NewState(keyConf, nil, nil, nil, nil) + SoMsg("Master0", state.keyConf.Master.Key0, ShouldResemble, mstr0) + SoMsg("Master1", state.keyConf.Master.Key1, ShouldResemble, mstr1) + SoMsg("Decrypt", state.keyConf.DecryptKey, ShouldResemble, dcrpt) + SoMsg("AS-Sign", state.keyConf.SignKey, ShouldResemble, asSig) + SoMsg("Issuer-Sign", state.keyConf.IssSigKey, ShouldResemble, issSig) + SoMsg("Online", state.keyConf.OnRootKey, ShouldResemble, online) + SoMsg("Offline", state.keyConf.OffRootKey, ShouldBeZeroValue) + }) + + Convey("Load non-core state", t, func() { + keyConf, err := LoadKeyConf("testdata", false) + SoMsg("err", err, ShouldBeNil) + state := NewState(keyConf, nil, nil, nil, nil) + SoMsg("Master0", state.keyConf.Master.Key0, ShouldResemble, mstr0) + SoMsg("Master1", state.keyConf.Master.Key1, ShouldResemble, mstr1) + SoMsg("Decrypt", state.keyConf.DecryptKey, ShouldResemble, dcrpt) + SoMsg("AS-Sign", state.keyConf.SignKey, ShouldResemble, asSig) + SoMsg("Issuer-Sign", state.keyConf.IssSigKey, ShouldBeZeroValue) + SoMsg("Online", state.keyConf.OnRootKey, ShouldBeZeroValue) + SoMsg("Offline", state.keyConf.OffRootKey, ShouldBeZeroValue) + }) +} diff --git a/go/cert_srv/internal/drkey/BUILD.bazel b/go/cert_srv/internal/drkey/BUILD.bazel new file mode 100644 index 0000000000..f0c6377ade --- /dev/null +++ b/go/cert_srv/internal/drkey/BUILD.bazel @@ -0,0 +1,48 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "prefetcher.go", + "secret_value_store.go", + "service_store.go", + "testcommon.go", + ], + importpath = "github.com/scionproto/scion/go/cert_srv/internal/drkey", + visibility = ["//go/cert_srv:__subpackages__"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/common:go_default_library", + "//go/lib/ctrl/cert_mgmt:go_default_library", + "//go/lib/ctrl/drkey_mgmt:go_default_library", + "//go/lib/drkey:go_default_library", + "//go/lib/drkey/protocol:go_default_library", + "//go/lib/drkeystorage:go_default_library", + "//go/lib/infra:go_default_library", + "//go/lib/infra/messenger:go_default_library", + "//go/lib/infra/modules/trust/v2:go_default_library", + "//go/lib/keyconf:go_default_library", + "//go/lib/log:go_default_library", + "//go/lib/periodic:go_default_library", + "//go/lib/scrypto:go_default_library", + "//go/lib/scrypto/cert:go_default_library", + "//go/lib/snet:go_default_library", + "//go/lib/util:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "prefetcher_test.go", + "store_test.go", + ], + data = glob(["testdata/**"]), + embed = [":go_default_library"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/common:go_default_library", + "//go/lib/drkey:go_default_library", + "//go/lib/util:go_default_library", + ], +) diff --git a/go/cert_srv/internal/drkey/prefetcher.go b/go/cert_srv/internal/drkey/prefetcher.go new file mode 100644 index 0000000000..cb5d079a66 --- /dev/null +++ b/go/cert_srv/internal/drkey/prefetcher.go @@ -0,0 +1,71 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkey + +import ( + "context" + "time" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/drkeystorage" + "github.com/scionproto/scion/go/lib/log" + "github.com/scionproto/scion/go/lib/periodic" +) + +var _ periodic.Task = (*Prefetcher)(nil) + +// Prefetcher is in charge of getting the level 1 keys before they expire. +type Prefetcher struct { + LocalIA addr.IA + Store drkeystorage.ServiceStore + KeyDuration time.Duration +} + +// Name returns the tasks name. +func (r *Prefetcher) Name() string { + return "drkey.Prefetcher" +} + +// Run requests the level 1 keys to other CSs. +func (f *Prefetcher) Run(ctx context.Context) { + ases, err := f.Store.KnownASes(ctx) + if err != nil { + log.Error("Could not prefetch level 1 keys", "error", err) + return + } + log.Debug("Prefetching level 1 DRKeys", "ASes", ases) + when := time.Now().Add(f.KeyDuration) + for _, srcIA := range ases { + srcIA := srcIA + go func() { + defer log.LogPanicAndExit() + getLvl1Key(ctx, f.Store, srcIA, f.LocalIA, when) + }() + } +} + +func getLvl1Key(ctx context.Context, + store drkeystorage.ServiceStore, srcIA, dstIA addr.IA, valTime time.Time) { + + meta := drkey.Lvl1Meta{ + SrcIA: srcIA, + DstIA: dstIA, + } + _, err := store.GetLvl1Key(ctx, meta, valTime) + if err != nil { + log.Error("Failed to prefetch the level 1 key", "remote AS", srcIA.String(), "error", err) + } +} diff --git a/go/cert_srv/internal/drkey/prefetcher_test.go b/go/cert_srv/internal/drkey/prefetcher_test.go new file mode 100644 index 0000000000..5fcfc8213e --- /dev/null +++ b/go/cert_srv/internal/drkey/prefetcher_test.go @@ -0,0 +1,177 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkey + +// import ( +// "context" +// "testing" +// "time" + +// "github.com/golang/mock/gomock" +// . "github.com/smartystreets/goconvey/convey" + +// "github.com/scionproto/scion/go/cert_srv/internal/config" +// "github.com/scionproto/scion/go/lib/addr" +// "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" +// "github.com/scionproto/scion/go/lib/drkeystorage/mock_drkeystorage" +// "github.com/scionproto/scion/go/lib/infra/mock_infra" +// "github.com/scionproto/scion/go/lib/infra/modules/trust/trustdb/mock_trustdb" +// "github.com/scionproto/scion/go/lib/scrypto/cert" +// "github.com/scionproto/scion/go/lib/xtest/matchers" +// ) + +// func TestUnionSet(t *testing.T) { +// Convey("Union", t, func() { +// TODO(juagargi) use xtest.MustParseIA +// a := asSet{ +// ia("1-ff00:0:111"): struct{}{}, +// } +// b := asSet{} +// un := unionSet(a, b) +// SoMsg("union", un, ShouldResemble, a) +// un = unionSet(un, a) +// SoMsg("union", un, ShouldResemble, a) +// b = asSet{ +// ia("1-ff00:0:112"): struct{}{}, +// } +// un = unionSet(a, b) +// SoMsg("union", len(un), ShouldEqual, 2) +// SoMsg("union", un, ShouldContainKey, ia("1-ff00:0:111")) +// SoMsg("union", un, ShouldContainKey, ia("1-ff00:0:112")) +// for i := range a { +// delete(a, i) +// } +// for i := range b { +// delete(b, i) +// } +// SoMsg("union", len(un), ShouldEqual, 2) +// SoMsg("union", un, ShouldContainKey, ia("1-ff00:0:111")) +// SoMsg("union", un, ShouldContainKey, ia("1-ff00:0:112")) +// }) +// Convey("Difference", t, func() { +// a := asSet{ +// ia("1-ff00:0:111"): struct{}{}, +// ia("1-ff00:0:112"): struct{}{}, +// } +// b := asSet{ +// ia("1-ff00:0:111"): struct{}{}, +// } +// expected := asSet{ +// ia("1-ff00:0:112"): struct{}{}, +// } +// diff := differenceSet(a, b) +// SoMsg("difference", diff, ShouldResemble, expected) +// diff = differenceSet(a, asSet{}) +// SoMsg("difference", diff, ShouldResemble, a) +// diff = differenceSet(asSet{}, a) +// SoMsg("difference", diff, ShouldResemble, asSet{}) +// }) +// } + +// func TestUpdatePending(t *testing.T) { +// ctx, cancelF := context.WithTimeout(context.Background(), time.Second) +// defer cancelF() +// Convey("From changes in DB", t, func() { +// ctrl, _, _, store, requester := setupRequester(t) +// defer ctrl.Finish() + +// SoMsg("pending ASes", len(requester.PendingASes.set), ShouldEqual, 0) +// asList := []addr.IA{ +// ia("1-ff00:0:111"), +// ia("1-ff00:0:112"), +// ia("1-ff00:0:113"), +// } +// validAsList := []addr.IA{ +// ia("1-ff00:0:112"), +// } +// store.EXPECT().GetLvl1SrcASes(gomock.Any()).Return(asList, nil) +// store.EXPECT().GetValidLvl1SrcASes(gomock.Any(), gomock.Any()).Return( +// validAsList, nil).Do( +// func(ctx context.Context, argValidTime uint32) { +// now := uint32(time.Now().Unix()) +// SoMsg("validTime", argValidTime, ShouldBeGreaterThanOrEqualTo, now) +// // 60 is how far in the future the key has to be valid +// SoMsg("validTime", argValidTime, ShouldBeLessThanOrEqualTo, now+uint32(60)) +// }) +// err := requester.UpdatePendingList(ctx) +// SoMsg("err", err, ShouldBeNil) +// asList = []addr.IA{ +// ia("1-ff00:0:113"), +// } +// SoMsg("pending ASes", requester.PendingASes.set, ShouldResemble, setFromList(asList)) +// }) +// } + +// func TestProcessPending(t *testing.T) { +// ctx, cancelF := context.WithTimeout(context.Background(), time.Millisecond) +// defer cancelF() +// Convey("Replies are on time", t, func() { +// // TODO(juagargi): if we move the logic of processing the reply to the reply +// // handler only, remove half of this test +// ctrl, msger, trustDB, store, requester := setupRequester(t) +// defer ctrl.Finish() + +// sv := getTestSV() +// pending := []addr.IA{ +// ia("1-ff00:0:112"), +// ia("1-ff00:0:113"), +// } +// requester.PendingASes.set = setFromList(pending) +// _, privateKey111, cert112, _ := loadCertsKeys(t) +// // use the same cert for both ASes (simplifies test): +// trustDB.EXPECT().GetChainMaxVersion(gomock.Any(), gomock.Any()).Return( +// &cert.Chain{Leaf: cert112}, nil).Times(2) +// newReply := func(srcIA addr.IA) *drkey_mgmt.DRKeyLvl1Rep { +// dstIA := ia("1-ff00:0:111") +// replyTo111, err := lvl1KeyBuildReply(srcIA, dstIA, &sv, cert112, privateKey111) +// if err != nil { +// panic("Logic error") +// } +// return replyTo111 +// } +// // both replies are encrypted using the same cert: +// msger.EXPECT().RequestDRKeyLvl1(gomock.Any(), gomock.Any(), matchers.IsSnetAddrWithIA( +// pending[0]), gomock.Any()).Return(newReply(pending[0]), nil) +// msger.EXPECT().RequestDRKeyLvl1(gomock.Any(), gomock.Any(), matchers.IsSnetAddrWithIA( +// pending[1]), gomock.Any()).Return(newReply(pending[1]), nil) +// store.EXPECT().InsertLvl1Key(gomock.Any(), gomock.Any()).Times(2) +// err := requester.ProcessPendingList(ctx) +// SoMsg("err", err, ShouldBeNil) +// _ = store +// }) +// Convey("On reply takes forever but doesn't block the rest", t, func() { +// // TODO(juagargi): better if we don't have to do this but rely on the L1 handler +// }) +// } + +// // setupRequester prepares the requester for ff00:0:111 +// func setupRequester(t *testing.T) (*gomock.Controller, *mock_infra.MockMessenger, +// *mock_trustdb.MockTrustDB, *mock_drkeystorage.MockStore, *Requester) { +// ctrl := gomock.NewController(t) +// msger := mock_infra.NewMockMessenger(ctrl) +// trustDB := mock_trustdb.NewMockTrustDB(ctrl) +// drkeyStore := mock_drkeystorage.NewMockStore(ctrl) +// requester := &Requester{ +// Msgr: msger, +// IA: ia("1-ff00:0:111"), +// } +// drkeyStore.EXPECT().SetMasterKey(gomock.Any()) +// var err error +// requester.State, err = config.LoadState("testdata/as111/", false, trustDB, nil, drkeyStore) +// if err != nil { +// t.Fatalf("Error loading state") +// } +// return ctrl, msger, trustDB, drkeyStore, requester +// } diff --git a/go/cert_srv/internal/drkey/secret_value_store.go b/go/cert_srv/internal/drkey/secret_value_store.go new file mode 100644 index 0000000000..d9e63abfcb --- /dev/null +++ b/go/cert_srv/internal/drkey/secret_value_store.go @@ -0,0 +1,147 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkey + +import ( + "runtime" + "sync" + "time" + + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/log" +) + +const ( + // HandlerTimeout is the level 1 request handler lifetime. + HandlerTimeout = 3 * time.Second +) + +// SecretValueStore keeps the current and next secret values and removes the expired ones. +type SecretValueStore struct { + cache map[int64]drkey.SV + mutex sync.Mutex + + keyDuration time.Duration + stopCleaning chan bool + timeNowFcn func() time.Time +} + +// NewSecretValueStore creates a new SecretValueStore and initializes the cleaner. +func NewSecretValueStore(keyDuration time.Duration) *SecretValueStore { + m := &SecretValueStore{ + cache: make(map[int64]drkey.SV), + keyDuration: keyDuration, + stopCleaning: make(chan bool), + timeNowFcn: time.Now, + } + runtime.SetFinalizer(m, stopCleaner) + go func() { + defer log.LogPanicAndExit() + m.startCleaner() + }() + return m +} + +// Get returns the element, and an indicator of its presence. +func (m *SecretValueStore) Get(idx int64) (drkey.SV, bool) { + m.mutex.Lock() + defer m.mutex.Unlock() + + k, found := m.cache[idx] + return k, found +} + +// Set sets the key, and registers this element in this shard. +func (m *SecretValueStore) Set(idx int64, key drkey.SV) { + m.mutex.Lock() + defer m.mutex.Unlock() + + m.cache[idx] = key +} + +// cleanExpired removes the current shard at once. +func (m *SecretValueStore) cleanExpired() { + m.mutex.Lock() + defer m.mutex.Unlock() + + now := m.timeNowFcn() + for idx, value := range m.cache { + if !value.Epoch.Contains(now) { + delete(m.cache, idx) + } + } +} + +func stopCleaner(m *SecretValueStore) { + m.stopCleaning <- true +} + +func (m *SecretValueStore) startCleaner() { + ticker := time.NewTicker(2 * m.keyDuration) + for { + select { + case <-ticker.C: + m.cleanExpired() + case <-m.stopCleaning: + ticker.Stop() + return + } + } +} + +// TODO(juagargi) merge SecretValueStore and SecretValueFactory + +// SecretValueFactory stores the secret value +type SecretValueFactory struct { + keyDuration time.Duration + masterKey common.RawBytes + keyMap *SecretValueStore + mapMutex sync.Mutex +} + +// NewSecretValueFactory return a default initialized SecretValueFactory. +func NewSecretValueFactory(masterKey common.RawBytes, + keyDuration time.Duration) *SecretValueFactory { + + s := &SecretValueFactory{ + masterKey: masterKey, + keyDuration: keyDuration, + } + s.keyMap = NewSecretValueStore(s.keyDuration) + return s +} + +// GetSecretValue derives or reuses the secret value for this time stamp. +func (s *SecretValueFactory) GetSecretValue(t time.Time) (drkey.SV, error) { + s.mapMutex.Lock() + defer s.mapMutex.Unlock() + + duration := int64(s.keyDuration / time.Second) // duration in seconds + idx := t.Unix() / duration + k, found := s.keyMap.Get(idx) + if !found { + begin := uint32(idx * duration) + end := begin + uint32(duration) + epoch := drkey.NewEpoch(begin, end) + var err error + k, err = drkey.DeriveSV(drkey.SVMeta{Epoch: epoch}, s.masterKey) + if err != nil { + return drkey.SV{}, common.NewBasicError("Cannot establish the DRKey secret value", err) + } + s.keyMap.Set(idx, k) + } + return k, nil +} diff --git a/go/cert_srv/internal/drkey/service_store.go b/go/cert_srv/internal/drkey/service_store.go new file mode 100644 index 0000000000..aa048d37dc --- /dev/null +++ b/go/cert_srv/internal/drkey/service_store.go @@ -0,0 +1,454 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkey + +import ( + "context" + "database/sql" + "fmt" + "net" + "time" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/ctrl/cert_mgmt" + "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/drkey/protocol" + "github.com/scionproto/scion/go/lib/drkeystorage" + "github.com/scionproto/scion/go/lib/infra" + "github.com/scionproto/scion/go/lib/infra/messenger" + "github.com/scionproto/scion/go/lib/infra/modules/trust/v2" + "github.com/scionproto/scion/go/lib/log" + "github.com/scionproto/scion/go/lib/scrypto" + "github.com/scionproto/scion/go/lib/scrypto/cert" + "github.com/scionproto/scion/go/lib/snet" + "github.com/scionproto/scion/go/lib/util" +) + +// ServiceStore keeps track of the level 1 drkey keys. It is backed by a drkey.DB . +type ServiceStore struct { + localIA addr.IA + db drkey.Lvl1DB + secretValues drkeystorage.SecretValueFactory + trustDB trust.DB + asDecryptKey common.RawBytes + msger infra.Messenger + // allowedDSs is a set of protocols per IP address (in 16 byte form). Represents the allowed + // protocols hosts can obtain delegation secrets for. + allowedDSs map[[16]byte]map[string]struct{} +} + +var _ drkeystorage.ServiceStore = &ServiceStore{} + +// NewServiceStore constructs a DRKey ServiceStore. +func NewServiceStore(local addr.IA, asDecryptKey common.RawBytes, db drkey.Lvl1DB, + trustDB trust.DB, svFac drkeystorage.SecretValueFactory, + allowedDS map[[16]byte]map[string]struct{}) *ServiceStore { + + return &ServiceStore{ + localIA: local, + asDecryptKey: asDecryptKey, + db: db, + secretValues: svFac, + trustDB: trustDB, + allowedDSs: allowedDS, + } +} + +// SetMessenger sets the messenter. Only callable once. +func (s *ServiceStore) SetMessenger(msger infra.Messenger) { + if s.msger != nil { + panic("messenger already set") + } + s.msger = msger +} + +// GetLvl1Key returns the level 1 drkey from the local DB or if not found, by asking any CS in +// the source AS of the key. +func (s *ServiceStore) GetLvl1Key(ctx context.Context, meta drkey.Lvl1Meta, + valTime time.Time) (drkey.Lvl1Key, error) { + + if meta.SrcIA == s.localIA { + return s.deriveLvl1(meta.DstIA, valTime) + } + // look in the DB + k, err := s.db.GetLvl1Key(ctx, meta, util.TimeToSecs(valTime)) + if err == nil { + return k, err + } + if err != sql.ErrNoRows { + return drkey.Lvl1Key{}, common.NewBasicError("Cannot retrieve key from DB", err) + } + // get it from another server + k, err = s.getLvl1FromOtherCS(ctx, meta.SrcIA, meta.DstIA, valTime) + if err != nil { + return drkey.Lvl1Key{}, common.NewBasicError("Cannot obtain level 1 key from CS", err) + } + // keep it in our DB + err = s.db.InsertLvl1Key(ctx, k) + if err != nil { + return drkey.Lvl1Key{}, common.NewBasicError("Cannot store obtained key in DB", err) + } + return k, nil +} + +// DeleteExpiredKeys will remove any expired keys. +func (s *ServiceStore) DeleteExpiredKeys(ctx context.Context) (int, error) { + i, err := s.db.RemoveOutdatedLvl1Keys(ctx, util.TimeToSecs(time.Now())) + return int(i), err +} + +// NewLvl1ReqHandler returns an infra.Handler for level 1 drkey requests coming from a +// peer, backed by the trust store. This method should only be used when servicing +// requests coming from remote nodes. +func (s *ServiceStore) NewLvl1ReqHandler() infra.Handler { + f := func(r *infra.Request) *infra.HandlerResult { + handler := &lvl1ReqHandler{ + request: r, + store: s, + } + return handler.Handle() + } + return infra.HandlerFunc(f) +} + +// NewLvl2ReqHandler returns an infra.Handler for level 1 drkey requests coming from a +// peer, backed by the trust store. This method should only be used when servicing +// requests coming from remote nodes. +func (s *ServiceStore) NewLvl2ReqHandler() infra.Handler { + f := func(r *infra.Request) *infra.HandlerResult { + handler := &lvl2ReqHandler{ + request: r, + store: s, + } + return handler.Handle() + } + return infra.HandlerFunc(f) +} + +// KnownASes returns a list with distinct AS seen as sources in level 1 DRKeys. +func (s *ServiceStore) KnownASes(ctx context.Context) ([]addr.IA, error) { + return s.db.GetLvl1SrcASes(ctx) +} + +func (s *ServiceStore) deriveLvl1(dstIA addr.IA, valTime time.Time) (drkey.Lvl1Key, error) { + log.Trace("[DRKey ServiceStore] deriving level 1", "dstIA", dstIA) + sv, err := s.secretValues.GetSecretValue(valTime) + if err != nil { + return drkey.Lvl1Key{}, common.NewBasicError("Unable to get secret value", err) + } + meta := drkey.Lvl1Meta{ + Epoch: sv.Epoch, + SrcIA: s.localIA, + DstIA: dstIA, + } + key, err := protocol.DeriveLvl1(meta, sv) + if err != nil { + return drkey.Lvl1Key{}, common.NewBasicError("Unable to derive level 1 key", err) + } + return key, nil +} + +// getLvl1FromOtherCS queries a CS for a level 1 key. +func (s *ServiceStore) getLvl1FromOtherCS(ctx context.Context, srcIA, dstIA addr.IA, + valTime time.Time) (drkey.Lvl1Key, error) { + + remoteChain, err := s.getCertChain(ctx, srcIA, scrypto.LatestVer) + if err != nil { + return drkey.Lvl1Key{}, + common.NewBasicError("Unable to fetch certificate for remote host", err) + } + csAddr := &snet.Addr{IA: srcIA, Host: addr.NewSVCUDPAppAddr(addr.SvcCS)} + lvl1Req := drkey_mgmt.NewLvl1Req(dstIA, util.TimeToSecs(valTime)) + lvl1Rep, err := s.msger.RequestDRKeyLvl1(ctx, &lvl1Req, csAddr, messenger.NextId()) + if err != nil { + return drkey.Lvl1Key{}, common.NewBasicError("Error requesting level 1 key to CS", err, + "cs addr", csAddr) + } + lvl1Key, err := getLvl1KeyFromReply(lvl1Rep, srcIA, remoteChain.Leaf, s.asDecryptKey) + if err != nil { + return drkey.Lvl1Key{}, common.NewBasicError("Cannot obtain level 1 key from reply", err) + } + return lvl1Key, nil +} + +// getCertChain gets the certificate chain for the AS from DB, or queries that remote CS. It can +// be called with version=scrypto.LatestVer to get the latest version. +func (s *ServiceStore) getCertChain(ctx context.Context, ia addr.IA, + version scrypto.Version) (*cert.Chain, error) { + + raw, err := s.trustDB.GetRawChain(ctx, trust.ChainID{IA: ia, Version: version}) + if err != nil { + return nil, common.NewBasicError("Error in trust DB while getting certificate for AS", err) + } + if raw != nil { + return cert.ChainFromRaw(raw, true) + } + chainReq := &cert_mgmt.ChainReq{ + RawIA: ia.IAInt(), + Version: version, + CacheOnly: true, + } + csAddr := &snet.Addr{IA: ia, Host: addr.NewSVCUDPAppAddr(addr.SvcCS)} + reply, err := s.msger.GetCertChain(ctx, chainReq, csAddr, messenger.NextId()) + if err != nil { + return nil, common.NewBasicError("Could not query CS for certificate", err, + "remote CS", csAddr) + } + chain, err := reply.Chain() + if err != nil { + return nil, common.NewBasicError("could not unpack the certificate reply response", err) + } + if chain == nil { + return nil, common.NewBasicError("The certificate chain is null", nil, "remote CS", csAddr) + } + return chain, nil +} + +// getLvl1KeyFromReply decrypts and extracts the level 1 drkey from the reply. +func getLvl1KeyFromReply(reply *drkey_mgmt.Lvl1Rep, srcIA addr.IA, cert *cert.Certificate, + privateKey common.RawBytes) (drkey.Lvl1Key, error) { + + lvl1Key, err := drkey.DecryptDRKeyLvl1( + reply.Cipher, reply.Nonce, cert.SubjectEncKey, privateKey) + if err != nil { + return lvl1Key, common.NewBasicError("Error decrypting the key from the reply", err) + } + log.Trace("[DRKey ServiceStore] DRKey received") + lvl1Key.Epoch = reply.Epoch() + return lvl1Key, nil +} + +// lvl1ReqHandler contains the necessary info to process a level 1 drkey request. +type lvl1ReqHandler struct { + request *infra.Request + store *ServiceStore +} + +// Handle receives a level 1 request and returns a level 1 reply via the +// infra.Messenger in the store. +func (h *lvl1ReqHandler) Handle() *infra.HandlerResult { + log.Trace("[DRKey ServiceStore.lvl1ReqHandler] got request") + ctx, cancelF := context.WithTimeout(h.request.Context(), HandlerTimeout) + defer cancelF() + + if err := h.validate(); err != nil { + log.Error("[DRKey ServiceStore.lvl1ReqHandler] Error validating request", "err", err) + return infra.MetricsErrInvalid + } + saddr := h.request.Peer.(*snet.Addr) + req := h.request.Message.(*drkey_mgmt.Lvl1Req) + dstIA := req.DstIA() + log.Trace("[DRKey ServiceStore.lvl1ReqHandler] Received request", "dstIA", dstIA) + lvl1Key, err := h.store.deriveLvl1(dstIA, req.ValTime()) + if err != nil { + log.Error("[DRKey ServiceStore.lvl1ReqHandler] Error deriving level 1 key", "err", err) + return infra.MetricsErrInternal + } + // Get the newest certificate for the remote AS + dstChain, err := h.store.getCertChain(ctx, dstIA, scrypto.LatestVer) + if err != nil { + log.Error("[DRKey ServiceStore.lvl1ReqHandler] Unable to fetch certificate for remote AS", + "err", err) + return infra.MetricsErrTrustStore(err) + } + + reply, err := h.buildReply(lvl1Key, dstChain.Leaf) + if err != nil { + log.Error("[DRKey ServiceStore.lvl1ReqHandler] Error building reply", "err", err) + return infra.MetricsErrInternal + } + if err := h.sendRep(ctx, saddr, &reply); err != nil { + log.Error("[DRKey ServiceStore.lvl1ReqHandler] Unable to send drkey reply", "err", err) + return infra.MetricsErrInternal + } + return infra.MetricsResultOk +} + +// validate checks that the request is well formed. +func (h *lvl1ReqHandler) validate() error { + req := h.request.Message.(*drkey_mgmt.Lvl1Req) + if req == nil { + return common.NewBasicError("Request is NULL", nil, + "type(req)", fmt.Sprintf("%T", h.request.Message)) + } + return nil +} + +// buildReply constructs the level 1 key exchange reply message: +// cipher = {A | B | K_{A->B}}_PK_B +func (h *lvl1ReqHandler) buildReply(key drkey.Lvl1Key, remoteCert *cert.Certificate) ( + drkey_mgmt.Lvl1Rep, error) { + + nonce, err := scrypto.Nonce(24) + if err != nil { + return drkey_mgmt.Lvl1Rep{}, + common.NewBasicError("Unable to get random nonce", err) + } + cipher, err := drkey.EncryptDRKeyLvl1(key, nonce, remoteCert.SubjectEncKey, + h.store.asDecryptKey) + if err != nil { + return drkey_mgmt.Lvl1Rep{}, common.NewBasicError("Unable to encrypt drkey", err) + } + reply := drkey_mgmt.Lvl1Rep{ + DstIARaw: key.DstIA.IAInt(), + EpochBegin: util.TimeToSecs(key.Epoch.Begin), + EpochEnd: util.TimeToSecs(key.Epoch.End), + Cipher: cipher, + Nonce: nonce, + CertVerDst: remoteCert.Version, + TimestampRaw: util.TimeToSecs(time.Now()), + } + return reply, nil +} + +// sendRep sends a level 1 reply to the requesting source. +func (h *lvl1ReqHandler) sendRep(ctx context.Context, addr net.Addr, rep *drkey_mgmt.Lvl1Rep, +) error { + + rw, ok := infra.ResponseWriterFromContext(ctx) + if !ok { + return common.NewBasicError("Unable to service request, no messenger found", nil) + } + return rw.SendDRKeyLvl1(ctx, rep) +} + +// lvl2ReqHandler contains the necessary information to handle a level 2 drkey request. +type lvl2ReqHandler struct { + request *infra.Request + store *ServiceStore +} + +// Handle receives a level 2 drkey request and sends a reply using the messenger in its store. +func (h *lvl2ReqHandler) Handle() *infra.HandlerResult { + log.Trace("[DRKey ServiceStore.lvl2ReqHandler] got request") + ctx, cancelF := context.WithTimeout(h.request.Context(), HandlerTimeout) + defer cancelF() + + if err := h.validate(); err != nil { + log.Error("[DRKey ServiceStore.lvl2ReqHandler] Error validating request", "err", err) + return infra.MetricsErrInvalid + } + saddr := h.request.Peer.(*snet.Addr) + req := h.request.Message.(*drkey_mgmt.Lvl2Req) + srcIA := req.SrcIA() + dstIA := req.DstIA() + log.Trace("[DRKey ServiceStore.lvl2ReqHandler] Received request", + "protocol", req.Protocol, "dstIA", dstIA) + + lvl1Meta := drkey.Lvl1Meta{ + SrcIA: srcIA, + DstIA: dstIA, + } + lvl1Key, err := h.store.GetLvl1Key(ctx, lvl1Meta, req.ValTime()) + if err != nil { + log.Error("[DRKey ServiceStore.lvl2ReqHandler] Error getting the level 1 key", + "err", err) + return infra.MetricsErrInternal + } + lvl2Meta := drkey.Lvl2Meta{ + Epoch: lvl1Key.Epoch, + SrcIA: srcIA, + DstIA: dstIA, + KeyType: drkey.Lvl2KeyType(req.ReqType), + Protocol: req.Protocol, + SrcHost: req.SrcHost.ToHostAddr(), + DstHost: req.DstHost.ToHostAddr(), + } + lvl2Key, err := h.deriveLvl2(lvl2Meta, lvl1Key) + if err != nil { + log.Error("[DRKey ServiceStore.lvl2ReqHandler] Error deriving level 2 key", "err", err) + return infra.MetricsErrInternal + } + + reply := drkey_mgmt.NewLvl2RepFromKey(lvl2Key, time.Now()) + if err := h.sendRep(ctx, saddr, reply); err != nil { + log.Error("[DRKey ServiceStore.lvl2ReqHandler] Unable to send drkey reply", "err", err) + return infra.MetricsErrInternal + } + return infra.MetricsResultOk +} + +// validate checks that the requester is in the destination of the key if AS2Host or host2host, +// and checks that the requester is authorized as to get a DS if AS2AS (AS2AS == DS). +func (h *lvl2ReqHandler) validate() error { + req := h.request.Message.(*drkey_mgmt.Lvl2Req) + if req == nil { + return common.NewBasicError("Request is NULL", nil, + "type(req)", fmt.Sprintf("%T", h.request.Message)) + } + // TODO(juagargi) do the checks depending on the key type + saddr, ok := h.request.Peer.(*snet.Addr) + if !ok { + return common.NewBasicError("Requester does not have a SCION address", nil) + } + localAddr := saddr.Host.L3 + log.Trace("lvl2ReqHandler validate", "saddr", saddr.String(), "localAddr", localAddr) + switch drkey.Lvl2KeyType(req.ReqType) { + case drkey.Host2Host: + if localAddr.Equal(req.SrcHost.ToHostAddr()) { + break + } + fallthrough + case drkey.AS2Host: + if localAddr.Equal(req.DstHost.ToHostAddr()) { + break + } + fallthrough + case drkey.AS2AS: + // check in the allowed endhosts list + var rawIP [16]byte + copy(rawIP[:], localAddr.IP().To16()) + protocolSet, foundSet := h.store.allowedDSs[rawIP] + if foundSet { + if _, found := protocolSet[req.Protocol]; found { + log.Trace("Authorized delegated secret", "ReqType", req.ReqType, + "requester address", localAddr, "SrcHost", req.SrcHost.ToHostAddr().String(), + "DstHost", req.DstHost.ToHostAddr().String()) + return nil + } + } + return common.NewBasicError("Endhost not allowed for DRKey request", nil, + "ReqType", req.ReqType, "endhost address", localAddr, + "SrcHost", req.SrcHost.ToHostAddr().String(), + "DstHost", req.DstHost.ToHostAddr().String()) + default: + return common.NewBasicError("Unknown request type", nil, "ReqType", req.ReqType) + } + return nil +} + +// deriveLvl2 will derive the level 2 key specified by the meta data and the level 1 key. +func (h *lvl2ReqHandler) deriveLvl2(meta drkey.Lvl2Meta, lvl1Key drkey.Lvl1Key) ( + drkey.Lvl2Key, error) { + + der, found := protocol.KnownDerivations[meta.Protocol] + if !found { + return drkey.Lvl2Key{}, fmt.Errorf("No derivation found for protocol \"%s\"", meta.Protocol) + } + return der.DeriveLvl2(meta, lvl1Key) +} + +// sendRep takes a level 2 drkey reply and sends it. +func (h *lvl2ReqHandler) sendRep(ctx context.Context, + addr net.Addr, rep *drkey_mgmt.Lvl2Rep) error { + + rw, ok := infra.ResponseWriterFromContext(ctx) + if !ok { + return common.NewBasicError("Unable to service request, no messenger found", nil) + } + return rw.SendDRKeyLvl2(ctx, rep) +} diff --git a/go/cert_srv/internal/drkey/store_test.go b/go/cert_srv/internal/drkey/store_test.go new file mode 100644 index 0000000000..9f94244475 --- /dev/null +++ b/go/cert_srv/internal/drkey/store_test.go @@ -0,0 +1,224 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkey + +import ( + "bytes" + "encoding/hex" + "testing" + "time" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/util" +) + +func TestSecretValueStore(t *testing.T) { + dur := time.Millisecond + c := NewSecretValueStore(dur) + c.timeNowFcn = func() time.Time { return time.Unix(10, 0) } + _, found := c.Get(1) + if found { + t.Fatalf("Should have not been found") + } + sv := drkey.SV{SVMeta: drkey.SVMeta{Epoch: drkey.NewEpoch(20, 21)}} + c.Set(1, sv) + _, found = c.Get(1) + if !found { + t.Fatalf("Should have been found") + } + // the ticker should remove the key: + c.timeNowFcn = func() time.Time { return time.Unix(30, 0) } + time.Sleep(10 * time.Millisecond) + _, found = c.Get(1) + if found { + t.Fatalf("Should have not been found") + } + + dur = time.Hour + c = NewSecretValueStore(dur) + k1 := drkey.SV{ + SVMeta: drkey.SVMeta{Epoch: drkey.NewEpoch(10, 12)}, + Key: drkey.DRKey(common.RawBytes{1, 2, 3}), + } + c.Set(1, k1) + k, found := c.Get(1) + if !found { + t.Fatalf("Should have been found") + } + if !k1.Equal(k) { + t.Fatalf("Both SVs should be equal. Expected: %v . Got: %v", k1, k) + } + if len(c.cache) != 1 { + t.Fatalf("The cache should contain 1 SV, but it contains %d", len(c.cache)) + } + time.Sleep(10 * time.Millisecond) + k2 := drkey.SV{ + SVMeta: drkey.SVMeta{Epoch: drkey.NewEpoch(11, 13)}, + Key: drkey.DRKey(common.RawBytes{2, 3, 4}), + } + c.Set(2, k2) + if len(c.cache) != 2 { + t.Fatalf("The cache should contain 2 SVs, but it contains %d", len(c.cache)) + } + c.timeNowFcn = func() time.Time { return time.Unix(12, 0).Add(-1 * time.Nanosecond) } + c.cleanExpired() + if len(c.cache) != 2 { + t.Fatalf("The cache should contain 2 SVs, but it contains %d", len(c.cache)) + } + c.timeNowFcn = func() time.Time { return time.Unix(12, 1) } + c.cleanExpired() + if len(c.cache) != 1 { + t.Fatalf("The cache should contain 1 SV, but it contains %d", len(c.cache)) + } + _, found = c.Get(1) + if found { + t.Fatalf("Should have not been found") + } + +} + +func TestSecretValueFactory(t *testing.T) { + master := common.RawBytes{} + fac := NewSecretValueFactory(master, 10*time.Second) + _, err := fac.GetSecretValue(time.Now()) + if err == nil { + t.Fatalf("Should have failed, wrong size of master key") + } + master = common.RawBytes{0, 1, 2, 3} + fac = NewSecretValueFactory(master, 10*time.Second) + k, err := fac.GetSecretValue(util.SecsToTime(10)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if k.Epoch.Begin.Unix() != 10 { + t.Fatalf("Wrong Epoch: %v", k.Epoch) + } + if k.Epoch.End.Unix() != 20 { + t.Fatalf("Wrong Epoch: %v", k.Epoch) + } + now := time.Unix(10, 0) + k, _ = fac.GetSecretValue(now) + savedCurrSV := k + // advance time 9 seconds + now = now.Add(9 * time.Second) + k, _ = fac.GetSecretValue(now) + if bytes.Compare(k.Key, savedCurrSV.Key) != 0 { + t.Fatalf("SV should be the same") + } + // advance it so we are in total 10 seconds in the future of the original clock + now = now.Add(time.Second) + k, _ = fac.GetSecretValue(now) + if bytes.Compare(k.Key, savedCurrSV.Key) == 0 { + t.Fatalf("SV should be different") + } + if k.Epoch.Begin != savedCurrSV.Epoch.End { + t.Fatalf("Current key should start when previous one ended (Begin = %v, End = %v)", + k.Epoch.Begin, savedCurrSV.Epoch.End) + } +} +func TestDeriveLvl1Key(t *testing.T) { + srcIA, _ := addr.IAFromString("1-ff00:0:112") + dstIA, _ := addr.IAFromString("1-ff00:0:111") + store := ServiceStore{ + localIA: srcIA, + secretValues: getSecretValueTestFactory(), + } + lvl1Key, err := store.deriveLvl1(dstIA, time.Now()) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + expectedKey, _ := hex.DecodeString("87ee10bcc9ef1501783949a267f8ec6b") + if bytes.Compare(lvl1Key.Key, expectedKey) != 0 { + t.Fatalf("Bad level 1 derivation. Expected: %s ; Got: %s", + hex.EncodeToString(expectedKey), hex.EncodeToString(lvl1Key.Key)) + } +} + +func TestLvl1KeyBuildReply(t *testing.T) { + srcIA, _ := addr.IAFromString("1-ff00:0:112") + dstIA, _ := addr.IAFromString("1-ff00:0:111") + cert111, privateKey111, cert112, privateKey112 := loadCertsKeys(t) + + store := ServiceStore{ + localIA: srcIA, + secretValues: getSecretValueTestFactory(), + asDecryptKey: privateKey112, + } + handler := &lvl1ReqHandler{ + request: nil, + store: &store, + } + lvl1Key, err := store.deriveLvl1(dstIA, time.Now()) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + reply, err := handler.buildReply(lvl1Key, cert111) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + retrievedLvl1Key, err := getLvl1KeyFromReply(&reply, srcIA, cert112, privateKey111) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if bytes.Compare(retrievedLvl1Key.Key, lvl1Key.Key) != 0 { + t.Fatalf("Bad level 1 reply. Expected: %s ; Got: %s", + hex.EncodeToString(lvl1Key.Key), hex.EncodeToString(retrievedLvl1Key.Key)) + } + if !retrievedLvl1Key.Equal(lvl1Key) { + t.Fatalf("Different keys. Expected: %v ; Got: %v", lvl1Key, retrievedLvl1Key) + } +} + +func TestDeriveLvl2Key(t *testing.T) { + srcIA, _ := addr.IAFromString("1-ff00:0:1") + dstIA, _ := addr.IAFromString("1-ff00:0:2") + handler := lvl2ReqHandler{} + + sv, err := getSecretValueTestFactory().GetSecretValue(util.SecsToTime(0)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + k, _ := hex.DecodeString("c584cad32613547c64823c756651b6f5") // just a level 1 key + lvl1Key := drkey.Lvl1Key{ + Key: k, + Lvl1Meta: drkey.Lvl1Meta{ + Epoch: sv.Epoch, + SrcIA: srcIA, + DstIA: dstIA, + }, + } + var srcHost addr.HostAddr = addr.HostNone{} + var dstHost addr.HostAddr = addr.HostNone{} + meta := drkey.Lvl2Meta{ + KeyType: drkey.AS2AS, + Protocol: "scmp", + Epoch: lvl1Key.Epoch, + SrcIA: srcIA, + DstIA: dstIA, + SrcHost: srcHost, + DstHost: dstHost, + } + lvl2Key, err := handler.deriveLvl2(meta, lvl1Key) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + expectedKey, _ := hex.DecodeString("b90ceff1586e5b5cc3313445df18f271") + if bytes.Compare(lvl2Key.Key, expectedKey) != 0 { + t.Fatalf("Bad level 2 derivation. Expected: %s ; Got: %s", + hex.EncodeToString(expectedKey), hex.EncodeToString(lvl2Key.Key)) + } +} diff --git a/go/cert_srv/internal/drkey/testcommon.go b/go/cert_srv/internal/drkey/testcommon.go new file mode 100644 index 0000000000..82e9bfbc5c --- /dev/null +++ b/go/cert_srv/internal/drkey/testcommon.go @@ -0,0 +1,69 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkey + +import ( + "testing" + "time" + + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/drkeystorage" + "github.com/scionproto/scion/go/lib/keyconf" + "github.com/scionproto/scion/go/lib/scrypto" + "github.com/scionproto/scion/go/lib/scrypto/cert" + "github.com/scionproto/scion/go/lib/util" +) + +func getTestMasterSecret() common.RawBytes { + return common.RawBytes{0, 1, 2, 3} +} + +// SecretValueTestFactory works as a SecretValueFactory but uses a user-controlled-variable instead +// of time.Now when calling GetSecretValue. +type SecretValueTestFactory struct { + SecretValueFactory + Now time.Time +} + +func (f *SecretValueTestFactory) GetSecretValue(t time.Time) (drkey.SV, error) { + return f.SecretValueFactory.GetSecretValue(f.Now) +} + +func getSecretValueTestFactory() drkeystorage.SecretValueFactory { + return &SecretValueTestFactory{ + SecretValueFactory: *NewSecretValueFactory(getTestMasterSecret(), 10*time.Second), + Now: util.SecsToTime(0), + } +} + +func loadCertsKeys(t *testing.T) (*cert.Certificate, + common.RawBytes, *cert.Certificate, common.RawBytes) { + + loadChain := func(filename string, t *testing.T) *cert.Chain { + chain, err := cert.ChainFromFile(filename, false) + if err != nil { + t.Fatalf("Error loading Certificate from '%s': %v", filename, err) + } + return chain + } + cert111 := loadChain("testdata/as111/certs/ISD1-ASff00_0_111-V1.crt", t).Leaf + privateKey111, _ := keyconf.LoadKey("testdata/as111/keys/as-decrypt.key", + scrypto.Curve25519xSalsa20Poly1305) + cert112 := loadChain("testdata/as112/certs/ISD1-ASff00_0_112-V1.crt", t).Leaf + privateKey112, _ := keyconf.LoadKey("testdata/as112/keys/as-decrypt.key", + scrypto.Curve25519xSalsa20Poly1305) + return cert111, privateKey111, cert112, privateKey112 +} diff --git a/go/cert_srv/internal/drkey/testdata/as111/certs/ISD1-ASff00_0_111-V1.crt b/go/cert_srv/internal/drkey/testdata/as111/certs/ISD1-ASff00_0_111-V1.crt new file mode 100644 index 0000000000..2af5a751c4 --- /dev/null +++ b/go/cert_srv/internal/drkey/testdata/as111/certs/ISD1-ASff00_0_111-V1.crt @@ -0,0 +1,32 @@ +{ + "0": { + "Subject": "1-ff00:0:111", + "Issuer": "1-ff00:0:110", + "EncAlgorithm": "curve25519xsalsa20poly1305", + "SubjectSignKey": "A14wYOYZQzDmSQ+1kT3GZCLL13dVpi/YNYar798oSoA=", + "SignAlgorithm": "ed25519", + "Comment": "AS Certificate", + "IssuingTime": 1562315697, + "CanIssue": false, + "ExpirationTime": 1593678897, + "SubjectEncKey": "bRnEIOeF7X/p6aN7BLvMNg+KRDP5u65Tu/fDkubrXGY=", + "Signature": "u5F6RjsLzhlC7JLDT4ARu8kXwlKw2Yow+nTUHvcn7lGSaOm5np72RmkD13fTMYvkpD3I7yKXiKEU/nAPEpFrDA==", + "TRCVersion": 1, + "Version": 1 + }, + "1": { + "Subject": "1-ff00:0:110", + "Issuer": "1-ff00:0:110", + "EncAlgorithm": "curve25519xsalsa20poly1305", + "SubjectSignKey": "tBYaqoSbQ3lr2NoRzTEMXPNjxnZa+D8bWa/X3++a2OQ=", + "SignAlgorithm": "ed25519", + "Comment": "Core AS Certificate", + "IssuingTime": 1562315695, + "CanIssue": true, + "ExpirationTime": 1593765295, + "SubjectEncKey": "PxYeyGgHSqoZPNRhsIjT3liC3u6g5FPmCBuFbQtoZlk=", + "Signature": "O5SRubH7zstVwi35YB5cefkA1R/+HVwAdk5ZZVIJysc4fsC7baG1RyvqyJ5xeo9cI9rZShVenP1Ra5YetQWwAQ==", + "TRCVersion": 1, + "Version": 1 + } +} diff --git a/go/cert_srv/internal/drkey/testdata/as111/certs/ISD1-V1.trc b/go/cert_srv/internal/drkey/testdata/as111/certs/ISD1-V1.trc new file mode 100644 index 0000000000..7590a06c0d --- /dev/null +++ b/go/cert_srv/internal/drkey/testdata/as111/certs/ISD1-V1.trc @@ -0,0 +1,26 @@ +{ + "CertLogs": {}, + "CoreASes": { + "1-ff00:0:110": { + "OfflineKey": "ZkThW2K8mz4WJbis/Ep7gAmol26LQgXpNf5QKreVc0s=", + "OfflineKeyAlg": "ed25519", + "OnlineKey": "BbanKjLwQZZnlxq0aPDyHXZkWB9/KZqRJte/5OvSOsY=", + "OnlineKeyAlg": "ed25519" + } + }, + "CreationTime": 1562315695, + "Description": "ISD 1", + "ExpirationTime": 1593851695, + "GracePeriod": 0, + "ISD": 1, + "Quarantine": false, + "QuorumCAs": 0, + "QuorumTRC": 1, + "RAINS": {}, + "RootCAs": {}, + "Signatures": { + "1-ff00:0:110": "W6+CT57auacwR666STViy/ycg5sgkREofbL6mtV/R+DU1c2T852hUMSnEGbiKHb+TeG7UKK3wpOPiKOjB00BDg==" + }, + "ThresholdEEPKI": 0, + "Version": 1 +} diff --git a/go/cert_srv/internal/drkey/testdata/as111/keys/as-decrypt.key b/go/cert_srv/internal/drkey/testdata/as111/keys/as-decrypt.key new file mode 100644 index 0000000000..42d5639c66 --- /dev/null +++ b/go/cert_srv/internal/drkey/testdata/as111/keys/as-decrypt.key @@ -0,0 +1 @@ +fTMVJTWHdsL71a+8aYcoo8ELwgd3RJq7VdmY3de5T9U= diff --git a/go/cert_srv/internal/drkey/testdata/as111/keys/as-sig.seed b/go/cert_srv/internal/drkey/testdata/as111/keys/as-sig.seed new file mode 100644 index 0000000000..b2033ff853 --- /dev/null +++ b/go/cert_srv/internal/drkey/testdata/as111/keys/as-sig.seed @@ -0,0 +1 @@ +9IJLpdNvz2tTzWz6LX49vHrqdx+vz/zsg8t87HsdvGs= diff --git a/go/cert_srv/internal/drkey/testdata/as111/keys/master0.key b/go/cert_srv/internal/drkey/testdata/as111/keys/master0.key new file mode 100644 index 0000000000..a42ce76957 --- /dev/null +++ b/go/cert_srv/internal/drkey/testdata/as111/keys/master0.key @@ -0,0 +1 @@ +IUlKx0bEQ9IvWViwl8ThdQ== \ No newline at end of file diff --git a/go/cert_srv/internal/drkey/testdata/as111/keys/master1.key b/go/cert_srv/internal/drkey/testdata/as111/keys/master1.key new file mode 100644 index 0000000000..4090a24563 --- /dev/null +++ b/go/cert_srv/internal/drkey/testdata/as111/keys/master1.key @@ -0,0 +1 @@ +wMETqZpnYu/hqN96LcRsCQ== \ No newline at end of file diff --git a/go/cert_srv/internal/drkey/testdata/as112/certs/ISD1-ASff00_0_112-V1.crt b/go/cert_srv/internal/drkey/testdata/as112/certs/ISD1-ASff00_0_112-V1.crt new file mode 100644 index 0000000000..ab5cf59dfa --- /dev/null +++ b/go/cert_srv/internal/drkey/testdata/as112/certs/ISD1-ASff00_0_112-V1.crt @@ -0,0 +1,32 @@ +{ + "0": { + "Subject": "1-ff00:0:112", + "Issuer": "1-ff00:0:110", + "EncAlgorithm": "curve25519xsalsa20poly1305", + "SubjectSignKey": "BL/9pQTbeySPVssoo9tsiVQYH1vBAk9gLq2yR2EvLOc=", + "SignAlgorithm": "ed25519", + "Comment": "AS Certificate", + "IssuingTime": 1562315697, + "CanIssue": false, + "ExpirationTime": 1593678897, + "SubjectEncKey": "7UxL+pGQUHttnltqsTDoWnU+B3MGK5/RRzPazyIqb2s=", + "Signature": "n5PSj0gIX5Dc697nbzsEgO7A+hpzq1DgQX8tViv7PQ3eCRX7MollyBkYJNAXSDzBx++t8jY/0Qc+oEGoZzyuDQ==", + "TRCVersion": 1, + "Version": 1 + }, + "1": { + "Subject": "1-ff00:0:110", + "Issuer": "1-ff00:0:110", + "EncAlgorithm": "curve25519xsalsa20poly1305", + "SubjectSignKey": "tBYaqoSbQ3lr2NoRzTEMXPNjxnZa+D8bWa/X3++a2OQ=", + "SignAlgorithm": "ed25519", + "Comment": "Core AS Certificate", + "IssuingTime": 1562315695, + "CanIssue": true, + "ExpirationTime": 1593765295, + "SubjectEncKey": "PxYeyGgHSqoZPNRhsIjT3liC3u6g5FPmCBuFbQtoZlk=", + "Signature": "O5SRubH7zstVwi35YB5cefkA1R/+HVwAdk5ZZVIJysc4fsC7baG1RyvqyJ5xeo9cI9rZShVenP1Ra5YetQWwAQ==", + "TRCVersion": 1, + "Version": 1 + } +} diff --git a/go/cert_srv/internal/drkey/testdata/as112/certs/ISD1-V1.trc b/go/cert_srv/internal/drkey/testdata/as112/certs/ISD1-V1.trc new file mode 100644 index 0000000000..7590a06c0d --- /dev/null +++ b/go/cert_srv/internal/drkey/testdata/as112/certs/ISD1-V1.trc @@ -0,0 +1,26 @@ +{ + "CertLogs": {}, + "CoreASes": { + "1-ff00:0:110": { + "OfflineKey": "ZkThW2K8mz4WJbis/Ep7gAmol26LQgXpNf5QKreVc0s=", + "OfflineKeyAlg": "ed25519", + "OnlineKey": "BbanKjLwQZZnlxq0aPDyHXZkWB9/KZqRJte/5OvSOsY=", + "OnlineKeyAlg": "ed25519" + } + }, + "CreationTime": 1562315695, + "Description": "ISD 1", + "ExpirationTime": 1593851695, + "GracePeriod": 0, + "ISD": 1, + "Quarantine": false, + "QuorumCAs": 0, + "QuorumTRC": 1, + "RAINS": {}, + "RootCAs": {}, + "Signatures": { + "1-ff00:0:110": "W6+CT57auacwR666STViy/ycg5sgkREofbL6mtV/R+DU1c2T852hUMSnEGbiKHb+TeG7UKK3wpOPiKOjB00BDg==" + }, + "ThresholdEEPKI": 0, + "Version": 1 +} diff --git a/go/cert_srv/internal/drkey/testdata/as112/keys/as-decrypt.key b/go/cert_srv/internal/drkey/testdata/as112/keys/as-decrypt.key new file mode 100644 index 0000000000..7cf432c7f2 --- /dev/null +++ b/go/cert_srv/internal/drkey/testdata/as112/keys/as-decrypt.key @@ -0,0 +1 @@ +PtXr2p/VFQ5TXPJdbfoVir8xTz51FlIddGxn1pO7Fqg= diff --git a/go/cert_srv/internal/drkey/testdata/as112/keys/as-sig.seed b/go/cert_srv/internal/drkey/testdata/as112/keys/as-sig.seed new file mode 100644 index 0000000000..7524be3099 --- /dev/null +++ b/go/cert_srv/internal/drkey/testdata/as112/keys/as-sig.seed @@ -0,0 +1 @@ +XV3itnlNC+tPkNCPWpnnReWsSyFRX/RmOog8URZph/4= diff --git a/go/cert_srv/internal/drkey/testdata/as112/keys/master0.key b/go/cert_srv/internal/drkey/testdata/as112/keys/master0.key new file mode 100644 index 0000000000..feb95d1b1e --- /dev/null +++ b/go/cert_srv/internal/drkey/testdata/as112/keys/master0.key @@ -0,0 +1 @@ +QuRotOnPtqpXWZukQpW4dQ== \ No newline at end of file diff --git a/go/cert_srv/internal/drkey/testdata/as112/keys/master1.key b/go/cert_srv/internal/drkey/testdata/as112/keys/master1.key new file mode 100644 index 0000000000..916fe0e69f --- /dev/null +++ b/go/cert_srv/internal/drkey/testdata/as112/keys/master1.key @@ -0,0 +1 @@ +eFJD1S7PICs0YAuWdPK4PA== \ No newline at end of file diff --git a/go/cert_srv/main.go b/go/cert_srv/main.go index a87dd5e8c2..7863b9334a 100644 --- a/go/cert_srv/main.go +++ b/go/cert_srv/main.go @@ -28,9 +28,11 @@ import ( "github.com/opentracing/opentracing-go" "github.com/scionproto/scion/go/cert_srv/internal/config" + "github.com/scionproto/scion/go/cert_srv/internal/drkey" "github.com/scionproto/scion/go/lib/addr" "github.com/scionproto/scion/go/lib/common" "github.com/scionproto/scion/go/lib/discovery" + "github.com/scionproto/scion/go/lib/drkeystorage" "github.com/scionproto/scion/go/lib/env" "github.com/scionproto/scion/go/lib/fatal" "github.com/scionproto/scion/go/lib/infra" @@ -170,6 +172,26 @@ func realMain() int { log.Crit("Error initializing signer", "err", err) return 1 } + var drkeyStore drkeystorage.ServiceStore + if cfg.DRKey.Enabled() { + keyConf, err := config.LoadKeyConf(cfg.General.ConfigDir, topo.Core()) + if err != nil { + log.Crit("Unable to load AS keys", "err", err) + return 1 + } + svFactory := drkey.NewSecretValueFactory( + keyConf.Master.Key0, cfg.DRKey.EpochDuration.Duration) + drkeyDB, err := cfg.DRKey.NewDB() + if err != nil { + log.Crit("Unable to initialize DRKey DB", "err", err) + return 1 + } + drkeyStore = drkey.NewServiceStore(topo.IA(), keyConf.DecryptKey, + drkeyDB, trustDB, svFactory, cfg.DRKey.Delegation.ToMapPerHost()) + log.Info("DRKey is enabled") + } else { + log.Warn("DRKey is DISABLED by configuration") + } msgr.AddHandler(infra.ChainRequest, trustStore.NewChainReqHandler()) msgr.AddHandler(infra.TRCRequest, trustStore.NewTRCReqHandler()) @@ -196,6 +218,7 @@ func realMain() int { TopoProvider: itopo.Provider(), Msgr: msgr, TrustDB: trustDB, + DRKeyStore: drkeyStore, } if err := tasks.Start(); err != nil { log.Crit("Unable to start periodic tasks", "err", err) @@ -215,9 +238,13 @@ type periodicTasks struct { TopoProvider topology.Provider Msgr infra.Messenger TrustDB trust.DB + // DRKeyStore is the drykey store with level 1 keys. + DRKeyStore drkeystorage.ServiceStore - corePusher *periodic.Runner - reissuance *periodic.Runner + corePusher *periodic.Runner + reissuance *periodic.Runner + drkeyStoreCleaner *periodic.Runner + drkeyStorePrefetcher *periodic.Runner mtx sync.Mutex running bool @@ -233,6 +260,8 @@ func (t *periodicTasks) Start() error { t.running = true // t.corePusher = t.startCorePusher() // t.reissuance = t.startReissuance(t.corePusher) + t.drkeyStorePrefetcher = t.startDrkeyStorePrefetcher() + t.drkeyStoreCleaner = t.startDrkeyStoreCleaner() log.Info("Started periodic tasks") return nil } @@ -246,6 +275,8 @@ func (t *periodicTasks) Kill() { } t.reissuance.Kill() t.corePusher.Kill() + t.drkeyStorePrefetcher.Kill() + t.drkeyStoreCleaner.Kill() t.nilTasks() t.running = false log.Info("Stopped periodic tasks.") @@ -259,6 +290,8 @@ func (t *periodicTasks) Kill() { func (t *periodicTasks) nilTasks() { t.reissuance = nil t.corePusher = nil + t.drkeyStoreCleaner = nil + t.drkeyStorePrefetcher = nil } func setupBasic() error { @@ -289,3 +322,29 @@ func setup() error { infraenv.InitInfraEnvironment(cfg.General.Topology) return nil } + +func (t *periodicTasks) startDrkeyStorePrefetcher() *periodic.Runner { + if !cfg.DRKey.Enabled() { + return nil + } + // TODO(juagargi): if there has been a change in the duration, we need to keep + // the already sent keys (and their duration) as they were already handed to other entities + cleanerPeriod := 2 * cfg.DRKey.EpochDuration.Duration + return periodic.Start( + drkeystorage.NewStoreCleaner(t.DRKeyStore), + cleanerPeriod, cleanerPeriod) +} + +func (t *periodicTasks) startDrkeyStoreCleaner() *periodic.Runner { + if !cfg.DRKey.Enabled() { + return nil + } + prefetchPeriod := cfg.DRKey.EpochDuration.Duration / 2 + return periodic.Start( + &drkey.Prefetcher{ + LocalIA: t.TopoProvider.Get().IA(), + Store: t.DRKeyStore, + KeyDuration: cfg.DRKey.EpochDuration.Duration, + }, + prefetchPeriod, prefetchPeriod) +} diff --git a/go/lib/ctrl/BUILD.bazel b/go/lib/ctrl/BUILD.bazel index c542260fa4..c435ca806d 100644 --- a/go/lib/ctrl/BUILD.bazel +++ b/go/lib/ctrl/BUILD.bazel @@ -15,6 +15,7 @@ go_library( "//go/lib/common:go_default_library", "//go/lib/ctrl/ack:go_default_library", "//go/lib/ctrl/cert_mgmt:go_default_library", + "//go/lib/ctrl/drkey_mgmt:go_default_library", "//go/lib/ctrl/extn:go_default_library", "//go/lib/ctrl/ifid:go_default_library", "//go/lib/ctrl/path_mgmt:go_default_library", diff --git a/go/lib/ctrl/ctrl.go b/go/lib/ctrl/ctrl.go index 4ead175e59..92c0321987 100644 --- a/go/lib/ctrl/ctrl.go +++ b/go/lib/ctrl/ctrl.go @@ -22,6 +22,7 @@ import ( "github.com/scionproto/scion/go/lib/common" "github.com/scionproto/scion/go/lib/ctrl/cert_mgmt" + "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" "github.com/scionproto/scion/go/lib/ctrl/path_mgmt" "github.com/scionproto/scion/go/proto" ) @@ -59,6 +60,16 @@ func NewCertMgmtPld(u proto.Cerealizable, certD *cert_mgmt.Data, ctrlD *Data) (* return NewPld(cpld, ctrlD) } +// NewDRKeyMgmtPld creates a new control payload, containing a new drkey_mgmt payload, +// which in turn contains the supplied Cerealizable instance. +func NewDRKeyMgmtPld(u proto.Cerealizable, drkeyD *drkey_mgmt.Data, ctrlD *Data) (*Pld, error) { + kpld, err := drkey_mgmt.NewPld(u, drkeyD) + if err != nil { + return nil, err + } + return NewPld(kpld, ctrlD) +} + func NewPldFromRaw(b common.RawBytes) (*Pld, error) { p := &Pld{Data: &Data{}} return p, proto.ParseFromRaw(p, b) @@ -98,6 +109,21 @@ func (p *Pld) GetPathMgmt() (*path_mgmt.Pld, *Data, error) { return pathP, p.Data, nil } +// GetDRKeyMgmt returns the DRKeyMgmt payload and the CtrlPld's non-union Data. +// If the union type is not DRKeyMgmt, an error is returned. +func (p *Pld) GetDRKeyMgmt() (*drkey_mgmt.Pld, *Data, error) { + u, err := p.Union() + if err != nil { + return nil, nil, err + } + drkeyP, ok := u.(*drkey_mgmt.Pld) + if !ok { + return nil, nil, common.NewBasicError("Non-matching ctrl pld contents", nil, + "expected", "*drkey_mgmt.Pld", "actual", common.TypeOf(u)) + } + return drkeyP, p.Data, nil +} + func (p *Pld) Len() int { return -1 } diff --git a/go/lib/ctrl/drkey_mgmt/BUILD.bazel b/go/lib/ctrl/drkey_mgmt/BUILD.bazel new file mode 100644 index 0000000000..793e4fd86d --- /dev/null +++ b/go/lib/ctrl/drkey_mgmt/BUILD.bazel @@ -0,0 +1,22 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "drkey_lvl1_rep.go", + "drkey_lvl1_req.go", + "drkey_lvl2_rep.go", + "drkey_lvl2_req.go", + "drkey_mgmt.go", + ], + importpath = "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt", + visibility = ["//visibility:public"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/common:go_default_library", + "//go/lib/drkey:go_default_library", + "//go/lib/scrypto:go_default_library", + "//go/lib/util:go_default_library", + "//go/proto:go_default_library", + ], +) diff --git a/go/lib/ctrl/drkey_mgmt/drkey_lvl1_rep.go b/go/lib/ctrl/drkey_mgmt/drkey_lvl1_rep.go new file mode 100644 index 0000000000..9762a6c550 --- /dev/null +++ b/go/lib/ctrl/drkey_mgmt/drkey_lvl1_rep.go @@ -0,0 +1,67 @@ +// Copyright 2018 ETH Zurich +// +// 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. + +// This file contains the Go representation of first order DRKey responses. + +package drkey_mgmt + +import ( + "fmt" + "time" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/scrypto" + "github.com/scionproto/scion/go/lib/util" + "github.com/scionproto/scion/go/proto" +) + +var _ proto.Cerealizable = (*Lvl1Rep)(nil) + +// Lvl1Rep represents the lvel 1 reply from one CS to another. +type Lvl1Rep struct { + DstIARaw addr.IAInt `capnp:"dstIA"` + EpochBegin uint32 + EpochEnd uint32 + Cipher common.RawBytes + Nonce common.RawBytes + CertVerDst scrypto.Version + TimestampRaw uint32 `capnp:"timestamp"` +} + +// DstIA returns the source ISD-AS of the DRKey. +func (c *Lvl1Rep) DstIA() addr.IA { + return c.DstIARaw.IA() +} + +// ProtoId returns the proto ID. +func (c *Lvl1Rep) ProtoId() proto.ProtoIdType { + return proto.DRKeyLvl1Rep_TypeID +} + +// Epoch returns the begin and end of the validity period of DRKey. +func (c *Lvl1Rep) Epoch() drkey.Epoch { + return drkey.NewEpoch(c.EpochBegin, c.EpochEnd) +} + +// Timestamp returns the time when this reply was created. +func (c *Lvl1Rep) Timestamp() time.Time { + return util.SecsToTime(c.TimestampRaw) +} + +func (c *Lvl1Rep) String() string { + return fmt.Sprintf("Timestamp: %v DstIA: %v EpochBegin: %d EpochEnd: %d CertVerEnc: %d", + util.TimeToCompact(c.Timestamp()), c.DstIA(), c.EpochBegin, c.EpochEnd, c.CertVerDst) +} diff --git a/go/lib/ctrl/drkey_mgmt/drkey_lvl1_req.go b/go/lib/ctrl/drkey_mgmt/drkey_lvl1_req.go new file mode 100644 index 0000000000..453153f52e --- /dev/null +++ b/go/lib/ctrl/drkey_mgmt/drkey_lvl1_req.go @@ -0,0 +1,69 @@ +// Copyright 2018 ETH Zurich +// +// 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. + +// This file contains the Go representation of first order DRKey requests. + +package drkey_mgmt + +import ( + "fmt" + "time" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/util" + "github.com/scionproto/scion/go/proto" +) + +var _ proto.Cerealizable = (*Lvl1Req)(nil) + +// Lvl1Req represents a level 1 request between certificate servers. +type Lvl1Req struct { + DstIARaw addr.IAInt `capnp:"dstIA"` + ValTimeRaw uint32 `capnp:"valTime"` + TimestampRaw uint32 `capnp:"timestamp"` +} + +// NewLvl1Req creates a new level 1 request struct. +func NewLvl1Req(dstIA addr.IA, valTime uint32) Lvl1Req { + return Lvl1Req{ + DstIARaw: dstIA.IAInt(), + ValTimeRaw: valTime, + TimestampRaw: uint32(time.Now().Unix()), + } +} + +// DstIA returns the source ISD-AS of the requested DRKey. +func (c *Lvl1Req) DstIA() addr.IA { + return c.DstIARaw.IA() +} + +// ProtoId returns the proto ID +func (c *Lvl1Req) ProtoId() proto.ProtoIdType { + return proto.DRKeyLvl1Req_TypeID +} + +// ValTime returns the validity time of the requested DRKey. +func (c *Lvl1Req) ValTime() time.Time { + return util.SecsToTime(c.ValTimeRaw) +} + +// Timestamp returns the time when this request was created. +func (c *Lvl1Req) Timestamp() time.Time { + return util.SecsToTime(c.TimestampRaw) +} + +func (c *Lvl1Req) String() string { + return fmt.Sprintf("Timestamp: %v DstIA: %s ValTime: %v", + util.TimeToCompact(c.Timestamp()), c.DstIA(), util.TimeToCompact(c.ValTime())) +} diff --git a/go/lib/ctrl/drkey_mgmt/drkey_lvl2_rep.go b/go/lib/ctrl/drkey_mgmt/drkey_lvl2_rep.go new file mode 100644 index 0000000000..25a7a4fe50 --- /dev/null +++ b/go/lib/ctrl/drkey_mgmt/drkey_lvl2_rep.go @@ -0,0 +1,85 @@ +// Copyright 2018 ETH Zurich +// +// 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. + +// This file contains the Go representation of first order DRKey responses. + +package drkey_mgmt + +import ( + "fmt" + "time" + + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/util" + "github.com/scionproto/scion/go/proto" +) + +var _ proto.Cerealizable = (*Lvl2Rep)(nil) + +// Lvl2Rep encodes the level 2 key response from a CS to an endhost. +type Lvl2Rep struct { + TimestampRaw uint32 `capnp:"timestamp"` + DRKeyRaw common.RawBytes `capnp:"drkey"` + EpochBegin uint32 + EpochEnd uint32 + Misc common.RawBytes +} + +// NewLvl2RepFromKey constructs a level 2 response from a standard level 2 key. +func NewLvl2RepFromKey(key drkey.Lvl2Key, timestamp time.Time) *Lvl2Rep { + return &Lvl2Rep{ + TimestampRaw: util.TimeToSecs(timestamp), + DRKeyRaw: common.RawBytes(key.Key), + EpochBegin: util.TimeToSecs(key.Epoch.Begin), + EpochEnd: util.TimeToSecs(key.Epoch.End), + } +} + +// ProtoId returns the proto ID. +func (c *Lvl2Rep) ProtoId() proto.ProtoIdType { + return proto.DRKeyLvl2Rep_TypeID +} + +// Epoch returns the begin and end of the validity period of DRKey. +func (c *Lvl2Rep) Epoch() drkey.Epoch { + return drkey.NewEpoch(c.EpochBegin, c.EpochEnd) +} + +// ToKey returns a drkey Lvl2 built from these values. +func (c *Lvl2Rep) ToKey(meta drkey.Lvl2Meta) drkey.Lvl2Key { + + return drkey.Lvl2Key{ + Lvl2Meta: drkey.Lvl2Meta{ + Epoch: c.Epoch(), + SrcIA: meta.SrcIA, + DstIA: meta.DstIA, + KeyType: meta.KeyType, + Protocol: meta.Protocol, + SrcHost: meta.SrcHost, + DstHost: meta.DstHost, + }, + Key: drkey.DRKey(c.DRKeyRaw), + } +} + +// Timestamp returns the time when this reply was created. +func (c *Lvl2Rep) Timestamp() time.Time { + return util.SecsToTime(c.TimestampRaw) +} + +func (c *Lvl2Rep) String() string { + return fmt.Sprintf("Timestamp: %v EpochBegin: %d EpochEnd: %d Misc: %v", + util.TimeToCompact(c.Timestamp()), c.EpochBegin, c.EpochEnd, c.Misc) +} diff --git a/go/lib/ctrl/drkey_mgmt/drkey_lvl2_req.go b/go/lib/ctrl/drkey_mgmt/drkey_lvl2_req.go new file mode 100644 index 0000000000..73fa11c59a --- /dev/null +++ b/go/lib/ctrl/drkey_mgmt/drkey_lvl2_req.go @@ -0,0 +1,119 @@ +// Copyright 2018 ETH Zurich +// +// 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. + +// This file contains the Go representation of first order DRKey requests. + +package drkey_mgmt + +import ( + "fmt" + "time" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/util" + "github.com/scionproto/scion/go/proto" +) + +var _ proto.Cerealizable = (*Lvl2Req)(nil) + +// Host represents a host part of a level 2 drkey. +type Host struct { + Type addr.HostAddrType // uint8 + Host common.RawBytes +} + +// NewHost returns a new Host from an addr.HostAddr. +func NewHost(host addr.HostAddr) Host { + if host == nil { + host = addr.HostNone{} + } + return Host{ + Type: host.Type(), + Host: host.Pack(), + } +} + +// ToHostAddr returns the host as a addr.HostAddr. +func (h *Host) ToHostAddr() addr.HostAddr { + host, err := addr.HostFromRaw(h.Host, addr.HostAddrType(h.Type)) + if err != nil { + panic("Could not convert addr.HostAddr to drkey.Host") + } + return host +} + +// Lvl2Req represents a level 2 key request from an endhost to a CS. +type Lvl2Req struct { + Protocol string + ReqType uint8 + ValTimeRaw uint32 `capnp:"valTime"` + SrcIARaw addr.IAInt `capnp:"srcIA"` + DstIARaw addr.IAInt `capnp:"dstIA"` + SrcHost Host + DstHost Host + Misc common.RawBytes +} + +// TODO(juagargi) it seems that we don't use Misc + +// NewLvl2ReqFromMeta constructs a level 2 request from a standard level 2 meta info. +func NewLvl2ReqFromMeta(meta drkey.Lvl2Meta, valTime time.Time) Lvl2Req { + return Lvl2Req{ + ReqType: uint8(meta.KeyType), + Protocol: meta.Protocol, + ValTimeRaw: util.TimeToSecs(valTime), + SrcIARaw: meta.SrcIA.IAInt(), + DstIARaw: meta.DstIA.IAInt(), + SrcHost: NewHost(meta.SrcHost), + DstHost: NewHost(meta.DstHost), + } +} + +// ProtoId returns the proto ID. +func (r *Lvl2Req) ProtoId() proto.ProtoIdType { + return proto.DRKeyLvl2Req_TypeID +} + +// SrcIA returns the source IA (fast path). +func (r *Lvl2Req) SrcIA() addr.IA { + return r.SrcIARaw.IA() +} + +// DstIA returns the destination IA (slow path). +func (r *Lvl2Req) DstIA() addr.IA { + return r.DstIARaw.IA() +} + +// ValTime returns the validity time of the requested DRKey. +func (r *Lvl2Req) ValTime() time.Time { + return util.SecsToTime(r.ValTimeRaw) +} + +func (r *Lvl2Req) ToMeta() drkey.Lvl2Meta { + return drkey.Lvl2Meta{ + KeyType: drkey.Lvl2KeyType(r.ReqType), + Protocol: r.Protocol, + SrcIA: r.SrcIA(), + DstIA: r.DstIA(), + SrcHost: r.SrcHost.ToHostAddr(), + DstHost: r.DstHost.ToHostAddr(), + } +} + +func (c *Lvl2Req) String() string { + return fmt.Sprintf("KeyType: %v Protocol: %s SrcIA: %s DstIA: %s ValTime: %v", + c.ReqType, c.Protocol, c.SrcIA(), c.DstIA(), util.TimeToCompact(c.ValTime())) +} diff --git a/go/lib/ctrl/drkey_mgmt/drkey_mgmt.go b/go/lib/ctrl/drkey_mgmt/drkey_mgmt.go new file mode 100644 index 0000000000..efa3e9efb5 --- /dev/null +++ b/go/lib/ctrl/drkey_mgmt/drkey_mgmt.go @@ -0,0 +1,103 @@ +// Copyright 2018 ETH Zurich +// +// 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 drkey_mgmt + +import ( + "fmt" + "strings" + + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/proto" +) + +type union struct { + Which proto.DRKeyMgmt_Which + Lvl1Req *Lvl1Req `capnp:"drkeyLvl1Req"` + Lvl1Rep *Lvl1Rep `capnp:"drkeyLvl1Rep"` + Lvl2Req *Lvl2Req `capnp:"drkeyLvl2Req"` + Lvl2Rep *Lvl2Rep `capnp:"drkeyLvl2Rep"` +} + +func (u *union) set(c proto.Cerealizable) error { + switch p := c.(type) { + case *Lvl1Req: + u.Which = proto.DRKeyMgmt_Which_drkeyLvl1Req + u.Lvl1Req = p + case *Lvl1Rep: + u.Which = proto.DRKeyMgmt_Which_drkeyLvl1Rep + u.Lvl1Rep = p + case *Lvl2Req: + u.Which = proto.DRKeyMgmt_Which_drkeyLvl2Req + u.Lvl2Req = p + case *Lvl2Rep: + u.Which = proto.DRKeyMgmt_Which_drkeyLvl2Rep + u.Lvl2Rep = p + default: + return common.NewBasicError("Unsupported drkey mgmt union type (set)", + nil, "type", common.TypeOf(c)) + } + return nil +} + +func (u *union) get() (proto.Cerealizable, error) { + switch u.Which { + case proto.DRKeyMgmt_Which_drkeyLvl1Req: + return u.Lvl1Req, nil + case proto.DRKeyMgmt_Which_drkeyLvl1Rep: + return u.Lvl1Rep, nil + case proto.DRKeyMgmt_Which_drkeyLvl2Req: + return u.Lvl2Req, nil + case proto.DRKeyMgmt_Which_drkeyLvl2Rep: + return u.Lvl2Rep, nil + } + return nil, common.NewBasicError("Unsupported drkey mgmt union type (get)", + nil, "type", u.Which) +} + +var _ proto.Cerealizable = (*Pld)(nil) + +type Pld struct { + union + *Data +} + +// NewPld creates a new drkey mgmt payload, containing the supplied Cerealizable instance. +func NewPld(u proto.Cerealizable, d *Data) (*Pld, error) { + p := &Pld{Data: d} + return p, p.union.set(u) +} + +func (p *Pld) Union() (proto.Cerealizable, error) { + return p.union.get() +} + +func (p *Pld) ProtoId() proto.ProtoIdType { + return proto.DRKeyMgmt_TypeID +} + +func (p *Pld) String() string { + desc := []string{"DRKeyMgmt: Union:"} + u, err := p.Union() + if err != nil { + desc = append(desc, err.Error()) + } else { + desc = append(desc, fmt.Sprintf("%+v", u)) + } + return strings.Join(desc, " ") +} + +type Data struct { + // For passing any future non-union data. +} diff --git a/go/lib/ctrl/union.go b/go/lib/ctrl/union.go index 38ee764272..f7f8d9db7d 100644 --- a/go/lib/ctrl/union.go +++ b/go/lib/ctrl/union.go @@ -19,6 +19,7 @@ import ( "github.com/scionproto/scion/go/lib/common" "github.com/scionproto/scion/go/lib/ctrl/ack" "github.com/scionproto/scion/go/lib/ctrl/cert_mgmt" + "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" "github.com/scionproto/scion/go/lib/ctrl/extn" "github.com/scionproto/scion/go/lib/ctrl/ifid" "github.com/scionproto/scion/go/lib/ctrl/path_mgmt" @@ -34,8 +35,8 @@ type union struct { IfID *ifid.IFID `capnp:"ifid"` CertMgmt *cert_mgmt.Pld PathMgmt *path_mgmt.Pld - Sibra []byte `capnp:"-"` // Omit for now - DRKeyMgmt []byte `capnp:"-"` // Omit for now + Sibra []byte `capnp:"-"` // Omit for now + DRKeyMgmt *drkey_mgmt.Pld `capnp:"drkeyMgmt"` Sig *sigmgmt.Pld Extn *extn.CtrlExtnDataList Ack *ack.Ack @@ -52,6 +53,9 @@ func (u *union) set(c proto.Cerealizable) error { case *path_mgmt.Pld: u.Which = proto.CtrlPld_Which_pathMgmt u.PathMgmt = p + case *drkey_mgmt.Pld: + u.Which = proto.CtrlPld_Which_drkeyMgmt + u.DRKeyMgmt = p case *sigmgmt.Pld: u.Which = proto.CtrlPld_Which_sig u.Sig = p @@ -79,6 +83,8 @@ func (u *union) get() (proto.Cerealizable, error) { return u.IfID, nil case proto.CtrlPld_Which_pathMgmt: return u.PathMgmt, nil + case proto.CtrlPld_Which_drkeyMgmt: + return u.DRKeyMgmt, nil case proto.CtrlPld_Which_sig: return u.Sig, nil case proto.CtrlPld_Which_certMgmt: diff --git a/go/lib/drkey/BUILD.bazel b/go/lib/drkey/BUILD.bazel new file mode 100644 index 0000000000..fcbd558ab2 --- /dev/null +++ b/go/lib/drkey/BUILD.bazel @@ -0,0 +1,24 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "db.go", + "drkey.go", + "epoch.go", + "level1.go", + "level2.go", + "secret_value.go", + "suite.go", + ], + importpath = "github.com/scionproto/scion/go/lib/drkey", + visibility = ["//visibility:public"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/common:go_default_library", + "//go/lib/infra/modules/db:go_default_library", + "//go/lib/scrypto:go_default_library", + "//go/lib/util:go_default_library", + "@org_golang_x_crypto//pbkdf2:go_default_library", + ], +) diff --git a/go/lib/drkey/db.go b/go/lib/drkey/db.go new file mode 100644 index 0000000000..ccb2f67d77 --- /dev/null +++ b/go/lib/drkey/db.go @@ -0,0 +1,46 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkey + +import ( + "context" + "io" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/infra/modules/db" +) + +type BaseDB interface { + io.Closer + db.LimitSetter +} + +// Lvl1DB is the drkey database interface for level 1. +type Lvl1DB interface { + BaseDB + GetLvl1Key(ctx context.Context, key Lvl1Meta, valTime uint32) (Lvl1Key, error) + InsertLvl1Key(ctx context.Context, key Lvl1Key) error + RemoveOutdatedLvl1Keys(ctx context.Context, cutoff uint32) (int64, error) + GetLvl1SrcASes(ctx context.Context) ([]addr.IA, error) + GetValidLvl1SrcASes(ctx context.Context, valTime uint32) ([]addr.IA, error) +} + +// Lvl2DB is the drkey database interface for level 2. +type Lvl2DB interface { + BaseDB + GetLvl2Key(ctx context.Context, key Lvl2Meta, valTime uint32) (Lvl2Key, error) + InsertLvl2Key(ctx context.Context, key Lvl2Key) error + RemoveOutdatedLvl2Keys(ctx context.Context, cutoff uint32) (int64, error) +} diff --git a/go/lib/drkey/drkey.go b/go/lib/drkey/drkey.go new file mode 100644 index 0000000000..9b3d3300b3 --- /dev/null +++ b/go/lib/drkey/drkey.go @@ -0,0 +1,26 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkey + +import ( + "github.com/scionproto/scion/go/lib/common" +) + +// DRKey represents a raw binary key +type DRKey common.RawBytes + +func (k DRKey) String() string { + return "[redacted key]" +} diff --git a/go/lib/drkey/drkeydbsqlite/BUILD.bazel b/go/lib/drkey/drkeydbsqlite/BUILD.bazel new file mode 100644 index 0000000000..3d6c94d63c --- /dev/null +++ b/go/lib/drkey/drkeydbsqlite/BUILD.bazel @@ -0,0 +1,31 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "common.go", + "lvl1db.go", + "lvl2db.go", + ], + importpath = "github.com/scionproto/scion/go/lib/drkey/drkeydbsqlite", + visibility = ["//visibility:public"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/common:go_default_library", + "//go/lib/drkey:go_default_library", + "//go/lib/infra/modules/db:go_default_library", + "@com_github_mattn_go_sqlite3//:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["db_test.go"], + embed = [":go_default_library"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/drkey:go_default_library", + "//go/lib/drkey/protocol:go_default_library", + "//go/lib/util:go_default_library", + ], +) diff --git a/go/lib/drkey/drkeydbsqlite/common.go b/go/lib/drkey/drkeydbsqlite/common.go new file mode 100644 index 0000000000..d035821c13 --- /dev/null +++ b/go/lib/drkey/drkeydbsqlite/common.go @@ -0,0 +1,120 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkeydbsqlite + +import ( + "database/sql" + + _ "github.com/mattn/go-sqlite3" + + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/infra/modules/db" +) + +const ( + unableToPrepareStmt = "Unable to prepare stmt" + unableToExecuteStmt = "Unable to execute stmt" +) + +const ( + // Lvl1SchemaVersion is the version of the SQLite schema understood by this backend. + // Whenever changes to the schema are made, this version number should be increased + // to prevent data corruption between incompatible database schemas. + Lvl1SchemaVersion = 1 + // Lvl1Schema is the SQLite database layout. + Lvl1Schema = ` + CREATE TABLE DRKeyLvl1 ( + SrcIsdID INTEGER NOT NULL, + SrcAsID INTEGER NOT NULL, + DstIsdID INTEGER NOT NULL, + DstAsID INTEGER NOT NULL, + EpochBegin INTEGER NOT NULL, + EpochEnd INTEGER NOT NULL, + Key TEXT NOT NULL, + PRIMARY KEY (SrcIsdID, SrcAsID, DstIsdID, DstAsID, EpochBegin) + );` + + // Lvl2SchemaVersion is the version of the SQLite schema understood by this backend. + // Whenever changes to the schema are made, this version number should be increased + // to prevent data corruption between incompatible database schemas. + Lvl2SchemaVersion = 1 + // Lvl2Schema is the SQLite database layout. + Lvl2Schema = ` + CREATE TABLE DRKeyLvl2 ( + Protocol TEXT NOT NULL, + Type INTEGER NOT NULL, + SrcIsdID INTEGER NOT NULL, + SrcAsID INTEGER NOT NULL, + DstIsdID INTEGER NOT NULL, + DstAsID INTEGER NOT NULL, + SrcHostIP TEXT, + DstHostIP TEXT, + EpochBegin INTEGER NOT NULL, + EpochEnd INTEGER NOT NULL, + Key TEXT NOT NULL, + PRIMARY KEY (Protocol, Type, SrcIsdID, SrcAsID, DstIsdID, DstAsID, + SrcHostIP, DstHostIP, EpochBegin) + );` +) + +// dbBaseBackend is the common part of all level backends. +type dbBaseBackend struct { + db *sql.DB +} + +// newBaseBackend builds the base backend common for all level backends. +func newBaseBackend(path, schema string, version int) (*dbBaseBackend, error) { + db, err := db.NewSqlite(path, schema, version) + if err != nil { + return nil, err + } + return &dbBaseBackend{ + db: db, + }, nil +} + +type preparedStmts map[string]**sql.Stmt + +// prepareAll will create the prepared statements or return an error as soon as one fails. +func (b *dbBaseBackend) prepareAll(stmts preparedStmts) error { + var err error + // On future errors, close the sql database before exiting + defer func() { + if err != nil { + b.Close() + } + }() + for str, stmt := range stmts { + if *stmt, err = b.db.Prepare(str); err != nil { + return common.NewBasicError(unableToPrepareStmt, err) + } + } + return nil +} + +// Close closes the database connection. +func (b *dbBaseBackend) Close() error { + return b.db.Close() +} + +// SetMaxOpenConns sets the maximum number of open connections. +func (b *dbBaseBackend) SetMaxOpenConns(maxOpenConns int) { + b.db.SetMaxOpenConns(maxOpenConns) +} + +// SetMaxIdleConns sets the maximum number of idle connections. +func (b *dbBaseBackend) SetMaxIdleConns(maxIdleConns int) { + b.db.SetMaxIdleConns(maxIdleConns) +} diff --git a/go/lib/drkey/drkeydbsqlite/db_test.go b/go/lib/drkey/drkeydbsqlite/db_test.go new file mode 100644 index 0000000000..8cb2cf4374 --- /dev/null +++ b/go/lib/drkey/drkeydbsqlite/db_test.go @@ -0,0 +1,293 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkeydbsqlite + +import ( + "bytes" + "context" + "encoding/hex" + "io/ioutil" + "net" + "os" + "testing" + "time" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/drkey/protocol" + "github.com/scionproto/scion/go/lib/util" +) + +const ( + timeOffset = 10 * 60 // 10 minutes +) + +var ( + asMasterPassword = []byte("0123456789012345") + rawSrcIA = []byte{0xF0, 0x11, 0xF2, 0x33, 0x44, 0x55, 0x66, 0x77} + rawDstIA = []byte{0xF0, 0x11, 0xF2, 0x33, 0x44, 0x55, 0x66, 0x88} + SrcHostIP = net.IPv4(192, 168, 1, 37) + DstHostIP = net.IPv4(192, 168, 1, 38) +) + +func TestDRKeyLvl1(t *testing.T) { + ctx, cancelF := context.WithTimeout(context.Background(), time.Second) + defer cancelF() + db, cleanF := newLvl1Database(t) + defer cleanF() + + epoch := drkey.Epoch{Begin: time.Now(), End: time.Now().Add(timeOffset * time.Second)} + sv, err := drkey.DeriveSV(drkey.SVMeta{Epoch: epoch}, asMasterPassword) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + drkeyLvl1, err := protocol.DeriveLvl1(drkey.Lvl1Meta{ + Epoch: epoch, + SrcIA: addr.IAFromRaw(rawSrcIA), + DstIA: addr.IAFromRaw(rawDstIA)}, sv) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // + err = db.InsertLvl1Key(ctx, drkeyLvl1) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + // same key again. It should be okay. + err = db.InsertLvl1Key(ctx, drkeyLvl1) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + newKey, err := db.GetLvl1Key(ctx, drkeyLvl1.Lvl1Meta, util.TimeToSecs(time.Now())) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if bytes.Compare(drkeyLvl1.Key, newKey.Key) != 0 { + t.Fatalf("Keys should be identical. Expected: %s. Got: %s", + hex.EncodeToString(drkeyLvl1.Key), hex.EncodeToString(newKey.Key)) + } + + rows, err := db.RemoveOutdatedLvl1Keys(ctx, + util.TimeToSecs(time.Now().Add(-timeOffset*time.Second))) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if rows != 0 { + t.Fatalf("Expecting 0 rows. Got %d", rows) + } + rows, err = db.RemoveOutdatedLvl1Keys(ctx, + util.TimeToSecs(time.Now().Add(2*timeOffset*time.Second))) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if rows != 1 { + t.Fatalf("Expecting 0 rows. Got %d", rows) + } + +} + +func TestDRKeyLvl2(t *testing.T) { + ctx, cancelF := context.WithTimeout(context.Background(), time.Second) + defer cancelF() + + db, cleanF := newLvl2Database(t) + defer cleanF() + + srcIA := addr.IAFromRaw(rawSrcIA) + dstIA := addr.IAFromRaw(rawDstIA) + epoch := drkey.Epoch{Begin: time.Now(), End: time.Now().Add(timeOffset * time.Second)} + sv, err := drkey.DeriveSV(drkey.SVMeta{Epoch: epoch}, asMasterPassword) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + drkeyLvl1, err := protocol.DeriveLvl1(drkey.Lvl1Meta{ + Epoch: epoch, + SrcIA: srcIA, + DstIA: dstIA, + }, sv) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + standardImpl := protocol.Standard{} + drkeyLvl2, err := standardImpl.DeriveLvl2(drkey.Lvl2Meta{ + KeyType: drkey.Host2Host, + Protocol: "test", + Epoch: epoch, + SrcIA: srcIA, + DstIA: dstIA, + SrcHost: addr.HostFromIP(SrcHostIP), + DstHost: addr.HostFromIP(DstHostIP), + }, drkeyLvl1) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + err = db.InsertLvl2Key(ctx, drkeyLvl2) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + err = db.InsertLvl2Key(ctx, drkeyLvl2) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + newKey, err := db.GetLvl2Key(ctx, drkeyLvl2.Lvl2Meta, util.TimeToSecs(time.Now())) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if bytes.Compare(drkeyLvl2.Key, newKey.Key) != 0 { + t.Fatalf("Keys should be identical. Expected: %s. Got: %s", + hex.EncodeToString(drkeyLvl2.Key), hex.EncodeToString(newKey.Key)) + } + + rows, err := db.RemoveOutdatedLvl2Keys(ctx, + util.TimeToSecs(time.Now().Add(-timeOffset*time.Second))) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if rows != 0 { + t.Fatalf("Expecting 0 rows. Got %d", rows) + } + rows, err = db.RemoveOutdatedLvl2Keys(ctx, + util.TimeToSecs(time.Now().Add(2*timeOffset*time.Second))) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if rows != 1 { + t.Fatalf("Expecting 1 rows. Got %d", rows) + } +} + +func TestGetMentionedASes(t *testing.T) { + ctx, cancelF := context.WithTimeout(context.Background(), time.Second) + defer cancelF() + + db, cleanF := newLvl1Database(t) + defer cleanF() + + pairsL1 := [][]interface{}{ + {"1-ff00:0:111", "1-ff00:0:112", 1}, + {"1-ff00:0:111", "1-ff00:0:110", 10}, + {"2-ff00:0:211", "1-ff00:0:113", 1}, + } + for _, p := range pairsL1 { + srcIA, _ := addr.IAFromString(p[0].(string)) + dstIA, _ := addr.IAFromString(p[1].(string)) + begin := time.Unix(0, 0) + epoch := drkey.Epoch{ + Begin: begin, + End: begin.Add(time.Duration(p[2].(int)) * time.Second), + } + sv, err := drkey.DeriveSV(drkey.SVMeta{Epoch: epoch}, asMasterPassword) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + key, err := protocol.DeriveLvl1(drkey.Lvl1Meta{ + Epoch: epoch, + SrcIA: srcIA, + DstIA: dstIA, + }, sv) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + err = db.InsertLvl1Key(ctx, key) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + } + + list, err := db.GetLvl1SrcASes(ctx) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + expected := []addr.IA{ + ia("1-ff00:0:111"), + ia("2-ff00:0:211"), + } + if !equalIASlices(expected, list) { + t.Fatalf("Wrong list. Expected: %v. Got: %v", expected, list) + } + + list, err = db.GetValidLvl1SrcASes(ctx, 3) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + expected = []addr.IA{ + ia("1-ff00:0:111"), + } + if !equalIASlices(expected, list) { + t.Fatalf("Wrong list. Expected: %v. Got: %v", expected, list) + } +} + +func equalIASlices(a, b []addr.IA) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if !a[i].Equal(b[i]) { + return false + } + } + return true +} + +func ia(iaStr string) addr.IA { + ia, err := addr.IAFromString(iaStr) + if err != nil { + panic("Invalid value") + } + return ia +} + +func newLvl1Database(t *testing.T) (*Lvl1Backend, func()) { + file, err := ioutil.TempFile("", "db-test-") + if err != nil { + t.Fatalf("unable to create temp file") + } + name := file.Name() + if err := file.Close(); err != nil { + t.Fatalf("unable to close temp file") + } + db, err := NewLvl1Backend(name) + if err != nil { + t.Fatalf("unable to initialize database") + } + return db, func() { + db.Close() + os.Remove(name) + } +} + +func newLvl2Database(t *testing.T) (*Lvl2Backend, func()) { + file, err := ioutil.TempFile("", "db-test-") + if err != nil { + t.Fatalf("unable to create temp file") + } + name := file.Name() + if err := file.Close(); err != nil { + t.Fatalf("unable to close temp file") + } + db, err := NewLvl2Backend(name) + if err != nil { + t.Fatalf("unable to initialize database") + } + return db, func() { + db.Close() + os.Remove(name) + } +} diff --git a/go/lib/drkey/drkeydbsqlite/lvl1db.go b/go/lib/drkey/drkeydbsqlite/lvl1db.go new file mode 100644 index 0000000000..00ef2be798 --- /dev/null +++ b/go/lib/drkey/drkeydbsqlite/lvl1db.go @@ -0,0 +1,181 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkeydbsqlite + +import ( + "context" + "database/sql" + + _ "github.com/mattn/go-sqlite3" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkey" +) + +var _ drkey.Lvl1DB = (*Lvl1Backend)(nil) + +// Lvl1Backend implements a level 1 drkey DB with sqlite. +type Lvl1Backend struct { + dbBaseBackend + getLvl1SrcASesStmt *sql.Stmt + getValidLvl1SrcASesStmt *sql.Stmt + getLvl1KeyStmt *sql.Stmt + insertLvl1KeyStmt *sql.Stmt + removeOutdatedLvl1KeysStmt *sql.Stmt +} + +// NewLvl1Backend creates a database and prepares all statements. +func NewLvl1Backend(path string) (*Lvl1Backend, error) { + base, err := newBaseBackend(path, Lvl1Schema, Lvl1SchemaVersion) + if err != nil { + return nil, err + } + b := &Lvl1Backend{ + dbBaseBackend: *base, + } + stmts := preparedStmts{ + getLvl1SrcASes: &b.getLvl1SrcASesStmt, + getValidLvl1SrcASes: &b.getValidLvl1SrcASesStmt, + getLvl1Key: &b.getLvl1KeyStmt, + insertLvl1Key: &b.insertLvl1KeyStmt, + removeOutdatedLvl1Keys: &b.removeOutdatedLvl1KeysStmt, + } + if err := base.prepareAll(stmts); err != nil { + return nil, err + } + return b, nil +} + +const getLvl1SrcASes = ` +SELECT SrcIsdID as I, SrcASID as A FROM DRKeyLvl1 +GROUP BY I, A +` + +// GetLvl1SrcASes returns a list of distinct ASes seen in the SRC of a level 1 key +func (b *Lvl1Backend) GetLvl1SrcASes(ctx context.Context) ([]addr.IA, error) { + rows, err := b.getLvl1SrcASesStmt.QueryContext(ctx) + if err != nil { + if err != sql.ErrNoRows { + err = common.NewBasicError(unableToExecuteStmt, err) + } + return nil, err + } + ases := []addr.IA{} + for rows.Next() { + var I, A int + if err := rows.Scan(&I, &A); err != nil { + return nil, common.NewBasicError("Cannot copy from SQL to memory", err) + } + ia := addr.IA{ + I: addr.ISD(I), + A: addr.AS(A), + } + ases = append(ases, ia) + } + return ases, nil +} + +const getValidLvl1SrcASes = ` +SELECT SrcIsdID as I, SrcASID as A FROM DRKeyLvl1 +WHERE EpochBegin <= ? AND ? < EpochEnd +GROUP BY I, A +` + +// GetValidLvl1SrcASes returns a list of distinct IAs that have a still valid level 1 key +// If the level 1 key is still valid according to valTime, its src IA will be in the list +func (b *Lvl1Backend) GetValidLvl1SrcASes(ctx context.Context, valTime uint32) ([]addr.IA, error) { + rows, err := b.getValidLvl1SrcASesStmt.QueryContext(ctx, valTime, valTime) + if err != nil { + if err != sql.ErrNoRows { + err = common.NewBasicError(unableToExecuteStmt, err) + } + return nil, err + } + ases := []addr.IA{} + for rows.Next() { + var I, A int + if err := rows.Scan(&I, &A); err != nil { + return nil, common.NewBasicError("Cannot copy from SQL to memory", err) + } + ia := addr.IA{ + I: addr.ISD(I), + A: addr.AS(A), + } + ases = append(ases, ia) + } + return ases, nil +} + +const getLvl1Key = ` +SELECT EpochBegin, EpochEnd, Key FROM DRKeyLvl1 +WHERE SrcIsdID=? AND SrcAsID=? AND DstIsdID=? AND DstAsID=? +AND EpochBegin<=? AND ?= EpochEnd +` + +// RemoveOutdatedLvl1Keys removes all expired first level DRKeys. I.e. all the keys +// which expiration time is strictly smaller than the cutoff +func (b *Lvl1Backend) RemoveOutdatedLvl1Keys(ctx context.Context, cutoff uint32) (int64, error) { + res, err := b.removeOutdatedLvl1KeysStmt.ExecContext(ctx, cutoff) + if err != nil { + return 0, err + } + return res.RowsAffected() +} diff --git a/go/lib/drkey/drkeydbsqlite/lvl2db.go b/go/lib/drkey/drkeydbsqlite/lvl2db.go new file mode 100644 index 0000000000..8854914fb9 --- /dev/null +++ b/go/lib/drkey/drkeydbsqlite/lvl2db.go @@ -0,0 +1,127 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkeydbsqlite + +import ( + "context" + "database/sql" + + _ "github.com/mattn/go-sqlite3" + + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkey" +) + +var _ drkey.Lvl2DB = (*Lvl2Backend)(nil) + +// Lvl2Backend implements a level 2 drkey DB with sqlite. +type Lvl2Backend struct { + dbBaseBackend + getLvl2KeyStmt *sql.Stmt + insertLvl2KeyStmt *sql.Stmt + removeOutdatedLvl2KeysStmt *sql.Stmt +} + +// NewLvl2Backend creates a database and prepares all statements. +func NewLvl2Backend(path string) (*Lvl2Backend, error) { + base, err := newBaseBackend(path, Lvl2Schema, Lvl2SchemaVersion) + if err != nil { + return nil, err + } + b := &Lvl2Backend{ + dbBaseBackend: *base, + } + stmts := preparedStmts{ + getLvl2Key: &b.getLvl2KeyStmt, + insertLvl2Key: &b.insertLvl2KeyStmt, + removeOutdatedLvl2Keys: &b.removeOutdatedLvl2KeysStmt, + } + if err := base.prepareAll(stmts); err != nil { + return nil, err + } + return b, nil +} + +const getLvl2Key = ` +SELECT EpochBegin, EpochEnd, Key +FROM DRKeyLvl2 WHERE Protocol=? AND Type=? AND SrcIsdID=? AND SrcAsID=? AND +DstIsdID=? AND DstAsID=? AND SrcHostIP=? AND DstHostIP=? +AND EpochBegin<=? AND ?= EpochEnd +` + +// RemoveOutdatedLvl2Keys removes all expired second level DRKeys, I.e. those keys +// which expiration time is strictly less than the cutoff +func (b *Lvl2Backend) RemoveOutdatedLvl2Keys(ctx context.Context, cutoff uint32) (int64, error) { + res, err := b.removeOutdatedLvl2KeysStmt.ExecContext(ctx, cutoff) + if err != nil { + return 0, err + } + return res.RowsAffected() +} diff --git a/go/lib/drkey/epoch.go b/go/lib/drkey/epoch.go new file mode 100644 index 0000000000..22c4a8cb5b --- /dev/null +++ b/go/lib/drkey/epoch.go @@ -0,0 +1,46 @@ +// Copyright 2018 ETH Zurich +// +// 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 drkey + +import ( + "time" + + "github.com/scionproto/scion/go/lib/util" +) + +// Epoch represents a validity period. +// TODO use Validity Periods https://github.com/scionproto/scion/pull/2842/files +type Epoch struct { + Begin time.Time + End time.Time +} + +// Equal returns true if both Epochs are identical. +func (e Epoch) Equal(other Epoch) bool { + return e.Begin == other.Begin && e.End == other.End +} + +// NewEpoch constructs an Epoch from its uint32 encoded begin and end parts. +func NewEpoch(begin, end uint32) Epoch { + return Epoch{ + Begin: util.SecsToTime(begin), + End: util.SecsToTime(end), + } +} + +// Contains indicates whether the time point is inside this Epoch. +func (e *Epoch) Contains(t time.Time) bool { + return t.After(e.Begin) && e.End.After(t) +} diff --git a/go/lib/drkey/level1.go b/go/lib/drkey/level1.go new file mode 100644 index 0000000000..3fb61f7ab8 --- /dev/null +++ b/go/lib/drkey/level1.go @@ -0,0 +1,44 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkey + +import ( + "bytes" + + "github.com/scionproto/scion/go/lib/addr" +) + +// Lvl1Meta represents the information about a level 1 DRKey other than the key itself. +type Lvl1Meta struct { + Epoch Epoch + SrcIA addr.IA + DstIA addr.IA +} + +// Equal returns true if both meta are identical. +func (m Lvl1Meta) Equal(other Lvl1Meta) bool { + return m.Epoch.Equal(other.Epoch) && m.SrcIA.Equal(other.SrcIA) && m.DstIA.Equal(other.DstIA) +} + +// Lvl1Key represents a level 1 DRKey. +type Lvl1Key struct { + Lvl1Meta + Key DRKey +} + +// Equal returns true if both level 1 keys are identical. +func (k Lvl1Key) Equal(other Lvl1Key) bool { + return k.Lvl1Meta.Equal(other.Lvl1Meta) && bytes.Compare(k.Key, other.Key) == 0 +} diff --git a/go/lib/drkey/level2.go b/go/lib/drkey/level2.go new file mode 100644 index 0000000000..90a68df8db --- /dev/null +++ b/go/lib/drkey/level2.go @@ -0,0 +1,68 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkey + +import ( + "bytes" + + "github.com/scionproto/scion/go/lib/addr" +) + +// Lvl2KeyType represents the different types of level 2 DRKeys (AS->AS, AS->host, host->host). +type Lvl2KeyType uint8 + +const ( + AS2AS Lvl2KeyType = iota + AS2Host + Host2Host +) + +// Lvl2Meta represents the information about a level 2 DRKey, without the key itself. +type Lvl2Meta struct { + KeyType Lvl2KeyType + Protocol string + Epoch Epoch + SrcIA addr.IA + DstIA addr.IA + SrcHost addr.HostAddr + DstHost addr.HostAddr +} + +// Equal returns true if both meta are identical. +func (m Lvl2Meta) Equal(other Lvl2Meta) bool { + return m.KeyType == other.KeyType && m.Protocol == other.Protocol && + m.Epoch.Equal(other.Epoch) && m.SrcIA.Equal(other.SrcIA) && m.DstIA.Equal(other.DstIA) && + m.SrcHost.Equal(other.SrcHost) && m.DstHost.Equal(other.DstHost) +} + +// Lvl2Key represents a level 2 DRKey. +type Lvl2Key struct { + Lvl2Meta + Key DRKey +} + +// Equal returns true if both level 2 keys are identical. +func (k Lvl2Key) Equal(other Lvl2Key) bool { + return k.Lvl2Meta.Equal(other.Lvl2Meta) && bytes.Compare(k.Key, other.Key) == 0 +} + +// DelegationSecret is similar to a level 2 key, type AS to AS. +type DelegationSecret struct { + Protocol string + Epoch Epoch + SrcIA addr.IA + DstIA addr.IA + Key DRKey +} diff --git a/go/lib/drkey/protocol/BUILD.bazel b/go/lib/drkey/protocol/BUILD.bazel new file mode 100644 index 0000000000..b8697a400b --- /dev/null +++ b/go/lib/drkey/protocol/BUILD.bazel @@ -0,0 +1,31 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "delegated.go", + "piskes.go", + "protocol.go", + "scmp.go", + "standard.go", + ], + importpath = "github.com/scionproto/scion/go/lib/drkey/protocol", + visibility = ["//visibility:public"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/common:go_default_library", + "//go/lib/drkey:go_default_library", + "//go/lib/scrypto:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["protocol_test.go"], + embed = [":go_default_library"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/common:go_default_library", + "//go/lib/drkey:go_default_library", + ], +) diff --git a/go/lib/drkey/protocol/delegated.go b/go/lib/drkey/protocol/delegated.go new file mode 100644 index 0000000000..56458d51b4 --- /dev/null +++ b/go/lib/drkey/protocol/delegated.go @@ -0,0 +1,99 @@ +// Copyright 2019 ETH Zurich +// +// 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 protocol + +import ( + "errors" + + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/scrypto" +) + +// Delegated implements the level 2 drkey derivation from level 1, without DS. It relies on the +// Standard implementation to derive the DS from the level 1 key. +type Delegated struct{} + +// DeriveLvl2 derives the level 2 DRKey without passing through a delegation secret. +func (p Delegated) DeriveLvl2(meta drkey.Lvl2Meta, key drkey.Lvl1Key) (drkey.Lvl2Key, error) { + metaForDS := meta + metaForDS.KeyType = drkey.AS2AS + dsKey, err := Standard{}.DeriveLvl2(metaForDS, key) + if err != nil { + return drkey.Lvl2Key{}, common.NewBasicError("Error deriving DS", err) + } + ds := drkey.DelegationSecret{ + Protocol: meta.Protocol, + Epoch: meta.Epoch, + SrcIA: meta.SrcIA, + DstIA: meta.DstIA, + Key: dsKey.Key, + } + return p.DeriveLvl2FromDS(meta, ds) +} + +// DeriveLvl2FromDS will derive the level 2 key from a delegation secret. +func (p Delegated) DeriveLvl2FromDS(meta drkey.Lvl2Meta, ds drkey.DelegationSecret) ( + drkey.Lvl2Key, error) { + + h, err := scrypto.InitMac(common.RawBytes(ds.Key)) + if err != nil { + return drkey.Lvl2Key{}, err + } + + pLen := 0 + buffs := []common.RawBytes{} + // add to buffs in reverse order: + switch meta.KeyType { + case drkey.Host2Host: + if meta.SrcHost.Size() == 0 { + return drkey.Lvl2Key{}, errors.New("Level 2 DRKey requires a src host, but it is empty") + } + b := meta.SrcHost.Pack() + buffs = []common.RawBytes{ + b, + {byte(len(b))}, + } + pLen += len(b) + 1 + fallthrough + case drkey.AS2Host: + if meta.DstHost.Size() == 0 { + return drkey.Lvl2Key{}, errors.New("Level 2 DRKey requires a dst host, but it is empty") + } + b := meta.DstHost.Pack() + buffs = append(buffs, + b, + common.RawBytes{byte(len(b))}) + pLen += len(b) + 1 + case drkey.AS2AS: + return drkey.Lvl2Key{ + Lvl2Meta: meta, + Key: ds.Key, + }, nil + default: + return drkey.Lvl2Key{}, common.NewBasicError("Unknown DRKey type", nil) + } + all := make(common.RawBytes, pLen) + pLen = 0 + for i := len(buffs) - 1; i >= 0; i-- { + copy(all[pLen:], buffs[i]) + pLen += len(buffs[i]) + } + h.Write(all) + return drkey.Lvl2Key{ + Lvl2Meta: meta, + Key: drkey.DRKey(h.Sum(nil)), + }, nil +} diff --git a/go/lib/drkey/protocol/piskes.go b/go/lib/drkey/protocol/piskes.go new file mode 100644 index 0000000000..44bc88777d --- /dev/null +++ b/go/lib/drkey/protocol/piskes.go @@ -0,0 +1,45 @@ +// Copyright 2019 ETH Zurich +// +// 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 protocol + +import ( + "github.com/scionproto/scion/go/lib/drkey" +) + +var _ DelegatedDerivation = piskes{} + +// piskes implements the derivation for the PISKES protocol. +type piskes struct{} + +// Name returns scmp. +func (piskes) Name() string { + return "piskes" +} + +// DeriveLvl2 uses the standard derivation. +func (piskes) DeriveLvl2(meta drkey.Lvl2Meta, key drkey.Lvl1Key) (drkey.Lvl2Key, error) { + return Delegated{}.DeriveLvl2(meta, key) +} + +func (piskes) DeriveLvl2FromDS(meta drkey.Lvl2Meta, ds drkey.DelegationSecret) ( + drkey.Lvl2Key, error) { + + return Delegated{}.DeriveLvl2FromDS(meta, ds) +} + +func init() { + p := piskes{} + KnownDerivations[p.Name()] = p +} diff --git a/go/lib/drkey/protocol/protocol.go b/go/lib/drkey/protocol/protocol.go new file mode 100644 index 0000000000..882c661519 --- /dev/null +++ b/go/lib/drkey/protocol/protocol.go @@ -0,0 +1,52 @@ +// Copyright 2019 ETH Zurich +// +// 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 protocol + +import ( + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/scrypto" +) + +// Derivation specifies the interface to implement for a derivation method. +type Derivation interface { + Name() string + DeriveLvl2(meta drkey.Lvl2Meta, key drkey.Lvl1Key) (drkey.Lvl2Key, error) +} + +// DelegatedDerivation extends a Derivation with a derivation from a DS. +type DelegatedDerivation interface { + Derivation + DeriveLvl2FromDS(meta drkey.Lvl2Meta, ds drkey.DelegationSecret) (drkey.Lvl2Key, error) +} + +// KnownDerivations maps the derivation names to their implementations. +var KnownDerivations = make(map[string]Derivation) + +// DeriveLvl1 constructs a new level 1 DRKey. +func DeriveLvl1(meta drkey.Lvl1Meta, sv drkey.SV) (drkey.Lvl1Key, error) { + mac, err := scrypto.InitMac(common.RawBytes(sv.Key)) + if err != nil { + return drkey.Lvl1Key{}, err + } + all := make(common.RawBytes, addr.IABytes) + meta.DstIA.Write(all) + mac.Write(all) + return drkey.Lvl1Key{ + Lvl1Meta: meta, + Key: drkey.DRKey(mac.Sum(nil)), + }, nil +} diff --git a/go/lib/drkey/protocol/protocol_test.go b/go/lib/drkey/protocol/protocol_test.go new file mode 100644 index 0000000000..85477833a7 --- /dev/null +++ b/go/lib/drkey/protocol/protocol_test.go @@ -0,0 +1,185 @@ +// Copyright 2019 ETH Zurich +// +// 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 protocol + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkey" +) + +func TestDeriveStandard(t *testing.T) { + lvl1 := getLvl1(t) + protoToKey := map[string]string{ + "foo": "def3aa32ce47d4374469148b5c04fac5", + "bar": "8ada021cabf2b14765f468f3c8995edb", + "fooo": "7f8e507aecf38c09e4cb10a0ff0cc497", + } + for proto, key := range protoToKey { + meta := drkey.Lvl2Meta{ + Protocol: proto, + KeyType: drkey.AS2AS, + SrcIA: lvl1.SrcIA, + DstIA: lvl1.DstIA, + } + lvl2, err := Standard{}.DeriveLvl2(meta, lvl1) + if err != nil { + t.Fatalf("Lvl2 failed") + } + hexKey := hex.EncodeToString(lvl2.Key) + if hexKey != key { + t.Fatalf("Unexpected lvl2 key for protocol [%s]: %s", proto, hexKey) + } + } + // TODO(juagargi): test as2host and host2host. Get the key values from an authoritative source. +} + +func TestDeriveDelegated(t *testing.T) { + lvl1 := getLvl1(t) + for _, proto := range []string{"foo", "bar", "fooo"} { + meta := drkey.Lvl2Meta{ + Protocol: proto, + KeyType: drkey.AS2AS, + SrcIA: lvl1.SrcIA, + DstIA: lvl1.DstIA, + } + lvl2standard, err := Delegated{}.DeriveLvl2(meta, lvl1) + if err != nil { + t.Fatalf("Lvl2 standard failed") + } + lvl2deleg, err := Delegated{}.DeriveLvl2(meta, lvl1) + if err != nil { + t.Fatalf("Lvl2 delegated failed") + } + if !bytes.Equal(lvl2deleg.Key, lvl2standard.Key) { + t.Fatalf("Keys must be equal for AS2AS") + } + } + protoToLvl2 := map[string]string{ + "foo": "b4279b032d7d81c38754ab7b253f5ac0", + "bar": "a30df8ad348bfce1ecdf1cf83c9e5265", + "fooo": "434817fb40cb602b36c80e88789aee46", + } + for proto, key := range protoToLvl2 { + meta := drkey.Lvl2Meta{ + Protocol: proto, + KeyType: drkey.AS2Host, + SrcIA: lvl1.SrcIA, + DstIA: lvl1.DstIA, + DstHost: addr.HostFromIPStr("127.0.0.1"), + } + lvl2, err := Delegated{}.DeriveLvl2(meta, lvl1) + if err != nil { + t.Fatalf("Lvl2 failed") + } + hexKey := hex.EncodeToString(lvl2.Key) + if hexKey != key { + t.Fatalf("Unexpected lvl2 key for protocol [%s]: %s", proto, hexKey) + } + } +} + +func TestDeriveDelegatedViaDS(t *testing.T) { + // derive DS and then derive key. Compare to derive directly key + lvl1Key := getLvl1(t) + meta := drkey.Lvl2Meta{ + Protocol: "piskes", + KeyType: drkey.AS2AS, + SrcIA: lvl1Key.SrcIA, + DstIA: lvl1Key.DstIA, + SrcHost: addr.HostNone{}, + DstHost: addr.HostNone{}, + } + lvl2Key, err := piskes{}.DeriveLvl2(meta, lvl1Key) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + ds := drkey.DelegationSecret{ + Protocol: lvl2Key.Protocol, + Epoch: lvl2Key.Epoch, + SrcIA: lvl2Key.SrcIA, + DstIA: lvl2Key.DstIA, + Key: lvl2Key.Key, + } + srcHost := addr.HostFromIPStr("1.1.1.1") + dstHost := addr.HostFromIPStr("2.2.2.2") + meta = drkey.Lvl2Meta{ + Protocol: meta.Protocol, + KeyType: drkey.Host2Host, + SrcIA: meta.SrcIA, + DstIA: meta.DstIA, + SrcHost: srcHost, + DstHost: dstHost, + } + lvl2KeyViaDS, err := piskes{}.DeriveLvl2FromDS(meta, ds) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + _ = lvl2KeyViaDS + // now get the level 2 key directly without explicitly going through DS + lvl2Key, err = piskes{}.DeriveLvl2(meta, lvl1Key) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !lvl2Key.Equal(lvl2KeyViaDS) { + t.Fatalf("Level 2 key from DS and direct should be equal. From DS = %s , direct = %s", + hex.EncodeToString(lvl2KeyViaDS.Key), hex.EncodeToString(lvl2Key.Key)) + } +} + +func getLvl1(t *testing.T) drkey.Lvl1Key { + master0 := common.RawBytes{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7} + epoch := drkey.NewEpoch(0, 1) + srcIA, _ := addr.IAFromString("1-ff00:0:111") + dstIA, _ := addr.IAFromString("1-ff00:0:112") + sv, err := drkey.DeriveSV(drkey.SVMeta{ + Epoch: epoch, + }, master0) + if err != nil { + t.Fatalf("SV failed") + } + if hex.EncodeToString(sv.Key) != "47bfbb7d94706dc9e79825e5a837b006" { + t.Fatalf("Unexpected sv: %s", hex.EncodeToString(sv.Key)) + } + lvl1, err := DeriveLvl1(drkey.Lvl1Meta{ + Epoch: epoch, + SrcIA: srcIA, + DstIA: dstIA, + }, sv) + if err != nil { + t.Fatalf("Lvl1 failed") + } + if hex.EncodeToString(lvl1.Key) != "51663adbc06e55f40a9ad899cf0775e5" { + t.Fatalf("Unexpected lvl1 key: %s", hex.EncodeToString(lvl1.Key)) + } + return lvl1 +} + +func TestExistingImplementations(t *testing.T) { + // we test that we have the four implementations we know for now (standard,deleg,scmp,piskes) + if len(KnownDerivations) != 2 { + t.Errorf("Wrong number of implementations, expecting 4, got %d", len(KnownDerivations)) + } + if _, found := KnownDerivations["scmp"]; !found { + t.Errorf("\"scmp\" implementation not found") + } + if _, found := KnownDerivations["piskes"]; !found { + t.Errorf("\"piskes\" implementation not found") + } +} diff --git a/go/lib/drkey/protocol/scmp.go b/go/lib/drkey/protocol/scmp.go new file mode 100644 index 0000000000..a3b788aacd --- /dev/null +++ b/go/lib/drkey/protocol/scmp.go @@ -0,0 +1,39 @@ +// Copyright 2019 ETH Zurich +// +// 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 protocol + +import ( + "github.com/scionproto/scion/go/lib/drkey" +) + +var _ Derivation = scmp{} + +// scmp implements the derivation for the SCMP protocol. +type scmp struct{} + +// Name returns scmp. +func (scmp) Name() string { + return "scmp" +} + +// DeriveLvl2 uses the standard derivation. +func (scmp) DeriveLvl2(meta drkey.Lvl2Meta, key drkey.Lvl1Key) (drkey.Lvl2Key, error) { + return Standard{}.DeriveLvl2(meta, key) +} + +func init() { + s := scmp{} + KnownDerivations[s.Name()] = s +} diff --git a/go/lib/drkey/protocol/standard.go b/go/lib/drkey/protocol/standard.go new file mode 100644 index 0000000000..ffc87f155c --- /dev/null +++ b/go/lib/drkey/protocol/standard.go @@ -0,0 +1,87 @@ +// Copyright 2019 ETH Zurich +// +// 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 protocol + +import ( + "errors" + + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/scrypto" +) + +// TODO(juagargi) the standard derivation (in this file) and delegated one will be just +// functions: won't implement any interface in particular. The way to configure +// a new protocol will look like the SCMP or PISKES protocols: new type with their functions, and +// registration with the correct name. No configuration will be allowed to change this (the +// configuration file will not include any mapping protocol->derivation). + +// Standard implements the level 2 drkey derivation from level 1, without DS. +type Standard struct{} + +// DeriveLvl2 derives the level 2 DRKey without passing through a delegation secret. +func (p Standard) DeriveLvl2(meta drkey.Lvl2Meta, key drkey.Lvl1Key) (drkey.Lvl2Key, error) { + h, err := scrypto.InitMac(common.RawBytes(key.Key)) + if err != nil { + return drkey.Lvl2Key{}, err + } + + pLen := 0 + // add to buffs in reverse order: + buffs := []common.RawBytes{} + switch meta.KeyType { + case drkey.Host2Host: + if meta.SrcHost.Size() == 0 { + return drkey.Lvl2Key{}, errors.New("Level 2 DRKey requires a src host, but it is empty") + } + b := meta.SrcHost.Pack() + buffs = []common.RawBytes{ + b, + {byte(len(b))}, + } + pLen += len(b) + 1 + fallthrough + case drkey.AS2Host: + if meta.DstHost.Size() == 0 { + return drkey.Lvl2Key{}, errors.New("Level 2 DRKey requires a dst host, but it is empty") + } + b := meta.DstHost.Pack() + buffs = append(buffs, + b, + common.RawBytes{byte(len(b))}) + pLen += len(b) + 1 + fallthrough + case drkey.AS2AS: + b := common.RawBytes(meta.Protocol) + buffs = append(buffs, + common.RawBytes{byte(meta.KeyType)}, + b, + common.RawBytes{byte(len(b))}) + pLen += len(b) + 2 + default: + return drkey.Lvl2Key{}, common.NewBasicError("Unknown DRKey type", nil) + } + all := make(common.RawBytes, pLen) + pLen = 0 + for i := len(buffs) - 1; i >= 0; i-- { + copy(all[pLen:], buffs[i]) + pLen += len(buffs[i]) + } + h.Write(all) + return drkey.Lvl2Key{ + Lvl2Meta: meta, + Key: drkey.DRKey(h.Sum(nil)), + }, nil +} diff --git a/go/lib/drkey/secret_value.go b/go/lib/drkey/secret_value.go new file mode 100644 index 0000000000..18e45162b8 --- /dev/null +++ b/go/lib/drkey/secret_value.go @@ -0,0 +1,64 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkey + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "errors" + + "golang.org/x/crypto/pbkdf2" + + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/util" +) + +const drkeySalt = "Derive DRKey Key" // same as in Python + +// SVMeta represents the information about a DRKey secret value. +type SVMeta struct { + Epoch Epoch +} + +// SV represents a DRKey secret value. +type SV struct { + SVMeta + Key DRKey +} + +// Equal returns true if both secret values are identical. +func (sv SV) Equal(other SV) bool { + return sv.Epoch.Equal(other.Epoch) && bytes.Compare(sv.Key, other.Key) == 0 +} + +// DeriveSV constructs a valid SV. asSecret is typically the AS master secret. +func DeriveSV(meta SVMeta, asSecret common.RawBytes) (SV, error) { + msLen := len(asSecret) + if msLen == 0 { + return SV{}, errors.New("Invalid zero sized secret") + } + all := make(common.RawBytes, 1+msLen+8) + copy(all, []byte{byte(msLen)}) + copy(all[1:], asSecret) + binary.LittleEndian.PutUint32(all[msLen+1:], util.TimeToSecs(meta.Epoch.Begin)) + binary.LittleEndian.PutUint32(all[msLen+5:], util.TimeToSecs(meta.Epoch.End)) + + key := pbkdf2.Key(all, []byte(drkeySalt), 1000, 16, sha256.New) + return SV{ + SVMeta: meta, + Key: DRKey(key), + }, nil +} diff --git a/go/lib/drkey/suite.go b/go/lib/drkey/suite.go new file mode 100644 index 0000000000..4a407152a7 --- /dev/null +++ b/go/lib/drkey/suite.go @@ -0,0 +1,55 @@ +// Copyright 2018 ETH Zurich +// +// 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 drkey + +import ( + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/scrypto" +) + +// EncryptDRKeyLvl1 does the encryption step in the first level key exchange +func EncryptDRKeyLvl1( + drkey Lvl1Key, nonce, pubkey, privkey common.RawBytes) (common.RawBytes, error) { + + keyLen := len(drkey.Key) + msg := make(common.RawBytes, addr.IABytes*2+keyLen) + drkey.SrcIA.Write(msg) + drkey.DstIA.Write(msg[addr.IABytes:]) + copy(msg[addr.IABytes*2:], drkey.Key) + cipher, err := scrypto.Encrypt(msg, nonce, pubkey, privkey, scrypto.Curve25519xSalsa20Poly1305) + if err != nil { + return nil, err + } + return cipher, nil +} + +// DecryptDRKeyLvl1 decrypts the cipher text received during the first level key exchange +func DecryptDRKeyLvl1(cipher, nonce, pubkey, privkey common.RawBytes) (Lvl1Key, error) { + msg, err := scrypto.Decrypt(cipher, nonce, pubkey, privkey, scrypto.Curve25519xSalsa20Poly1305) + if err != nil { + return Lvl1Key{}, err + } + srcIA := addr.IAFromRaw(msg[:addr.IABytes]) + dstIA := addr.IAFromRaw(msg[addr.IABytes : addr.IABytes*2]) + key := msg[addr.IABytes*2:] + return Lvl1Key{ + Lvl1Meta: Lvl1Meta{ + SrcIA: srcIA, + DstIA: dstIA, + }, + Key: DRKey(key), + }, nil +} diff --git a/go/lib/drkeystorage/BUILD.bazel b/go/lib/drkeystorage/BUILD.bazel new file mode 100644 index 0000000000..9e62315eee --- /dev/null +++ b/go/lib/drkeystorage/BUILD.bazel @@ -0,0 +1,31 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "config.go", + "sample.go", + "store.go", + ], + importpath = "github.com/scionproto/scion/go/lib/drkeystorage", + visibility = ["//visibility:public"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/common:go_default_library", + "//go/lib/config:go_default_library", + "//go/lib/drkey:go_default_library", + "//go/lib/drkey/drkeydbsqlite:go_default_library", + "//go/lib/drkey/protocol:go_default_library", + "//go/lib/infra:go_default_library", + "//go/lib/infra/modules/cleaner:go_default_library", + "//go/lib/log:go_default_library", + "//go/lib/util:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["config_test.go"], + embed = [":go_default_library"], + deps = ["@com_github_burntsushi_toml//:go_default_library"], +) diff --git a/go/lib/drkeystorage/config.go b/go/lib/drkeystorage/config.go new file mode 100644 index 0000000000..96a8c635ba --- /dev/null +++ b/go/lib/drkeystorage/config.go @@ -0,0 +1,240 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkeystorage + +import ( + "fmt" + "io" + "strconv" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/config" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/drkey/drkeydbsqlite" + "github.com/scionproto/scion/go/lib/drkey/protocol" + "github.com/scionproto/scion/go/lib/log" + "github.com/scionproto/scion/go/lib/util" +) + +// Backend indicates the database backend type. +type Backend string + +const ( + // backendNone is the empty backend. It defaults to sqlite. + backendNone Backend = "" + // BackendSqlite indicates an sqlite backend. + BackendSqlite Backend = "sqlite" +) + +const ( + // BackendKey is the backend key in the config mapping. + BackendKey = "backend" + // ConnectionKey is the connection key in the config mapping. + ConnectionKey = "connection" + // MaxOpenConnsKey is the key for max open conns in the config mapping. + MaxOpenConnsKey = "maxopenconns" + // MaxIdleConnsKey is the key for max idle conns in the config mapping. + MaxIdleConnsKey = "maxidleconns" +) + +// DRKeyDBConf is the configuration used to describe both a level 1 and 2 DRKey DB. +type DRKeyDBConf map[string]string + +// DelegationList configures which endhosts can get delegation secrets, per protocol. +type DelegationList map[string][]string + +var _ (config.Config) = (*DRKeyDBConf)(nil) +var _ (config.Config) = (*DelegationList)(nil) + +// InitDefaults chooses the sqlite backend if no backend is set and sets all keys to lower case. +func (cfg *DRKeyDBConf) InitDefaults() { + if *cfg == nil { + *cfg = make(DRKeyDBConf) + } + m := *cfg + util.LowerKeys(m) + if cfg.Backend() == backendNone { + m[BackendKey] = string(BackendSqlite) + } +} + +// Backend returns the database backend type. +func (cfg *DRKeyDBConf) Backend() Backend { + return Backend((*cfg)[BackendKey]) +} + +// Connection returns the database connection information. +func (cfg *DRKeyDBConf) Connection() string { + return (*cfg)[ConnectionKey] +} + +// MaxOpenConns returns the limit for maximum open connections to the database. +func (cfg *DRKeyDBConf) MaxOpenConns() (int, bool) { + val, ok, _ := cfg.parsedInt(MaxOpenConnsKey) + return val, ok +} + +// MaxIdleConns returns the limit for maximum idle connections to the database. +func (cfg *DRKeyDBConf) MaxIdleConns() (int, bool) { + val, ok, _ := cfg.parsedInt(MaxIdleConnsKey) + return val, ok +} + +func (cfg *DRKeyDBConf) parsedInt(key string) (int, bool, error) { + val := (*cfg)[key] + if val == "" { + return 0, false, nil + } + i, err := strconv.Atoi(val) + return i, true, err +} + +// Validate validates that all values are parsable, and the backend is set. +func (cfg *DRKeyDBConf) Validate() error { + if err := cfg.validateLimits(); err != nil { + return err + } + switch cfg.Backend() { + case BackendSqlite: + return nil + case backendNone: + return common.NewBasicError("No backend set", nil) + } + return common.NewBasicError("Unsupported backend", nil, "backend", cfg.Backend()) +} + +func (cfg *DRKeyDBConf) validateLimits() error { + if _, _, err := cfg.parsedInt(MaxOpenConnsKey); err != nil { + return common.NewBasicError("Invalid MaxOpenConns", nil, "value", (*cfg)[MaxOpenConnsKey]) + } + if _, _, err := cfg.parsedInt(MaxIdleConnsKey); err != nil { + return common.NewBasicError("Invalid MaxIdleConns", nil, "value", (*cfg)[MaxIdleConnsKey]) + } + return nil +} + +// Sample writes a config sample to the writer. +func (cfg *DRKeyDBConf) Sample(dst io.Writer, path config.Path, ctx config.CtxMap) { + config.WriteString(dst, fmt.Sprintf(drkeyLvl1DBSample, ctx[config.ID])) +} + +// ConfigName is the key in the toml file. +func (cfg *DRKeyDBConf) ConfigName() string { + return "drkeyDB" +} + +// newDB is an internal function that returns a new drkey DB. Call this with a pointer to +// a function func(string)(drkey.Lvl1DB, error) (also drkey.Lvl2DB) such as +// drkeydbsqlite.NewLvl1Backend . +func (cfg *DRKeyDBConf) newDB(newdbFcn func(string) (drkey.BaseDB, error)) (drkey.BaseDB, error) { + log.Info("Connecting DRKeyDB", "backend", cfg.Backend(), "connection", cfg.Connection()) + var err error + var db drkey.BaseDB + switch cfg.Backend() { + case BackendSqlite: + db, err = newdbFcn(cfg.Connection()) + default: + return nil, common.NewBasicError("Unsupported backend", nil, "backend", cfg.Backend()) + } + if err != nil { + return nil, err + } + cfg.setConnLimits(db) + return db, nil +} + +// NewLvl1DB returns a new level 1 drkey DB. +func (cfg *DRKeyDBConf) NewLvl1DB() (drkey.Lvl1DB, error) { + db, err := + cfg.newDB(func(s string) (drkey.BaseDB, error) { return drkeydbsqlite.NewLvl1Backend(s) }) + if err != nil { + return nil, err + } + return db.(drkey.Lvl1DB), nil +} + +// NewLvl2DB returns a new level 2 drkey DB. +func (cfg *DRKeyDBConf) NewLvl2DB() (drkey.Lvl2DB, error) { + db, err := + cfg.newDB(func(s string) (drkey.BaseDB, error) { return drkeydbsqlite.NewLvl2Backend(s) }) + if err != nil { + return nil, err + } + return db.(drkey.Lvl2DB), nil +} + +func (cfg *DRKeyDBConf) setConnLimits(db drkey.BaseDB) { + if m, ok := cfg.MaxOpenConns(); ok { + db.SetMaxOpenConns(m) + } + if m, ok := cfg.MaxIdleConns(); ok { + db.SetMaxIdleConns(m) + } +} + +// InitDefaults will not add or modify any entry in the config. +func (cfg *DelegationList) InitDefaults() { + if *cfg == nil { + *cfg = make(DelegationList) + } +} + +// Validate validates that the protocols exist, and their addresses are parsable. +func (cfg *DelegationList) Validate() error { + for proto, list := range *cfg { + if _, found := protocol.KnownDerivations[proto]; !found { + return common.NewBasicError("Configured protocol not found", nil, "protocol", proto) + } + for _, ip := range list { + if h := addr.HostFromIPStr(ip); h == nil { + return common.NewBasicError("Syntax error: not a valid address", nil, "ip", ip) + } + } + } + return nil +} + +// Sample writes a config sample to the writer. +func (cfg *DelegationList) Sample(dst io.Writer, path config.Path, ctx config.CtxMap) { + config.WriteString(dst, drkeyDelegationListSample) +} + +// ConfigName is the key in the toml file. +func (cfg *DelegationList) ConfigName() string { + return "delegation" +} + +// ToMapPerHost will return map where there is a set of supported protocols per host. +func (cfg *DelegationList) ToMapPerHost() map[[16]byte]map[string]struct{} { + m := make(map[[16]byte]map[string]struct{}) + for proto, ipList := range *cfg { + for _, ip := range ipList { + host := addr.HostFromIPStr(ip) + if host == nil { + continue + } + var rawHost [16]byte + copy(rawHost[:], host.IP().To16()) + protoSet := m[rawHost] + if protoSet == nil { + protoSet = make(map[string]struct{}) + } + protoSet[proto] = struct{}{} + m[rawHost] = protoSet + } + } + return m +} diff --git a/go/lib/drkeystorage/config_test.go b/go/lib/drkeystorage/config_test.go new file mode 100644 index 0000000000..6b55d1bf77 --- /dev/null +++ b/go/lib/drkeystorage/config_test.go @@ -0,0 +1,158 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkeystorage + +import ( + "io/ioutil" + "net" + "os" + "testing" + + "github.com/BurntSushi/toml" +) + +func TestDelegationListDefaults(t *testing.T) { + var cfg DelegationList + cfg.InitDefaults() + if cfg == nil { + t.Errorf("InitDefaults should have initialized the map, but did not") + } + if len(cfg) != 0 { + t.Errorf("InitDefaults should leave the map empty but is not: %+v", cfg) + } +} + +func TestDelegationListSyntax(t *testing.T) { + var cfg DelegationList + sample1 := `piskes = ["1.1.1.1"]` + meta, err := toml.Decode(sample1, &cfg) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(meta.Undecoded()) != 0 { + t.Fatalf("Should be empty but it's not: %+v", meta.Undecoded()) + } + if err := cfg.Validate(); err != nil { + t.Fatalf("Unexpected validation error: %v", err) + } + + sample2 := `piskes = ["not an address"]` + meta, err = toml.Decode(sample2, &cfg) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(meta.Undecoded()) != 0 { + t.Fatalf("Should be empty but it's not: %+v", meta.Undecoded()) + } + if err := cfg.Validate(); err == nil { + t.Fatalf("Expected validation error but got none") + } +} + +func TestToMapPerHost(t *testing.T) { + var cfg DelegationList + sample := `piskes = ["1.1.1.1", "2.2.2.2"] + scmp = ["1.1.1.1"]` + toml.Decode(sample, &cfg) + if err := cfg.Validate(); err != nil { + t.Fatalf("Unexpected validation error: %v", err) + } + m := cfg.ToMapPerHost() + if len(m) != 2 { + t.Fatalf("The map should contain the two hosts. Map: %v", m) + } + + var rawIP [16]byte + copy(rawIP[:], net.ParseIP("1.1.1.1").To16()) + if len(m[rawIP]) != 2 { + t.Fatalf("Expecting 2 protocols for 1.1.1.1. Content: %+v", m[rawIP]) + } + if _, found := m[rawIP]["piskes"]; !found { + t.Fatalf("Expected to find piskes for 1.1.1.1 but not. Content: %+v", m[rawIP]) + } + if _, found := m[rawIP]["scmp"]; !found { + t.Fatalf("Expected to find scmp for 1.1.1.1 but not. Content: %+v", m[rawIP]) + } + + copy(rawIP[:], net.ParseIP("2.2.2.2").To16()) + if len(m[rawIP]) != 1 { + t.Fatalf("Expecting 1 protocol for 2.2.2.2 ; Content: %+v", m[rawIP]) + } + if _, found := m[rawIP]["piskes"]; !found { + t.Fatalf("Expected to find piskes for 2.2.2.2 but not. Content: %+v", m[rawIP]) + } +} + +func TestInitDRKeyDBDefaults(t *testing.T) { + var cfg DRKeyDBConf + cfg.InitDefaults() + if err := cfg.Validate(); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if string(cfg.Backend()) != "sqlite" { + t.Errorf("Unexpected configuration value: %v", cfg.Backend()) + } + if cfg.Connection() != "" { + t.Errorf("Unexpected configuration value: %v", cfg.Connection()) + } +} + +func TestNewLvl1DB(t *testing.T) { + cfg := &DRKeyDBConf{ + "backend": "sqlite", + "connection": tempFile(t), + } + db, err := cfg.NewLvl1DB() + defer func() { + db.Close() + os.Remove(cfg.Connection()) + }() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if db == nil { + t.Fatal("Returned DB is nil") + } +} + +func TestNewLvl2DB(t *testing.T) { + cfg := &DRKeyDBConf{ + "backend": "sqlite", + "connection": tempFile(t), + } + db, err := cfg.NewLvl2DB() + defer func() { + db.Close() + os.Remove(cfg.Connection()) + }() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if db == nil { + t.Fatal("Returned DB is nil") + } +} + +func tempFile(t *testing.T) string { + file, err := ioutil.TempFile("", "db-test-") + if err != nil { + t.Fatalf("unable to create temp file") + } + name := file.Name() + if err := file.Close(); err != nil { + t.Fatalf("unable to close temp file") + } + return name +} diff --git a/go/lib/drkeystorage/sample.go b/go/lib/drkeystorage/sample.go new file mode 100644 index 0000000000..5e28a70228 --- /dev/null +++ b/go/lib/drkeystorage/sample.go @@ -0,0 +1,36 @@ +// 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 drkeystorage + +const drkeyLvl1DBSample = ` +# The type of drkeydb backend. (default sqlite) +Backend = "sqlite" + +# Connection for the drkey database. +Connection = "/var/lib/scion/drkeydb/%s.drkey.db" + +# The maximum number of open connections to the database. In case of the +# empty string, the limit is not set and uses the go default. (default "") +MaxOpenConns = "" + +# The maximum number of idle connections to the database. In case of the +# empty string, the limit is not set and uses the go default. (default "") +MaxIdleConns = "" +` + +const drkeyDelegationListSample = ` +# The list of hosts authorized to get a DS per protocol. +piskes = [ "127.0.0.1", "127.0.0.2"] +` diff --git a/go/lib/drkeystorage/store.go b/go/lib/drkeystorage/store.go new file mode 100644 index 0000000000..5421a7b62b --- /dev/null +++ b/go/lib/drkeystorage/store.go @@ -0,0 +1,62 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkeystorage + +import ( + "context" + "time" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/drkey" + "github.com/scionproto/scion/go/lib/infra" + "github.com/scionproto/scion/go/lib/infra/modules/cleaner" +) + +// SecretValueFactory has the functionality to store secret values. +type SecretValueFactory interface { + GetSecretValue(time.Time) (drkey.SV, error) +} + +// BaseStore is the common base for any drkey store. +type BaseStore interface { + DeleteExpiredKeys(ctx context.Context) (int, error) +} + +// ServiceStore is the level 1 drkey store, used by the CS. +// It will keep a cache of those keys that were retrieved from the network. +// It automatically removes expired keys. +type ServiceStore interface { + BaseStore + SetMessenger(msger infra.Messenger) + GetLvl1Key(ctx context.Context, meta drkey.Lvl1Meta, valTime time.Time) (drkey.Lvl1Key, error) + NewLvl1ReqHandler() infra.Handler + NewLvl2ReqHandler() infra.Handler + KnownASes(ctx context.Context) ([]addr.IA, error) +} + +// ClientStore is the level 2 drkey store, used by sciond. +// It can get level 2 keys from its backes storage, or by +// asking a remote CS. +type ClientStore interface { + BaseStore + GetLvl2Key(ctx context.Context, meta drkey.Lvl2Meta, valTime time.Time) (drkey.Lvl2Key, error) +} + +// NewStoreCleaner creates a Cleaner task that removes expired level 1 drkeys. +func NewStoreCleaner(s BaseStore) *cleaner.Cleaner { + return cleaner.New(func(ctx context.Context) (int, error) { + return s.DeleteExpiredKeys(ctx) + }, "drkey") +} diff --git a/go/lib/infra/BUILD.bazel b/go/lib/infra/BUILD.bazel index 526be2f9d8..c53c690d31 100644 --- a/go/lib/infra/BUILD.bazel +++ b/go/lib/infra/BUILD.bazel @@ -15,6 +15,7 @@ go_library( "//go/lib/ctrl:go_default_library", "//go/lib/ctrl/ack:go_default_library", "//go/lib/ctrl/cert_mgmt:go_default_library", + "//go/lib/ctrl/drkey_mgmt:go_default_library", "//go/lib/ctrl/ifid:go_default_library", "//go/lib/ctrl/path_mgmt:go_default_library", "//go/lib/ctrl/seg:go_default_library", diff --git a/go/lib/infra/common.go b/go/lib/infra/common.go index 9273f69f60..40b0014c2b 100644 --- a/go/lib/infra/common.go +++ b/go/lib/infra/common.go @@ -25,6 +25,7 @@ import ( "github.com/scionproto/scion/go/lib/ctrl" "github.com/scionproto/scion/go/lib/ctrl/ack" "github.com/scionproto/scion/go/lib/ctrl/cert_mgmt" + "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" "github.com/scionproto/scion/go/lib/ctrl/ifid" "github.com/scionproto/scion/go/lib/ctrl/path_mgmt" "github.com/scionproto/scion/go/lib/ctrl/seg" @@ -152,6 +153,10 @@ const ( HPSegReply HPCfgRequest HPCfgReply + DRKeyLvl1Request + DRKeyLvl1Reply + DRKeyLvl2Request + DRKeyLvl2Reply ) func (mt MessageType) String() string { @@ -208,6 +213,14 @@ func (mt MessageType) String() string { return "HPCfgRequest" case HPCfgReply: return "HPCfgReply" + case DRKeyLvl1Request: + return "DRKeyLvl1Request" + case DRKeyLvl1Reply: + return "DRKeyLvl1Reply" + case DRKeyLvl2Request: + return "DRKeyLvl2Request" + case DRKeyLvl2Reply: + return "DRKeyLvl2Reply" default: return fmt.Sprintf("Unknown (%d)", mt) } @@ -362,6 +375,15 @@ type Messenger interface { id uint64) error SendBeacon(ctx context.Context, msg *seg.Beacon, a net.Addr, id uint64) error UpdateSigner(signer Signer, types []MessageType) + // TODO(juagargi) rename these functions: + RequestDRKeyLvl1(ctx context.Context, msg *drkey_mgmt.Lvl1Req, a net.Addr, + id uint64) (*drkey_mgmt.Lvl1Rep, error) + SendDRKeyLvl1(ctx context.Context, msg *drkey_mgmt.Lvl1Rep, a net.Addr, + id uint64) error + RequestDRKeyLvl2(ctx context.Context, msg *drkey_mgmt.Lvl2Req, a net.Addr, + id uint64) (*drkey_mgmt.Lvl2Rep, error) + SendDRKeyLvl2(ctx context.Context, msg *drkey_mgmt.Lvl2Rep, a net.Addr, + id uint64) error UpdateVerifier(verifier Verifier) AddHandler(msgType MessageType, h Handler) ListenAndServe() @@ -377,6 +399,8 @@ type ResponseWriter interface { SendIfStateInfoReply(ctx context.Context, msg *path_mgmt.IFStateInfos) error SendHPSegReply(ctx context.Context, msg *path_mgmt.HPSegReply) error SendHPCfgReply(ctx context.Context, msg *path_mgmt.HPCfgReply) error + SendDRKeyLvl1(ctx context.Context, msg *drkey_mgmt.Lvl1Rep) error + SendDRKeyLvl2(ctx context.Context, msg *drkey_mgmt.Lvl2Rep) error } func ResponseWriterFromContext(ctx context.Context) (ResponseWriter, bool) { diff --git a/go/lib/infra/messenger/BUILD.bazel b/go/lib/infra/messenger/BUILD.bazel index 593fb26a74..86389f60ff 100644 --- a/go/lib/infra/messenger/BUILD.bazel +++ b/go/lib/infra/messenger/BUILD.bazel @@ -21,6 +21,7 @@ go_library( "//go/lib/ctrl/ack:go_default_library", "//go/lib/ctrl/cert_mgmt:go_default_library", "//go/lib/ctrl/ctrl_msg:go_default_library", + "//go/lib/ctrl/drkey_mgmt:go_default_library", "//go/lib/ctrl/ifid:go_default_library", "//go/lib/ctrl/path_mgmt:go_default_library", "//go/lib/ctrl/seg:go_default_library", diff --git a/go/lib/infra/messenger/messenger.go b/go/lib/infra/messenger/messenger.go index d6de06a42b..780ab9aa9e 100644 --- a/go/lib/infra/messenger/messenger.go +++ b/go/lib/infra/messenger/messenger.go @@ -39,6 +39,10 @@ // infra.HPCfgReply -> ctrl.SignedPld/ctrl.Pld/path_mgmt.HPCfgReply // infra.ChainIssueRequest -> ctrl.SignedPld/ctrl.Pld/cert_mgmt.ChainIssReq // infra.ChainIssueReply -> ctrl.SignedPld/ctrl.Pld/cert_mgmt.ChainIssRep +// infra.DRKeyLvl1Request -> ctrl.SignedPld/ctrl.Pld/drkey_mgmt.Lvl1Req +// infra.DRKeyLvl1Reply -> ctrl.SignedPld/ctrl.Pld/drkey_mgmt.Lvl1Rep +// infra.DRKeyLvl2Request -> ctrl.SignedPld/ctrl.Pld/drkey_mgmt.Lvl2Req +// infra.DRKeyLvl2Reply -> ctrl.SignedPld/ctrl.Pld/drkey_mgmt.Lvl2Rep // // To start processing messages received via the Messenger, call // ListenAndServe. The method runs in the current goroutine, and spawns new @@ -92,6 +96,7 @@ import ( "github.com/scionproto/scion/go/lib/ctrl/ack" "github.com/scionproto/scion/go/lib/ctrl/cert_mgmt" "github.com/scionproto/scion/go/lib/ctrl/ctrl_msg" + "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" "github.com/scionproto/scion/go/lib/ctrl/ifid" "github.com/scionproto/scion/go/lib/ctrl/path_mgmt" "github.com/scionproto/scion/go/lib/ctrl/seg" @@ -717,6 +722,82 @@ func (m *Messenger) sendMessage(ctx context.Context, msg proto.Cerealizable, a n return err } +func (m *Messenger) RequestDRKeyLvl1(ctx context.Context, msg *drkey_mgmt.Lvl1Req, a net.Addr, + id uint64) (*drkey_mgmt.Lvl1Rep, error) { + + pld, err := ctrl.NewDRKeyMgmtPld(msg, nil, &ctrl.Data{ReqId: id}) + if err != nil { + return nil, err + } + log.Debug("[Messenger] Sending request", "req_type", infra.DRKeyLvl1Request, + "msg_id", id, "request", msg, "peer", a) + replyCtrlPld, err := + m.getFallbackRequester(infra.DRKeyLvl1Request).Request(ctx, pld, a, false) + if err != nil { + return nil, common.NewBasicError("[Messenger] Request error", err) + } + _, replyMsg, err := validate(replyCtrlPld) + if err != nil { + return nil, common.NewBasicError("[Messenger] Reply validation failed", err) + } + reply, ok := replyMsg.(*drkey_mgmt.Lvl1Rep) + if !ok { + err := newTypeAssertErr("*drkey_mgmt.Lvl1Rep", replyMsg) + return nil, common.NewBasicError("[Messenger] Type assertion failed", err) + } + log.Debug("[Messenger] Received reply") + return reply, nil +} + +func (m *Messenger) SendDRKeyLvl1(ctx context.Context, msg *drkey_mgmt.Lvl1Rep, a net.Addr, + id uint64) error { + + pld, err := ctrl.NewDRKeyMgmtPld(msg, nil, &ctrl.Data{ReqId: id}) + if err != nil { + return err + } + log.Debug("[Messenger] Sending Notify", "type", infra.DRKeyLvl1Request, "to", a, "id", id) + return m.getFallbackRequester(infra.DRKeyLvl1Request).Notify(ctx, pld, a) +} + +func (m *Messenger) RequestDRKeyLvl2(ctx context.Context, msg *drkey_mgmt.Lvl2Req, a net.Addr, + id uint64) (*drkey_mgmt.Lvl2Rep, error) { + + pld, err := ctrl.NewDRKeyMgmtPld(msg, nil, &ctrl.Data{ReqId: id}) + if err != nil { + return nil, err + } + log.Debug("[Messenger] Sending request", "req_type", infra.DRKeyLvl2Request, + "msg_id", id, "request", msg, "peer", a) + replyCtrlPld, err := + m.getFallbackRequester(infra.DRKeyLvl2Request).Request(ctx, pld, a, false) + if err != nil { + return nil, common.NewBasicError("[Messenger] Request error", err) + } + _, replyMsg, err := validate(replyCtrlPld) + if err != nil { + return nil, common.NewBasicError("[Messenger] Reply validation failed", err) + } + reply, ok := replyMsg.(*drkey_mgmt.Lvl2Rep) + if !ok { + err := newTypeAssertErr("*drkey_mgmt.Lvl2Rep", replyMsg) + return nil, common.NewBasicError("[Messenger] Type assertion failed", err) + } + log.Debug("[Messenger] Received reply") + return reply, nil +} + +func (m *Messenger) SendDRKeyLvl2(ctx context.Context, msg *drkey_mgmt.Lvl2Rep, a net.Addr, + id uint64) error { + + pld, err := ctrl.NewDRKeyMgmtPld(msg, nil, &ctrl.Data{ReqId: id}) + if err != nil { + return err + } + log.Debug("[Messenger] Sending Notify", "type", infra.DRKeyLvl2Request, "to", a, "id", id) + return m.getFallbackRequester(infra.DRKeyLvl2Request).Notify(ctx, pld, a) +} + // AddHandler registers a handler for msgType. func (m *Messenger) AddHandler(msgType infra.MessageType, handler infra.Handler) { m.handlersLock.Lock() @@ -1117,6 +1198,21 @@ func validate(pld *ctrl.Pld) (infra.MessageType, proto.Cerealizable, error) { } case proto.CtrlPld_Which_ack: return infra.Ack, pld.Ack, nil + case proto.CtrlPld_Which_drkeyMgmt: + switch pld.DRKeyMgmt.Which { + case proto.DRKeyMgmt_Which_drkeyLvl1Req: + return infra.DRKeyLvl1Request, pld.DRKeyMgmt.Lvl1Req, nil + case proto.DRKeyMgmt_Which_drkeyLvl1Rep: + return infra.DRKeyLvl1Reply, pld.DRKeyMgmt.Lvl1Rep, nil + case proto.DRKeyMgmt_Which_drkeyLvl2Req: + return infra.DRKeyLvl2Request, pld.DRKeyMgmt.Lvl2Req, nil + case proto.DRKeyMgmt_Which_drkeyLvl2Rep: + return infra.DRKeyLvl2Reply, pld.DRKeyMgmt.Lvl2Rep, nil + default: + return infra.None, nil, + common.NewBasicError("Unsupported SignedPld.CtrlPld.DRKeyMgmt.Xxx message type", + nil, "capnp_which", pld.DRKeyMgmt.Which) + } default: return infra.None, nil, common.NewBasicError("Unsupported SignedPld.Pld.Xxx message type", nil, "capnp_which", pld.Which) diff --git a/go/lib/infra/messenger/quic_response_writer.go b/go/lib/infra/messenger/quic_response_writer.go index 4928e4ed84..6b7f0a0ca3 100644 --- a/go/lib/infra/messenger/quic_response_writer.go +++ b/go/lib/infra/messenger/quic_response_writer.go @@ -20,6 +20,7 @@ import ( "github.com/scionproto/scion/go/lib/ctrl" "github.com/scionproto/scion/go/lib/ctrl/ack" "github.com/scionproto/scion/go/lib/ctrl/cert_mgmt" + "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" "github.com/scionproto/scion/go/lib/ctrl/path_mgmt" "github.com/scionproto/scion/go/lib/infra" "github.com/scionproto/scion/go/lib/infra/rpc" @@ -122,6 +123,19 @@ func (rw *QUICResponseWriter) SendHPSegReply(ctx context.Context, msg *path_mgmt return rw.sendMessage(ctrlPld) } +func (rw *QUICResponseWriter) SendDRKeyLvl1(ctx context.Context, msg *drkey_mgmt.Lvl1Rep) error { + go func() { + defer log.LogPanicAndExit() + <-ctx.Done() + rw.ReplyWriter.Close() + }() + ctrlPld, err := ctrl.NewDRKeyMgmtPld(msg, nil, &ctrl.Data{ReqId: rw.ID}) + if err != nil { + return err + } + return rw.sendMessage(ctrlPld) +} + func (rw *QUICResponseWriter) SendHPCfgReply(ctx context.Context, msg *path_mgmt.HPCfgReply) error { go func() { defer log.LogPanicAndExit() @@ -135,6 +149,19 @@ func (rw *QUICResponseWriter) SendHPCfgReply(ctx context.Context, msg *path_mgmt return rw.sendMessage(ctrlPld) } +func (rw *QUICResponseWriter) SendDRKeyLvl2(ctx context.Context, msg *drkey_mgmt.Lvl2Rep) error { + go func() { + defer log.LogPanicAndExit() + <-ctx.Done() + rw.ReplyWriter.Close() + }() + ctrlPld, err := ctrl.NewDRKeyMgmtPld(msg, nil, &ctrl.Data{ReqId: rw.ID}) + if err != nil { + return err + } + return rw.sendMessage(ctrlPld) +} + func (rw *QUICResponseWriter) sendMessage(ctrlPld *ctrl.Pld) error { signedCtrlPld, err := ctrlPld.SignedPld(infra.NullSigner) if err != nil { diff --git a/go/lib/infra/messenger/udp_response_writer.go b/go/lib/infra/messenger/udp_response_writer.go index 308799addb..da528bec6e 100644 --- a/go/lib/infra/messenger/udp_response_writer.go +++ b/go/lib/infra/messenger/udp_response_writer.go @@ -20,6 +20,7 @@ import ( "github.com/scionproto/scion/go/lib/ctrl/ack" "github.com/scionproto/scion/go/lib/ctrl/cert_mgmt" + "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" "github.com/scionproto/scion/go/lib/ctrl/path_mgmt" "github.com/scionproto/scion/go/lib/infra" ) @@ -67,3 +68,10 @@ func (rw *UDPResponseWriter) SendHPSegReply(ctx context.Context, msg *path_mgmt. func (rw *UDPResponseWriter) SendHPCfgReply(ctx context.Context, msg *path_mgmt.HPCfgReply) error { return rw.Messenger.SendHPCfgReply(ctx, msg, rw.Remote, rw.ID) } + +func (rw *UDPResponseWriter) SendDRKeyLvl1(ctx context.Context, msg *drkey_mgmt.Lvl1Rep) error { + return rw.Messenger.SendDRKeyLvl1(ctx, msg, rw.Remote, rw.ID) +} +func (rw *UDPResponseWriter) SendDRKeyLvl2(ctx context.Context, msg *drkey_mgmt.Lvl2Rep) error { + return rw.Messenger.SendDRKeyLvl2(ctx, msg, rw.Remote, rw.ID) +} diff --git a/go/lib/infra/mock_infra/BUILD.bazel b/go/lib/infra/mock_infra/BUILD.bazel index 7fa30c89e1..af26711e0f 100644 --- a/go/lib/infra/mock_infra/BUILD.bazel +++ b/go/lib/infra/mock_infra/BUILD.bazel @@ -11,6 +11,7 @@ go_library( "//go/lib/ctrl:go_default_library", "//go/lib/ctrl/ack:go_default_library", "//go/lib/ctrl/cert_mgmt:go_default_library", + "//go/lib/ctrl/drkey_mgmt:go_default_library", "//go/lib/ctrl/ifid:go_default_library", "//go/lib/ctrl/path_mgmt:go_default_library", "//go/lib/ctrl/seg:go_default_library", diff --git a/go/lib/infra/mock_infra/infra.go b/go/lib/infra/mock_infra/infra.go index 76b35e3586..abd834f34f 100644 --- a/go/lib/infra/mock_infra/infra.go +++ b/go/lib/infra/mock_infra/infra.go @@ -12,6 +12,7 @@ import ( ctrl "github.com/scionproto/scion/go/lib/ctrl" ack "github.com/scionproto/scion/go/lib/ctrl/ack" cert_mgmt "github.com/scionproto/scion/go/lib/ctrl/cert_mgmt" + drkey_mgmt "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" ifid "github.com/scionproto/scion/go/lib/ctrl/ifid" path_mgmt "github.com/scionproto/scion/go/lib/ctrl/path_mgmt" seg "github.com/scionproto/scion/go/lib/ctrl/seg" @@ -255,6 +256,36 @@ func (mr *MockMessengerMockRecorder) RequestChainIssue(arg0, arg1, arg2, arg3 in return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestChainIssue", reflect.TypeOf((*MockMessenger)(nil).RequestChainIssue), arg0, arg1, arg2, arg3) } +// RequestDRKeyLvl1 mocks base method +func (m *MockMessenger) RequestDRKeyLvl1(arg0 context.Context, arg1 *drkey_mgmt.Lvl1Req, arg2 net.Addr, arg3 uint64) (*drkey_mgmt.Lvl1Rep, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RequestDRKeyLvl1", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*drkey_mgmt.Lvl1Rep) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RequestDRKeyLvl1 indicates an expected call of RequestDRKeyLvl1 +func (mr *MockMessengerMockRecorder) RequestDRKeyLvl1(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestDRKeyLvl1", reflect.TypeOf((*MockMessenger)(nil).RequestDRKeyLvl1), arg0, arg1, arg2, arg3) +} + +// RequestDRKeyLvl2 mocks base method +func (m *MockMessenger) RequestDRKeyLvl2(arg0 context.Context, arg1 *drkey_mgmt.Lvl2Req, arg2 net.Addr, arg3 uint64) (*drkey_mgmt.Lvl2Rep, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RequestDRKeyLvl2", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*drkey_mgmt.Lvl2Rep) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RequestDRKeyLvl2 indicates an expected call of RequestDRKeyLvl2 +func (mr *MockMessengerMockRecorder) RequestDRKeyLvl2(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestDRKeyLvl2", reflect.TypeOf((*MockMessenger)(nil).RequestDRKeyLvl2), arg0, arg1, arg2, arg3) +} + // SendAck mocks base method func (m *MockMessenger) SendAck(arg0 context.Context, arg1 *ack.Ack, arg2 net.Addr, arg3 uint64) error { m.ctrl.T.Helper() @@ -311,6 +342,34 @@ func (mr *MockMessengerMockRecorder) SendChainIssueReply(arg0, arg1, arg2, arg3 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendChainIssueReply", reflect.TypeOf((*MockMessenger)(nil).SendChainIssueReply), arg0, arg1, arg2, arg3) } +// SendDRKeyLvl1 mocks base method +func (m *MockMessenger) SendDRKeyLvl1(arg0 context.Context, arg1 *drkey_mgmt.Lvl1Rep, arg2 net.Addr, arg3 uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendDRKeyLvl1", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendDRKeyLvl1 indicates an expected call of SendDRKeyLvl1 +func (mr *MockMessengerMockRecorder) SendDRKeyLvl1(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendDRKeyLvl1", reflect.TypeOf((*MockMessenger)(nil).SendDRKeyLvl1), arg0, arg1, arg2, arg3) +} + +// SendDRKeyLvl2 mocks base method +func (m *MockMessenger) SendDRKeyLvl2(arg0 context.Context, arg1 *drkey_mgmt.Lvl2Rep, arg2 net.Addr, arg3 uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendDRKeyLvl2", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendDRKeyLvl2 indicates an expected call of SendDRKeyLvl2 +func (mr *MockMessengerMockRecorder) SendDRKeyLvl2(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendDRKeyLvl2", reflect.TypeOf((*MockMessenger)(nil).SendDRKeyLvl2), arg0, arg1, arg2, arg3) +} + // SendHPCfgReply mocks base method func (m *MockMessenger) SendHPCfgReply(arg0 context.Context, arg1 *path_mgmt.HPCfgReply, arg2 net.Addr, arg3 uint64) error { m.ctrl.T.Helper() @@ -568,6 +627,34 @@ func (mr *MockResponseWriterMockRecorder) SendChainIssueReply(arg0, arg1 interfa return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendChainIssueReply", reflect.TypeOf((*MockResponseWriter)(nil).SendChainIssueReply), arg0, arg1) } +// SendDRKeyLvl1 mocks base method +func (m *MockResponseWriter) SendDRKeyLvl1(arg0 context.Context, arg1 *drkey_mgmt.Lvl1Rep) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendDRKeyLvl1", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendDRKeyLvl1 indicates an expected call of SendDRKeyLvl1 +func (mr *MockResponseWriterMockRecorder) SendDRKeyLvl1(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendDRKeyLvl1", reflect.TypeOf((*MockResponseWriter)(nil).SendDRKeyLvl1), arg0, arg1) +} + +// SendDRKeyLvl2 mocks base method +func (m *MockResponseWriter) SendDRKeyLvl2(arg0 context.Context, arg1 *drkey_mgmt.Lvl2Rep) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendDRKeyLvl2", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendDRKeyLvl2 indicates an expected call of SendDRKeyLvl2 +func (mr *MockResponseWriterMockRecorder) SendDRKeyLvl2(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendDRKeyLvl2", reflect.TypeOf((*MockResponseWriter)(nil).SendDRKeyLvl2), arg0, arg1) +} + // SendHPCfgReply mocks base method func (m *MockResponseWriter) SendHPCfgReply(arg0 context.Context, arg1 *path_mgmt.HPCfgReply) error { m.ctrl.T.Helper() diff --git a/go/lib/log/BUILD.bazel b/go/lib/log/BUILD.bazel index 91a3ade81e..73fc506b05 100644 --- a/go/lib/log/BUILD.bazel +++ b/go/lib/log/BUILD.bazel @@ -17,7 +17,6 @@ go_library( "//go/lib/serrors:go_default_library", "@com_github_inconshreveable_log15//:go_default_library", "@com_github_inconshreveable_log15//ext:go_default_library", - #"@com_github_inconshreveable_log15_ext//:go_default_library", "@com_github_kormat_fmt15//:go_default_library", "@com_github_mattn_go_isatty//:go_default_library", "@com_github_opentracing_opentracing_go//:go_default_library", diff --git a/go/lib/sciond/BUILD.bazel b/go/lib/sciond/BUILD.bazel index f2e2ce5106..c23b84d3cc 100644 --- a/go/lib/sciond/BUILD.bazel +++ b/go/lib/sciond/BUILD.bazel @@ -13,7 +13,9 @@ go_library( deps = [ "//go/lib/addr:go_default_library", "//go/lib/common:go_default_library", + "//go/lib/ctrl/drkey_mgmt:go_default_library", "//go/lib/ctrl/path_mgmt:go_default_library", + "//go/lib/drkey:go_default_library", "//go/lib/hostinfo:go_default_library", "//go/lib/infra/disp:go_default_library", "//go/lib/log:go_default_library", diff --git a/go/lib/sciond/fake/BUILD.bazel b/go/lib/sciond/fake/BUILD.bazel index e149000815..015fb63564 100644 --- a/go/lib/sciond/fake/BUILD.bazel +++ b/go/lib/sciond/fake/BUILD.bazel @@ -9,6 +9,7 @@ go_library( "//go/lib/addr:go_default_library", "//go/lib/common:go_default_library", "//go/lib/ctrl/path_mgmt:go_default_library", + "//go/lib/drkey:go_default_library", "//go/lib/sciond:go_default_library", "//go/lib/serrors:go_default_library", "//go/lib/snet:go_default_library", diff --git a/go/lib/sciond/fake/fake.go b/go/lib/sciond/fake/fake.go index 54e2570cfb..502beab047 100644 --- a/go/lib/sciond/fake/fake.go +++ b/go/lib/sciond/fake/fake.go @@ -13,6 +13,7 @@ import ( "github.com/scionproto/scion/go/lib/addr" "github.com/scionproto/scion/go/lib/common" "github.com/scionproto/scion/go/lib/ctrl/path_mgmt" + "github.com/scionproto/scion/go/lib/drkey" "github.com/scionproto/scion/go/lib/sciond" "github.com/scionproto/scion/go/lib/serrors" "github.com/scionproto/scion/go/lib/snet" @@ -221,6 +222,12 @@ func (c connector) RevNotification(ctx context.Context, panic("not implemented") } +func (c connector) DRKeyGetLvl2Key(ctx context.Context, + meta drkey.Lvl2Meta, valTime uint32) (drkey.Lvl2Key, error) { + + panic("not implemented") +} + func (c connector) Close(ctx context.Context) error { return nil } diff --git a/go/lib/sciond/mock_sciond/BUILD.bazel b/go/lib/sciond/mock_sciond/BUILD.bazel index 35ac0eb938..280af974dc 100644 --- a/go/lib/sciond/mock_sciond/BUILD.bazel +++ b/go/lib/sciond/mock_sciond/BUILD.bazel @@ -9,6 +9,7 @@ go_library( "//go/lib/addr:go_default_library", "//go/lib/common:go_default_library", "//go/lib/ctrl/path_mgmt:go_default_library", + "//go/lib/drkey:go_default_library", "//go/lib/sciond:go_default_library", "//go/lib/snet:go_default_library", "//go/proto:go_default_library", diff --git a/go/lib/sciond/mock_sciond/sciond.go b/go/lib/sciond/mock_sciond/sciond.go index c018cf4750..2b14f6c215 100644 --- a/go/lib/sciond/mock_sciond/sciond.go +++ b/go/lib/sciond/mock_sciond/sciond.go @@ -10,6 +10,7 @@ import ( addr "github.com/scionproto/scion/go/lib/addr" common "github.com/scionproto/scion/go/lib/common" path_mgmt "github.com/scionproto/scion/go/lib/ctrl/path_mgmt" + drkey "github.com/scionproto/scion/go/lib/drkey" sciond "github.com/scionproto/scion/go/lib/sciond" snet "github.com/scionproto/scion/go/lib/snet" proto "github.com/scionproto/scion/go/proto" @@ -107,6 +108,21 @@ func (mr *MockConnectorMockRecorder) Close(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockConnector)(nil).Close), arg0) } +// DRKeyGetLvl2Key mocks base method +func (m *MockConnector) DRKeyGetLvl2Key(arg0 context.Context, arg1 drkey.Lvl2Meta, arg2 uint32) (drkey.Lvl2Key, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DRKeyGetLvl2Key", arg0, arg1, arg2) + ret0, _ := ret[0].(drkey.Lvl2Key) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DRKeyGetLvl2Key indicates an expected call of DRKeyGetLvl2Key +func (mr *MockConnectorMockRecorder) DRKeyGetLvl2Key(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DRKeyGetLvl2Key", reflect.TypeOf((*MockConnector)(nil).DRKeyGetLvl2Key), arg0, arg1, arg2) +} + // IFInfo mocks base method func (m *MockConnector) IFInfo(arg0 context.Context, arg1 []common.IFIDType) (map[common.IFIDType]*net.UDPAddr, error) { m.ctrl.T.Helper() diff --git a/go/lib/sciond/sciond.go b/go/lib/sciond/sciond.go index 0f8c19cd11..bd96ab3782 100644 --- a/go/lib/sciond/sciond.go +++ b/go/lib/sciond/sciond.go @@ -34,7 +34,9 @@ import ( "github.com/scionproto/scion/go/lib/addr" "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" "github.com/scionproto/scion/go/lib/ctrl/path_mgmt" + "github.com/scionproto/scion/go/lib/drkey" "github.com/scionproto/scion/go/lib/infra/disp" "github.com/scionproto/scion/go/lib/log" "github.com/scionproto/scion/go/lib/sciond/internal/metrics" @@ -101,6 +103,8 @@ type Connector interface { RevNotificationFromRaw(ctx context.Context, b []byte) (*RevReply, error) // RevNotification sends a RevocationInfo message to SCIOND. RevNotification(ctx context.Context, sRevInfo *path_mgmt.SignedRevInfo) (*RevReply, error) + // DRKey Level 2 request + DRKeyGetLvl2Key(ctx context.Context, meta drkey.Lvl2Meta, valTime uint32) (drkey.Lvl2Key, error) // Close shuts down the connection to a SCIOND server. Close(ctx context.Context) error } @@ -327,6 +331,42 @@ func dial(ctx context.Context, socketName string) (*disp.Dispatcher, error) { ), nil } +func (c *conn) DRKeyGetLvl2Key(ctx context.Context, + meta drkey.Lvl2Meta, valTime uint32) (drkey.Lvl2Key, error) { + + roundTripper, err := c.ctxAwareConnect(ctx) + if err != nil { + metrics.Revocations.Inc(errorToPrometheusLabel(err)) + return drkey.Lvl2Key{}, serrors.Wrap(ErrUnableToConnect, err) + } + defer roundTripper.Close(ctx) + reply, err := roundTripper.Request( + ctx, + &Pld{ + Which: proto.SCIONDMsg_Which_drkeyLvl2Req, + DRKeyLvl2Req: &drkey_mgmt.Lvl2Req{ + Protocol: meta.Protocol, + ReqType: uint8(meta.KeyType), + ValTimeRaw: valTime, + SrcIARaw: meta.SrcIA.IAInt(), + DstIARaw: meta.DstIA.IAInt(), + SrcHost: drkey_mgmt.NewHost(meta.SrcHost), + DstHost: drkey_mgmt.NewHost(meta.DstHost), + }, + }, + nil, + ) + // TODO(juagargi): return error istead of timing out, when an error + // occurs such as protocol not found + if err != nil { + return drkey.Lvl2Key{}, common.NewBasicError( + "[sciond-API] Failed to send DRKeyLvl2Req", err, + "meta", meta, "valTime", valTime) + } + lvl2rep := reply.(*Pld).DRKeyLvl2Rep + return lvl2rep.ToKey(meta), nil +} + // GetDefaultSCIONDPath return default sciond path for a given IA func GetDefaultSCIONDPath(ia *addr.IA) string { if ia == nil || ia.IsZero() { diff --git a/go/lib/sciond/types.go b/go/lib/sciond/types.go index e0f7eb05ca..7dd60a7834 100644 --- a/go/lib/sciond/types.go +++ b/go/lib/sciond/types.go @@ -22,6 +22,7 @@ import ( "github.com/scionproto/scion/go/lib/addr" "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" "github.com/scionproto/scion/go/lib/ctrl/path_mgmt" "github.com/scionproto/scion/go/lib/hostinfo" "github.com/scionproto/scion/go/lib/util" @@ -74,6 +75,8 @@ type Pld struct { IfInfoReply *IFInfoReply ServiceInfoRequest *ServiceInfoRequest ServiceInfoReply *ServiceInfoReply + DRKeyLvl2Req *drkey_mgmt.Lvl2Req `capnp:"drkeyLvl2Req"` + DRKeyLvl2Rep *drkey_mgmt.Lvl2Rep `capnp:"drkeyLvl2Rep"` } func NewPldFromRaw(b common.RawBytes) (*Pld, error) { @@ -118,6 +121,10 @@ func (p *Pld) union() (interface{}, error) { return p.ServiceInfoRequest, nil case proto.SCIONDMsg_Which_serviceInfoReply: return p.ServiceInfoReply, nil + case proto.SCIONDMsg_Which_drkeyLvl2Req: + return p.DRKeyLvl2Req, nil + case proto.SCIONDMsg_Which_drkeyLvl2Rep: + return p.DRKeyLvl2Rep, nil } return nil, common.NewBasicError("Unsupported SCIOND union type", nil, "type", p.Which) } diff --git a/go/lib/util/time.go b/go/lib/util/time.go index 1a679b9d59..6527642ed3 100644 --- a/go/lib/util/time.go +++ b/go/lib/util/time.go @@ -80,3 +80,12 @@ func TimeToCompact(t time.Time) string { } return TimeToString(t) } + +// TimeDistance returns the distance as a positive time duration between two points in time. +func TimeDistance(a, b time.Time) time.Duration { + d := a.Sub(b) + if d < 0 { + d = -d + } + return d +} diff --git a/go/proto/drkey_mgmt.capnp.go b/go/proto/drkey_mgmt.capnp.go index 344162dff8..d2703ff3f2 100644 --- a/go/proto/drkey_mgmt.capnp.go +++ b/go/proto/drkey_mgmt.capnp.go @@ -9,269 +9,574 @@ import ( schemas "zombiezen.com/go/capnproto2/schemas" ) -type DRKeyReq struct{ capnp.Struct } -type DRKeyReq_flags DRKeyReq +type DRKeyLvl1Req struct{ capnp.Struct } -// DRKeyReq_TypeID is the unique identifier for the type DRKeyReq. -const DRKeyReq_TypeID = 0x9f50d21c9d4ce7ef +// DRKeyLvl1Req_TypeID is the unique identifier for the type DRKeyLvl1Req. +const DRKeyLvl1Req_TypeID = 0xfa255f8dc1ac13e3 -func NewDRKeyReq(s *capnp.Segment) (DRKeyReq, error) { - st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 24, PointerCount: 1}) - return DRKeyReq{st}, err +func NewDRKeyLvl1Req(s *capnp.Segment) (DRKeyLvl1Req, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 0}) + return DRKeyLvl1Req{st}, err } -func NewRootDRKeyReq(s *capnp.Segment) (DRKeyReq, error) { - st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 24, PointerCount: 1}) - return DRKeyReq{st}, err +func NewRootDRKeyLvl1Req(s *capnp.Segment) (DRKeyLvl1Req, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 0}) + return DRKeyLvl1Req{st}, err } -func ReadRootDRKeyReq(msg *capnp.Message) (DRKeyReq, error) { +func ReadRootDRKeyLvl1Req(msg *capnp.Message) (DRKeyLvl1Req, error) { root, err := msg.RootPtr() - return DRKeyReq{root.Struct()}, err + return DRKeyLvl1Req{root.Struct()}, err } -func (s DRKeyReq) String() string { - str, _ := text.Marshal(0x9f50d21c9d4ce7ef, s.Struct) +func (s DRKeyLvl1Req) String() string { + str, _ := text.Marshal(0xfa255f8dc1ac13e3, s.Struct) return str } -func (s DRKeyReq) Isdas() uint64 { +func (s DRKeyLvl1Req) DstIA() uint64 { return s.Struct.Uint64(0) } -func (s DRKeyReq) SetIsdas(v uint64) { +func (s DRKeyLvl1Req) SetDstIA(v uint64) { s.Struct.SetUint64(0, v) } -func (s DRKeyReq) Timestamp() uint32 { +func (s DRKeyLvl1Req) ValTime() uint32 { return s.Struct.Uint32(8) } -func (s DRKeyReq) SetTimestamp(v uint32) { +func (s DRKeyLvl1Req) SetValTime(v uint32) { s.Struct.SetUint32(8, v) } -func (s DRKeyReq) Signature() ([]byte, error) { +func (s DRKeyLvl1Req) Timestamp() uint32 { + return s.Struct.Uint32(12) +} + +func (s DRKeyLvl1Req) SetTimestamp(v uint32) { + s.Struct.SetUint32(12, v) +} + +// DRKeyLvl1Req_List is a list of DRKeyLvl1Req. +type DRKeyLvl1Req_List struct{ capnp.List } + +// NewDRKeyLvl1Req creates a new list of DRKeyLvl1Req. +func NewDRKeyLvl1Req_List(s *capnp.Segment, sz int32) (DRKeyLvl1Req_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 16, PointerCount: 0}, sz) + return DRKeyLvl1Req_List{l}, err +} + +func (s DRKeyLvl1Req_List) At(i int) DRKeyLvl1Req { return DRKeyLvl1Req{s.List.Struct(i)} } + +func (s DRKeyLvl1Req_List) Set(i int, v DRKeyLvl1Req) error { return s.List.SetStruct(i, v.Struct) } + +func (s DRKeyLvl1Req_List) String() string { + str, _ := text.MarshalList(0xfa255f8dc1ac13e3, s.List) + return str +} + +// DRKeyLvl1Req_Promise is a wrapper for a DRKeyLvl1Req promised by a client call. +type DRKeyLvl1Req_Promise struct{ *capnp.Pipeline } + +func (p DRKeyLvl1Req_Promise) Struct() (DRKeyLvl1Req, error) { + s, err := p.Pipeline.Struct() + return DRKeyLvl1Req{s}, err +} + +type DRKeyLvl1Rep struct{ capnp.Struct } + +// DRKeyLvl1Rep_TypeID is the unique identifier for the type DRKeyLvl1Rep. +const DRKeyLvl1Rep_TypeID = 0xd70d7b2bf8abab14 + +func NewDRKeyLvl1Rep(s *capnp.Segment) (DRKeyLvl1Rep, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 32, PointerCount: 2}) + return DRKeyLvl1Rep{st}, err +} + +func NewRootDRKeyLvl1Rep(s *capnp.Segment) (DRKeyLvl1Rep, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 32, PointerCount: 2}) + return DRKeyLvl1Rep{st}, err +} + +func ReadRootDRKeyLvl1Rep(msg *capnp.Message) (DRKeyLvl1Rep, error) { + root, err := msg.RootPtr() + return DRKeyLvl1Rep{root.Struct()}, err +} + +func (s DRKeyLvl1Rep) String() string { + str, _ := text.Marshal(0xd70d7b2bf8abab14, s.Struct) + return str +} + +func (s DRKeyLvl1Rep) DstIA() uint64 { + return s.Struct.Uint64(0) +} + +func (s DRKeyLvl1Rep) SetDstIA(v uint64) { + s.Struct.SetUint64(0, v) +} + +func (s DRKeyLvl1Rep) EpochBegin() uint32 { + return s.Struct.Uint32(8) +} + +func (s DRKeyLvl1Rep) SetEpochBegin(v uint32) { + s.Struct.SetUint32(8, v) +} + +func (s DRKeyLvl1Rep) EpochEnd() uint32 { + return s.Struct.Uint32(12) +} + +func (s DRKeyLvl1Rep) SetEpochEnd(v uint32) { + s.Struct.SetUint32(12, v) +} + +func (s DRKeyLvl1Rep) Cipher() ([]byte, error) { p, err := s.Struct.Ptr(0) return []byte(p.Data()), err } -func (s DRKeyReq) HasSignature() bool { +func (s DRKeyLvl1Rep) HasCipher() bool { p, err := s.Struct.Ptr(0) return p.IsValid() || err != nil } -func (s DRKeyReq) SetSignature(v []byte) error { +func (s DRKeyLvl1Rep) SetCipher(v []byte) error { return s.Struct.SetData(0, v) } -func (s DRKeyReq) CertVer() uint32 { - return s.Struct.Uint32(12) +func (s DRKeyLvl1Rep) Nonce() ([]byte, error) { + p, err := s.Struct.Ptr(1) + return []byte(p.Data()), err } -func (s DRKeyReq) SetCertVer(v uint32) { - s.Struct.SetUint32(12, v) +func (s DRKeyLvl1Rep) HasNonce() bool { + p, err := s.Struct.Ptr(1) + return p.IsValid() || err != nil } -func (s DRKeyReq) TrcVer() uint32 { - return s.Struct.Uint32(16) +func (s DRKeyLvl1Rep) SetNonce(v []byte) error { + return s.Struct.SetData(1, v) } -func (s DRKeyReq) SetTrcVer(v uint32) { - s.Struct.SetUint32(16, v) +func (s DRKeyLvl1Rep) CertVerDst() uint64 { + return s.Struct.Uint64(16) } -func (s DRKeyReq) Flags() DRKeyReq_flags { return DRKeyReq_flags(s) } +func (s DRKeyLvl1Rep) SetCertVerDst(v uint64) { + s.Struct.SetUint64(16, v) +} -func (s DRKeyReq_flags) Prefetch() bool { - return s.Struct.Bit(160) +func (s DRKeyLvl1Rep) Timestamp() uint32 { + return s.Struct.Uint32(24) } -func (s DRKeyReq_flags) SetPrefetch(v bool) { - s.Struct.SetBit(160, v) +func (s DRKeyLvl1Rep) SetTimestamp(v uint32) { + s.Struct.SetUint32(24, v) } -// DRKeyReq_List is a list of DRKeyReq. -type DRKeyReq_List struct{ capnp.List } +// DRKeyLvl1Rep_List is a list of DRKeyLvl1Rep. +type DRKeyLvl1Rep_List struct{ capnp.List } -// NewDRKeyReq creates a new list of DRKeyReq. -func NewDRKeyReq_List(s *capnp.Segment, sz int32) (DRKeyReq_List, error) { - l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 24, PointerCount: 1}, sz) - return DRKeyReq_List{l}, err +// NewDRKeyLvl1Rep creates a new list of DRKeyLvl1Rep. +func NewDRKeyLvl1Rep_List(s *capnp.Segment, sz int32) (DRKeyLvl1Rep_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 32, PointerCount: 2}, sz) + return DRKeyLvl1Rep_List{l}, err } -func (s DRKeyReq_List) At(i int) DRKeyReq { return DRKeyReq{s.List.Struct(i)} } +func (s DRKeyLvl1Rep_List) At(i int) DRKeyLvl1Rep { return DRKeyLvl1Rep{s.List.Struct(i)} } -func (s DRKeyReq_List) Set(i int, v DRKeyReq) error { return s.List.SetStruct(i, v.Struct) } +func (s DRKeyLvl1Rep_List) Set(i int, v DRKeyLvl1Rep) error { return s.List.SetStruct(i, v.Struct) } -func (s DRKeyReq_List) String() string { - str, _ := text.MarshalList(0x9f50d21c9d4ce7ef, s.List) +func (s DRKeyLvl1Rep_List) String() string { + str, _ := text.MarshalList(0xd70d7b2bf8abab14, s.List) return str } -// DRKeyReq_Promise is a wrapper for a DRKeyReq promised by a client call. -type DRKeyReq_Promise struct{ *capnp.Pipeline } +// DRKeyLvl1Rep_Promise is a wrapper for a DRKeyLvl1Rep promised by a client call. +type DRKeyLvl1Rep_Promise struct{ *capnp.Pipeline } -func (p DRKeyReq_Promise) Struct() (DRKeyReq, error) { +func (p DRKeyLvl1Rep_Promise) Struct() (DRKeyLvl1Rep, error) { s, err := p.Pipeline.Struct() - return DRKeyReq{s}, err + return DRKeyLvl1Rep{s}, err +} + +type DRKeyHost struct{ capnp.Struct } + +// DRKeyHost_TypeID is the unique identifier for the type DRKeyHost. +const DRKeyHost_TypeID = 0x929b462d5a0499b7 + +func NewDRKeyHost(s *capnp.Segment) (DRKeyHost, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1}) + return DRKeyHost{st}, err +} + +func NewRootDRKeyHost(s *capnp.Segment) (DRKeyHost, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1}) + return DRKeyHost{st}, err +} + +func ReadRootDRKeyHost(msg *capnp.Message) (DRKeyHost, error) { + root, err := msg.RootPtr() + return DRKeyHost{root.Struct()}, err +} + +func (s DRKeyHost) String() string { + str, _ := text.Marshal(0x929b462d5a0499b7, s.Struct) + return str +} + +func (s DRKeyHost) Type() uint8 { + return s.Struct.Uint8(0) +} + +func (s DRKeyHost) SetType(v uint8) { + s.Struct.SetUint8(0, v) +} + +func (s DRKeyHost) Host() ([]byte, error) { + p, err := s.Struct.Ptr(0) + return []byte(p.Data()), err +} + +func (s DRKeyHost) HasHost() bool { + p, err := s.Struct.Ptr(0) + return p.IsValid() || err != nil +} + +func (s DRKeyHost) SetHost(v []byte) error { + return s.Struct.SetData(0, v) } -func (p DRKeyReq_Promise) Flags() DRKeyReq_flags_Promise { return DRKeyReq_flags_Promise{p.Pipeline} } +// DRKeyHost_List is a list of DRKeyHost. +type DRKeyHost_List struct{ capnp.List } -// DRKeyReq_flags_Promise is a wrapper for a DRKeyReq_flags promised by a client call. -type DRKeyReq_flags_Promise struct{ *capnp.Pipeline } +// NewDRKeyHost creates a new list of DRKeyHost. +func NewDRKeyHost_List(s *capnp.Segment, sz int32) (DRKeyHost_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 1}, sz) + return DRKeyHost_List{l}, err +} + +func (s DRKeyHost_List) At(i int) DRKeyHost { return DRKeyHost{s.List.Struct(i)} } + +func (s DRKeyHost_List) Set(i int, v DRKeyHost) error { return s.List.SetStruct(i, v.Struct) } + +func (s DRKeyHost_List) String() string { + str, _ := text.MarshalList(0x929b462d5a0499b7, s.List) + return str +} + +// DRKeyHost_Promise is a wrapper for a DRKeyHost promised by a client call. +type DRKeyHost_Promise struct{ *capnp.Pipeline } -func (p DRKeyReq_flags_Promise) Struct() (DRKeyReq_flags, error) { +func (p DRKeyHost_Promise) Struct() (DRKeyHost, error) { s, err := p.Pipeline.Struct() - return DRKeyReq_flags{s}, err + return DRKeyHost{s}, err } -type DRKeyRep struct{ capnp.Struct } +type DRKeyLvl2Req struct{ capnp.Struct } -// DRKeyRep_TypeID is the unique identifier for the type DRKeyRep. -const DRKeyRep_TypeID = 0xc3fe25dd82681d64 +// DRKeyLvl2Req_TypeID is the unique identifier for the type DRKeyLvl2Req. +const DRKeyLvl2Req_TypeID = 0xe5a448baf4040d94 -func NewDRKeyRep(s *capnp.Segment) (DRKeyRep, error) { - st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 32, PointerCount: 2}) - return DRKeyRep{st}, err +func NewDRKeyLvl2Req(s *capnp.Segment) (DRKeyLvl2Req, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 24, PointerCount: 4}) + return DRKeyLvl2Req{st}, err } -func NewRootDRKeyRep(s *capnp.Segment) (DRKeyRep, error) { - st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 32, PointerCount: 2}) - return DRKeyRep{st}, err +func NewRootDRKeyLvl2Req(s *capnp.Segment) (DRKeyLvl2Req, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 24, PointerCount: 4}) + return DRKeyLvl2Req{st}, err } -func ReadRootDRKeyRep(msg *capnp.Message) (DRKeyRep, error) { +func ReadRootDRKeyLvl2Req(msg *capnp.Message) (DRKeyLvl2Req, error) { root, err := msg.RootPtr() - return DRKeyRep{root.Struct()}, err + return DRKeyLvl2Req{root.Struct()}, err } -func (s DRKeyRep) String() string { - str, _ := text.Marshal(0xc3fe25dd82681d64, s.Struct) +func (s DRKeyLvl2Req) String() string { + str, _ := text.Marshal(0xe5a448baf4040d94, s.Struct) return str } -func (s DRKeyRep) Isdas() uint64 { - return s.Struct.Uint64(0) +func (s DRKeyLvl2Req) Protocol() (string, error) { + p, err := s.Struct.Ptr(0) + return p.Text(), err } -func (s DRKeyRep) SetIsdas(v uint64) { - s.Struct.SetUint64(0, v) +func (s DRKeyLvl2Req) HasProtocol() bool { + p, err := s.Struct.Ptr(0) + return p.IsValid() || err != nil } -func (s DRKeyRep) Timestamp() uint32 { - return s.Struct.Uint32(8) +func (s DRKeyLvl2Req) ProtocolBytes() ([]byte, error) { + p, err := s.Struct.Ptr(0) + return p.TextBytes(), err } -func (s DRKeyRep) SetTimestamp(v uint32) { - s.Struct.SetUint32(8, v) +func (s DRKeyLvl2Req) SetProtocol(v string) error { + return s.Struct.SetText(0, v) } -func (s DRKeyRep) ExpTime() uint32 { - return s.Struct.Uint32(12) +func (s DRKeyLvl2Req) ReqType() uint8 { + return s.Struct.Uint8(0) } -func (s DRKeyRep) SetExpTime(v uint32) { - s.Struct.SetUint32(12, v) +func (s DRKeyLvl2Req) SetReqType(v uint8) { + s.Struct.SetUint8(0, v) } -func (s DRKeyRep) Cipher() ([]byte, error) { - p, err := s.Struct.Ptr(0) +func (s DRKeyLvl2Req) ValTime() uint32 { + return s.Struct.Uint32(4) +} + +func (s DRKeyLvl2Req) SetValTime(v uint32) { + s.Struct.SetUint32(4, v) +} + +func (s DRKeyLvl2Req) SrcIA() uint64 { + return s.Struct.Uint64(8) +} + +func (s DRKeyLvl2Req) SetSrcIA(v uint64) { + s.Struct.SetUint64(8, v) +} + +func (s DRKeyLvl2Req) DstIA() uint64 { + return s.Struct.Uint64(16) +} + +func (s DRKeyLvl2Req) SetDstIA(v uint64) { + s.Struct.SetUint64(16, v) +} + +func (s DRKeyLvl2Req) SrcHost() (DRKeyHost, error) { + p, err := s.Struct.Ptr(1) + return DRKeyHost{Struct: p.Struct()}, err +} + +func (s DRKeyLvl2Req) HasSrcHost() bool { + p, err := s.Struct.Ptr(1) + return p.IsValid() || err != nil +} + +func (s DRKeyLvl2Req) SetSrcHost(v DRKeyHost) error { + return s.Struct.SetPtr(1, v.Struct.ToPtr()) +} + +// NewSrcHost sets the srcHost field to a newly +// allocated DRKeyHost struct, preferring placement in s's segment. +func (s DRKeyLvl2Req) NewSrcHost() (DRKeyHost, error) { + ss, err := NewDRKeyHost(s.Struct.Segment()) + if err != nil { + return DRKeyHost{}, err + } + err = s.Struct.SetPtr(1, ss.Struct.ToPtr()) + return ss, err +} + +func (s DRKeyLvl2Req) DstHost() (DRKeyHost, error) { + p, err := s.Struct.Ptr(2) + return DRKeyHost{Struct: p.Struct()}, err +} + +func (s DRKeyLvl2Req) HasDstHost() bool { + p, err := s.Struct.Ptr(2) + return p.IsValid() || err != nil +} + +func (s DRKeyLvl2Req) SetDstHost(v DRKeyHost) error { + return s.Struct.SetPtr(2, v.Struct.ToPtr()) +} + +// NewDstHost sets the dstHost field to a newly +// allocated DRKeyHost struct, preferring placement in s's segment. +func (s DRKeyLvl2Req) NewDstHost() (DRKeyHost, error) { + ss, err := NewDRKeyHost(s.Struct.Segment()) + if err != nil { + return DRKeyHost{}, err + } + err = s.Struct.SetPtr(2, ss.Struct.ToPtr()) + return ss, err +} + +func (s DRKeyLvl2Req) Misc() ([]byte, error) { + p, err := s.Struct.Ptr(3) return []byte(p.Data()), err } -func (s DRKeyRep) HasCipher() bool { - p, err := s.Struct.Ptr(0) +func (s DRKeyLvl2Req) HasMisc() bool { + p, err := s.Struct.Ptr(3) return p.IsValid() || err != nil } -func (s DRKeyRep) SetCipher(v []byte) error { - return s.Struct.SetData(0, v) +func (s DRKeyLvl2Req) SetMisc(v []byte) error { + return s.Struct.SetData(3, v) } -func (s DRKeyRep) Signature() ([]byte, error) { - p, err := s.Struct.Ptr(1) +// DRKeyLvl2Req_List is a list of DRKeyLvl2Req. +type DRKeyLvl2Req_List struct{ capnp.List } + +// NewDRKeyLvl2Req creates a new list of DRKeyLvl2Req. +func NewDRKeyLvl2Req_List(s *capnp.Segment, sz int32) (DRKeyLvl2Req_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 24, PointerCount: 4}, sz) + return DRKeyLvl2Req_List{l}, err +} + +func (s DRKeyLvl2Req_List) At(i int) DRKeyLvl2Req { return DRKeyLvl2Req{s.List.Struct(i)} } + +func (s DRKeyLvl2Req_List) Set(i int, v DRKeyLvl2Req) error { return s.List.SetStruct(i, v.Struct) } + +func (s DRKeyLvl2Req_List) String() string { + str, _ := text.MarshalList(0xe5a448baf4040d94, s.List) + return str +} + +// DRKeyLvl2Req_Promise is a wrapper for a DRKeyLvl2Req promised by a client call. +type DRKeyLvl2Req_Promise struct{ *capnp.Pipeline } + +func (p DRKeyLvl2Req_Promise) Struct() (DRKeyLvl2Req, error) { + s, err := p.Pipeline.Struct() + return DRKeyLvl2Req{s}, err +} + +func (p DRKeyLvl2Req_Promise) SrcHost() DRKeyHost_Promise { + return DRKeyHost_Promise{Pipeline: p.Pipeline.GetPipeline(1)} +} + +func (p DRKeyLvl2Req_Promise) DstHost() DRKeyHost_Promise { + return DRKeyHost_Promise{Pipeline: p.Pipeline.GetPipeline(2)} +} + +type DRKeyLvl2Rep struct{ capnp.Struct } + +// DRKeyLvl2Rep_TypeID is the unique identifier for the type DRKeyLvl2Rep. +const DRKeyLvl2Rep_TypeID = 0xdfa735fef0302b84 + +func NewDRKeyLvl2Rep(s *capnp.Segment) (DRKeyLvl2Rep, error) { + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 2}) + return DRKeyLvl2Rep{st}, err +} + +func NewRootDRKeyLvl2Rep(s *capnp.Segment) (DRKeyLvl2Rep, error) { + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 2}) + return DRKeyLvl2Rep{st}, err +} + +func ReadRootDRKeyLvl2Rep(msg *capnp.Message) (DRKeyLvl2Rep, error) { + root, err := msg.RootPtr() + return DRKeyLvl2Rep{root.Struct()}, err +} + +func (s DRKeyLvl2Rep) String() string { + str, _ := text.Marshal(0xdfa735fef0302b84, s.Struct) + return str +} + +func (s DRKeyLvl2Rep) Timestamp() uint32 { + return s.Struct.Uint32(0) +} + +func (s DRKeyLvl2Rep) SetTimestamp(v uint32) { + s.Struct.SetUint32(0, v) +} + +func (s DRKeyLvl2Rep) Drkey() ([]byte, error) { + p, err := s.Struct.Ptr(0) return []byte(p.Data()), err } -func (s DRKeyRep) HasSignature() bool { - p, err := s.Struct.Ptr(1) +func (s DRKeyLvl2Rep) HasDrkey() bool { + p, err := s.Struct.Ptr(0) return p.IsValid() || err != nil } -func (s DRKeyRep) SetSignature(v []byte) error { - return s.Struct.SetData(1, v) +func (s DRKeyLvl2Rep) SetDrkey(v []byte) error { + return s.Struct.SetData(0, v) } -func (s DRKeyRep) CertVerSrc() uint32 { - return s.Struct.Uint32(16) +func (s DRKeyLvl2Rep) EpochBegin() uint32 { + return s.Struct.Uint32(4) } -func (s DRKeyRep) SetCertVerSrc(v uint32) { - s.Struct.SetUint32(16, v) +func (s DRKeyLvl2Rep) SetEpochBegin(v uint32) { + s.Struct.SetUint32(4, v) } -func (s DRKeyRep) CertVerDst() uint32 { - return s.Struct.Uint32(20) +func (s DRKeyLvl2Rep) EpochEnd() uint32 { + return s.Struct.Uint32(8) } -func (s DRKeyRep) SetCertVerDst(v uint32) { - s.Struct.SetUint32(20, v) +func (s DRKeyLvl2Rep) SetEpochEnd(v uint32) { + s.Struct.SetUint32(8, v) } -func (s DRKeyRep) TrcVer() uint32 { - return s.Struct.Uint32(24) +func (s DRKeyLvl2Rep) Misc() ([]byte, error) { + p, err := s.Struct.Ptr(1) + return []byte(p.Data()), err } -func (s DRKeyRep) SetTrcVer(v uint32) { - s.Struct.SetUint32(24, v) +func (s DRKeyLvl2Rep) HasMisc() bool { + p, err := s.Struct.Ptr(1) + return p.IsValid() || err != nil +} + +func (s DRKeyLvl2Rep) SetMisc(v []byte) error { + return s.Struct.SetData(1, v) } -// DRKeyRep_List is a list of DRKeyRep. -type DRKeyRep_List struct{ capnp.List } +// DRKeyLvl2Rep_List is a list of DRKeyLvl2Rep. +type DRKeyLvl2Rep_List struct{ capnp.List } -// NewDRKeyRep creates a new list of DRKeyRep. -func NewDRKeyRep_List(s *capnp.Segment, sz int32) (DRKeyRep_List, error) { - l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 32, PointerCount: 2}, sz) - return DRKeyRep_List{l}, err +// NewDRKeyLvl2Rep creates a new list of DRKeyLvl2Rep. +func NewDRKeyLvl2Rep_List(s *capnp.Segment, sz int32) (DRKeyLvl2Rep_List, error) { + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 16, PointerCount: 2}, sz) + return DRKeyLvl2Rep_List{l}, err } -func (s DRKeyRep_List) At(i int) DRKeyRep { return DRKeyRep{s.List.Struct(i)} } +func (s DRKeyLvl2Rep_List) At(i int) DRKeyLvl2Rep { return DRKeyLvl2Rep{s.List.Struct(i)} } -func (s DRKeyRep_List) Set(i int, v DRKeyRep) error { return s.List.SetStruct(i, v.Struct) } +func (s DRKeyLvl2Rep_List) Set(i int, v DRKeyLvl2Rep) error { return s.List.SetStruct(i, v.Struct) } -func (s DRKeyRep_List) String() string { - str, _ := text.MarshalList(0xc3fe25dd82681d64, s.List) +func (s DRKeyLvl2Rep_List) String() string { + str, _ := text.MarshalList(0xdfa735fef0302b84, s.List) return str } -// DRKeyRep_Promise is a wrapper for a DRKeyRep promised by a client call. -type DRKeyRep_Promise struct{ *capnp.Pipeline } +// DRKeyLvl2Rep_Promise is a wrapper for a DRKeyLvl2Rep promised by a client call. +type DRKeyLvl2Rep_Promise struct{ *capnp.Pipeline } -func (p DRKeyRep_Promise) Struct() (DRKeyRep, error) { +func (p DRKeyLvl2Rep_Promise) Struct() (DRKeyLvl2Rep, error) { s, err := p.Pipeline.Struct() - return DRKeyRep{s}, err + return DRKeyLvl2Rep{s}, err } type DRKeyMgmt struct{ capnp.Struct } type DRKeyMgmt_Which uint16 const ( - DRKeyMgmt_Which_unset DRKeyMgmt_Which = 0 - DRKeyMgmt_Which_drkeyReq DRKeyMgmt_Which = 1 - DRKeyMgmt_Which_drkeyRep DRKeyMgmt_Which = 2 + DRKeyMgmt_Which_unset DRKeyMgmt_Which = 0 + DRKeyMgmt_Which_drkeyLvl1Req DRKeyMgmt_Which = 1 + DRKeyMgmt_Which_drkeyLvl1Rep DRKeyMgmt_Which = 2 + DRKeyMgmt_Which_drkeyLvl2Req DRKeyMgmt_Which = 3 + DRKeyMgmt_Which_drkeyLvl2Rep DRKeyMgmt_Which = 4 ) func (w DRKeyMgmt_Which) String() string { - const s = "unsetdrkeyReqdrkeyRep" + const s = "unsetdrkeyLvl1ReqdrkeyLvl1RepdrkeyLvl2ReqdrkeyLvl2Rep" switch w { case DRKeyMgmt_Which_unset: return s[0:5] - case DRKeyMgmt_Which_drkeyReq: - return s[5:13] - case DRKeyMgmt_Which_drkeyRep: - return s[13:21] + case DRKeyMgmt_Which_drkeyLvl1Req: + return s[5:17] + case DRKeyMgmt_Which_drkeyLvl1Rep: + return s[17:29] + case DRKeyMgmt_Which_drkeyLvl2Req: + return s[29:41] + case DRKeyMgmt_Which_drkeyLvl2Rep: + return s[41:53] } return "DRKeyMgmt_Which(" + strconv.FormatUint(uint64(w), 10) + ")" @@ -308,15 +613,15 @@ func (s DRKeyMgmt) SetUnset() { } -func (s DRKeyMgmt) DrkeyReq() (DRKeyReq, error) { +func (s DRKeyMgmt) DrkeyLvl1Req() (DRKeyLvl1Req, error) { if s.Struct.Uint16(0) != 1 { - panic("Which() != drkeyReq") + panic("Which() != drkeyLvl1Req") } p, err := s.Struct.Ptr(0) - return DRKeyReq{Struct: p.Struct()}, err + return DRKeyLvl1Req{Struct: p.Struct()}, err } -func (s DRKeyMgmt) HasDrkeyReq() bool { +func (s DRKeyMgmt) HasDrkeyLvl1Req() bool { if s.Struct.Uint16(0) != 1 { return false } @@ -324,32 +629,32 @@ func (s DRKeyMgmt) HasDrkeyReq() bool { return p.IsValid() || err != nil } -func (s DRKeyMgmt) SetDrkeyReq(v DRKeyReq) error { +func (s DRKeyMgmt) SetDrkeyLvl1Req(v DRKeyLvl1Req) error { s.Struct.SetUint16(0, 1) return s.Struct.SetPtr(0, v.Struct.ToPtr()) } -// NewDrkeyReq sets the drkeyReq field to a newly -// allocated DRKeyReq struct, preferring placement in s's segment. -func (s DRKeyMgmt) NewDrkeyReq() (DRKeyReq, error) { +// NewDrkeyLvl1Req sets the drkeyLvl1Req field to a newly +// allocated DRKeyLvl1Req struct, preferring placement in s's segment. +func (s DRKeyMgmt) NewDrkeyLvl1Req() (DRKeyLvl1Req, error) { s.Struct.SetUint16(0, 1) - ss, err := NewDRKeyReq(s.Struct.Segment()) + ss, err := NewDRKeyLvl1Req(s.Struct.Segment()) if err != nil { - return DRKeyReq{}, err + return DRKeyLvl1Req{}, err } err = s.Struct.SetPtr(0, ss.Struct.ToPtr()) return ss, err } -func (s DRKeyMgmt) DrkeyRep() (DRKeyRep, error) { +func (s DRKeyMgmt) DrkeyLvl1Rep() (DRKeyLvl1Rep, error) { if s.Struct.Uint16(0) != 2 { - panic("Which() != drkeyRep") + panic("Which() != drkeyLvl1Rep") } p, err := s.Struct.Ptr(0) - return DRKeyRep{Struct: p.Struct()}, err + return DRKeyLvl1Rep{Struct: p.Struct()}, err } -func (s DRKeyMgmt) HasDrkeyRep() bool { +func (s DRKeyMgmt) HasDrkeyLvl1Rep() bool { if s.Struct.Uint16(0) != 2 { return false } @@ -357,18 +662,84 @@ func (s DRKeyMgmt) HasDrkeyRep() bool { return p.IsValid() || err != nil } -func (s DRKeyMgmt) SetDrkeyRep(v DRKeyRep) error { +func (s DRKeyMgmt) SetDrkeyLvl1Rep(v DRKeyLvl1Rep) error { s.Struct.SetUint16(0, 2) return s.Struct.SetPtr(0, v.Struct.ToPtr()) } -// NewDrkeyRep sets the drkeyRep field to a newly -// allocated DRKeyRep struct, preferring placement in s's segment. -func (s DRKeyMgmt) NewDrkeyRep() (DRKeyRep, error) { +// NewDrkeyLvl1Rep sets the drkeyLvl1Rep field to a newly +// allocated DRKeyLvl1Rep struct, preferring placement in s's segment. +func (s DRKeyMgmt) NewDrkeyLvl1Rep() (DRKeyLvl1Rep, error) { s.Struct.SetUint16(0, 2) - ss, err := NewDRKeyRep(s.Struct.Segment()) + ss, err := NewDRKeyLvl1Rep(s.Struct.Segment()) + if err != nil { + return DRKeyLvl1Rep{}, err + } + err = s.Struct.SetPtr(0, ss.Struct.ToPtr()) + return ss, err +} + +func (s DRKeyMgmt) DrkeyLvl2Req() (DRKeyLvl2Req, error) { + if s.Struct.Uint16(0) != 3 { + panic("Which() != drkeyLvl2Req") + } + p, err := s.Struct.Ptr(0) + return DRKeyLvl2Req{Struct: p.Struct()}, err +} + +func (s DRKeyMgmt) HasDrkeyLvl2Req() bool { + if s.Struct.Uint16(0) != 3 { + return false + } + p, err := s.Struct.Ptr(0) + return p.IsValid() || err != nil +} + +func (s DRKeyMgmt) SetDrkeyLvl2Req(v DRKeyLvl2Req) error { + s.Struct.SetUint16(0, 3) + return s.Struct.SetPtr(0, v.Struct.ToPtr()) +} + +// NewDrkeyLvl2Req sets the drkeyLvl2Req field to a newly +// allocated DRKeyLvl2Req struct, preferring placement in s's segment. +func (s DRKeyMgmt) NewDrkeyLvl2Req() (DRKeyLvl2Req, error) { + s.Struct.SetUint16(0, 3) + ss, err := NewDRKeyLvl2Req(s.Struct.Segment()) + if err != nil { + return DRKeyLvl2Req{}, err + } + err = s.Struct.SetPtr(0, ss.Struct.ToPtr()) + return ss, err +} + +func (s DRKeyMgmt) DrkeyLvl2Rep() (DRKeyLvl2Rep, error) { + if s.Struct.Uint16(0) != 4 { + panic("Which() != drkeyLvl2Rep") + } + p, err := s.Struct.Ptr(0) + return DRKeyLvl2Rep{Struct: p.Struct()}, err +} + +func (s DRKeyMgmt) HasDrkeyLvl2Rep() bool { + if s.Struct.Uint16(0) != 4 { + return false + } + p, err := s.Struct.Ptr(0) + return p.IsValid() || err != nil +} + +func (s DRKeyMgmt) SetDrkeyLvl2Rep(v DRKeyLvl2Rep) error { + s.Struct.SetUint16(0, 4) + return s.Struct.SetPtr(0, v.Struct.ToPtr()) +} + +// NewDrkeyLvl2Rep sets the drkeyLvl2Rep field to a newly +// allocated DRKeyLvl2Rep struct, preferring placement in s's segment. +func (s DRKeyMgmt) NewDrkeyLvl2Rep() (DRKeyLvl2Rep, error) { + s.Struct.SetUint16(0, 4) + ss, err := NewDRKeyLvl2Rep(s.Struct.Segment()) if err != nil { - return DRKeyRep{}, err + return DRKeyLvl2Rep{}, err } err = s.Struct.SetPtr(0, ss.Struct.ToPtr()) return ss, err @@ -400,59 +771,86 @@ func (p DRKeyMgmt_Promise) Struct() (DRKeyMgmt, error) { return DRKeyMgmt{s}, err } -func (p DRKeyMgmt_Promise) DrkeyReq() DRKeyReq_Promise { - return DRKeyReq_Promise{Pipeline: p.Pipeline.GetPipeline(0)} -} - -func (p DRKeyMgmt_Promise) DrkeyRep() DRKeyRep_Promise { - return DRKeyRep_Promise{Pipeline: p.Pipeline.GetPipeline(0)} -} - -const schema_f85d2602085656c1 = "x\xda\x84\x92OHT]\x18\xc6\xdf\xe7}\xef\x9dQ" + - "p`\x0e3\x1f\xc8\xc7\xf7a\x9b\x846\x92\xbas\xa3" + - "\x84B\x7f\x0c<\xa3(D\x12\xc3x\x1c\x87\x1a\xb9\xde" + - "{\x85\xdc$\xb5k\xd1\xaeM\x90d\x90\xe0\xa2(\xa1" + - "\xa8E-\xa2E\x10m\x1a*,\x12\x12\x94Z$\x15" + - "\x14\x08\xa17\x8eW\xc7!\x94v3\xcfy\xce{\x9f" + - "\xe7\xfc\xde\xc3C\xe8\xe2V\xb7\x08\"\x9dv\x13\xd1\xd7" + - "O\xbd3\xffU\xfan\x90V\x90\xe8\xe9\xe0`\x1d7" + - "\x0f\xaf\x93\x8b$\x91\xbaVQsI\xa2\xd6\x9bC " + - "D/\xbeg~\xbd}\xf8d\xc1Z\xb1k\xedAR" + - "\x882\xef\xf0>\xb3joe\x96q\x97\x10\x8d\xfc?" + - "vi\xe9\xe0\xe63\xebvj\x06\xb3\xb5\\\xe4J\xe6" + - "\x8a\xfd\xd5~\x99\x9f\xdb\xd9\x8f\x9b\x0f\x05\x17\xd6\xe6+" + - "\xa4\xff\x85\xec\x86\xfa\x07I\x10\xb5\x0f;\x0cB&\xef" + - "t\xda\xc1\xfeY3u\xa6\\\xe4r\xd8R\xc8{\xe3" + - "^Gw\xee\x84\x99\xca\x19L\xf4\x01\xbaQ\x1c\"\x07" + - "6}\x1b\x91\xbe*\xd0\xb3\x0c\x05\xce\xc2\x8a39\"" + - "}]\xa0\xe7\x19Vc\"5g\xb5[\x02}\x8f\xa1" + - "D\xb2\x10\"u\xe7\x08\x91\x9e\x17\xe8\xfb\x0c\xe58Y" + - "8Dj\xa1\x83H\xdf\x16\xe8G\x0c\xb8\xa8\xc9\xad\x1e" + - "\xb4\x117\x95\x82\x91|\x80zb\xd4\x13\xa2\xb0T6" + - "A\x98/\x13<\xd4\x11\xa3\x8e\x10\x05\xa5\xe2x>\x9c" + - "\xf4\x09\x06)b\xa4\x08\xd3\x05\xe3\x87\x83\xc6\xdf\xf1t" + - "\x86~\xa1\xe6o\xd3\xe8\xb9|1\xd8\xaf\xf6\xc9\xa2\x94" + - "C\xdb\xbbA\x9c\x86(\xda*\xdec\x8bw\x09t/" + - "#\x85\xcd(n~\xec8\x91>*\xd0\x03\x8c\x14o" + - "Dqwm\xd5>\x81>\xcdh\x9a\x1c\x0fLH\x89" + - "\xf8[93ADH\xef\xd2 \xbd\x03 g\xbc" + - "\xf8\xb4\xca\xb9\xf6tO<\x9e\x8dy\xa0\x8a\xe7\x95M" + - "\xf9R\xa0\x17k\xf0\xbc\xb1(^\x0b\xf4G\x86b\x89" + - "3.Y\x14\x8b\x02\xbd\xc2\xc06\x9eeK\xe2\x83@" + - "\x7f\xb6x\x10\xe3Y\xb5\xb7W\x04\xfa\x1bC\xb9N\x16" + - ".\x91Z;E\xa4\xbf\x08\xf4:C%\xdc,\x12D" + - "\xea\xa7\x15\x7f\x08r`\xa8d\"\xbb\xb5\xee\x1bv\xe6" + - "\xba\xa0\xdf\x01\xe3\xef,\xa7\xcdyo\xa0T6Un" + - "\x85\x927f\xfc\x1d\xac{\xa1\x8e\xb6Q\xf7\x93\xf8\x85" + - "\xeaNl\x8b\xdd$A\xb8\xcf\x12T\x9fU\xfe|\xd6" + - "\x89\x96\xad\xfd \xed\x88\x93\x9e\x8d+\xa7,\xd4\x06\x81" + - "ndD\x9eoFMX\x18\xb3\xb8@\x0c\x10~\x07" + - "\x00\x00\xff\xff\x9fS\xf7\xb5" +func (p DRKeyMgmt_Promise) DrkeyLvl1Req() DRKeyLvl1Req_Promise { + return DRKeyLvl1Req_Promise{Pipeline: p.Pipeline.GetPipeline(0)} +} + +func (p DRKeyMgmt_Promise) DrkeyLvl1Rep() DRKeyLvl1Rep_Promise { + return DRKeyLvl1Rep_Promise{Pipeline: p.Pipeline.GetPipeline(0)} +} + +func (p DRKeyMgmt_Promise) DrkeyLvl2Req() DRKeyLvl2Req_Promise { + return DRKeyLvl2Req_Promise{Pipeline: p.Pipeline.GetPipeline(0)} +} + +func (p DRKeyMgmt_Promise) DrkeyLvl2Rep() DRKeyLvl2Rep_Promise { + return DRKeyLvl2Rep_Promise{Pipeline: p.Pipeline.GetPipeline(0)} +} + +const schema_f85d2602085656c1 = "x\xda\x8c\x94_h\x1cU\x14\xc6\xcfw\xce\xecn\x0a" + + "Y\x92\xcb\x0c\xa8`Y\x1f\xacXK\xa5I\xf5\xc1\xbe" + + "\xd8\x86D\x92\x98@o\xa8A\x02\xa5\x84\xcd\x90D\xb3" + + "\x7f\xb23F\x83\x86(\xb4\xd0\x82\xe2\x1f\xf2`\xd0\x07" + + "\x1f\x0a>TP\x1f\xa4*\x0a\x06\x0a\xfeA\xa1\x85\xa2" + + "\xb6T\xac\xd8B\x85\x0am\xe8C\xb4\xb6#'\xb3;" + + "\xb3n\xa3\xf6i\x87\xb3\xdf=\xf7~\xbf\xf3\xdd\xbb\xe3" + + "\x11\xde\xcd]\x99;\x98\xc8\xde\x99\xc9F\xc7\x97\x9d\xb1" + + "\xed\x8f\xbd\xf5\x06Y\x03D+\xa3\xa3m|\xdf\xfe5" + + "\xca Gd\xae\x9e57\xf4\xf7\x8fg\x09\xd17W" + + "\xdd\xeb\xdf\x1f\xff\xfc\xc3\x16e\x1fr\x19\"\xf7\x09\x9c" + + "u\xc7u\xd1\xce\xfd(\x80\x10y\xc7\x8e\xadm{>" + + "\xff\x83\xca\x9d\xa6\xc6\x9c#r\x17x\xd5=\xa2_;" + + "\x0f\xf1\xab\xaa>\xb8m\xc7\x95\x9b\x0f\xbf\xfb\xb3\xaa\xb9" + + "U\xbd\xd9Yu\xb7:\xaa\xde\xe2\xac\xf7^\xca;\xd7" + + ">\xed?zQ\xd5\xd2\xa4V\x8d\xbb'\xb3\xea\x0eg" + + "T=\x90\xf9R\xd5\xbf\xba\xef\xad\xbcr`\xcb\x9f-" + + "\xbd\xd7\xc5&\xb7\xean\xce\xe9\xd7]\xb9\xf7\x09\xd1D" + + "\xedi\x7f\xfe@i\x92K\xe1\x83\xc5\xf1j\xb9\xba\xab" + + "w\xe4q\x7f\xbe\xbf\"A\xb8\x17\xb0m\xe2\x109 " + + "2[\x1f \xb2\xf7\x0a\xec\x0e\x06\xe0Ak\xdb\xb5v" + + "\xbf\xc0>\xc4\xe8\x08\xe7\xab>\xb2\xc4\xc8\x12:\xa6*" + + "A\x88<1\xf2\xff\xbe\xc9\xf0\xa4\x94\xd67\xf1\xc4i" + + "\x8f\xa2\xf5]\x16\xba\x89\xecs\x02{\x90\x91\xc7\xcd(" + + "\xde\xe7\xa5\xa7\x88\xec\x8b\x02\xfb2#\xcf7\"\x0fL" + + "d\x8eh\xf5\xb0\xc0.1\xf2\xf2W\xe4A\x88\xcc\xeb" + + "Z}M`\xdff\xe4\x9d\xeb\x91\x07\x87\xc8,k\xf5" + + "M\x81=\xca(" + + "R:\x1f\x08\xecg\x0c\x03\x8e\xe1|2Fd?\x16" + + "\xd8\x13\x0c\xc3\x12\xb3Y\x19$\xb2_\x08\xec\xb7\x0cH" + + "L\xe6\xeb]D\xf6\x84\xc0\x9ed\x18\x071\x98\xef\xb4" + + "\xe5W\x02{\x9aa2\xec!CdNi\xcb\x93\x02" + + "{\x8ea\xb2Y\x0fY\"sf\x84\xc8\xfe(\xb0\x17" + + "\x18\x85\x89 \x1c\xd8\x83M\xc4\xd8D\x88\xfcj\xa58" + + "\xd5\xe3O\x92L\x97\xd1F\x8c\xb6F\xb1\xaf0\x9d\xe9s\x1e_\xa7\xc5\x89 \xdc\xf8" + + "\x9f\xdb\x86\xdbU\x87\xdb\x9e\xc0\xedS\x12\xbb\x05v\xa8" + + "\xe9\xd6\x0d\xa8\xe9^\x81\xdd\xdbt\xeb\x865kC\x02" + + "\xfbd\xeb\x15i\xb5\xb7Ql\xfe\x0e\x00\x00\xff\xff\xf9" + + "*\x89+" func init() { schemas.Register(schema_f85d2602085656c1, - 0x9f50d21c9d4ce7ef, + 0x929b462d5a0499b7, 0xb1bdb7d6fb13f1ca, - 0xc3fe25dd82681d64, - 0xd2a8ed7e732926bc) + 0xd70d7b2bf8abab14, + 0xdfa735fef0302b84, + 0xe5a448baf4040d94, + 0xfa255f8dc1ac13e3) } diff --git a/go/proto/sciond.capnp.go b/go/proto/sciond.capnp.go index 950ef93708..0f9607f48e 100644 --- a/go/proto/sciond.capnp.go +++ b/go/proto/sciond.capnp.go @@ -26,10 +26,12 @@ const ( SCIONDMsg_Which_revReply SCIONDMsg_Which = 10 SCIONDMsg_Which_segTypeHopReq SCIONDMsg_Which = 11 SCIONDMsg_Which_segTypeHopReply SCIONDMsg_Which = 12 + SCIONDMsg_Which_drkeyLvl2Req SCIONDMsg_Which = 13 + SCIONDMsg_Which_drkeyLvl2Rep SCIONDMsg_Which = 14 ) func (w SCIONDMsg_Which) String() string { - const s = "unsetpathReqpathReplyasInfoReqasInfoReplyrevNotificationifInfoRequestifInfoReplyserviceInfoRequestserviceInfoReplyrevReplysegTypeHopReqsegTypeHopReply" + const s = "unsetpathReqpathReplyasInfoReqasInfoReplyrevNotificationifInfoRequestifInfoReplyserviceInfoRequestserviceInfoReplyrevReplysegTypeHopReqsegTypeHopReplydrkeyLvl2ReqdrkeyLvl2Rep" switch w { case SCIONDMsg_Which_unset: return s[0:5] @@ -57,6 +59,10 @@ func (w SCIONDMsg_Which) String() string { return s[122:135] case SCIONDMsg_Which_segTypeHopReply: return s[135:150] + case SCIONDMsg_Which_drkeyLvl2Req: + return s[150:162] + case SCIONDMsg_Which_drkeyLvl2Rep: + return s[162:174] } return "SCIONDMsg_Which(" + strconv.FormatUint(uint64(w), 10) + ")" @@ -497,6 +503,72 @@ func (s SCIONDMsg) NewSegTypeHopReply() (SegTypeHopReply, error) { return ss, err } +func (s SCIONDMsg) DrkeyLvl2Req() (DRKeyLvl2Req, error) { + if s.Struct.Uint16(8) != 13 { + panic("Which() != drkeyLvl2Req") + } + p, err := s.Struct.Ptr(0) + return DRKeyLvl2Req{Struct: p.Struct()}, err +} + +func (s SCIONDMsg) HasDrkeyLvl2Req() bool { + if s.Struct.Uint16(8) != 13 { + return false + } + p, err := s.Struct.Ptr(0) + return p.IsValid() || err != nil +} + +func (s SCIONDMsg) SetDrkeyLvl2Req(v DRKeyLvl2Req) error { + s.Struct.SetUint16(8, 13) + return s.Struct.SetPtr(0, v.Struct.ToPtr()) +} + +// NewDrkeyLvl2Req sets the drkeyLvl2Req field to a newly +// allocated DRKeyLvl2Req struct, preferring placement in s's segment. +func (s SCIONDMsg) NewDrkeyLvl2Req() (DRKeyLvl2Req, error) { + s.Struct.SetUint16(8, 13) + ss, err := NewDRKeyLvl2Req(s.Struct.Segment()) + if err != nil { + return DRKeyLvl2Req{}, err + } + err = s.Struct.SetPtr(0, ss.Struct.ToPtr()) + return ss, err +} + +func (s SCIONDMsg) DrkeyLvl2Rep() (DRKeyLvl2Rep, error) { + if s.Struct.Uint16(8) != 14 { + panic("Which() != drkeyLvl2Rep") + } + p, err := s.Struct.Ptr(0) + return DRKeyLvl2Rep{Struct: p.Struct()}, err +} + +func (s SCIONDMsg) HasDrkeyLvl2Rep() bool { + if s.Struct.Uint16(8) != 14 { + return false + } + p, err := s.Struct.Ptr(0) + return p.IsValid() || err != nil +} + +func (s SCIONDMsg) SetDrkeyLvl2Rep(v DRKeyLvl2Rep) error { + s.Struct.SetUint16(8, 14) + return s.Struct.SetPtr(0, v.Struct.ToPtr()) +} + +// NewDrkeyLvl2Rep sets the drkeyLvl2Rep field to a newly +// allocated DRKeyLvl2Rep struct, preferring placement in s's segment. +func (s SCIONDMsg) NewDrkeyLvl2Rep() (DRKeyLvl2Rep, error) { + s.Struct.SetUint16(8, 14) + ss, err := NewDRKeyLvl2Rep(s.Struct.Segment()) + if err != nil { + return DRKeyLvl2Rep{}, err + } + err = s.Struct.SetPtr(0, ss.Struct.ToPtr()) + return ss, err +} + func (s SCIONDMsg) TraceId() ([]byte, error) { p, err := s.Struct.Ptr(1) return []byte(p.Data()), err @@ -585,6 +657,14 @@ func (p SCIONDMsg_Promise) SegTypeHopReply() SegTypeHopReply_Promise { return SegTypeHopReply_Promise{Pipeline: p.Pipeline.GetPipeline(0)} } +func (p SCIONDMsg_Promise) DrkeyLvl2Req() DRKeyLvl2Req_Promise { + return DRKeyLvl2Req_Promise{Pipeline: p.Pipeline.GetPipeline(0)} +} + +func (p SCIONDMsg_Promise) DrkeyLvl2Rep() DRKeyLvl2Rep_Promise { + return DRKeyLvl2Rep_Promise{Pipeline: p.Pipeline.GetPipeline(0)} +} + type PathReq struct{ capnp.Struct } type PathReq_flags PathReq @@ -2262,131 +2342,136 @@ func (p SegTypeHopReplyEntry_Promise) Struct() (SegTypeHopReplyEntry, error) { return SegTypeHopReplyEntry{s}, err } -const schema_8f4bd412642c9517 = "x\xda\x94W}l\x1bg\x19\x7f\x9e\xf7\xec\xd8Nl" + - "\x9f\xddsF\x09\x82\xd0\xaaU\x9bj\xa9\x96t\x85\xae" + - "\x82\xa5M\xdb,\x1et\xcd\xd9\x11\x7fLC\xcc\x8b_" + - "'FN\xec\xdc]\xd2z\xa2\x84\xa2\x06XaZ\xab" + - "-\x82\xd1N\xb4\x1b\x94\x06\x86\xd8J5\xb1\x82&\xd0" + - ":>\xa2\x09\x98T4-\x1a\xac-\xeb\xdat\xabX" + - "\xba\x8e~\xd0r\xe8\xb9;\xdf]/\xd7\x8f\xe5\xaf7" + - "\xfe=\xfe\xbd\xcf\xd7\xefy^\xdf1/\xb8\x86\xb5\x05" + - "\xff\x1d\x02\x90\x1f\x08\xd6\xe9\x1f<\xf7\x8b\xfd\xef\x9e\x7f" + - "\xf8\xdb\x90\x8c\xa1\xfe\xb1\x89\xdb\xf3\xf3\x8e~\xe11\x08" + - "b\x08@\xfa\x870-\xcd\x08t:)t\x00\xea\xe7" + - "\xa7/}\xe5\xa5\xa9\xb7v\x80\x1cC\xb71#\x93\xc6" + - "\xc0\x94\xb4 @\xa7O\x06N\x01\xeaM\xc9=]o" + - "+\xdb\x1e\xf3\x18\x1b\x16\xc1\xe0A)\x16\xa4S$H" + - "\xc4]/w\x8d\x1d\xda}f\x17\xd92\xc7v\x03\x0b" + - "\xc50 \xb5\x04\x0fKmd\xbd\xa25\xb8I\x00\xd4" + - "\x9f:\x9d:\xb1t\xfe\xd7\x9f\xf0\xf3\xf9\xcf\xe1)\xe9" + - "\xefa:\xbd\x16&\xea}[\x1b\x0e\xac\\S\x9d\xf0" + - "P\x1bn`dZ\x8aE\x0c7\"\x9b\x01\xf5\x99\xce" + - "\xb7\xc6\x7f:^\xb7\xdb\x8f\x97G\xceH\xc3\x86\xed`" + - "\x84x\xa7\xdf\xd8q\xfax\xf0/\xbbAnDA\x7f" + - "\xf7\x99#o\xb65\xfe\xe1\x084b\x08\x01\xa4]\x91" + - "i@i\xc2`\x9d\xd7\xb6\xb7\xed\x81\xf0\xa6I\x1f\xd6" + - "\x15\xb3\x11\x86\xd2U\x83\xf6\x92A{hvR\xbe\x7f" + - "\xfe\xc5g\xbd)6\xac[\xeb\xe7\xa1\xf4\xf9z\xb2\xbe" + - "\xab\xfe\x97\x80\xfa\xa7\x17?\xbe9\xb8\xa4\xe9\xa0oA" + - "f\xea\x0fJ\xb3\x86\xf1\xd9z\xf2\xe3\xf4\xb9\xdbFO" + - "\x9e]\xf3\xb2_t\xad\x0dg\xa4\xbb\x1a\xe8\xb4\xb2\x81" + - "\xdc\xb0\xe3\x91c(\xccIE\xc3\xcf\xa4A2^Q" + - "lhF@\xfd\xbd\xd1\xefWz\x97\xeb\xafx\x98\x0d" + - "/\x86\xa3'\xa4\xadQ:U\xa3\xe4\x85\xc8\xff\xba\xb6" + - "s\xfb\xa7\xa6\xfc\xda\xe2\x8d\xe8\xb4t\xd2\xb0=\x1e%" + - "/~\xf2\xce\xa2=\x07\x9e\xe6\xaf\xfa\xd9b\xec\xb0\x14" + - "\x89\x19\xcd\x14#\xdb7\x8f\xfff\xff#\x8f/9\xe5" + - "\x9b\xb8\xc5\xb1&\x94V\x1a\xd6m1J\\\xe9X\xe6" + - "KM\xaf]8\xe5\x97\x8b\xe3\xb1)\xe9\xaca;c" + - "0\xafZ\xf2\xfa\xb7\xfa\x1b_y\xdf\x8fY\xfax\xfc" + - "\x9c\xb48N\xa7\x05q\x0a\xaf\xe3\x9d\xbb[^\x98\x11" + - "g}\x8d\xb7\xc5\x0fK\x8f\x18\xc6\xe3\x86\xf1\x8b/m" + - "\x99\xfc\xee\xeb\xfb/\xf8y\xf1a\xfc\x9c\x84\"\x9d\xae" + - "\xc6\xc9\x8bh\xd3?\x7f\xde\xbf\xf8\xe4%\x90oCW" + - "\xe1\x1b\x99\xd1o\xad\xe2\x09@\xa9M$\xd6_\xbd\xf0" + - "\xf0=\x87\x9ey\xfe\xb2_\xc7?*\x9e\x93\x9e4X" + - "'D\xca\x83\xdaW,\x0f\xe5\x97\xf7\xb1\\e\xa8\xb2" + - ":\xdd\x95\x1e*\x943|x\x84\x0b\xaa\xd6\x83(\x07" + - "\x84\x00@\x00\x01\x92\xb1v\x009,\xa0\xbc\x88as" + - "\xb1\x90^\xafb\x1c\xb0G@\x8c\x00\xa3\xa3\x87\xabk" + - "s\xbe'\xa7\x0dl\xe4Z\x0e\x80\xa8\x126U\xae\x93" + - "f\x8f\x80\xf2\x00C\xc4\x14\xd2g|!\x80\xfc\xa0\x80" + - "r\x89a\x92a\x0a\x19@\xb2x?\x80< \xa0\xbc" + - "\x9daR\xc0\x14\x0a\x00\xc9m\xf4\xed\xaf\x09(\x7f\x87" + - "\xe1X\xc1\xbc\x05c\xc00\x06\x18\x1a\xd4F0\x04\x0c" + - "C\x80zqH\xe3J!\xd7\x07\x02\xb7}M8\xf3" + - "\x00\x90>\x1c\xe3[*\xbd\xc5A\x8ea`\x18vE" + - "\x81F\x14\x19>\xda\x9c\xe1\x95R\xd5\x93\x8c\xd5V2" + - "R\x0c;\x14\xae\x8e\x944\xfb\xdak\x09\xb2\xeb\xd2\x1d" + - "\x9b\xee[\xbfQ\xed'\x86\xcf\xd5\x18\xa4\x09l\x02\xc8" + - "\xeeD\x01\xb3{\x90a\x0cu\xddH\x84\xf4$\xb6\x03" + - "d\x9f `/\x01\xec\x7f\xba\x91\x0c\xe9)\xec\x04\xc8" + - "\xfe\x80\x80\x1f\x13 \\\xd5\x8d\x84H\xfb0\x03\x90\xdd" + - "K\xc0\xb3\x04\x04\xae\xe8)\x0c\x00H\x93\x06p\x80\x80" + - "C\x04\x04\xff\xab\xa70\x08 =\x8f\x0f\x01d\x9f#" + - "\xe0\xb7\x04\xd4]\xd6SX\x07 \xbd\x88\xdf\x04\xc8\xfe" + - "\x9a\x80#\x04\x84.\xe9)\xa3\x1b\x7f\x8f\x0a@\xf6w" + - "\x04\xbcJ@\xf8\xa2\x9e\xc20\x8d[\x83\xeaO\x04\x1c" + - "% rAOa\x84\xa6/\xfe\x10 {\x94\x80c" + - "\x04\xd4\xffGOa=-\x15\xdc\x01\x90=F\xc0{" + - "\x044|\xa8\xa7\xb0\x81\xd4\x86\xf7\x02dO\x13p\x9e" + - "\x80\xe8y=\x85Q\x00i\xd6\xb8\xfc}\x02\xae\x10\x10" + - "\xfb@Oa\x8cF\xa6\xe1\xeeE\x02\x02\x8ca2\x8e" + - ")\x8c\xd3l`\x94\xaa+\xf4y\x981\x14\x8ay\xa3" + - "I#\x80\xcd#C*\xd7\xa0n\xac\x92\xd3\x062|" + - "\x18\x13\xce\xa8\x03\xc4\x04\xa0n\"\x95\x12`\x15\x13\x8e" + - "\x9e-4\xa7\x9a\x12\x01\xa4\xef\xda\x03\xca\x8b\x86*%" + - "\xfa\xb6\xbd\xa8,\\\xe1\xa3\xf7\x95\xb5b\x01\x8b}9" + - "\xadX\x1e\x02L8K\xc7\xb2)\x16,\x8e\xe6\xe1\x11" + - "\xaej\x98pV\xb4\xd7\xc2\xba\xc5\x1ef\x16\xaere" + - "\xb4\xd8\xc7\xd3\xe8\x123&\x9c=\xe4kV)U\x81" + - "\xdc\xb1g\x92\xe3\xb2\x05\x12j/u\x9b\xa3\xbf\xb7Z" + - "\xe1\xdd\xd0\\\xae\x98\xe9\xb4\xe7\xbb\xc7\x02\xc9\x80x0" + - "\xe1l\"\xd3fLSr}<\x9d\xaf\xa9\xd83I" + - "\xd6f\xd3\x8e\x87\x1e\x1dv:Ci\x8c\x0fiJ\xd1" + - "-u{\x10\x9aR\xf7\xd0\xd2\xdcH\x9b#B\xe8\xe3" + - "\xc4\x1b\xb6y[h\xd8-\x12P\xbe\x83a\xb26\xa2" + - "Z\x97\x01\xc8K\x05\x94\xef\xa4\x09\xa8\xe6sj\xad\xab" + - "D\x9a\x87\xb5\x7f<\xd7d\xac\x92\x17\xfbr\"\x95\xdc" + - "\x13\xc0\xbd\x00rT@y>C]\xcd\xf0Q\x0a\xd5" + - "Lu\xe6_\x97?;~O\xfb\x8f\xecD\xce\xf1>" + - "\xc3\x87\x97\x17J9\xa1_%\xd7\x13;\xcd\xf9\xd8\xd2" + - "\xe9\xf6}\x971\x0a\x92\xad\xab\x1d\xdf\xc7\x14^P\xb8" + - ":\x80\x08\x0c\x11\xb0c\xa0\x98\xcf\xf3\xa1\xda\xbf\xf6E" + - "\x829\xc0\xac.\xa9\xf5\x92\xaayk\xf0U+\x84\xa5" + - "\xcc\xee\xa9^\x10\xab\x15\xa7\x14\xa2\xae\xf5\xff\xed\x13-" + - "\xad\x99\x13\xdeR\xd4\xee0{\xc4j\x91\x0dC\x9a\x82" + - "\xc6\xc4\x8d\xda\xb7l\xa0U\xb0^@\xf9Agg|" + - "9\xe3\xec\x11{g\xf0Ng\x91\xdc\xda\x0a\xd0\xb5\xe2" + - " W\xb5\xdc `\xa5\xb6\x06n\xb2\x16\xba\xcbj\xb3" + - "F)\xf1\xb4\xcd2'\xf5\xf4\xe7,\xeddk;0" + - "\xb1RV\xec=\xd1\x9c\xcb\xe7\x15\xd5SUW\"D" + - "\x9f\xads\xc3n\xb7_\x8e\x9e\x14c\xad_Dj\x18" + - "bL\xd9\x8c[i\xebn\xb1\x16l-\xad\xdb\x16:" + - "\x0b6\xc9\xc2fZ\xc7\xa9Q\xb7\x0b(\xefd\x88\x02" + - "\xba\xde\xbf\xc9G\xdb\x81a\xc0\xd8,\xc9\x11\xea\xb3\x8a" + - "\x80\xf2\xf7\x18\x86\xf2\xaaV\x13EHU\xfal\x81\x0c" + - "\xe6\xb6P\xfb\xaa\xd4\xe7\xb5l\x14J\xb9~\xb5c\xa0" + - "\xb2\xae\xd0\xef\x8ai\xfe\x86\xb7\xef\x96\xfe\xb8\xe0\xf0\xf5" + - "\x15l5LHS\xaa\xd7\xaf\x85#a\x8a\xe2v\x01" + - "\xe5U\x0cE\x1a\xf4\x98p~\xd4X:\x1b(\xab\x9a" + - "\xa3B\xfb\xa1\xe5\xabBW\xbd\x043\xb7\xaej-s" + - "\xde\x08\xa2V\xadp\x14\xf5o\xacz\xba\x9eO^\xd8" + - "Gl\xe2\x9c\x1a\xad\xcd\xa6;L\x99]\xe7\xe9\x95\xf2" + - "\x0e\x9e\x1bi\xd5\x14\x92\xa0x\x85\xf4\x90%\xa4\x1eW" + - "^6R\xc9\xbb\x05\x94{\x19\"3+.\x93\xbaz" + - ",!\xd9\xba\x0e\x99\x81\xb8\xf5,\x02\x864\xaddK" + - "\xc5N \xba*\xe9\xcec\xfc\xba\x0f\xcf\x8f<\xe2\xed" + - "\xb7\xf9\xcdh\x9bi\xa8To$W\x9f\x0e\xb9f\xac" + - "\xdfZ_\xd8j\xeb\x18\xb0\xdf\x8d\xae\x1b3\xce\x18\xae" + - "\xdd\xd8\xd6i\xdd\xd8\xcdP\xe7\x8aRV\xd6\x95\xf3\x80" + - "\xbc&\x8d\xb9A\xdb?\xb7|\x83v5\x81\xef\xd3\xf5" + - "\x86\xf9\xb4\x7fE\xf9Rw[)X\x9e\xcb\x87\xf2\x8a" + - "j\x06fF1Gn\xcc\xb31\xc5be\xf4\xce\xda" + - "\x86\xa7\x7f>s\xf3u\xef\x14\xcd\xd5\xbf$\x865\x02" + - "\xca_\xa4\x8b\x02\xe6\xed\xe9\x85\xae\xa6f=\xe6\xed\x1b" + - "W;M}\xadl\xdc?\x13:\x8a\xea\xba\xb2\xc2k" + - "\xdb\xef\xff\x01\x00\x00\xff\xff\x18V:\xa1" +const schema_8f4bd412642c9517 = "x\xda\x94W\x7fp\x14w\x15\x7f\xef\xbbw\\\xeeW" + + "\xf6\x8e\xef\xa6\xc68\x9a\xc2\xd0\x81 0$PE\xa6" + + "6\x10~4WK\x9b\xbdCg\xec\xd4\xb1Gn/" + + "\xb9z\xc9\x1d\xbb\x9b\xc0u\xc4\x18\x07T\xd0N\xdbi" + + ";\x8eRGi\x85\x16\x95Q\x90v\x868\xd6?\xda" + + "\xaa\x93\xa9\xda\xce\xd4i\x9bA)H\x0a\xa1e$i" + + "\x14\x82m\xd7y\xbbw\xbb\x9beC\xeb\xfd\xf5\xbd\xfd" + + "\xbc\xfd|\xdf{\xdf\xf7y\xef\xbb+\xdb\x82\xebXk" + + "\xf0_!\x00\xf9\x9e\xe0<\xe3\xdd_\x1f9\xf4\xf6\xf4" + + "\xfd\xdf\x81d\x1c\x8d\x8f=\xb6,7\xff\xd5/<\x08" + + "A\x0c\x01\xf0\xbf\x0bc|B\xa0\xd5\xb8\xd0\x0ehL" + + "\x8f\xcd|\xf5\xb9\xd1S\xfb@\x8e\xa3\xdb\x98\x91IC" + + "`\x94/\x08\xd0\xea\x93\x81s\x80FS\xf2\xf1\xcdg" + + "\xd5\xe1\x07=\xc6\xa6E0x\x8c\xc7\x83\xb4\x0a\x07\x89" + + "x\xf3\xf3\x9b\x87\x8e\xef\xbf\xf00\xd92\xc7v\x13\x0b" + + "\x89\x18\xe0-\xc1\x11\xdeJ\xd6\xab\x96\x07\x7f/\x00\x1a" + + "?>/\x9dY\xd2\xf8\x8dG\xfd|N\x85G\xf9\x17" + + "\xc3\xb4\x92\xc3D}`W\xf4\xe9\x9b\xd7U\x1e\xf3P" + + "\x9bn\x0c\x87\xc7\xf8\x03\xa6\xed\xde\xf0\x0e@c\xa2\xe3" + + "\xd4\x9e\xa7\xf6\xcc\xdb\xef\xc7;\x1e\xbe\xc0'M\xdb\x8b" + + "&\xef\xd8\x1b\xfb\xce\x9f\x0e\xfee?\xc8\x0d(\x18o" + + "?\xf9\xc2\xc9\xd6\x86?\xbc\x00\x0d\x18B\x00\x9e\x8c\x8c" + + "\x01\xf2\x86\x08\xb1\xceo\xfdi\xeb=uw\x1d\xf6a" + + "]\xb5=\xc2\x90\xef\x8a\x10m%B\xb4\xc7'\x0f\xcb" + + "w7^\xf9\xa57\xc5\xa6\xf5\x91\xc8|\xe4\xbf3\xad" + + "OD~\x05h\xdcx\xd3#;\x82\x8b\x9b\x8e\xf9\x1e" + + "H!z\x8co\x8f\xd2\xaa/J~\x9c\x9f\xbaap" + + "\xfc\xe2\xba\xe7\xfd\xa2;\x12\xbd\xc0O\x98\xb6\xcfD\xc9" + + "\x0d;\x1e9\x8e\xc25\xa9\x88\xfe\x9c_$\xe3U\x13" + + "\xd1f\x044\xde\x19\xfcAy\xeb\x0a\xe3E\x0f\xb3\xe9" + + "\xc5d\xec\x0c\x7f?F\xab\x99\x18y!*\x7f]\xdf" + + "\xb1\xfbS\xa3~e\xf1\xe5\xf8\x18W\xe2\xb4\xca\xc6\xc9" + + "\x8b\x83o-z\xfc\xe9'\x94\x97\xfcl\x87\xe3#|" + + "\xafi\xbb\xc7\xb4=y\xfa\xb7\x87\xf6>\xb2\xf8\x9co" + + "\xe2\x0e\xc6\x9b\x90?cZ\x1f\x8dS\xe2\x8ao\xa6\xbf" + + "\xd4\xf4\xca\xe5s~\xb9\xc8\xd6\x8f\xf2\xbez3\x83\xf5" + + "\xc4\xbcf\xf1k\xdf\xeeix\xf1\x92\x1f3\xffa\xfd" + + "\x14?h\x1a\x1f\xa8\xa7\xf0\xda\xdf\xba\xb5\xe5\xd9\x09q" + + "\xd2\xd7\x18\xc5\x11\x1e\x16M\x01\x88d|\xe2\xb9\x9d\x87" + + "\xbf\xf7\xda\xa1\xcb~^\x0c\x88S|\xd8\xb4\xdd%\x92" + + "\x17\xb1\xa6\x7f\xfc\xa2\xe7\xa6\xf1\x19\x90o@\xd7\xc17" + + "0\xb3\xde\x8e\x88g\x00\xf9Q\x93\xf57\xcf\xde\x7f\xdb" + + "\xf1'\x8f^\xf5\xab\xf8xb\x8a\x7f0\xccd\xf0\xbfa\x07@\xe6" + + "e\x02N\x12 \xbco\x98\x09\xe1o`\x1a \xf3:" + + "\x01g\x09\x08\xbcgH\x18\x00\xe0\xa7M\xe0M\x02\xde" + + "! \xf8_C\xc2 \x00\x9f\xc0m\x00\x99\xf3\x04L" + + "\x130\xef\xaa!\xe1<\x12+~\x0b s\x89\x80\xf7" + + "\x08\x08\xcd\x18\x92Y\x8d3\xa8\x02d\xae\x10\x10`\x0c" + + "\xe3uW\x0c\x09\xeb\xa8\xa4\xd96\x804\x130\x13\xa3" + + "\xe7\xe1\xcb\x86\x84a\xea\xeb\xecG\x00\x99\x18\x01\x8d\x04" + + "D\xfecH\x18\xa1\xbac\xfb\x002\x8d\x04,\" " + + "\xfaoC\xc2(\x00_\xc0n\x07\xc8\xdcH\xc02\x02" + + "b\xd3\x86\x841\x00\xde\xc2h\xef%\x04\xac& \xfe" + + "\xae!a\x1c\x80\xb72\xf2v%\x01\xb70\x86\xc9\x04" + + "JX\x0f\xc0?\xc7(S\xab\xe9\xf9:z\xa1~\xca" + + "\x90P\x04\xe0\x9fg\xf7\x01dn!\xa0\x93\x00q\xd2" + + "\x900\x01\xc07\x99\xc0F\x02\xba\x18C\xa1\x903\xab" + + ":\x0c\xd8<\xd0\xaf):\xcc\x1b*g\xf5\xde\xb4\xb2" + + "\x1d\x13No\x04\xc4\x04\xa0a!\xe5\"`\x05\x13N" + + "\x03\xa8\xa2Y\xcd\xd2\x14 \xbdkw4/\x1a*\x17" + + "\xe9m{\xb2UqU\x19\xbc\xb3\xa4\x17\xf2X\xe8\xce" + + "\xea\x85R?`\xc2\x99RU\x9bB\xbe\xca\xd1\xbc}" + + "@\xd1tL83\xddkQ\xdd\xc5\xee~U\\S" + + "\xd4\xc1B\xb7\x92B\x97\xfa1\xe1\x0c._\xb3r\xb1" + + "\x02\xe4\x8e\xdd\xc4\x1c\x97\xab \xa1\xf6-\xc0\xe6\xe8\xd9" + + "Z)+\x9d\xd0\\*[\xe9\xb4\x07\x82\xc7\x02\xc9\x80" + + "x0\xe1\x8c.\xcbfHW\xb3\xddJ*W\x93\xbd" + + "\x91S\xbf\xa6T\xee\x18,\x82\xd8f\x91>\x1a\x0fL" + + "\x8ft\xfel\xbcF:\xcb\xa0\x8c\x09c\xf7\xa7W^" + + "\xfa\xe0\xe6\xa7N\xd9\xbb\xcej^\xeb3)'F\x8f" + + "\xf4;\x9c>8\xa4\xf4\xebj\xc1\xdd]\xec\xdeku" + + "\x17\x0f-\xb5\xaa\x94\xd5\x95\x84n\x85x\xebl\xde\x16" + + "\xea\xaf\x8b\x04\x94W2L\xd6\xba\xe2\xf2\xa5\x00\xf2\x12" + + "\x01\xe5\xd5\xd4t\xb5\\V\xab\xd5\xa5H-\xb8\xf6\xc7" + + "\xb3M\xbaZ4\x85\xee\xacHE\xe3\x09\xe0v\x009" + + "&\xa0\xdc\xc8\xd0\xd0\xd2\xca \x85j\x1dV\xfa\x9fW" + + "?\xbb\xe7\xb6\xb6\x9f\xf8'\xa5\xcbR\xc0\x8a|1+" + + "\xf4h\xe4z\xe2!\xab%\xb7t\xb8}\x7f\xd8\xec>" + + "\xc9\xe5k\x1d\xdf\x87T%\xaf*Z/\"0D\xc0" + + "\xf6\xdeB.\xa7\xf4\xd7\xfe\xda\x1b\x09V\xcf\xac\xd6Y" + + "\xad\x1a5\xdd{\x06\xf7UCX\xc2\xec\xaa\xdc\x0ab" + + "\xa5\xec\x1c\x85h\xe8=/\x7f\xa2ey\xfa\x8c\xf7(" + + "j{XUV-\xb2M\xfd\xba\x8af\x93\x8f\xd9\xbb" + + "l\xa2\xe9\xb3Q@\xf9^gL}%\xed\x8c.{" + + "L)\x1d\xce\xec\xfahS\xc7\xd0\x0b}\x8a\xa6g\xfb" + + "\x00\xcb\xb5\xc9\xf3!\x93\xa8\xb3\xa45\xeb\x94\x12O\xd9" + + ",uRO?\xe7\x9e\x90\\\xde\x06L,\x97T{" + + "45gs9U\xf3\x9c\xaa+\x11\xa2\xcf\xa0\xbbn" + + "\xb5\xdb\x97UO\x8a\xb1V/\"\x15\x0c1J6\xe3" + + ".\x1a\xf4;\xab3\xbd\x96\xd6\xe1\x85\xceLO\xb2:" + + "+\xad{\xa8Pw\x0b(?\xc4\x10\x05t]\xb9\x93" + + "\x0f\xb4\x01\xc3\x809\xcc\x92\x03Tge\x01\xe5\xef3" + + "\x0c\xe54\xbd&\x8a\x90\xa6v\xdb\x02\xe9\xcb\xee\xa4\xf2" + + "\xd5\xa8\xcek\xd9\xc8\x17\xb3=Z{oyC\xbe\xc7" + + "\x15S\xe3\xa6\xb3\xb7\xf2?.\x18\x99[\xc1\xd5\x82\x09" + + "\xe9je\xee\xb3p$LQ,\x13P^\xc3P\xa4" + + "Q\x81\x09\xe7;\xaa\xaa\xb3\xde\x92\xa6;*\xb4\xefv" + + "\xbe*t\x9d\x97`\xe5\xd6uZK\x9dk\x89\xa8W" + + "\xca\x0a\x8a\xc67\xd7<\x11Q\x0e_>@l\xe25" + + "g\xb4>\x93j\xb7d6\xc7mO\xf26\x9e\xebi" + + "\xd5\x12\x92\xa0z\x85\xb4\xad*\xa4.W^\xb6\xd0\x91" + + "w\x0a(oe\x88\xcc:q\x99\xd4\xd5U\x15\x92\xad" + + "\xeb\x90\x15\x88[\xcf\"`H\xd7\x8b\xb6T\xec\x04\xa2" + + "\xeb$\xddy\xac\x9f\xf3\xae\xfb\x7f\xb7x\xfbs\xe0\xc3" + + "h\x9b\xa9\xa9T\xae'W\x9f\x0a\x99\xd5\xd6?Z]" + + "\xd8jk\xef\xb5\xaf\xaa\xae\x1d\xd3N\x1b\xae\xed\xd8\xda" + + "Q\xdd\xb1\x93\xa1\xa1\xa8jI\xddP\xca\x01*5i" + + "\\\x1b\xb4\xfd\x85\xe7\x1b\xb4\xab\x08|o\xcb\xd7\xcd\xa7" + + "\xfd\xe1\xe6K\xddYM\xc1\x8al.\x94S5+0" + + "+\x8ak\xe4\xc6<\x13S,\x94\x07W\xd7\xee\x08\xf4" + + "\xe73\xf6\x85a\xceq\xef\x1c\x9a\xab~I\x0c\xeb\x04" + + "\x94\xef\xa0\x8d\x02\xd6\xee\xa9\x85\xae\xa2f]\xd6\xee[" + + "\xd6:E=[6\xee/\x93\xf6\x82\xb6\xa1\xa4*\xb5" + + "\xe9\xf7\xbf\x00\x00\x00\xff\xff\x915Sv" func init() { schemas.Register(schema_8f4bd412642c9517, diff --git a/go/sciond/BUILD.bazel b/go/sciond/BUILD.bazel index 8a1c674875..c3bed22b2e 100644 --- a/go/sciond/BUILD.bazel +++ b/go/sciond/BUILD.bazel @@ -10,6 +10,7 @@ go_library( "//go/lib/addr:go_default_library", "//go/lib/common:go_default_library", "//go/lib/discovery:go_default_library", + "//go/lib/drkeystorage:go_default_library", "//go/lib/env:go_default_library", "//go/lib/fatal:go_default_library", "//go/lib/infra:go_default_library", @@ -29,6 +30,7 @@ go_library( "//go/lib/topology:go_default_library", "//go/proto:go_default_library", "//go/sciond/internal/config:go_default_library", + "//go/sciond/internal/drkey:go_default_library", "//go/sciond/internal/fetcher:go_default_library", "//go/sciond/internal/servers:go_default_library", "@com_github_burntsushi_toml//:go_default_library", diff --git a/go/sciond/internal/config/BUILD.bazel b/go/sciond/internal/config/BUILD.bazel index 0de9a303ce..0df3d5aed6 100644 --- a/go/sciond/internal/config/BUILD.bazel +++ b/go/sciond/internal/config/BUILD.bazel @@ -11,6 +11,7 @@ go_library( deps = [ "//go/lib/common:go_default_library", "//go/lib/config:go_default_library", + "//go/lib/drkeystorage:go_default_library", "//go/lib/env:go_default_library", "//go/lib/infra/modules/idiscovery:go_default_library", "//go/lib/pathstorage:go_default_library", diff --git a/go/sciond/internal/config/config.go b/go/sciond/internal/config/config.go index f02616d134..585b2a6d15 100644 --- a/go/sciond/internal/config/config.go +++ b/go/sciond/internal/config/config.go @@ -21,6 +21,7 @@ import ( "github.com/scionproto/scion/go/lib/common" "github.com/scionproto/scion/go/lib/config" + "github.com/scionproto/scion/go/lib/drkeystorage" "github.com/scionproto/scion/go/lib/env" "github.com/scionproto/scion/go/lib/infra/modules/idiscovery" "github.com/scionproto/scion/go/lib/pathstorage" @@ -114,9 +115,12 @@ type SDConfig struct { // QueryInterval specifies after how much time segments // for a destination should be refetched. QueryInterval util.DurWrap + // DRKeyDB contains the DRKey DB configuration. + DRKeyDB drkeystorage.DRKeyDBConf } func (cfg *SDConfig) InitDefaults() { + if cfg.Reliable == "" { cfg.Reliable = sciond.DefaultSCIONDPath } @@ -129,7 +133,7 @@ func (cfg *SDConfig) InitDefaults() { if cfg.QueryInterval.Duration == 0 { cfg.QueryInterval.Duration = DefaultQueryInterval } - config.InitAll(&cfg.PathDB, &cfg.RevCache) + config.InitAll(&cfg.PathDB, &cfg.RevCache, &cfg.DRKeyDB) } func (cfg *SDConfig) Validate() error { @@ -145,7 +149,7 @@ func (cfg *SDConfig) Validate() error { if cfg.QueryInterval.Duration == 0 { return serrors.New("QueryInterval must not be zero") } - return config.ValidateAll(&cfg.PathDB, &cfg.RevCache) + return config.ValidateAll(&cfg.PathDB, &cfg.RevCache, &cfg.DRKeyDB) } func (cfg *SDConfig) Sample(dst io.Writer, path config.Path, ctx config.CtxMap) { diff --git a/go/sciond/internal/drkey/BUILD.bazel b/go/sciond/internal/drkey/BUILD.bazel new file mode 100644 index 0000000000..e59bfd0be4 --- /dev/null +++ b/go/sciond/internal/drkey/BUILD.bazel @@ -0,0 +1,19 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["client_store.go"], + importpath = "github.com/scionproto/scion/go/sciond/internal/drkey", + visibility = ["//go/sciond:__subpackages__"], + deps = [ + "//go/lib/addr:go_default_library", + "//go/lib/common:go_default_library", + "//go/lib/ctrl/drkey_mgmt:go_default_library", + "//go/lib/drkey:go_default_library", + "//go/lib/drkeystorage:go_default_library", + "//go/lib/infra:go_default_library", + "//go/lib/infra/messenger:go_default_library", + "//go/lib/snet:go_default_library", + "//go/lib/util:go_default_library", + ], +) diff --git a/go/sciond/internal/drkey/client_store.go b/go/sciond/internal/drkey/client_store.go new file mode 100644 index 0000000000..1b31e9291d --- /dev/null +++ b/go/sciond/internal/drkey/client_store.go @@ -0,0 +1,87 @@ +// Copyright 2019 ETH Zurich +// +// 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 drkey + +import ( + "context" + "database/sql" + // "fmt" + // "net" + "time" + + "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" + // "github.com/scionproto/scion/go/lib/ctrl/cert_mgmt" + "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" + "github.com/scionproto/scion/go/lib/drkey" + // "github.com/scionproto/scion/go/lib/drkey/protocol" + "github.com/scionproto/scion/go/lib/drkeystorage" + "github.com/scionproto/scion/go/lib/infra" + "github.com/scionproto/scion/go/lib/infra/messenger" + // "github.com/scionproto/scion/go/lib/infra/modules/trust/trustdb" + // "github.com/scionproto/scion/go/lib/log" + // "github.com/scionproto/scion/go/lib/scrypto" + // "github.com/scionproto/scion/go/lib/scrypto/cert" + "github.com/scionproto/scion/go/lib/snet" + "github.com/scionproto/scion/go/lib/util" +) + +// ClientStore is the DRKey store used in the client side, i.e. sciond. +// It implements drkeystorage.ClientStore. +type ClientStore struct { + ia addr.IA + db drkey.Lvl2DB + msger infra.Messenger +} + +var _ drkeystorage.ClientStore = &ClientStore{} + +// NewClientStore constructs a new client store without assigned messenger. +func NewClientStore(local addr.IA, db drkey.Lvl2DB, msger infra.Messenger) *ClientStore { + return &ClientStore{ + ia: local, + db: db, + msger: msger, + } +} + +// GetLvl2Key returns the level 2 drkey from the local DB or if not found, by asking our local CS. +func (s *ClientStore) GetLvl2Key(ctx context.Context, meta drkey.Lvl2Meta, + valTime time.Time) (drkey.Lvl2Key, error) { + + // is it in storage? + k, err := s.db.GetLvl2Key(ctx, meta, util.TimeToSecs(valTime)) + if err == nil { + return k, err + } + if err != sql.ErrNoRows { + return drkey.Lvl2Key{}, common.NewBasicError("Cannot retrieve key from DB", err) + } + // if not, ask our CS for it + req := drkey_mgmt.NewLvl2ReqFromMeta(meta, valTime) + csAddress := &snet.Addr{IA: s.ia, Host: addr.NewSVCUDPAppAddr(addr.SvcCS)} + rep, err := s.msger.RequestDRKeyLvl2(ctx, &req, csAddress, messenger.NextId()) + if err != nil { + return drkey.Lvl2Key{}, + common.NewBasicError("Error sending DRKey lvl2 request via messenger", err) + } + return rep.ToKey(meta), nil +} + +// DeleteExpiredKeys will remove any expired keys. +func (s *ClientStore) DeleteExpiredKeys(ctx context.Context) (int, error) { + i, err := s.db.RemoveOutdatedLvl2Keys(ctx, util.TimeToSecs(time.Now())) + return int(i), err +} diff --git a/go/sciond/internal/servers/BUILD.bazel b/go/sciond/internal/servers/BUILD.bazel index a57014e9d2..1120a88a89 100644 --- a/go/sciond/internal/servers/BUILD.bazel +++ b/go/sciond/internal/servers/BUILD.bazel @@ -11,7 +11,9 @@ go_library( visibility = ["//go/sciond:__subpackages__"], deps = [ "//go/lib/common:go_default_library", + "//go/lib/ctrl/drkey_mgmt:go_default_library", "//go/lib/ctrl/path_mgmt:go_default_library", + "//go/lib/drkeystorage:go_default_library", "//go/lib/hostinfo:go_default_library", "//go/lib/infra:go_default_library", "//go/lib/infra/modules/itopo:go_default_library", @@ -23,6 +25,7 @@ go_library( "//go/lib/serrors:go_default_library", "//go/lib/sock/reliable:go_default_library", "//go/lib/tracing:go_default_library", + "//go/lib/util:go_default_library", "//go/proto:go_default_library", "//go/sciond/internal/fetcher:go_default_library", "//go/sciond/internal/metrics:go_default_library", diff --git a/go/sciond/internal/servers/handlers.go b/go/sciond/internal/servers/handlers.go index b8c2c64bf7..f0be147181 100644 --- a/go/sciond/internal/servers/handlers.go +++ b/go/sciond/internal/servers/handlers.go @@ -20,7 +20,9 @@ import ( "net" "time" + "github.com/scionproto/scion/go/lib/ctrl/drkey_mgmt" "github.com/scionproto/scion/go/lib/ctrl/path_mgmt" + "github.com/scionproto/scion/go/lib/drkeystorage" "github.com/scionproto/scion/go/lib/hostinfo" "github.com/scionproto/scion/go/lib/infra" "github.com/scionproto/scion/go/lib/infra/modules/itopo" @@ -30,6 +32,7 @@ import ( "github.com/scionproto/scion/go/lib/revcache" "github.com/scionproto/scion/go/lib/sciond" "github.com/scionproto/scion/go/lib/serrors" + "github.com/scionproto/scion/go/lib/util" "github.com/scionproto/scion/go/proto" "github.com/scionproto/scion/go/sciond/internal/fetcher" "github.com/scionproto/scion/go/sciond/internal/metrics" @@ -342,6 +345,37 @@ func isUnknown(err error) bool { return err != nil } +type DrKeyLvl2RequestHandler struct { + Store drkeystorage.ClientStore +} + +func (h *DrKeyLvl2RequestHandler) Handle(ctx context.Context, conn net.PacketConn, + src net.Addr, pld *sciond.Pld) { + + req := pld.DRKeyLvl2Req + logger := log.FromCtx(ctx) + logger.Debug("[DrKeyLvl2RequestHandler] Received request", "req", req) + workCtx, workCancelF := context.WithTimeout(ctx, DefaultWorkTimeout) + defer workCancelF() + + key, err := h.Store.GetLvl2Key(workCtx, req.ToMeta(), util.SecsToTime(req.ValTimeRaw)) + if err != nil { + logger.Error("Error sending DRKey lvl2 request via messenger", "err", err) + return + } + + replyToSend := &sciond.Pld{ + Id: pld.Id, + Which: proto.SCIONDMsg_Which_drkeyLvl2Rep, + DRKeyLvl2Rep: drkey_mgmt.NewLvl2RepFromKey(key, time.Now()), + } + if err := sendReply(replyToSend, conn, src); err != nil { + logger.Warn("Unable to reply to client", "client", src, "err", err, "reply", replyToSend) + } else { + logger.Trace("Sent reply", "DRKeyLvl2Rep", drkey_mgmt.Lvl2Rep{}) + } +} + func sendReply(pld *sciond.Pld, conn net.PacketConn, src net.Addr) error { b, err := proto.PackRoot(pld) if err != nil { diff --git a/go/sciond/main.go b/go/sciond/main.go index eec0fa8d16..045e9f6c0e 100644 --- a/go/sciond/main.go +++ b/go/sciond/main.go @@ -30,6 +30,7 @@ import ( "github.com/scionproto/scion/go/lib/addr" "github.com/scionproto/scion/go/lib/common" "github.com/scionproto/scion/go/lib/discovery" + "github.com/scionproto/scion/go/lib/drkeystorage" "github.com/scionproto/scion/go/lib/env" "github.com/scionproto/scion/go/lib/fatal" "github.com/scionproto/scion/go/lib/infra" @@ -49,6 +50,7 @@ import ( "github.com/scionproto/scion/go/lib/topology" "github.com/scionproto/scion/go/proto" "github.com/scionproto/scion/go/sciond/internal/config" + "github.com/scionproto/scion/go/sciond/internal/drkey" "github.com/scionproto/scion/go/sciond/internal/fetcher" "github.com/scionproto/scion/go/sciond/internal/servers" ) @@ -189,6 +191,24 @@ func realMain() int { NextQueryCleaner: segfetcher.NextQueryCleaner{PathDB: pathDB}, }, } + + drkeyEnabled := cfg.SD.DRKeyDB.Connection() != "" + log.Info("DRKey", "enabled", drkeyEnabled) + if drkeyEnabled { + drkeyDB, err := cfg.SD.DRKeyDB.NewLvl2DB() + if err != nil { + log.Crit("Unable to initialize drkey storage", "err", err) + return 1 + } + defer drkeyDB.Close() + drkeyStore := drkey.NewClientStore(itopo.Get().IA(), drkeyDB, msger) + drkeyCleaner := periodic.Start(drkeystorage.NewStoreCleaner(drkeyStore), + time.Hour, 10*time.Minute) + defer drkeyCleaner.Stop() + handlers[proto.SCIONDMsg_Which_drkeyLvl2Req] = &servers.DrKeyLvl2RequestHandler{ + Store: drkeyStore, + } + } cleaner := periodic.Start(pathdb.NewCleaner(pathDB, "sd_segments"), 300*time.Second, 295*time.Second) defer cleaner.Stop() diff --git a/proto/ctrl_pld.capnp b/proto/ctrl_pld.capnp index 7d9693c6ce..b9f3a3a457 100644 --- a/proto/ctrl_pld.capnp +++ b/proto/ctrl_pld.capnp @@ -14,6 +14,7 @@ using SIG = import "sig.capnp"; using CtrlExtn = import "ctrl_extn.capnp"; using Ack = import "ack.capnp"; + struct SignedCtrlPld { blob @0 :Data; # Raw CtrlPld sign @1 :Sign.Sign; diff --git a/proto/drkey_mgmt.capnp b/proto/drkey_mgmt.capnp index 0c716cde74..db16588bec 100644 --- a/proto/drkey_mgmt.capnp +++ b/proto/drkey_mgmt.capnp @@ -4,33 +4,53 @@ using Go = import "go.capnp"; $Go.package("proto"); $Go.import("github.com/scionproto/scion/go/proto"); -struct DRKeyReq { - isdas @0 :UInt64; # Src ISD-AS of the requested DRKey - timestamp @1 :UInt32; # Timestamp, seconds since Unix Epoch - signature @2 :Data; # Signature of (isdas, prefetch, timestamp) - certVer @3 :UInt32; # Version cert used to sign - trcVer @4 :UInt32; # Version of TRC, which signed cert - flags :group { - prefetch @5 :Bool; # Indicator request for current (false) or next (true) DRKey - } +struct DRKeyLvl1Req { + dstIA @0 :UInt64; # Dst ISD-AS of the requested DRKey + valTime @1 :UInt32; # Point in time where requested DRKey is valid. Used to identify the epoch + timestamp @2 :UInt32; # Point in time when the request was created } -struct DRKeyRep { - isdas @0 :UInt64; # Src ISD-AS of the DRKey - timestamp @1 :UInt32; # Timestamp, seconds since Unix Epoch - expTime @2 :UInt32; # Expiration time of the DRKey, seconds since Unix Epoch +struct DRKeyLvl1Rep { + dstIA @0 :UInt64; # Dst ISD-AS of the DRKey + epochBegin @1 :UInt32; # Begin of validity period of DRKey + epochEnd @2 :UInt32; # End of validity period of DRKey cipher @3 :Data; # Encrypted DRKey - signature @4 :Data; # Signature (isdas, cipher, timestamp, expTime) - certVerSrc @5 :UInt32; # Version of cert used to sign - certVerDst @6 :UInt32; # Version of cert of public key used to encrypt - trcVer @7 :UInt32; # Version of TRC, of signing cert + nonce @4 :Data; # Nonce used for encryption + certVerDst @5 :UInt64; # Version of cert of public key used to encrypt + timestamp @6 :UInt32; # Creation time of this reply +} + +struct DRKeyHost { + type @0 :UInt8; # AddrType + host @1 :Data; # Host address +} + +struct DRKeyLvl2Req { + protocol @0 :Text; # Protocol identifier + reqType @1 :UInt8; # Requested DRKeyProtoKeyType + valTime @2 :UInt32; # Point in time where requested DRKey is valid. Used to identify the epoch + srcIA @3 :UInt64; # Src ISD-AS of the requested DRKey + dstIA @4 :UInt64; # Dst ISD-AS of the requested DRKey + srcHost @5 :DRKeyHost; # Src Host of the request DRKey (optional) + dstHost @6 :DRKeyHost; # Dst Host of the request DRKey (optional) + misc @7 :Data; # Additional information (optional) +} + +struct DRKeyLvl2Rep { + timestamp @0 :UInt32; # Timestamp + drkey @1 :Data; # Derived DRKey + epochBegin @2 :UInt32; # Begin of validity period of DRKey + epochEnd @3 :UInt32; # End of validity period of DRKey + misc @4 :Data; # Additional information (optional) } struct DRKeyMgmt { union { unset @0 :Void; - drkeyReq @1 :DRKeyReq; - drkeyRep @2 :DRKeyRep; + drkeyLvl1Req @1 :DRKeyLvl1Req; + drkeyLvl1Rep @2 :DRKeyLvl1Rep; + drkeyLvl2Req @3 :DRKeyLvl2Req; + drkeyLvl2Rep @4 :DRKeyLvl2Rep; } } diff --git a/proto/sciond.capnp b/proto/sciond.capnp index 0c93317a08..04dcba001f 100644 --- a/proto/sciond.capnp +++ b/proto/sciond.capnp @@ -7,6 +7,7 @@ using Common = import "common.capnp"; using Sign = import "sign.capnp"; using PSeg = import "path_seg.capnp"; using PathMgmt = import "path_mgmt.capnp"; +using DRKey = import "drkey_mgmt.capnp"; struct SCIONDMsg { id @0 :UInt64; # Request ID @@ -24,6 +25,8 @@ struct SCIONDMsg { revReply @11 :RevReply; segTypeHopReq @12 :SegTypeHopReq; segTypeHopReply @13 :SegTypeHopReply; + drkeyLvl2Req @15 :DRKey.DRKeyLvl2Req; + drkeyLvl2Rep @16 :DRKey.DRKeyLvl2Rep; } traceId @14 :Data; }