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) + } +}