From 5101b16df37a4e319e7454b504335cc52b87e044 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 23 Mar 2022 13:13:32 +0100 Subject: [PATCH] Return dedicated error if proxy receives token that is not valid yet. This can happen for example if the times of the machines running the signaling server and proxy don't match. --- proxy/proxy_server.go | 8 +++ proxy/proxy_server_test.go | 127 +++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 proxy/proxy_server_test.go diff --git a/proxy/proxy_server.go b/proxy/proxy_server.go index 990b5e8..6868779 100644 --- a/proxy/proxy_server.go +++ b/proxy/proxy_server.go @@ -73,6 +73,7 @@ var ( TimeoutCreatingSubscriber = signaling.NewError("timeout", "Timeout creating subscriber.") TokenAuthFailed = signaling.NewError("auth_failed", "The token could not be authenticated.") TokenExpired = signaling.NewError("token_expired", "The token is expired.") + TokenNotValidYet = signaling.NewError("token_not_valid_yet", "The token is not valid yet.") UnknownClient = signaling.NewError("unknown_client", "Unknown client id given.") UnsupportedCommand = signaling.NewError("bad_request", "Unsupported command received.") UnsupportedMessage = signaling.NewError("bad_request", "Unsupported message received.") @@ -865,8 +866,15 @@ func (s *ProxyServer) NewSession(hello *signaling.HelloProxyClientMessage) (*Pro reason = "unsupported-issuer" return nil, fmt.Errorf("No key found for issuer") } + return tokenKey.key, nil }) + if err, ok := err.(*jwt.ValidationError); ok { + if err.Errors&jwt.ValidationErrorIssuedAt == jwt.ValidationErrorIssuedAt { + statsTokenErrorsTotal.WithLabelValues("not-valid-yet").Inc() + return nil, TokenNotValidYet + } + } if err != nil { statsTokenErrorsTotal.WithLabelValues(reason).Inc() return nil, TokenAuthFailed diff --git a/proxy/proxy_server_test.go b/proxy/proxy_server_test.go new file mode 100644 index 0000000..b67569f --- /dev/null +++ b/proxy/proxy_server_test.go @@ -0,0 +1,127 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2022 struktur AG + * + * @author Joachim Bauch + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package main + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "io/ioutil" + "os" + "testing" + "time" + + "github.com/dlintw/goconf" + "github.com/golang-jwt/jwt" + "github.com/gorilla/mux" + signaling "github.com/strukturag/nextcloud-spreed-signaling" +) + +const ( + KeypairSizeForTest = 2048 + TokenIdForTest = "foo" +) + +func newProxyServerForTest(t *testing.T) (*ProxyServer, *rsa.PrivateKey, func()) { + tempdir, err := ioutil.TempDir("", "test") + if err != nil { + t.Fatalf("could not create temporary folder: %s", err) + } + var server *ProxyServer + shutdown := func() { + if server != nil { + server.Stop() + } + os.RemoveAll(tempdir) + } + + r := mux.NewRouter() + key, err := rsa.GenerateKey(rand.Reader, KeypairSizeForTest) + if err != nil { + t.Fatalf("could not generate key: %s", err) + } + priv := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(key), + } + privkey, err := ioutil.TempFile(tempdir, "privkey*.pem") + if err != nil { + t.Fatalf("could not create temporary file for private key: %s", err) + } + if err := pem.Encode(privkey, priv); err != nil { + t.Fatalf("could not encode private key: %s", err) + } + + pubData, err := x509.MarshalPKIXPublicKey(&key.PublicKey) + if err != nil { + t.Fatalf("could not marshal public key: %s", err) + } + pub := &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: pubData, + } + pubkey, err := ioutil.TempFile(tempdir, "pubkey*.pem") + if err != nil { + t.Fatalf("could not create temporary file for public key: %s", err) + } + if err := pem.Encode(pubkey, pub); err != nil { + t.Fatalf("could not encode public key: %s", err) + } + + config := goconf.NewConfigFile() + config.AddOption("tokens", TokenIdForTest, pubkey.Name()) + + if server, err = NewProxyServer(r, "0.0", config); err != nil { + t.Fatalf("could not create server: %s", err) + } + return server, key, shutdown +} + +func TestTokenInFuture(t *testing.T) { + server, key, shutdown := newProxyServerForTest(t) + defer shutdown() + + claims := &signaling.TokenClaims{ + StandardClaims: jwt.StandardClaims{ + IssuedAt: time.Now().Add(time.Hour).Unix(), + Issuer: TokenIdForTest, + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + tokenString, err := token.SignedString(key) + if err != nil { + t.Fatalf("could not create token: %s", err) + } + + hello := &signaling.HelloProxyClientMessage{ + Version: "1.0", + Token: tokenString, + } + session, err := server.NewSession(hello) + if session != nil { + defer session.Close() + t.Errorf("should not have created session") + } else if err != TokenNotValidYet { + t.Errorf("could have failed with TokenNotValidYet, got %s", err) + } +}