diff --git a/socketserver/cmd/socketserver/socketserver.go b/socketserver/cmd/socketserver/socketserver.go index 1e41434a..49db8872 100644 --- a/socketserver/cmd/socketserver/socketserver.go +++ b/socketserver/cmd/socketserver/socketserver.go @@ -2,9 +2,9 @@ package main // import "bitbucket.org/stendec/frankerfacez/socketserver/cmd/sock import ( "flag" - "../../internal/server" "log" "net/http" + "../../internal/server" ) var origin *string = flag.String("origin", "localhost:8001", "Client-visible origin of the socket server") @@ -12,14 +12,18 @@ var bindAddress *string = flag.String("listen", "", "Address to bind to, if diff var usessl *bool = flag.Bool("ssl", false, "Enable the use of SSL for connecting clients and backend connections") var certificateFile *string = flag.String("crt", "ssl.crt", "CA-signed SSL certificate file") var privateKeyFile *string = flag.String("key", "ssl.key", "SSL private key file") -var backendRootFile *string = flag.String("peerroot", "backend_issuer.pem", "Root certificate that issued client certificates for backend servers") -var backendCertFile *string = flag.String("peercrt", "backend_cert.crt", "Backend-trusted certificate, for use as a client certificate") -var backendKeyFile *string = flag.String("peerkey", "backend_cert.key", "Private key for backend-trusted certificate, for use as a client certificate") -var basicAuthPwd *string = flag.String("password", "", "Password for HTTP Basic Auth") // TODO + +var naclKeysFile *string = flag.String("naclkey", "naclkeys.json", "Keypairs for the NaCl crypto library, for communicating with the backend.") +var generateKeys *bool = flag.Bool("genkeys", false, "Generate NaCl keys instead of serving requests.\nArguments: [int serverId] [base64 backendPublic]\nThe backend public key can either be specified in base64 on the command line, or put in the json file later.") func main() { flag.Parse() + if *generateKeys { + GenerateKeys(*naclKeysFile) + return + } + if *origin == "" { log.Fatalln("--origin argument required") } @@ -33,23 +37,21 @@ func main() { conf := &server.Config { SSLKeyFile: *privateKeyFile, SSLCertificateFile: *certificateFile, - UseSSL: *certificateFile != "", - BackendRootCertFile: *backendRootFile, - BackendClientCertFile: *backendCertFile, - BackendClientKeyFile: *backendKeyFile, + UseSSL: *usessl, + NaclKeysFile: *naclKeysFile, SocketOrigin: *origin, } httpServer := &http.Server{ - Addr: *bindAddress + Addr: *bindAddress, } server.SetupServerAndHandle(conf, httpServer.TLSConfig) var err error if conf.UseSSL { - err = httpServer.ListenAndServeTLS(nil, nil) + err = httpServer.ListenAndServeTLS(*certificateFile, *privateKeyFile) } else { err = httpServer.ListenAndServe() } @@ -58,3 +60,15 @@ func main() { log.Fatal("ListenAndServe: ", err) } } + + +func GenerateKeys(outputFile string) { + if flag.NArg() < 1 { + log.Fatal("The server ID must be specified") + } + if flag.NArg() >= 2 { + server.GenerateKeys(outputFile, flag.Arg(0), flag.Arg(1)) + } else { + server.GenerateKeys(outputFile, flag.Arg(0), "") + } +} \ No newline at end of file diff --git a/socketserver/internal/server/backend.go b/socketserver/internal/server/backend.go index 2366996f..24766025 100644 --- a/socketserver/internal/server/backend.go +++ b/socketserver/internal/server/backend.go @@ -1,6 +1,7 @@ package server import ( + "golang.org/x/crypto/nacl/box" "net/http" "time" "fmt" @@ -9,9 +10,12 @@ import ( "strconv" "io/ioutil" "encoding/json" - "crypto/tls" - "crypto/x509" + "sync" "log" + "os" + "crypto/rand" + "encoding/base64" + "strings" ) var backendHttpClient http.Client @@ -20,6 +24,10 @@ var responseCache *cache.Cache var getBacklogUrl string +var backendSharedKey [32]byte + +var messageBufferPool sync.Pool + func SetupBackend(config *Config) { backendHttpClient.Timeout = 60 * time.Second backendUrl = config.BackendUrl @@ -29,26 +37,45 @@ func SetupBackend(config *Config) { responseCache = cache.New(60 * time.Second, 120 * time.Second) getBacklogUrl = fmt.Sprintf("%s/backlog", backendUrl) -} -func SetupBackendCertificates(config *Config, certPool x509.CertPool) { - myCert, err := tls.LoadX509KeyPair(config.BackendClientCertFile, config.BackendClientKeyFile) + messageBufferPool.New = NewByteBuffer + + var keys CryptoKeysBuf + file, err := os.Open(config.NaclKeysFile) if err != nil { log.Fatal(err) } - tlsConfig := tls.Config{ - Certificates: []tls.Certificate{myCert}, - RootCAs: certPool, + dec := json.NewDecoder(file) + err = dec.Decode(&keys) + if err != nil { + log.Fatal(err) } - tlsConfig.BuildNameToCertificate() - transport := &http.Transport{TLSClientConfig: tlsConfig} - backendHttpClient.Transport = transport + + box.Precompute(&backendSharedKey, &keys.TheirPublicKey, &keys.OurPrivateKey) } func getCacheKey(remoteCommand, data string) string { return fmt.Sprintf("%s/%s", remoteCommand, data) } +func SealRequest(form url.Values) ([]byte, error) { + asString := form.Encode() + var nonce [24]byte + var err error + + err = FillCryptoRandom(nonce[:]) + if err != nil { + return nil, err + } + + message := []byte(asString) + out := make([]byte, len(message) + box.Overhead) + box.SealAfterPrecomputation(out, message, &nonce, &backendSharedKey) + + // TODO + return nil, nil +} + func RequestRemoteDataCached(remoteCommand, data string, auth AuthInfo) (string, error) { cached, ok := responseCache.Get(getCacheKey(remoteCommand, data)) if ok { @@ -57,7 +84,7 @@ func RequestRemoteDataCached(remoteCommand, data string, auth AuthInfo) (string, return RequestRemoteData(remoteCommand, data, auth) } -func RequestRemoteData(remoteCommand, data string, auth AuthInfo) (string, error) { +func RequestRemoteData(remoteCommand, data string, auth AuthInfo) (responseStr string, err error) { destUrl := fmt.Sprintf("%s/cmd/%s", backendUrl, remoteCommand) var authKey string if auth.UsernameValidated { @@ -70,9 +97,6 @@ func RequestRemoteData(remoteCommand, data string, auth AuthInfo) (string, error "clientData": []string{data}, authKey: []string{auth.TwitchUsername}, } - if gconfig.BasicAuthPassword != "" { - formData["password"] = gconfig.BasicAuthPassword - } resp, err := backendHttpClient.PostForm(destUrl, formData) if err != nil { @@ -85,7 +109,7 @@ func RequestRemoteData(remoteCommand, data string, auth AuthInfo) (string, error return "", err } - responseJson := string(respBytes) + responseStr = string(respBytes) if resp.Header.Get("FFZ-Cache") != "" { durSecs, err := strconv.ParseInt(resp.Header.Get("FFZ-Cache"), 10, 64) @@ -93,10 +117,10 @@ func RequestRemoteData(remoteCommand, data string, auth AuthInfo) (string, error return "", fmt.Errorf("The RPC server returned a non-integer cache duration: %v", err) } duration := time.Duration(durSecs) * time.Second - responseCache.Set(getCacheKey(remoteCommand, data), responseJson, duration) + responseCache.Set(getCacheKey(remoteCommand, data), responseStr, duration) } - return responseJson, nil + return } func FetchBacklogData(chatSubs, channelSubs []string) ([]ClientMessage, error) { @@ -117,4 +141,43 @@ func FetchBacklogData(chatSubs, channelSubs []string) ([]ClientMessage, error) { } return messages, nil -} \ No newline at end of file +} + +func GenerateKeys(outputFile, serverId, theirPublicStr string) { + var err error + output := CryptoKeysBuf{} + + output.ServerId, err = strconv.Atoi(serverId) + if err != nil { + log.Fatal(err) + } + + ourPublic, ourPrivate, err := box.GenerateKey(rand.Reader) + if err != nil { + log.Fatal(err) + } + output.OurPublicKey, output.OurPrivateKey = *ourPublic, *ourPrivate + + if theirPublicStr != "" { + reader := base64.NewDecoder(base64.RawURLEncoding, strings.NewReader(theirPublicStr)) + theirPublic, err := ioutil.ReadAll(reader) + if err != nil { + log.Fatal(err) + } + copy(output.TheirPublicKey[:], theirPublic) + } + + file, err := os.Create(outputFile) + if err != nil { + log.Fatal(err) + } + enc := json.NewEncoder(file) + err = enc.Encode(output) + if err != nil { + log.Fatal(err) + } + err = file.Close() + if err != nil { + log.Fatal(err) + } +} diff --git a/socketserver/internal/server/handlecore.go b/socketserver/internal/server/handlecore.go index ce7e0462..b6161db7 100644 --- a/socketserver/internal/server/handlecore.go +++ b/socketserver/internal/server/handlecore.go @@ -10,24 +10,19 @@ import ( "encoding/json" "fmt" "sync" - "crypto/x509" - "io/ioutil" + "log" ) const MAX_PACKET_SIZE = 1024 type Config struct { - // SSL + // SSL/TLS SSLCertificateFile string SSLKeyFile string UseSSL bool - // CA for client validation (pub/sub commands only) - BackendRootCertFile string - BackendClientCertFile string - BackendClientKeyFile string - // Password for client validation (pub/sub commands only) - BasicAuthPassword string + // NaCl keys for backend messages + NaclKeysFile string // Hostname of the socket server SocketOrigin string @@ -93,7 +88,7 @@ func setupServer(config *Config, tlsConfig *tls.Config) *websocket.Server { gconfig = config sockConf, err := websocket.NewConfig("/", config.SocketOrigin) if err != nil { - panic(err) + log.Fatal(err) } SetupBackend(config) @@ -101,25 +96,13 @@ func setupServer(config *Config, tlsConfig *tls.Config) *websocket.Server { if config.UseSSL { cert, err := tls.LoadX509KeyPair(config.SSLCertificateFile, config.SSLKeyFile) if err != nil { - panic(err) + log.Fatal(err) } tlsConfig.Certificates = []tls.Certificate{cert} tlsConfig.ServerName = config.SocketOrigin tlsConfig.BuildNameToCertificate() sockConf.TlsConfig = tlsConfig - certBytes, err := ioutil.ReadFile(config.BackendRootCertFile) - if err != nil { - panic(err) - } - clientCA, err := x509.ParseCertificate(certBytes) - if err != nil { - panic(err) - } - certPool := x509.NewCertPool() - certPool.AddCert(clientCA) - tlsConfig.ClientCAs = certPool - SetupBackendCertificates(config, certPool) } sockServer := &websocket.Server{} @@ -214,7 +197,7 @@ func HandleSocketConnection(conn *websocket.Conn) { // Launch message draining goroutine - we aren't out of the pub/sub records go func() { - for _ := range _serverMessageChan {} + for _ = range _serverMessageChan {} }() // Stop getting messages... diff --git a/socketserver/internal/server/publisher.go b/socketserver/internal/server/publisher.go index 571da17b..61d521d8 100644 --- a/socketserver/internal/server/publisher.go +++ b/socketserver/internal/server/publisher.go @@ -46,9 +46,7 @@ func PublishToWatchers(channel string, msg ClientMessage) { } func HandlePublishRequest(w http.ResponseWriter, r *http.Request) { - if r.TLS { - PeerCertificates - } + // TODO - box.Open() } // Add a channel to the subscriptions while holding a read-lock to the map. @@ -63,7 +61,7 @@ func _subscribeWhileRlocked(which map[string]*SubscriberList, channelName string rlocker.Unlock() wlocker.Lock() list = &SubscriberList{} - list.Members = &[]chan <- ClientMessage{value} // Create it populated, to avoid reaper + list.Members = []chan <- ClientMessage{value} // Create it populated, to avoid reaper which[channelName] = list wlocker.Unlock() rlocker.Lock() @@ -84,7 +82,7 @@ func SubscribeBatch(client *ClientInfo, chatSubs, channelSubs []string) { rlocker := ChatSubscriptionLock.RLocker() rlocker.Lock() for _, v := range chatSubs { - _subscribeWhileRlocked(ChatSubscriptionInfo, v, mchan, rlocker, ChatSubscriptionLock) + _subscribeWhileRlocked(ChatSubscriptionInfo, v, mchan, rlocker, &ChatSubscriptionLock) } rlocker.Unlock() } @@ -92,7 +90,7 @@ func SubscribeBatch(client *ClientInfo, chatSubs, channelSubs []string) { rlocker := WatchingSubscriptionLock.RLocker() rlocker.Lock() for _, v := range channelSubs { - _subscribeWhileRlocked(WatchingSubscriptionInfo, v, mchan, rlocker, WatchingSubscriptionLock) + _subscribeWhileRlocked(WatchingSubscriptionInfo, v, mchan, rlocker, &WatchingSubscriptionLock) } rlocker.Unlock() } diff --git a/socketserver/internal/server/types.go b/socketserver/internal/server/types.go index 9c94c7a1..046afcca 100644 --- a/socketserver/internal/server/types.go +++ b/socketserver/internal/server/types.go @@ -6,6 +6,15 @@ import ( "time" ) +const CryptoBoxKeyLength = 32 + +type CryptoKeysBuf struct { + OurPrivateKey [CryptoBoxKeyLength]byte + OurPublicKey [CryptoBoxKeyLength]byte + TheirPublicKey [CryptoBoxKeyLength]byte + ServerId int +} + type ClientMessage struct { // Message ID. Increments by 1 for each message sent from the client. // When replying to a command, the message ID must be echoed. diff --git a/socketserver/internal/server/utils.go b/socketserver/internal/server/utils.go index 639eac5c..1d6d437a 100644 --- a/socketserver/internal/server/utils.go +++ b/socketserver/internal/server/utils.go @@ -1,8 +1,25 @@ package server import ( + "crypto/rand" ) +func FillCryptoRandom(buf []byte) error { + remaining := len(buf) + for remaining > 0 { + count, err := rand.Read(buf) + if err != nil { + return err + } + remaining -= count + } + return nil +} + +func NewByteBuffer() interface{} { + return make([]byte, 1024) +} + func AddToSliceS(ary *[]string, val string) bool { slice := *ary for _, v := range slice {