// Copyright 2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package server

import (
	"bufio"
	crand "crypto/rand"
	"encoding/base64"
	"encoding/json"
	"fmt"
	mrand "math/rand"
	"strings"
	"testing"
	"time"

	"github.com/nats-io/nkeys"
)

// Nonce has to be a string since we used different encoding by default than json.Unmarshal.
type nonceInfo struct {
	Id    string `json:"server_id"`
	CID   uint64 `json:"client_id,omitempty"`
	Nonce string `json:"nonce,omitempty"`
}

// This is a seed for a user. We can extract public and private keys from this for testing.
var seed = []byte("SUAKYRHVIOREXV7EUZTBHUHL7NUMHPMAS7QMDU3GTIUWEI5LDNOXD43IZY")

func nkeyBasicSetup() (*Server, *testAsyncClient, *bufio.Reader, string) {
	kp, _ := nkeys.FromSeed(seed)
	pub, _ := kp.PublicKey()
	opts := defaultServerOptions
	opts.Nkeys = []*NkeyUser{{Nkey: string(pub)}}
	return rawSetup(opts)
}

func mixedSetup() (*Server, *testAsyncClient, *bufio.Reader, string) {
	kp, _ := nkeys.FromSeed(seed)
	pub, _ := kp.PublicKey()
	opts := defaultServerOptions
	opts.Nkeys = []*NkeyUser{{Nkey: string(pub)}}
	opts.Users = []*User{{Username: "derek", Password: "foo"}}
	return rawSetup(opts)
}

func TestServerInfoNonceAlwaysEnabled(t *testing.T) {
	opts := defaultServerOptions
	opts.AlwaysEnableNonce = true
	s, c, _, l := rawSetup(opts)
	defer s.WaitForShutdown()
	defer s.Shutdown()
	defer c.close()

	if !strings.HasPrefix(l, "INFO ") {
		t.Fatalf("INFO response incorrect: %s\n", l)
	}

	var info nonceInfo
	err := json.Unmarshal([]byte(l[5:]), &info)
	if err != nil {
		t.Fatalf("Could not parse INFO json: %v\n", err)
	}
	if info.Nonce == "" {
		t.Fatalf("Expected a non-empty nonce with AlwaysEnableNonce set")
	}
}

func TestServerInfoNonce(t *testing.T) {
	c, l := setUpClientWithResponse()
	defer c.close()
	if !strings.HasPrefix(l, "INFO ") {
		t.Fatalf("INFO response incorrect: %s\n", l)
	}
	// Make sure payload is proper json
	var info nonceInfo
	err := json.Unmarshal([]byte(l[5:]), &info)
	if err != nil {
		t.Fatalf("Could not parse INFO json: %v\n", err)
	}
	if info.Nonce != "" {
		t.Fatalf("Expected an empty nonce with no nkeys defined")
	}

	// Now setup server with auth and nkeys to trigger nonce generation
	s, c, _, l := nkeyBasicSetup()
	defer c.close()

	if !strings.HasPrefix(l, "INFO ") {
		t.Fatalf("INFO response incorrect: %s\n", l)
	}
	// Make sure payload is proper json
	err = json.Unmarshal([]byte(l[5:]), &info)
	if err != nil {
		t.Fatalf("Could not parse INFO json: %v\n", err)
	}
	if info.Nonce == "" {
		t.Fatalf("Expected a non-empty nonce with nkeys defined")
	}

	// Make sure new clients get new nonces
	oldNonce := info.Nonce

	c, _, l = newClientForServer(s)
	defer c.close()

	err = json.Unmarshal([]byte(l[5:]), &info)
	if err != nil {
		t.Fatalf("Could not parse INFO json: %v\n", err)
	}
	if info.Nonce == "" {
		t.Fatalf("Expected a non-empty nonce")
	}
	if strings.Compare(oldNonce, info.Nonce) == 0 {
		t.Fatalf("Expected subsequent nonces to be different\n")
	}
}

func TestNkeyClientConnect(t *testing.T) {
	s, c, cr, _ := nkeyBasicSetup()
	defer c.close()
	// Send CONNECT with no signature or nkey, should fail.
	connectOp := "CONNECT {\"verbose\":true,\"pedantic\":true}\r\n"
	c.parseAsync(connectOp)
	l, _ := cr.ReadString("\n")
	if !strings.HasPrefix(l, "-ERR ") {
		t.Fatalf("Expected an error")
	}

	kp, _ := nkeys.FromSeed(seed)
	pubKey, _ := kp.PublicKey()

	// Send nkey but no signature
	c, cr, _ = newClientForServer(s)
	defer c.close()
	cs := fmt.Sprintf("CONNECT {\"nkey\":%q, \"verbose\":true,\"pedantic\":true}\r\n", pubKey)
	c.parseAsync(cs)
	l, _ = cr.ReadString("\n")
	if !strings.HasPrefix(l, "-ERR ") {
		t.Fatalf("Expected an error")
	}

	// Now improperly sign etc.
	c, cr, _ = newClientForServer(s)
	defer c.close()
	cs = fmt.Sprintf("CONNECT {\"nkey\":%q,\"sig\":%q,\"verbose\":true,\"pedantic\":true}\r\n", pubKey, "bad_sig")
	c.parseAsync(cs)
	l, _ = cr.ReadString("\n")
	if !strings.HasPrefix(l, "-ERR ") {
		t.Fatalf("Expected an error")
	}

	// Now properly sign the nonce
	c, cr, l = newClientForServer(s)
	defer c.close()
	// Check for Nonce
	var info nonceInfo
	err := json.Unmarshal([]byte(l[5:]), &info)
	if err != nil {
		t.Fatalf("Could not parse INFO json: %v\n", err)
	}
	if info.Nonce == "" {
		t.Fatalf("Expected a non-empty nonce with nkeys defined")
	}
	sigraw, err := kp.Sign([]byte(info.Nonce))
	if err != nil {
		t.Fatalf("Failed signing nonce: %v", err)
	}
	sig := base64.RawURLEncoding.EncodeToString(sigraw)

	// PING needed to flush the +OK to us.
	cs = fmt.Sprintf("CONNECT {\"nkey\":%q,\"sig\":\"%s\",\"verbose\":true,\"pedantic\":true}\r\nPING\r\n", pubKey, sig)
	c.parseAsync(cs)
	l, _ = cr.ReadString("\n")
	if !strings.HasPrefix(l, "+OK") {
		t.Fatalf("Expected an OK, got: %v", l)
	}
}

func TestMixedClientConnect(t *testing.T) {
	s, c, cr, _ := mixedSetup()
	defer c.close()
	// Normal user/pass
	c.parseAsync("CONNECT {\"user\":\"derek\",\"pass\":\"foo\",\"verbose\":true,\"pedantic\":true}\r\nPING\r\n")
	l, _ := cr.ReadString("\n")
	if !strings.HasPrefix(l, "+OK") {
		t.Fatalf("Expected an OK, got: %v", l)
	}

	kp, _ := nkeys.FromSeed(seed)
	pubKey, _ := kp.PublicKey()

	c, cr, l = newClientForServer(s)
	defer c.close()
	// Check for Nonce
	var info nonceInfo
	err := json.Unmarshal([]byte(l[5:]), &info)
	if err != nil {
		t.Fatalf("Could not parse INFO json: %v\n", err)
	}
	if info.Nonce == "" {
		t.Fatalf("Expected a non-empty nonce with nkeys defined")
	}
	sigraw, err := kp.Sign([]byte(info.Nonce))
	if err != nil {
		t.Fatalf("Failed signing nonce: %v", err)
	}
	sig := base64.RawURLEncoding.EncodeToString(sigraw)

	// PING needed to flush the +OK to us.
	cs := fmt.Sprintf("CONNECT {\"nkey\":%q,\"sig\":\"%s\",\"verbose\":true,\"pedantic\":true}\r\nPING\r\n", pubKey, sig)
	c.parseAsync(cs)
	l, _ = cr.ReadString("\n")
	if !strings.HasPrefix(l, "+OK") {
		t.Fatalf("Expected an OK, got: %v", l)
	}
}

func TestMixedClientConfig(t *testing.T) {
	confFileName := createConfFile(t, []byte(`
    authorization {
      users = [
        {nkey: "UDKTV7HZVYJFJN64LLMYQBUR6MTNNYCDC3LAZH4VHURW3GZLL3FULBXV"}
        {user: alice, password: foo}
      ]
    }`))
	opts, err := ProcessConfigFile(confFileName)
	if err != nil {
		t.Fatalf("Received an error processing config file: %v", err)
	}
	if len(opts.Nkeys) != 1 {
		t.Fatalf("Expected 1 nkey, got %d", len(opts.Nkeys))
	}
	if len(opts.Users) != 1 {
		t.Fatalf("Expected 1 user, got %d", len(opts.Users))
	}
}

func BenchmarkCryptoRandGeneration(b *testing.B) {
	data := make([]byte, 16)
	for i := 0; i < b.N; i++ {
		crand.Read(data)
	}
}

func BenchmarkMathRandGeneration(b *testing.B) {
	data := make([]byte, 16)
	prng := mrand.New(mrand.NewSource(time.Now().UnixNano()))
	for i := 0; i < b.N; i++ {
		prng.Read(data)
	}
}

func BenchmarkNonceGeneration(b *testing.B) {
	data := make([]byte, nonceRawLen)
	b64 := make([]byte, nonceLen)
	prand := mrand.New(mrand.NewSource(time.Now().UnixNano()))
	for i := 0; i < b.N; i++ {
		prand.Read(data)
		base64.RawURLEncoding.Encode(b64, data)
	}
}

func BenchmarkPublicVerify(b *testing.B) {
	data := make([]byte, nonceRawLen)
	nonce := make([]byte, nonceLen)
	mrand.Read(data)
	base64.RawURLEncoding.Encode(nonce, data)

	user, err := nkeys.CreateUser()
	if err != nil {
		b.Fatalf("Error creating User Nkey: %v", err)
	}
	sig, err := user.Sign(nonce)
	if err != nil {
		b.Fatalf("Error sigining nonce: %v", err)
	}
	pk, err := user.PublicKey()
	if err != nil {
		b.Fatalf("Could not extract public key from user: %v", err)
	}
	pub, err := nkeys.FromPublicKey(pk)
	if err != nil {
		b.Fatalf("Could not create public key pair from public key string: %v", err)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		if err := pub.Verify(nonce, sig); err != nil {
			b.Fatalf("Error verifying nonce: %v", err)
		}
	}
}

Related articles

NATS tls_bad_curve_prefs

# Simple TLS config file listen: 127.0.0.1:4443 tls { cert_file: "./configs/certs/server.pem" key_file: "./configs/certs/key.pem" timeout: 2 curve_preferences: [ "GARBAGE" ] }

NATS jetstream_cluster

// Copyright 2020-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses

NATS gwb

listen: "127.0.0.1:-1" gateway { name: "B" listen: "127.0.0.1:5228" include "gws.conf" }

NATS server no ou

-----BEGIN CERTIFICATE----- MIIDhTCCAm2gAwIBAgIUbXHf4iAemXfIpLSWpRMkEVsdjy8wDQYJKoZIhvcNAQEL BQAwTDEkMCIGA1UEChMbU3luYWRpYSBDb21tdW5pY2F0aW9ucyBJbmMuMRAwDgYD VQQLEwdOQVRTLmlvMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTkwMjE4MjE0MjAw WhcNMjQwMjE3MjE0MjAwWjAUMRIwEAY

NATS multi_accounts

listen: 127.0.0.1:4033 http: 127.0.0.1:8033 password = "s3cr3t!" accounts: { engineering: { users = [ {user: alice, password: $password} {user: bob, password: $password} ] } legal: { users = [ {us

NATS auth_seed

# Cluster Seed Node listen: 127.0.0.1:5222 http: 8222 cluster { listen: 127.0.0.1:4248 name: xyz authorization { user: ruser password: T0PS3cr3T! timeout: 1 } } no_sys_acc: true

NATS tls_cert_san_auth

listen: localhost:9335 tls { cert_file = "./configs/certs/sans/server.pem" key_file = "./configs/certs/sans/server-key.pem" ca_file = "./configs/certs/sans/ca.pem" verify = true verify_and_map = true } authorization { # Default permissions

NATS pse_freebsd_amd64

// Copyright 2015-2020 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses

NATS tlsverify_noca

# Simple TLS config file listen: 127.0.0.1:5443 tls { # Server cert cert_file: "./configs/certs/server-cert.pem" # Server private key key_file: "./configs/certs/server-key.pem" # Specified time for handshake to complete timeout: 2 # Requ

NATS CODE OF CONDUCT

## Community Code of Conduct NATS follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).