diff --git a/docker/Makefile b/docker/Makefile index 93d8960..98093dc 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -23,7 +23,8 @@ mount_opt=-v $(shell readlink -f ..):/go/opt docker_run_sh=$(docker) run ${docker_args} $(mount_net_name) $(mount_opt) --rm katzenpost-$(distro)_base $(sh) -c katzenpost_dir?=/tmp/katzenpost.opt -katzenpost_version?=$(shell grep -E '^ github.com/katzenpost/katzenpost ' ../go.mod | awk '{print $$2}') +#katzenpost_version?=$(shell grep -E '^ github.com/katzenpost/katzenpost ' ../go.mod | awk '{print $$2}') +katzenpost_version=rc-v0.0.44 net_dir=$(katzenpost_dir)/docker/$(net_name) # export variables to the environment for consumption by invoked Makefile(s) diff --git a/go.mod b/go.mod index bba048e..5a37cfc 100644 --- a/go.mod +++ b/go.mod @@ -8,9 +8,10 @@ require ( github.com/carlmjohnson/versioninfo v0.22.5 github.com/charmbracelet/log v0.4.0 github.com/fxamacker/cbor/v2 v2.7.0 - github.com/katzenpost/hpqc v0.0.45 - github.com/katzenpost/katzenpost v0.0.43 - github.com/quic-go/quic-go v0.47.0 + github.com/katzenpost/hpqc v0.0.49 + github.com/katzenpost/katzenpost v0.0.44-0.20250101172607-166260628ac7 + github.com/quic-go/quic-go v0.48.2 + golang.org/x/net v0.29.0 gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 gopkg.in/yaml.v3 v3.0.1 ) @@ -30,7 +31,7 @@ require ( github.com/katzenpost/circl v1.3.9-0.20240222183521-1cd9a34e9a0c // indirect github.com/katzenpost/nyquist v0.0.10 // indirect github.com/katzenpost/sntrup4591761 v0.0.0-20231024131303-8755eb1986b8 // indirect - github.com/katzenpost/sphincsplus v0.0.2-0.20240114192234-1dc77b544e31 // indirect + github.com/katzenpost/sphincsplus v0.0.2 // indirect github.com/lesismal/nbio v1.5.11 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect @@ -50,12 +51,11 @@ require ( gitlab.com/yawning/x448.git v0.0.0-20221003101044-617eb9b7d9b7 // indirect go.etcd.io/bbolt v1.3.10 // indirect go.uber.org/mock v0.4.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect - golang.org/x/tools v0.24.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.25.0 // indirect ) diff --git a/go.sum b/go.sum index 246e8e7..9424ab7 100644 --- a/go.sum +++ b/go.sum @@ -41,16 +41,16 @@ github.com/katzenpost/chacha20 v0.0.0-20190910113340-7ce890d6a556 h1:9gHByAWH1Ly github.com/katzenpost/chacha20 v0.0.0-20190910113340-7ce890d6a556/go.mod h1:d9kxwmGOcutgP6bQwr2xaLInaW5yJsxsoPRyUIG0J/E= github.com/katzenpost/circl v1.3.9-0.20240222183521-1cd9a34e9a0c h1:FYy03rLIjdyjklBOI6YSCb3q7OubTx0dVDWYOgDsvA8= github.com/katzenpost/circl v1.3.9-0.20240222183521-1cd9a34e9a0c/go.mod h1:+EBrwiGYs9S+qZqaqxujN1CReTNCMAG6p+31KkEDeeA= -github.com/katzenpost/hpqc v0.0.45 h1:CiNTvwUe7CaGdIeA0tEtHY+O3CKk6lTgdAb4iQfSy4k= -github.com/katzenpost/hpqc v0.0.45/go.mod h1:yMxuQLTjgzgHdvQlJIbWFiusyizyMW94fpH6wxTTur8= -github.com/katzenpost/katzenpost v0.0.43 h1:BAZxLxl3he+bNodTaXv6GW0BYA9Qj6jGQcsVHOjeiN0= -github.com/katzenpost/katzenpost v0.0.43/go.mod h1:+aRwtsFwBT7GTU9Mj07MlQ3QoM4XQ6YLP/w0/j1gOHc= +github.com/katzenpost/hpqc v0.0.49 h1:hAKhsW56+NL0froAbD93Hd5PBL4qhG/JWMZEo2GcBKw= +github.com/katzenpost/hpqc v0.0.49/go.mod h1:0UyPt4dt8j5K6oPTk0ITwxYOalvFcpwfpzZbqMntHeU= +github.com/katzenpost/katzenpost v0.0.44-0.20250101172607-166260628ac7 h1:HsDMH+zK5X17q4nh4FfJnl2Yn+n74MvARotrmL1Z0iQ= +github.com/katzenpost/katzenpost v0.0.44-0.20250101172607-166260628ac7/go.mod h1:AVnqhTpXa409iSMVvHEP9ED/Fsumkuzs1iFMotpfNqc= github.com/katzenpost/nyquist v0.0.10 h1:rh9TCEXCsutsg+cvbV6ASVFnzSAYBisWQ3fnwQSPa34= github.com/katzenpost/nyquist v0.0.10/go.mod h1:tyK92JiCptgsaE0iUAMlt5W2v2Rdw6mnUpIdIidIGHo= github.com/katzenpost/sntrup4591761 v0.0.0-20231024131303-8755eb1986b8 h1:TsKxH0x2RUwf5rBw67k15bqVM3oVbexA9oaTZQLIy3Y= github.com/katzenpost/sntrup4591761 v0.0.0-20231024131303-8755eb1986b8/go.mod h1:Hmcrwom7jcEmGdo0CsyuJNnldPeyS+M07FuCbo7I8fw= -github.com/katzenpost/sphincsplus v0.0.2-0.20240114192234-1dc77b544e31 h1:fKGa/too1Br31gmoYmV2kE61gydj47Ed5K/g/CE+3Bs= -github.com/katzenpost/sphincsplus v0.0.2-0.20240114192234-1dc77b544e31/go.mod h1:VFrCPnmbxQLBi+qJfWHUqvpvTMZrYBMZEEy0AidY0nE= +github.com/katzenpost/sphincsplus v0.0.2 h1:W1UWejLK62Lk0uK2R08H/sWEaQrRHWCaMEKO181SoOE= +github.com/katzenpost/sphincsplus v0.0.2/go.mod h1:ChO9+ojgCH1yEuplGgW4mSI1FwZWtyEmEkG1xL3w264= github.com/lesismal/llib v1.1.13/go.mod h1:70tFXXe7P1FZ02AU9l8LgSOK7d7sRrpnkUr3rd3gKSg= github.com/lesismal/nbio v1.5.11 h1:MVjrzcej4NSJQMRT+S0dPZvVaiFUHD1JWnvr+FHIHOo= github.com/lesismal/nbio v1.5.11/go.mod h1:QsxE0fKFe1PioyjuHVDn2y8ktYK7xv9MFbpkoRFj8vI= @@ -79,14 +79,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y= -github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E= +github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= +github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/rfjakob/eme v1.1.2 h1:SxziR8msSOElPayZNFfQw4Tjx/Sbaeeh3eRvrHVMUs4= github.com/rfjakob/eme v1.1.2/go.mod h1:cVvpasglm/G3ngEfcfT/Wt0GwhkuO32pf/poW6Nyk1k= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/schwarmco/go-cartesian-product v0.0.0-20230921023625-e02d1c150053 h1:h7EwPM2KjupG0zVAG+EYxbR2cHnbiP1d4DTAZ+G09LY= +github.com/schwarmco/go-cartesian-product v0.0.0-20230921023625-e02d1c150053/go.mod h1:/TRiIlxvQQAtfnBXEqqbnYBYPmE6XT5iZxSx+hJ9zGw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -105,8 +107,8 @@ go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/crypto v0.0.0-20210513122933-cd7d49e622d5/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= @@ -115,26 +117,26 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190902133755-9109b7679e13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/pki/config/config.go b/pki/config/config.go index 676188a..cf01935 100644 --- a/pki/config/config.go +++ b/pki/config/config.go @@ -2,6 +2,7 @@ package config import ( + "bytes" "errors" "fmt" "io" @@ -11,9 +12,14 @@ import ( "strings" "github.com/BurntSushi/toml" + "golang.org/x/net/idna" + "github.com/katzenpost/hpqc/hash" + "github.com/katzenpost/hpqc/kem" + kempem "github.com/katzenpost/hpqc/kem/pem" "github.com/katzenpost/hpqc/kem/schemes" "github.com/katzenpost/hpqc/rand" + "github.com/katzenpost/hpqc/sign" signpem "github.com/katzenpost/hpqc/sign/pem" signSchemes "github.com/katzenpost/hpqc/sign/schemes" "github.com/katzenpost/katzenpost/core/sphinx/geo" @@ -241,6 +247,126 @@ func (dCfg *Debug) applyDefaults() { } } +// Authority is the authority configuration for a peer. +type Authority struct { + // Identifier is the human readable identifier for the node (eg: FQDN). + Identifier string + + // IdentityPublicKeyPem is a string in PEM format containing + // the public identity key key. + IdentityPublicKey sign.PublicKey + + // PKISignatureScheme specifies the cryptographic signature scheme + PKISignatureScheme string + + // LinkPublicKeyPem is string containing the PEM format of the peer's public link layer key. + LinkPublicKey kem.PublicKey + // WireKEMScheme is the wire protocol KEM scheme to use. + WireKEMScheme string + // Addresses are the listener addresses specified by a URL, e.g. tcp://1.2.3.4:1234 or quic://1.2.3.4:1234 + // Both IPv4 and IPv6 as well as hostnames are valid. + Addresses []string +} + +// UnmarshalTOML deserializes into non-nil instances of sign.PublicKey and kem.PublicKey +func (a *Authority) UnmarshalTOML(v interface{}) error { + + data, ok := v.(map[string]interface{}) + if !ok { + return errors.New("type assertion failed") + } + + pkiSignatureSchemeStr, ok := data["PKISignatureScheme"].(string) + if !ok { + return errors.New("PKISignatureScheme failed type assertion") + } + pkiSignatureScheme := signSchemes.ByName(pkiSignatureSchemeStr) + if pkiSignatureScheme == nil { + return fmt.Errorf("pki signature scheme `%s` not found", pkiSignatureScheme) + } + a.PKISignatureScheme = pkiSignatureSchemeStr + + // identifier + var err error + a.IdentityPublicKey, _, err = pkiSignatureScheme.GenerateKey() + if err != nil { + return err + } + a.Identifier, ok = data["Identifier"].(string) + if !ok { + return errors.New("Authority.Identifier type assertion failed") + } + + // identity key + idPublicKeyString, _ := data["IdentityPublicKey"].(string) + + a.IdentityPublicKey, err = signpem.FromPublicPEMString(idPublicKeyString, pkiSignatureScheme) + if err != nil { + return err + } + + // link key + linkPublicKeyString, ok := data["LinkPublicKey"].(string) + if !ok { + return errors.New("type assertion failed") + } + + kemSchemeName, ok := data["WireKEMScheme"].(string) + if !ok { + return errors.New("WireKEMScheme failed type assertion") + } + + a.WireKEMScheme = kemSchemeName + s := schemes.ByName(kemSchemeName) + if s == nil { + return fmt.Errorf("scheme `%s` not found", a.WireKEMScheme) + } + a.LinkPublicKey, err = kempem.FromPublicPEMString(linkPublicKeyString, s) + if err != nil { + return err + } + + // address + addresses := make([]string, 0) + pos, ok := data["Addresses"] + if !ok { + return errors.New("map entry not found") + } + for _, addr := range pos.([]interface{}) { + addresses = append(addresses, addr.(string)) + } + a.Addresses = addresses + return nil +} + +// Validate parses and checks the Authority configuration. +func (a *Authority) Validate() error { + if a.WireKEMScheme == "" { + return errors.New("WireKEMScheme is not set") + } else { + s := schemes.ByName(a.WireKEMScheme) + if s == nil { + return errors.New("KEM Scheme not found") + } + } + for _, v := range a.Addresses { + if u, err := url.Parse(v); err != nil { + return fmt.Errorf("config: Authority: Address '%v' is invalid: %v", v, err) + } else if u.Port() == "" { + return fmt.Errorf("config: Authority: Address '%v' is invalid: Must contain Port", v) + } + } + if a.IdentityPublicKey == nil { + return fmt.Errorf("config: %v: Authority is missing Identity Key", a) + } + + if a.LinkPublicKey == nil { + return fmt.Errorf("config: %v: Authority is missing Link Key PEM filename", a) + } + + return nil +} + // Node is an authority mix node or provider entry. type Node struct { // Identifier is the human readable node identifier, to be set iff @@ -252,6 +378,21 @@ type Node struct { IdentityPublicKeyPem string } +func (n *Node) validate(isProvider bool) error { + if n.Identifier == "" { + return errors.New("config: Node is missing Identifier") + } + var err error + n.Identifier, err = idna.Lookup.ToASCII(n.Identifier) + if err != nil { + return fmt.Errorf("config: Failed to normalize Identifier: %v", err) + } + if n.IdentityPublicKeyPem == "" { + return errors.New("config: Node is missing IdentityPublicKeyPem") + } + return nil +} + type Server struct { // Identifier is the human readable identifier for the node (eg: FQDN). Identifier string @@ -316,16 +457,17 @@ func (sCfg *Server) validate() error { // Config is the top level authority configuration. type Config struct { - Server *Server - Logging *Logging - Parameters *Parameters - Debug *Debug - - // Note: these are an iterative step; useful to register non-volunteer nodes within the appchain - Mixes []*Node - GatewayNodes []*Node - ServiceNodes []*Node - Topology *Topology + Server *Server + Authorities []*Authority + Logging *Logging + Parameters *Parameters + Debug *Debug + + Mixes []*Node + GatewayNodes []*Node + ServiceNodes []*Node + StorageReplicas []*Node + Topology *Topology SphinxGeometry *geo.Geometry } @@ -340,6 +482,30 @@ type Topology struct { Layers []Layer } +// ValidateAuthorities takes as an argument the dirauth server's own public key +// and tries to find a match in the dirauth peers. Returns an error if no +// match is found. Dirauths must be their own peer. +func (cfg *Config) ValidateAuthorities(linkPubKey kem.PublicKey) error { + linkblob1, err := linkPubKey.MarshalText() + if err != nil { + return err + } + match := false + for i := 0; i < len(cfg.Authorities); i++ { + linkblob, err := cfg.Authorities[i].LinkPublicKey.MarshalText() + if err != nil { + return err + } + if bytes.Equal(linkblob1, linkblob) { + match = true + } + } + if !match { + return errors.New("Authority must be it's own peer") + } + return nil +} + // FixupAndValidate applies defaults to config entries and validates the // supplied configuration. Most people should call one of the Load variants // instead. @@ -387,10 +553,82 @@ func (cfg *Config) FixupAndValidate(forceGenOnly bool) error { pkiSignatureScheme := signSchemes.ByName(cfg.Server.PKISignatureScheme) + allNodes := make([]*Node, 0, len(cfg.Mixes)+len(cfg.GatewayNodes)+len(cfg.ServiceNodes)) + for _, v := range cfg.Mixes { + allNodes = append(allNodes, v) + } + for _, v := range cfg.GatewayNodes { + allNodes = append(allNodes, v) + } + for _, v := range cfg.ServiceNodes { + allNodes = append(allNodes, v) + } + + var identityKey sign.PublicKey + if forceGenOnly { return nil } + idMap := make(map[string]*Node) + pkMap := make(map[[publicKeyHashSize]byte]*Node) + for _, v := range allNodes { + if _, ok := idMap[v.Identifier]; ok { + return fmt.Errorf("config: Node: Identifier '%v' is present more than once", v.Identifier) + } + if err := v.validate(true); err != nil { + return err + } + idMap[v.Identifier] = v + + // Note: ZK-PKI: respect absolute or relative file paths + pemFilePath := v.IdentityPublicKeyPem + if !filepath.IsAbs(pemFilePath) { + pemFilePath = filepath.Join(cfg.Server.DataDir, pemFilePath) + } + identityKey, err = signpem.FromPublicPEMFile(pemFilePath, pkiSignatureScheme) + if err != nil { + return err + } + + tmp := hash.Sum256From(identityKey) + if _, ok := pkMap[tmp]; ok { + return fmt.Errorf("config: Nodes: IdentityPublicKeyPem '%v' is present more than once", v.IdentityPublicKeyPem) + } + pkMap[tmp] = v + } + + idMap = make(map[string]*Node) + pkMap = make(map[[publicKeyHashSize]byte]*Node) + for _, v := range cfg.StorageReplicas { + if _, ok := idMap[v.Identifier]; ok { + return fmt.Errorf("config: Storage Replica Node: Identifier '%v' is present more than once", v.Identifier) + } + if err := v.validate(true); err != nil { + return err + } + idMap[v.Identifier] = v + + // Note: ZK-PKI: respect absolute or relative file paths + pemFilePath := v.IdentityPublicKeyPem + if !filepath.IsAbs(pemFilePath) { + pemFilePath = filepath.Join(cfg.Server.DataDir, pemFilePath) + } + identityKey, err = signpem.FromPublicPEMFile(pemFilePath, pkiSignatureScheme) + if err != nil { + return err + } + + tmp := hash.Sum256From(identityKey) + if _, ok := pkMap[tmp]; ok { + return fmt.Errorf("config: Storage Replica Node: IdentityPublicKeyPem '%v' is present more than once", v.IdentityPublicKeyPem) + } + pkMap[tmp] = v + } + + // if our own identity is not in cfg.Authorities return error + selfInAuthorities := false + ourPubKeyFile := filepath.Join(cfg.Server.DataDir, "identity.public.pem") f, err := os.Open(ourPubKeyFile) if err != nil { @@ -401,11 +639,24 @@ func (cfg *Config) FixupAndValidate(forceGenOnly bool) error { return err } - _, err = signpem.FromPublicPEMBytes(pemData, pkiSignatureScheme) + ourPubKey, err := signpem.FromPublicPEMBytes(pemData, pkiSignatureScheme) if err != nil { return err } + ourPubKeyHash := hash.Sum256From(ourPubKey) + for _, auth := range cfg.Authorities { + err := auth.Validate() + if err != nil { + return err + } + if hash.Sum256From(auth.IdentityPublicKey) == ourPubKeyHash { + selfInAuthorities = true + } + } + if !selfInAuthorities { + return errors.New("Authorities section must contain self") + } return nil } diff --git a/pki/state.go b/pki/state.go index 1531f93..a158985 100644 --- a/pki/state.go +++ b/pki/state.go @@ -22,6 +22,7 @@ import ( "github.com/katzenpost/katzenpost/core/epochtime" "github.com/katzenpost/katzenpost/core/pki" "github.com/katzenpost/katzenpost/core/sphinx/constants" + "github.com/katzenpost/katzenpost/core/sphinx/geo" "github.com/katzenpost/katzenpost/core/worker" "github.com/0KnowledgeNetwork/appchain-agent/clients/go/chainbridge" @@ -59,6 +60,7 @@ type state struct { worker.Worker s *Server + geo *geo.Geometry log *logging.Logger chainBridge *chainbridge.ChainBridge ccbor cbor.EncMode // a la katzenpost:core/pki/document.go @@ -67,8 +69,11 @@ type state struct { // mix descriptor uploads to this authority are restricted to this node authorizedNode *chainbridge.Node - documents map[uint64]*pki.Document - descriptors map[uint64]map[[publicKeyHashSize]byte]*pki.MixDescriptor + authorizedReplicaNodes map[[publicKeyHashSize]byte]string + + documents map[uint64]*pki.Document + descriptors map[uint64]map[[publicKeyHashSize]byte]*pki.MixDescriptor + replicaDescriptors map[uint64]map[[publicKeyHashSize]byte]*pki.ReplicaDescriptor votingEpoch uint64 genesisEpoch uint64 @@ -91,7 +96,7 @@ func (s *state) worker() { } } -// Returns a random delay to distribute load across synchronized nodes +// ZKN-PKI: Returns a random delay to distribute load across synchronized nodes func (s *state) courtessyDelay() time.Duration { return time.Duration(rand.NewMath().Float64() * float64(RandomCourtessyDelay)) } @@ -186,10 +191,15 @@ func (s *state) getVote(epoch uint64) (*pki.Document, error) { return nil, err } + replicaDescriptors := []*pki.ReplicaDescriptor{} + for _, desc := range s.replicaDescriptors[epoch] { + replicaDescriptors = append(replicaDescriptors, desc) + } + // vote topology is irrelevent. // TODO: use an appchain block hash as srv var zeros [32]byte - doc := s.getDocument(descriptors, s.s.cfg.Parameters, zeros[:]) + doc := s.getDocument(descriptors, replicaDescriptors, s.s.cfg.Parameters, zeros[:]) // Note: For appchain-pki, upload unsigned document and sign it upon local save. // simulate SignDocument's setting of doc version, required by IsDocumentWellFormed @@ -218,7 +228,7 @@ func (s *state) doSignDocument(signer sign.PrivateKey, verifier sign.PublicKey, return sig, err } -func (s *state) getDocument(descriptors []*pki.MixDescriptor, params *config.Parameters, srv []byte) *pki.Document { +func (s *state) getDocument(descriptors []*pki.MixDescriptor, replicaDescriptors []*pki.ReplicaDescriptor, params *config.Parameters, srv []byte) *pki.Document { // Carve out the descriptors between providers and nodes. gateways := []*pki.MixDescriptor{} serviceNodes := []*pki.MixDescriptor{} @@ -270,9 +280,10 @@ func (s *state) getDocument(descriptors []*pki.MixDescriptor, params *config.Par Topology: topology, GatewayNodes: gateways, ServiceNodes: serviceNodes, + StorageReplicas: replicaDescriptors, SharedRandomValue: srv, - PriorSharedRandom: [][]byte{srv}, // this is made up, only to suffice IsDocumentWellFormed - SphinxGeometryHash: s.s.geo.Hash(), + PriorSharedRandom: [][]byte{srv}, // ZKN-PKI: this is made up, only to suffice IsDocumentWellFormed + SphinxGeometryHash: s.geo.Hash(), PKISignatureScheme: s.s.cfg.Server.PKISignatureScheme, } return doc @@ -406,6 +417,20 @@ func (s *state) pruneDocuments() { delete(s.descriptors, e) } } + for e := range s.replicaDescriptors { + if e < cmpEpoch { + delete(s.replicaDescriptors, e) + } + } +} + +func (s *state) isReplicaDescriptorAuthorized(desc *pki.ReplicaDescriptor) bool { + pk := hash.Sum256(desc.IdentityKey) + name, ok := s.authorizedReplicaNodes[pk] + if !ok { + return false + } + return name == desc.Name } // Ensure that the descriptor is from the local registered node @@ -474,6 +499,50 @@ func (s *state) onDescriptorUpload(rawDesc []byte, desc *pki.MixDescriptor, epoc return nil } +func (s *state) onReplicaDescriptorUpload(rawDesc []byte, desc *pki.ReplicaDescriptor, epoch uint64) error { + s.Lock() + defer s.Unlock() + + // Note: Caller ensures that the epoch is the current epoch +- 1. + pk := hash.Sum256(desc.IdentityKey) + + // Get the public key -> descriptor map for the epoch. + _, ok := s.replicaDescriptors[epoch] + if !ok { + s.replicaDescriptors[epoch] = make(map[[publicKeyHashSize]byte]*pki.ReplicaDescriptor) + } + + // Check for redundant uploads. + d, ok := s.replicaDescriptors[epoch][pk] + if ok { + // If the descriptor changes, then it will be rejected to prevent + // nodes from reneging on uploads. + serialized, err := d.Marshal() + if err != nil { + return err + } + if !hmac.Equal(serialized, rawDesc) { + return fmt.Errorf("state: node %s (%x): Conflicting descriptor for epoch %v", desc.Name, hash.Sum256(desc.IdentityKey), epoch) + } + + // Redundant uploads that don't change are harmless. + return nil + } + + // Ok, this is a new descriptor. + if s.documents[epoch] != nil { + // If there is a document already, the descriptor is late, and will + // never appear in a document, so reject it. + return fmt.Errorf("state: Node %v: Late descriptor upload for for epoch %v", desc.IdentityKey, epoch) + } + + // Store the parsed descriptor + s.replicaDescriptors[epoch][pk] = desc + + s.log.Noticef("Node %x: Successfully submitted replica descriptor for epoch %v.", pk, epoch) + return nil +} + func (s *state) submitDescriptorToAppchain(desc *pki.MixDescriptor, epoch uint64) { // Register the mix descriptor with the appchain, which will: // - reject redundant descriptors (even those that didn't change) @@ -529,6 +598,7 @@ func (s *state) documentForEpoch(epoch uint64) ([]byte, error) { func newState(s *Server) (*state, error) { st := new(state) st.s = s + st.geo = s.geo st.log = s.logBackend.GetLogger("state") // set voting schedule at runtime @@ -616,6 +686,28 @@ func newState(s *Server) (*state, error) { st.log.Noticef("✅ Node registered with Identifier '%s', Identity key hash '%x'", v.Identifier, pk) + st.authorizedReplicaNodes = make(map[[publicKeyHashSize]byte]string) + for _, v := range st.s.cfg.StorageReplicas { + var identityPublicKey sign.PublicKey + var err error + + if filepath.IsAbs(v.IdentityPublicKeyPem) { + identityPublicKey, err = signpem.FromPublicPEMFile(v.IdentityPublicKeyPem, pkiSignatureScheme) + if err != nil { + panic(err) + } + } else { + pemFilePath := filepath.Join(s.cfg.Server.DataDir, v.IdentityPublicKeyPem) + identityPublicKey, err = signpem.FromPublicPEMFile(pemFilePath, pkiSignatureScheme) + if err != nil { + panic(err) + } + } + + pk := hash.Sum256From(identityPublicKey) + st.authorizedReplicaNodes[pk] = v.Identifier + } + st.log.Debugf("State initialized with epoch Period: %s", epochtime.Period) st.documents = make(map[uint64]*pki.Document) diff --git a/pki/wire_handler.go b/pki/wire_handler.go index cf847cc..5d86f97 100644 --- a/pki/wire_handler.go +++ b/pki/wire_handler.go @@ -81,6 +81,8 @@ func (s *Server) onConn(conn net.Conn) { resp = s.onClient(rAddr, cmd) } else if auth.isMix { resp = s.onMix(rAddr, cmd, auth.peerIdentityKeyHash) + } else if auth.isReplica { + resp = s.onReplica(rAddr, cmd, auth.peerIdentityKeyHash) } else { panic("wtf") // should only happen if there is a bug in wireAuthenticator } @@ -122,6 +124,19 @@ func (s *Server) onMix(rAddr net.Addr, cmd commands.Command, peerIdentityKeyHash return resp } +func (s *Server) onReplica(rAddr net.Addr, cmd commands.Command, peerIdentityKeyHash []byte) commands.Command { + s.log.Debug("onReplica") + var resp commands.Command + switch c := cmd.(type) { + case *commands.PostReplicaDescriptor: + resp = s.onPostReplicaDescriptor(rAddr, c, peerIdentityKeyHash) + default: + s.log.Debugf("Peer %v: Invalid request: %T", rAddr, c) + return nil + } + return resp +} + func (s *Server) onGetConsensus(rAddr net.Addr, cmd *commands.GetConsensus) commands.Command { s.log.Debugf("onGetConsensus: rAddr: %v, cmd: %+v", rAddr, cmd) resp := &commands.Consensus{} @@ -141,6 +156,81 @@ func (s *Server) onGetConsensus(rAddr net.Addr, cmd *commands.GetConsensus) comm return resp } +func (s *Server) onPostReplicaDescriptor(rAddr net.Addr, cmd *commands.PostReplicaDescriptor, pubKeyHash []byte) commands.Command { + resp := &commands.PostReplicaDescriptorStatus{ + ErrorCode: commands.DescriptorInvalid, + } + + // Ensure the epoch is somewhat sane. + now, _, _ := epochtime.Now() + switch cmd.Epoch { + case now - 1, now, now + 1: + // Nodes will always publish the descriptor for the current epoch on + // launch, which may be off by one period, depending on how skewed + // the node's clock is and the current time. + default: + // The peer is publishing for an epoch that's invalid. + s.log.Errorf("Peer %v: Invalid descriptor epoch '%v'", rAddr, cmd.Epoch) + return resp + } + + // Validate and deserialize the SignedReplicaUpload. + signedUpload := new(pki.SignedReplicaUpload) + err := signedUpload.Unmarshal(cmd.Payload) + if err != nil { + s.log.Errorf("Peer %v: Invalid descriptor: %v", rAddr, err) + return resp + } + + desc := signedUpload.ReplicaDescriptor + + // Ensure that the descriptor is signed by the peer that is posting. + identityKeyHash := hash.Sum256(desc.IdentityKey) + if !hmac.Equal(identityKeyHash[:], pubKeyHash) { + s.log.Errorf("Peer %v: Identity key hash '%x' is not link key '%v'.", rAddr, hash.Sum256(desc.IdentityKey), pubKeyHash) + resp.ErrorCode = commands.DescriptorForbidden + return resp + } + pkiSignatureScheme := signSchemes.ByName(s.cfg.Server.PKISignatureScheme) + + descIdPubKey, err := pkiSignatureScheme.UnmarshalBinaryPublicKey(desc.IdentityKey) + if err != nil { + s.log.Error("failed to unmarshal descriptor IdentityKey") + resp.ErrorCode = commands.DescriptorForbidden + return resp + } + + if !signedUpload.Verify(descIdPubKey) { + s.log.Error("PostDescriptorStatus contained a SignedUpload with an invalid signature") + resp.ErrorCode = commands.DescriptorForbidden + return resp + } + + // Ensure that the descriptor is from an allowed peer. + if !s.state.isReplicaDescriptorAuthorized(desc) { + s.log.Errorf("Peer %v: Identity key hash '%x' not authorized", rAddr, hash.Sum256(desc.IdentityKey)) + resp.ErrorCode = commands.DescriptorForbidden + return resp + } + + // Hand the replica descriptor off to the state worker. As long as this returns + // a nil, the authority "accepts" the replica descriptor. + err = s.state.onReplicaDescriptorUpload(cmd.Payload, desc, cmd.Epoch) + if err != nil { + // This is either a internal server error or the peer is trying to + // retroactively modify their descriptor. This should disambituate + // the condition, but the latter is more likely. + s.log.Errorf("Peer %v: Rejected probably a conflict: %v", rAddr, err) + resp.ErrorCode = commands.DescriptorConflict + return resp + } + + // Return a successful response. + s.log.Debugf("Peer %v: Accepted replica descriptor for epoch %v", rAddr, cmd.Epoch) + resp.ErrorCode = commands.DescriptorOk + return resp +} + func (s *Server) onPostDescriptor(rAddr net.Addr, cmd *commands.PostDescriptor, pubKeyHash []byte) commands.Command { s.log.Debugf("onPostDescriptor: from rAddr: %v, for epoch: %d", rAddr, cmd.Epoch) resp := &commands.PostDescriptorStatus{ @@ -201,10 +291,13 @@ func (s *Server) onPostDescriptor(rAddr net.Addr, cmd *commands.PostDescriptor, // TODO: Use the packet loss statistics to make decisions about how to generate the consensus document. - // Hand the descriptor off to the state. As long as this returns + // Hand the descriptor off to the state worker. As long as this returns // a nil, the authority "accepts" the descriptor. err = s.state.onDescriptorUpload(cmd.Payload, desc, cmd.Epoch) if err != nil { + // This is either a internal server error or the peer is trying to + // retroactively modify their descriptor. This should disambituate + // the condition, but the latter is more likely. s.log.Errorf("Peer %v: Rejected descriptor for epoch %v: %v", rAddr, cmd.Epoch, err) resp.ErrorCode = commands.DescriptorConflict return resp @@ -222,6 +315,7 @@ type wireAuthenticator struct { peerIdentityKeyHash []byte isClient bool isMix bool + isReplica bool } func (a *wireAuthenticator) IsPeerValid(creds *wire.PeerCredentials) bool { @@ -249,6 +343,5 @@ func (a *wireAuthenticator) IsPeerValid(creds *wire.PeerCredentials) bool { a.s.log.Warning("Rejecting authority authentication, public key mismatch.") return false } - - return false // Not reached. + // not reached }