diff --git a/.travis.yml b/.travis.yml index c8e0474..771999d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ script: - go test -coverprofile=vrf.coverprofile ./crypto/vrf - go test -coverprofile=merkletree.coverprofile ./merkletree - go test -coverprofile=utils.coverprofile ./utils + - go test -coverprofile=keyserver.coverprofile ./keyserver - $HOME/gopath/bin/gover - $HOME/gopath/bin/goveralls -coverprofile=gover.coverprofile -service travis-ci diff --git a/LICENSE b/LICENSE index 3df9b8c..b7a86f8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,8 @@ This license applies to all parts of the CONIKS Golang library and reference implementation except the following: - The ed25519 subpackage, located in crypto/ed25519 This subpackage is copyrighted by the Go Authors. +- The mkkey.go ultility, located in utils/keygen/mkkey.go +This source code is copyrighted by the Dename Authors. - Coname utility functions, located in utils/coname.go - The vrf subpackage, located in crypto/vrf - The kv subpackage, located in storage/kv diff --git a/README.md b/README.md index 2fc962e..093eb9c 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,9 @@ The packages in this library implement the various components of the CONIKS syst - ``client``: Client-side protocol cgo hooks - ``crypto``: Cryptographic algorithms and operations - ``merkletree``: Merkle prefix tree and related data structures -- ``utils``: Utility functions +- ``keyserver``: Key server reference implementation +- ``utils``: Utility functions +- ``protocol``: CONIKS protocols implementation/library - ``storage``: DB hooks for storage backend (currently the library supports key-value db only) ## Disclaimer diff --git a/keyserver/README.md b/keyserver/README.md new file mode 100644 index 0000000..d812216 --- /dev/null +++ b/keyserver/README.md @@ -0,0 +1,30 @@ +# CONIKS Server implementation in Golang + +## Usage +Generate new config file with proper format +``` +go run keyserver/coniksserver/main.go -genconfig +``` + +Generate new key pair for VRF and signing +``` +go run utils/keygen/mkkey.go -vrf -signing +``` + +Generate a private key for secure connection (TLS) +``` +openssl ecparam -genkey -name prime256v1 -out server.key +``` + +Generation of self-signed(x509) public key (PEM-encodings `.pem`) based on the private (`.key`) +``` +openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650 +``` + +Run the server +``` +go run keyserver/coniksserver/main.go +``` + +## Disclaimer +Please keep in mind that this CONIKS server implementation is under active development. The repository may contain experimental features that aren't fully tested. diff --git a/keyserver/coniksserver/main.go b/keyserver/coniksserver/main.go new file mode 100644 index 0000000..cb5ea11 --- /dev/null +++ b/keyserver/coniksserver/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "bytes" + "flag" + "os" + "os/signal" + + "github.com/BurntSushi/toml" + "github.com/coniks-sys/coniks-go/keyserver" + "github.com/coniks-sys/coniks-go/utils" +) + +const ConfigFile = "config.toml" +const PoliciesFile = "policies.toml" + +func main() { + genConfigPtr := flag.Bool("genconfig", false, "Generate server config") + configPathPtr := flag.String("config", "config.toml", "path to config file") + flag.Parse() + + if *genConfigPtr { + mkConfig() + return + } + + // set up a CONIKS server from config file + conf := keyserver.LoadServerConfig(*configPathPtr) + serv := keyserver.NewConiksServer(conf) + + // run the server until receiving an interrupt signal + serv.RunWithConfig(conf) + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt) + <-ch + serv.Shutdown() +} + +func mkConfig() { + var conf = keyserver.ServerConfig{ + SigningKeyPath: "ed25519.secret", + DatabasePath: "coniks.db", + RegistrationAddress: "127.0.0.1:3001", + Address: "0.0.0.0:3000", + PoliciesPath: "policies.toml", + LoadedHistoryLength: 1000000, + RegistrationCapacity: 100000, + } + conf.TLS.TLSCertPath = "server.pem" + conf.TLS.TLSKeyPath = "server.key" + + var policies = keyserver.ServerPolicies{ + EpochDeadline: 60, + VRFKeyPath: "vrf.secret", + } + + var confBuf bytes.Buffer + var policiesBuf bytes.Buffer + + e := toml.NewEncoder(&confBuf) + err := e.Encode(conf) + if err != nil { + panic(err) + } + util.WriteFile(ConfigFile, confBuf) + + e = toml.NewEncoder(&policiesBuf) + err = e.Encode(policies) + if err != nil { + panic(err) + } + util.WriteFile(PoliciesFile, policiesBuf) +} diff --git a/keyserver/handler.go b/keyserver/handler.go new file mode 100644 index 0000000..264489e --- /dev/null +++ b/keyserver/handler.go @@ -0,0 +1,49 @@ +package keyserver + +import ( + . "github.com/coniks-sys/coniks-go/protocol" + "github.com/coniks-sys/coniks-go/utils" +) + +func (server *ConiksServer) handleRegistrationMessage(reg *RegistrationRequest) (Response, error) { + if len(reg.Username) == 0 || len(reg.Key) == 0 { + return NewErrorResponse(ErrorMalformedClientMessage), + ErrorMalformedClientMessage.Error() + } + + server.Lock() + + ap, tb, errCode := server.directory.Register(reg.Username, []byte(reg.Key)) + if errCode != Success { + server.Unlock() + return NewErrorResponse(errCode), + errCode.Error() + } + server.Unlock() + + // store the user policies into DB + err := server.StoreUserPoliciesToKV(&ConiksUserPolicies{ + AllowUnsignedKeychange: reg.AllowUnsignedKeychange, + AllowPublicLookup: reg.AllowPublicLookup, + }) + if err != nil { + return NewErrorResponse(ErrorInternalServer), + err + } + + return &RegistrationResponse{ + Type: RegistrationType, + STR: server.directory.LatestSTR(), + AP: ap, + TB: tb, + }, nil +} + +func (server *ConiksServer) StoreUserPoliciesToKV(up *ConiksUserPolicies) error { + buf := make([]byte, 0, 1) + buf = append(util.ToBytes([]bool{up.AllowUnsignedKeychange, up.AllowPublicLookup})) + if err := server.db.Put([]byte(up.Username), buf); err != nil { + return err + } + return nil +} diff --git a/keyserver/listener.go b/keyserver/listener.go new file mode 100644 index 0000000..d40b351 --- /dev/null +++ b/keyserver/listener.go @@ -0,0 +1,146 @@ +package keyserver + +import ( + "encoding/json" + "io" + "log" + "net" + "time" + + . "github.com/coniks-sys/coniks-go/protocol" +) + +func (server *ConiksServer) listenForRequests(ln net.Listener, handler func(msg []byte) ([]byte, error)) { + defer server.waitStop.Done() + defer ln.Close() + go func() { + <-server.stop + ln.Close() + }() + + for { + conn, err := ln.Accept() + if err != nil { + select { + case <-server.stop: + return + default: + log.Printf("accept client: %s", err) + continue + } + } + server.waitStop.Add(1) + go server.acceptClient(conn, handler) + } +} + +func (server *ConiksServer) acceptClient(conn net.Conn, handler func(msg []byte) ([]byte, error)) { + defer conn.Close() + defer server.waitStop.Done() + closed := make(chan struct{}) + defer close(closed) + go func() { + select { + case <-server.stop: + conn.Close() + case <-closed: + } + }() + + conn.SetDeadline(time.Now().Add(5 * time.Second)) + + // handle request + buf := make([]byte, 4096) + for { + n, err := conn.Read(buf) + if err != nil { + select { + case <-server.stop: + return + default: + if err != io.EOF { + log.Printf("client read %v: %v", conn.RemoteAddr(), err) + } + return + } + } + + res, err := handler(buf[:n]) + if err != nil { + log.Printf("client handle %v: %v", conn.RemoteAddr(), err) + } + + n, err = conn.Write([]byte(res)) + if err != nil { + select { + case <-server.stop: + return + default: + log.Printf("client write %v: %v", conn.RemoteAddr(), err) + return + } + } + } +} + +func malformedClientMsg() []byte { + res, err := MarshalErrorResponse(ErrorMalformedClientMessage) + if err != nil { + panic(err) + } + return res +} + +// handleClientMessage returns a byte slice of marshaled response +// and an error for server logging +func (server *ConiksServer) handleClientMessage(msg []byte) ([]byte, error) { + // get request message + req, _, err := UnmarshalRequest(msg) + if err != nil { + return malformedClientMsg(), err + } + + // handle request + switch req.Type { + default: + log.Printf("unknown message type: %q", req.Type) + return malformedClientMsg(), ErrorMalformedClientMessage.Error() + } +} + +func (server *ConiksServer) handleBotMessage(msg []byte) ([]byte, error) { + var response Response + var err error + + // get request message + req, content, err := UnmarshalRequest(msg) + if err != nil { + return malformedClientMsg(), err + } + + // handle request + switch req.Type { + case RegistrationType: + var reg RegistrationRequest + if err = json.Unmarshal(content, ®); err != nil { + return malformedClientMsg(), err + } + response, err = server.handleRegistrationMessage(®) + if err == nil { + res, e := MarshalRegistrationResponse(response.(*RegistrationResponse)) + if e != nil { + panic(e) + } + return res, nil + } + res, e := MarshalErrorResponse(response.(*ErrorResponse).Error) + if e != nil { + panic(e) + } + return res, nil + + default: + log.Printf("unknown message type: %q", req.Type) + return malformedClientMsg(), ErrorMalformedClientMessage.Error() + } +} diff --git a/keyserver/policy.go b/keyserver/policy.go new file mode 100644 index 0000000..abc2870 --- /dev/null +++ b/keyserver/policy.go @@ -0,0 +1,38 @@ +package keyserver + +import ( + "io/ioutil" + "log" + + "github.com/BurntSushi/toml" + "github.com/coniks-sys/coniks-go/crypto/vrf" + "github.com/coniks-sys/coniks-go/merkletree" +) + +type ServerPolicies struct { + EpochDeadline merkletree.TimeStamp `toml:"epoch_deadline"` + VRFKeyPath string `toml:"vrf_key_path"` +} + +func readPolicies(path string) (*ServerPolicies, *vrf.PrivateKey, error) { + var p ServerPolicies + if _, err := toml.DecodeFile(path, &p); err != nil { + log.Fatalf("Failed to load config: %v", err) + return nil, nil, err + } + + // load vrf key + skBytes, err := ioutil.ReadFile(p.VRFKeyPath) + if err != nil { + log.Fatalf("Cannot read VRF key: %v", err) + return nil, nil, nil + } + if len(skBytes) != vrf.PrivateKeySize { + log.Fatalf("Signing key must be 64 bytes (got %d)", len(skBytes)) + return nil, nil, nil + } + + vrfKey := new(vrf.PrivateKey) + copy(vrfKey[:], skBytes) + return &p, vrfKey, nil +} diff --git a/keyserver/server.go b/keyserver/server.go new file mode 100644 index 0000000..8931ec4 --- /dev/null +++ b/keyserver/server.go @@ -0,0 +1,176 @@ +package keyserver + +import ( + "crypto/tls" + "io/ioutil" + "log" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/BurntSushi/toml" + "github.com/coniks-sys/coniks-go/crypto/sign" + "github.com/coniks-sys/coniks-go/protocol" + "github.com/coniks-sys/coniks-go/storage/kv" + "github.com/coniks-sys/coniks-go/storage/kv/leveldbkv" +) + +type ServerConfig struct { + SigningKeyPath string `toml:"signing_key"` + DatabasePath string `toml:"database"` + RegistrationAddress string `toml:"reg_address"` + Address string `toml:"address"` // address:port + LoadedHistoryLength uint64 `toml:"loaded_history_length"` + RegistrationCapacity uint64 `toml:"registration_capacity"` + PoliciesPath string `toml:"policies"` + TLS TLSInfo `toml:"tls"` +} + +type TLSInfo struct { + TLSCertPath string `toml:"cert"` + TLSKeyPath string `toml:"key"` +} + +type ConiksServer struct { + sync.RWMutex + directory *protocol.ConiksDirectory + + stop chan struct{} + waitStop sync.WaitGroup + + db kv.DB + + policies *ServerPolicies + policiesFilePath string // should be final + reloadChan chan os.Signal + epochTimer *time.Timer +} + +func LoadServerConfig(path string) *ServerConfig { + var conf ServerConfig + if _, err := toml.DecodeFile(path, &conf); err != nil { + log.Fatalf("Failed to load config: %v", err) + return nil + } + return &conf +} + +func NewConiksServer(conf *ServerConfig) *ConiksServer { + // load signing key + skBytes, err := ioutil.ReadFile(conf.SigningKeyPath) + if err != nil { + log.Fatalf("Cannot read signing key: %v", err) + return nil + } + if len(skBytes) != sign.PrivateKeySize { + log.Fatalf("Signing key must be 64 bytes (got %d)", len(skBytes)) + return nil + } + + sk := make([]byte, sign.PrivateKeySize) + copy(sk, skBytes[:sign.PrivateKeySize]) + + // open db + kvdb := leveldbkv.OpenDB(conf.DatabasePath) + + // read server policies + policies, vrfKey, err := readPolicies(conf.PoliciesPath) + if err != nil { + log.Fatalf("Cannot read policies config: %v", err) + return nil + } + + // create server instance + server := new(ConiksServer) + server.stop = make(chan struct{}) + server.db = kvdb + server.policies = policies + server.policiesFilePath = conf.PoliciesPath + server.reloadChan = make(chan os.Signal, 1) + signal.Notify(server.reloadChan, syscall.SIGUSR2) + server.epochTimer = time.NewTimer(time.Duration(policies.EpochDeadline) * time.Second) + server.directory = protocol.InitDirectory( + policies.EpochDeadline, vrfKey, + sk, conf.LoadedHistoryLength, + true, conf.RegistrationCapacity) + + return server +} + +func (server *ConiksServer) RunWithConfig(conf *ServerConfig) { + server.waitStop.Add(1) + go server.EpochUpdate() + + // server listener + cer, err := tls.LoadX509KeyPair(conf.TLS.TLSCertPath, conf.TLS.TLSKeyPath) + if err != nil { + panic(err) + } + + config := &tls.Config{Certificates: []tls.Certificate{cer}} + // server public port + clientLn, err := tls.Listen("tcp", conf.Address, config) + if err != nil { + panic(err) + } + + // server registration port + botLn, err := tls.Listen("tcp", conf.RegistrationAddress, config) + if err != nil { + panic(err) + } + + server.waitStop.Add(1) + go server.listenForRequests(clientLn, server.handleClientMessage) + + server.waitStop.Add(1) + go server.listenForRequests(botLn, server.handleBotMessage) + + server.waitStop.Add(1) + go server.updatePolicies() +} + +func (server *ConiksServer) EpochUpdate() { + defer server.waitStop.Done() + for { + select { + case <-server.stop: + return + case <-server.epochTimer.C: + server.Lock() + server.directory.Update() + server.epochTimer.Reset(time.Duration(server.policies.EpochDeadline) * time.Second) + server.Unlock() + } + } +} + +func (server *ConiksServer) Shutdown() error { + close(server.stop) + server.waitStop.Wait() + return server.db.Close() +} + +func (server *ConiksServer) updatePolicies() { + defer server.waitStop.Done() + for { + select { + case <-server.stop: + return + case <-server.reloadChan: + // read server policies + policies, vrfKey, err := readPolicies(server.policiesFilePath) + if err != nil { + log.Fatalf("Cannot read policies config: %v", err) + return + } + server.Lock() + server.directory.SetPolicies(policies.EpochDeadline, vrfKey) + server.policies = policies + server.Unlock() + log.Println("Policies reloaded!") + } + } +} diff --git a/keyserver/server_test.go b/keyserver/server_test.go new file mode 100644 index 0000000..7832aa5 --- /dev/null +++ b/keyserver/server_test.go @@ -0,0 +1,204 @@ +package keyserver + +import ( + "encoding/json" + "os" + "os/signal" + "path" + "syscall" + "testing" + "time" + + "github.com/coniks-sys/coniks-go/crypto/sign" + "github.com/coniks-sys/coniks-go/crypto/vrf" + "github.com/coniks-sys/coniks-go/keyserver/testutil" + "github.com/coniks-sys/coniks-go/merkletree" + "github.com/coniks-sys/coniks-go/protocol" + "github.com/coniks-sys/coniks-go/storage/kv" + "github.com/coniks-sys/coniks-go/utils" +) + +func startServer(t *testing.T, kvdb kv.DB, epDeadline merkletree.TimeStamp, policiesPath string) (*ConiksServer, func()) { + dir, teardown := testutil.CreateTLSCert(t) + + sk, err := sign.GenerateKey() + if err != nil { + t.Fatal(err) + } + + _, vrfKey, err := vrf.GenerateKey(nil) + if err != nil { + t.Fatal(err) + } + + var loadedHistoryLength uint64 = 100 + var registrationCapacity uint64 = 100 + + server := new(ConiksServer) + server.stop = make(chan struct{}) + server.db = kvdb + server.policies = &ServerPolicies{EpochDeadline: epDeadline} + server.policiesFilePath = policiesPath + server.reloadChan = make(chan os.Signal, 1) + signal.Notify(server.reloadChan, syscall.SIGUSR2) + server.epochTimer = time.NewTimer(time.Duration(server.policies.EpochDeadline) * time.Second) + server.directory = protocol.InitDirectory(epDeadline, vrfKey, + sk, loadedHistoryLength, + true, registrationCapacity) + + conf := &ServerConfig{ + Address: testutil.PublicConnection, + RegistrationAddress: testutil.LocalConnection, + TLS: TLSInfo{ + TLSCertPath: path.Join(dir, "server.pem"), + TLSKeyPath: path.Join(dir, "server.key"), + }, + } + server.RunWithConfig(conf) + return server, func() { + server.Shutdown() + teardown() + } +} + +func TestServerStartStop(t *testing.T) { + util.WithDB(func(db kv.DB) { + _, teardown := startServer(t, db, 60, "") + defer teardown() + }) +} + +func TestBotSendsRegistration(t *testing.T) { + util.WithDB(func(db kv.DB) { + _, teardown := startServer(t, db, 60, "") + defer teardown() + reg := ` +{ + "type": 0, + "request": { + "username": "alice@twitter", + "key": "AA==", + "allow_unsigned_key_change": true, + "allow_public_lookup": true + } +} +` + rev, err := testutil.NewClient(t, testutil.LocalConnection, []byte(reg)) + if err != nil { + t.Fatal(err) + } + + type ExpectingResponse struct { + Type int + Content json.RawMessage + } + var response ExpectingResponse + err = json.Unmarshal(rev, &response) + if err != nil { + t.Fatal(err) + } + if response.Type != protocol.RegistrationType { + t.Fatal("Expect a registration response") + } + }) +} + +func TestSendsRegistrationFromOutside(t *testing.T) { + util.WithDB(func(db kv.DB) { + _, teardown := startServer(t, db, 60, "") + defer teardown() + reg := ` +{ + "type": 0, + "request": { + "username": "alice@twitter", + "key": "AA==", + "allow_unsigned_key_change": true, + "allow_public_lookup": true + } +} +` + rev, err := testutil.NewClient(t, testutil.PublicConnection, []byte(reg)) + if err != nil { + t.Fatal(err) + } + var response protocol.ErrorResponse + err = json.Unmarshal(rev, &response) + if err != nil { + t.Fatal(err) + } + if response.Error != protocol.ErrorMalformedClientMessage { + t.Fatalf("Expect error code %d", protocol.ErrorMalformedClientMessage) + } + }) +} + +func TestUpdateDirectory(t *testing.T) { + util.WithDB(func(db kv.DB) { + server, teardown := startServer(t, db, 1, "") + defer teardown() + str0 := server.directory.LatestSTR() + rs := createMultiRegistrationRequests(10) + for i := range rs { + _, err := server.handleRegistrationMessage(rs[i]) + if err != nil { + t.Fatal("Error while submitting registration request number", i, "to server") + } + } + timer := time.NewTimer(1 * time.Second) + <-timer.C + str1 := server.directory.LatestSTR() + if str0.Epoch != 0 || str1.Epoch != 1 || !merkletree.VerifyHashChain(str1.PreviousSTRHash, str0.Signature) { + t.Fatal("Expect next STR in hash chain") + } + }) +} + +func createMultiRegistrationRequests(N uint64) []*protocol.RegistrationRequest { + var rs []*protocol.RegistrationRequest + for i := uint64(0); i < N; i++ { + r := new(protocol.RegistrationRequest) + r.Username = "user" + string(i) + r.Key = "key" + string(i) + r.AllowPublicLookup = true + r.AllowUnsignedKeychange = true + rs = append(rs, r) + } + return rs +} + +func TestRegisterDuplicateUserInOneEpoch(t *testing.T) { + util.WithDB(func(db kv.DB) { + server, teardown := startServer(t, db, 60, "") + defer teardown() + r0 := createMultiRegistrationRequests(1)[0] + r1 := createMultiRegistrationRequests(1)[0] + _, err := server.handleRegistrationMessage(r0) + if err != nil { + t.Fatal("Error while submitting registration request") + } + _, err = server.handleRegistrationMessage(r1) + if err != protocol.ErrorNameExisted.Error() { + t.Fatal("Expect error code", protocol.ErrorNameExisted) + } + }) +} + +func TestRegisterDuplicateUserInDifferentEpoches(t *testing.T) { + util.WithDB(func(db kv.DB) { + server, teardown := startServer(t, db, 1, "") + defer teardown() + r0 := createMultiRegistrationRequests(1)[0] + _, err := server.handleRegistrationMessage(r0) + if err != nil { + t.Fatal("Error while submitting registration request") + } + timer := time.NewTimer(1 * time.Second) + <-timer.C + r1 := createMultiRegistrationRequests(1)[0] + _, err = server.handleRegistrationMessage(r1) + if err != protocol.ErrorNameExisted.Error() { + t.Fatal("Expect error code", protocol.ErrorNameExisted) + } + }) +} diff --git a/keyserver/testutil/testutil.go b/keyserver/testutil/testutil.go new file mode 100644 index 0000000..0d6cfde --- /dev/null +++ b/keyserver/testutil/testutil.go @@ -0,0 +1,113 @@ +package testutil + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "io/ioutil" + "math/big" + "net" + "os" + "path" + "testing" + "time" +) + +const ( + TestDir = "coniksServerTest" + PublicConnection = "127.0.0.1:3000" + LocalConnection = "127.0.0.1:3001" +) + +func CreateTLSCert(t *testing.T) (string, func()) { + dir, err := ioutil.TempDir("", TestDir) + if err != nil { + t.Fatal(err) + } + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate private key: %s", err) + } + + notBefore := time.Now() + notAfter := notBefore.Add(1 * time.Hour) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + t.Fatalf("failed to generate serial number: %s", err) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Coniks.org"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: true, + } + template.Subject.CommonName = "localhost" + template.IPAddresses = append(template.IPAddresses, net.ParseIP("127.0.0.1")) + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + t.Fatalf("Failed to create certificate: %s", err) + } + + certOut, err := os.Create(path.Join(dir, "server.pem")) + if err != nil { + t.Fatalf("failed to open server.pem for writing: %s", err) + } + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + + keyOut, err := os.OpenFile(path.Join(dir, "server.key"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + t.Fatal("failed to open server.key for writing:", err) + } + + b, err := x509.MarshalECPrivateKey(priv) + if err != nil { + t.Fatalf("Unable to marshal ECDSA private key: %v", err) + } + pemBlock := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + pem.Encode(keyOut, pemBlock) + keyOut.Close() + + return dir, func() { + os.RemoveAll(dir) + } +} + +func NewClient(t *testing.T, address string, msg []byte) ([]byte, error) { + conf := &tls.Config{ + InsecureSkipVerify: true, + } + + conn, err := tls.Dial("tcp", address, conf) + if err != nil { + return nil, err + } + defer conn.Close() + _, err = conn.Write([]byte(msg)) + if err != nil { + return nil, err + } + + buf := make([]byte, 4096) + n, err := conn.Read(buf) + if err != nil { + return nil, err + } + + return buf[:n], nil +} diff --git a/merkletree/policy.go b/merkletree/policy.go index 4a7284b..6edfe22 100644 --- a/merkletree/policy.go +++ b/merkletree/policy.go @@ -1,8 +1,6 @@ package merkletree import ( - "reflect" - "github.com/coniks-sys/coniks-go/crypto" "github.com/coniks-sys/coniks-go/crypto/vrf" "github.com/coniks-sys/coniks-go/utils" @@ -11,7 +9,6 @@ import ( type TimeStamp uint64 type Policies interface { - Iterate() map[string][]byte Serialize() []byte vrfPrivate() *vrf.PrivateKey } @@ -20,6 +17,7 @@ type ConiksPolicies struct { LibVersion string HashID string vrfPrivateKey *vrf.PrivateKey + VrfPubKey []byte EpochDeadline TimeStamp } @@ -30,31 +28,11 @@ func NewPolicies(epDeadline TimeStamp, vrfPrivKey *vrf.PrivateKey) Policies { LibVersion: Version, HashID: crypto.HashID, vrfPrivateKey: vrfPrivKey, + VrfPubKey: vrfPrivKey.Public(), EpochDeadline: epDeadline, } } -// Iterate returns a map of exported fields' name to their values -func (p *ConiksPolicies) Iterate() map[string][]byte { - s := reflect.ValueOf(p).Elem() - typeOfT := s.Type() - fields := make(map[string][]byte, s.NumField()) - for i := 0; i < s.NumField(); i++ { - f := s.Field(i) - if !f.CanInterface() { - continue - } - switch f.Interface().(type) { - case string: - fields[typeOfT.Field(i).Name] = []byte(f.Interface().(string)) - case TimeStamp: - fields[typeOfT.Field(i).Name] = util.ULongToBytes(uint64(f.Interface().(TimeStamp))) - } - } - fields["VRFPublic"] = p.vrfPrivateKey.Public() - return fields -} - // Serialize encodes the policy to a byte array with the following format: // [lib version, cryptographic algorithm in use, epoch deadline, vrf public key] func (p *ConiksPolicies) Serialize() []byte { diff --git a/protocol/directory.go b/protocol/directory.go new file mode 100644 index 0000000..99c6b5b --- /dev/null +++ b/protocol/directory.go @@ -0,0 +1,89 @@ +package protocol + +import ( + "bytes" + + "github.com/coniks-sys/coniks-go/crypto/sign" + "github.com/coniks-sys/coniks-go/crypto/vrf" + "github.com/coniks-sys/coniks-go/merkletree" +) + +type ConiksUserPolicies struct { + Username string + AllowUnsignedKeychange bool + AllowPublicLookup bool +} + +type ConiksDirectory struct { + pad *merkletree.PAD + useTBs bool + tbs map[string]*merkletree.TemporaryBinding + policies merkletree.Policies +} + +func InitDirectory(epDeadline merkletree.TimeStamp, vrfKey *vrf.PrivateKey, + signKey sign.PrivateKey, dirSize uint64, + useTBs bool, capacity uint64) *ConiksDirectory { + d := new(ConiksDirectory) + d.SetPolicies(epDeadline, vrfKey) + pad, err := merkletree.NewPAD(d.policies, signKey, dirSize) + if err != nil { + panic(err) + } + d.pad = pad + d.useTBs = useTBs + if useTBs { + d.tbs = make(map[string]*merkletree.TemporaryBinding, capacity) + } + return d +} + +func (d *ConiksDirectory) Update() { + d.pad.Update(d.policies) + // clear issued temporary bindings + for key := range d.tbs { + delete(d.tbs, key) + } +} + +func (d *ConiksDirectory) SetPolicies(epDeadline merkletree.TimeStamp, vrfKey *vrf.PrivateKey) { + d.policies = merkletree.NewPolicies(epDeadline, vrfKey) +} + +func (d *ConiksDirectory) LatestSTR() *merkletree.SignedTreeRoot { + return d.pad.LatestSTR() +} + +func (d *ConiksDirectory) Register(uname string, key []byte) ( + *merkletree.AuthenticationPath, *merkletree.TemporaryBinding, ErrorCode) { + // check whether the name already exists + // in the directory before we register + ap, err := d.pad.Lookup(uname) + if err != nil { + return nil, nil, ErrorInternalServer + } + if bytes.Equal(ap.LookupIndex, ap.Leaf.Index()) { + return ap, nil, ErrorNameExisted + } + if d.useTBs { + // also check the temporary bindings array + // currently the server allows only one registration/key change per epoch + if d.tbs[uname] != nil { + return nil, nil, ErrorNameExisted + } + + // insert new data to the directory on-the-fly + tb, err := d.pad.TB(uname, key) + if err != nil { + return nil, nil, ErrorInternalServer + } + d.tbs[uname] = tb + return ap, tb, Success + } + + if err = d.pad.Set(uname, key); err != nil { + return nil, nil, ErrorInternalServer + } + + return ap, nil, Success +} diff --git a/protocol/encoding.go b/protocol/encoding.go new file mode 100644 index 0000000..85ed83c --- /dev/null +++ b/protocol/encoding.go @@ -0,0 +1,119 @@ +// Defines methods/functions to encode/decode messages between client and server. +// Currently this module supports JSON marshal/unmarshal only. +// Protobuf would be supported in the feature. + +package protocol + +import ( + "encoding/base64" + "encoding/json" + + "github.com/coniks-sys/coniks-go/merkletree" +) + +var b64en = base64.StdEncoding.EncodeToString + +func MarshalTemporaryBinding(tb *merkletree.TemporaryBinding) ([]byte, error) { + return json.Marshal(tb) +} + +func MarshalSTR(str *merkletree.SignedTreeRoot) ([]byte, error) { + policies, err := json.Marshal(str.Policies) + if err != nil { + return nil, err + } + return json.Marshal(&struct { + TreeHash []byte + Epoch uint64 + PreviousEpoch uint64 + PreviousSTRHash []byte + Policies json.RawMessage + Signature []byte + }{ + TreeHash: str.Root(), + Epoch: str.Epoch, + PreviousEpoch: str.PreviousEpoch, + PreviousSTRHash: str.PreviousSTRHash, + Policies: policies, + Signature: str.Signature, + }) +} + +func MarshalAuthenticationPath(ap *merkletree.AuthenticationPath) ([]byte, error) { + var prunedTree []string + for i := range ap.PrunedTree { + prunedTree = append(prunedTree, b64en(ap.PrunedTree[i][:])) + } + + type Leaf struct { + Level int + Index string + Value string + IsEmpty bool + Commitment string + } + + return json.Marshal(&struct { + TreeNonce string + LookupIndex string + VrfProof string + PrunedTree []string + Leaf Leaf + }{ + TreeNonce: b64en(ap.TreeNonce), + LookupIndex: b64en(ap.LookupIndex), + VrfProof: b64en(ap.VrfProof), + PrunedTree: prunedTree, + Leaf: Leaf{ + Level: ap.Leaf.Level(), + Index: b64en(ap.Leaf.Index()), + Value: b64en(ap.Leaf.Value()), + Commitment: b64en(ap.Leaf.Commitment()), + IsEmpty: ap.Leaf.IsEmpty(), + }, + }) +} + +func MarshalRegistrationResponse(res *RegistrationResponse) ([]byte, error) { + tbEncoded, e := MarshalTemporaryBinding(res.TB) + if e != nil { + return nil, e + } + apEncoded, e := MarshalAuthenticationPath(res.AP) + if e != nil { + return nil, e + } + strEncoded, e := MarshalSTR(res.STR) + if e != nil { + return nil, e + } + + encoded, e := json.Marshal(&struct { + Type int + STR json.RawMessage + AP json.RawMessage + TB json.RawMessage `json:",omitempty"` + }{ + Type: res.Type, + STR: strEncoded, + AP: apEncoded, + TB: tbEncoded, + }) + if e != nil { + return nil, e + } + return encoded, nil +} + +func MarshalErrorResponse(errCode ErrorCode) ([]byte, error) { + return json.Marshal(NewErrorResponse(errCode)) +} + +func UnmarshalRequest(msg []byte) (Request, json.RawMessage, error) { + var content json.RawMessage + req := Request{ + Request: &content, + } + e := json.Unmarshal(msg, &req) + return req, content, e +} diff --git a/protocol/error.go b/protocol/error.go new file mode 100644 index 0000000..021c9f3 --- /dev/null +++ b/protocol/error.go @@ -0,0 +1,30 @@ +// Defines constants representing the types +// of errors that the server may return to a client. + +package protocol + +import "errors" + +type ErrorCode int + +const ( + Success ErrorCode = iota + 10 + ErrorInternalServer + ErrorNameExisted + ErrorMalformedClientMessage +) + +var ( + errorMessages = map[ErrorCode]error{ + ErrorMalformedClientMessage: errors.New("[coniks] Malformed client request"), + ErrorNameExisted: errors.New("[coniks] Registering identity is already registered"), + ErrorInternalServer: errors.New("[coniks] Internal server error"), + } +) + +func (e ErrorCode) Error() error { + if errorMessages[e] == nil { + return errorMessages[ErrorInternalServer] + } + return errorMessages[e] +} diff --git a/protocol/message.go b/protocol/message.go new file mode 100644 index 0000000..1dfbebf --- /dev/null +++ b/protocol/message.go @@ -0,0 +1,38 @@ +package protocol + +import "github.com/coniks-sys/coniks-go/merkletree" + +// Defines constants representing the types +// of messages exchanged by clients and servers. +const ( + RegistrationType = 0 +) + +type Request struct { + Type int + Request interface{} +} + +type Response interface{} + +type ErrorResponse struct { + Error ErrorCode +} + +func NewErrorResponse(e ErrorCode) Response { + return &ErrorResponse{Error: e} +} + +type RegistrationRequest struct { + Username string `json:"username"` + Key string `json:"key"` + AllowUnsignedKeychange bool `json:"allow_unsigned_key_change,omitempty"` + AllowPublicLookup bool `json:"allow_public_lookup,omitempty"` +} + +type RegistrationResponse struct { + Type int + STR *merkletree.SignedTreeRoot + AP *merkletree.AuthenticationPath + TB *merkletree.TemporaryBinding `json:",omitempty"` +} diff --git a/utils/coname.go b/utils/coname.go index 69e7fd3..f6f4aca 100644 --- a/utils/coname.go +++ b/utils/coname.go @@ -14,6 +14,15 @@ package util +import ( + "io/ioutil" + "os" + + "github.com/coniks-sys/coniks-go/storage/kv" + "github.com/coniks-sys/coniks-go/storage/kv/leveldbkv" + "github.com/syndtr/goleveldb/leveldb" +) + // In each byte, the bits are ordered MSB to LSB func ToBytes(bits []bool) []byte { bs := make([]byte, (len(bits)+7)/8) @@ -33,3 +42,17 @@ func ToBits(bs []byte) []bool { } return bits } + +func WithDB(f func(kv.DB)) { + dir, err := ioutil.TempDir("", "merkletree") + if err != nil { + panic(err) + } + defer os.RemoveAll(dir) + db, err := leveldb.OpenFile(dir, nil) + if err != nil { + panic(err) + } + defer db.Close() + f(leveldbkv.Wrap(db)) +} diff --git a/utils/keygen/mkkey.go b/utils/keygen/mkkey.go new file mode 100644 index 0000000..328e531 --- /dev/null +++ b/utils/keygen/mkkey.go @@ -0,0 +1,99 @@ +// Copyright 2014 The Dename Authors. +// +// 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 main + +import ( + "crypto/rand" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + + "github.com/coniks-sys/coniks-go/crypto/vrf" + "golang.org/x/crypto/ed25519" +) + +const ( + SECRET_KEY string = "ed25519.secret" + PUBLIC_KEY string = "ed25519.pub" + VRF_SECRET string = "vrf.secret" + VRF_PUBLIC string = "vrf.pub" +) + +func main() { + var vrf = flag.Bool("vrf", false, "Generate VRF key pair") + var sign = flag.Bool("signing", false, "Generate Signing key pair") + flag.Parse() + + if *vrf { + mkVrfKey() + } + if *sign { + mkSigningKey() + } +} + +func mkSigningKey() { + if _, err := os.Stat(SECRET_KEY); err == nil { + fmt.Fprintf(os.Stderr, "%s already exists\n", SECRET_KEY) + return + } + if _, err := os.Stat(PUBLIC_KEY); err == nil { + fmt.Fprintf(os.Stderr, "%s already exists\n", PUBLIC_KEY) + return + } + + pk, sk, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + log.Print(err) + return + } + if err := ioutil.WriteFile(SECRET_KEY, sk[:], 0600); err != nil { + log.Print(err) + return + } + + if err = ioutil.WriteFile(PUBLIC_KEY, pk[:], 0644); err != nil { + log.Print(err) + return + } +} + +func mkVrfKey() { + if _, err := os.Stat(VRF_SECRET); err == nil { + fmt.Fprintf(os.Stderr, "%s already exists\n", VRF_SECRET) + return + } + if _, err := os.Stat(VRF_PUBLIC); err == nil { + fmt.Fprintf(os.Stderr, "%s already exists\n", VRF_PUBLIC) + return + } + + pk, sk, err := vrf.GenerateKey(rand.Reader) + if err != nil { + log.Print(err) + return + } + if err := ioutil.WriteFile(VRF_SECRET, sk[:], 0600); err != nil { + log.Print(err) + return + } + + if err = ioutil.WriteFile(VRF_PUBLIC, pk[:], 0644); err != nil { + log.Print(err) + return + } +} diff --git a/utils/util.go b/utils/util.go index 7902327..6b22db2 100644 --- a/utils/util.go +++ b/utils/util.go @@ -1,6 +1,12 @@ package util -import "encoding/binary" +import ( + "bytes" + "encoding/binary" + "io/ioutil" + "log" + "os" +) // GetNthBit finds the bit in the byte array bs // at offset offset, and determines whether it is 1 or 0. @@ -35,3 +41,22 @@ func IntToBytes(num int) []byte { binary.LittleEndian.PutUint32(buf, uint32(num)) return buf } + +func BoolToByte(b bool) byte { + if b { + return 1 + } + return 0 +} + +func WriteFile(filename string, buf bytes.Buffer) { + if _, err := os.Stat(filename); err == nil { + log.Printf("%s already exists\n", filename) + return + } + + if err := ioutil.WriteFile(filename, []byte(buf.String()), 0644); err != nil { + log.Printf(err.Error()) + return + } +}