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