From 128c384b1908f52c7425367d8c9579d50c639c77 Mon Sep 17 00:00:00 2001 From: ale <ale@incal.net> Date: Fri, 16 Feb 2018 21:12:09 +0000 Subject: [PATCH] Add a couple of basic tests --- server/keystore.go | 54 ++++++++------ server/keystore_test.go | 157 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 23 deletions(-) create mode 100644 server/keystore_test.go diff --git a/server/keystore.go b/server/keystore.go index b43677aa..a84570ed 100644 --- a/server/keystore.go +++ b/server/keystore.go @@ -81,12 +81,7 @@ type KeyStore struct { validator sso.Validator } -// NewKeyStore creates a new KeyStore with the given config and returns it. -func NewKeyStore(config *Config) (*KeyStore, error) { - if err := config.check(); err != nil { - return nil, err - } - +func newKeyStoreWithBackend(config *Config, db Database) (*KeyStore, error) { ssoKey, err := ioutil.ReadFile(config.SSOPublicKeyFile) if err != nil { return nil, err @@ -96,33 +91,46 @@ func NewKeyStore(config *Config) (*KeyStore, error) { return nil, err } + s := &KeyStore{ + userKeys: make(map[string]userKey), + service: config.SSOService, + validator: v, + db: db, + } + go s.expireLoop() + return s, nil +} + +// NewKeyStore creates a new KeyStore with the given config and returns it. +func NewKeyStore(config *Config) (*KeyStore, error) { + if err := config.check(); err != nil { + return nil, err + } + // There is only one supported backend type, ldap. ldap, err := backend.NewLDAPBackend(config.LDAPConfig) if err != nil { return nil, err } - s := &KeyStore{ - userKeys: make(map[string]userKey), - service: config.SSOService, - validator: v, - db: ldap, + return newKeyStoreWithBackend(config, ldap) +} + +func (s *KeyStore) expire(t time.Time) { + s.mx.Lock() + for u, k := range s.userKeys { + if k.expiry.Before(t) { + log.Printf("forgetting key for %s", u) + wipeBytes(k.pkey) + delete(s.userKeys, u) + } } - go s.expire() - return s, nil + s.mx.Unlock() } -func (s *KeyStore) expire() { +func (s *KeyStore) expireLoop() { for t := range time.NewTicker(600 * time.Second).C { - s.mx.Lock() - for u, k := range s.userKeys { - if k.expiry.Before(t) { - log.Printf("forgetting key for %s", u) - wipeBytes(k.pkey) - delete(s.userKeys, u) - } - } - s.mx.Unlock() + s.expire(t) } } diff --git a/server/keystore_test.go b/server/keystore_test.go new file mode 100644 index 00000000..3e734eae --- /dev/null +++ b/server/keystore_test.go @@ -0,0 +1,157 @@ +package server + +import ( + "bytes" + "context" + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "golang.org/x/crypto/ed25519" + + "git.autistici.org/id/go-sso" + "git.autistici.org/id/keystore/userenckey" +) + +type testContext struct { + dir string + pubkeyPath string + signer sso.Signer +} + +func newTestContext(t testing.TB) *testContext { + dir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + + ctx := &testContext{ + dir: dir, + pubkeyPath: filepath.Join(dir, "public.key"), + } + + pub, priv, err := ed25519.GenerateKey(nil) + if err != nil { + t.Fatal("ed25519.GenerateKey():", err) + } + ctx.signer, err = sso.NewSigner(priv) + if err != nil { + t.Fatal("sso.NewSigner():", err) + } + + ioutil.WriteFile(ctx.pubkeyPath, pub, 0644) + + return ctx +} + +func (c *testContext) Close() { + os.RemoveAll(c.dir) +} + +func (c *testContext) sign(user, service, domain string) string { + tkt, _ := c.signer.Sign(sso.NewTicket(user, service, domain, "", nil, 600*time.Second)) + return tkt +} + +type testDB struct { + keys map[string][][]byte +} + +func (t *testDB) GetPrivateKeys(_ context.Context, username string) ([][]byte, error) { + keys, ok := t.keys[username] + if !ok { + return nil, nil + } + return keys, nil +} + +var ( + privKey = []byte("fairly secret key") + pw = []byte("equally secret password") + encPrivKey []byte +) + +func init() { + var err error + encPrivKey, err = userenckey.Encrypt(privKey, pw) + if err != nil { + panic(err) + } +} + +func TestKeystore_OpenAndGet(t *testing.T) { + c := newTestContext(t) + defer c.Close() + + db := &testDB{ + keys: map[string][][]byte{ + "testuser": [][]byte{encPrivKey}, + }, + } + + keystore, err := newKeyStoreWithBackend(&Config{ + SSOPublicKeyFile: c.pubkeyPath, + SSOService: "keystore/", + SSODomain: "domain", + }, db) + if err != nil { + t.Fatal(err) + } + + // Decrypt the private key with the right password. + err = keystore.Open(context.Background(), "testuser", string(pw), 60) + if err != nil { + t.Fatal("keystore.Open():", err) + } + + keystore.expire(time.Now()) + + // Sign a valid SSO ticket and use it to obtain the private + // key we just stored. + ssoTicket := c.sign("testuser", "keystore/", "domain") + result, err := keystore.Get("testuser", ssoTicket) + if err != nil { + t.Fatal("keystore.Get():", err) + } + if !bytes.Equal(result, privKey) { + t.Fatalf("keystore.Get() returned bad key: got %v, expected %v", result, privKey) + } +} + +func TestKeystore_Expire(t *testing.T) { + c := newTestContext(t) + defer c.Close() + + db := &testDB{ + keys: map[string][][]byte{ + "testuser": [][]byte{encPrivKey}, + }, + } + + keystore, err := newKeyStoreWithBackend(&Config{ + SSOPublicKeyFile: c.pubkeyPath, + SSOService: "keystore/", + SSODomain: "domain", + }, db) + if err != nil { + t.Fatal(err) + } + + // Decrypt the private key with the right password. + err = keystore.Open(context.Background(), "testuser", string(pw), 60) + if err != nil { + t.Fatal("keystore.Open():", err) + } + + keystore.expire(time.Now().Add(3600 * time.Second)) + + // Sign a valid SSO ticket and use it to obtain the private + // key we just stored. + ssoTicket := c.sign("testuser", "keystore/", "domain") + _, err = keystore.Get("testuser", ssoTicket) + if err != errNoKeys { + t.Fatal("keystore.Get():", err) + } +} -- GitLab