Files
nats-server/test/ocsp_peer_test.go

2927 lines
105 KiB
Go

// Copyright 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/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 test
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"testing"
"time"
"golang.org/x/crypto/ocsp"
"github.com/nats-io/nats-server/v2/server"
"github.com/nats-io/nats.go"
)
func newOCSPResponderRootCA(t *testing.T) *http.Server {
t.Helper()
respCertPEM := "configs/certs/ocsp_peer/mini-ca/caocsp/caocsp_cert.pem"
respKeyPEM := "configs/certs/ocsp_peer/mini-ca/caocsp/private/caocsp_keypair.pem"
issuerCertPEM := "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
return newOCSPResponderDesignatedCustomAddress(t, issuerCertPEM, respCertPEM, respKeyPEM, "127.0.0.1:8888")
}
func newOCSPResponderIntermediateCA1(t *testing.T) *http.Server {
t.Helper()
respCertPEM := "configs/certs/ocsp_peer/mini-ca/ocsp1/ocsp1_bundle.pem"
respKeyPEM := "configs/certs/ocsp_peer/mini-ca/ocsp1/private/ocsp1_keypair.pem"
issuerCertPEM := "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem"
return newOCSPResponderDesignatedCustomAddress(t, issuerCertPEM, respCertPEM, respKeyPEM, "127.0.0.1:18888")
}
func newOCSPResponderIntermediateCA1Undelegated(t *testing.T) *http.Server {
t.Helper()
issuerCertPEM := "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem"
issuerCertKey := "configs/certs/ocsp_peer/mini-ca/intermediate1/private/intermediate1_keypair.pem"
return newOCSPResponderCustomAddress(t, issuerCertPEM, issuerCertKey, "127.0.0.1:18888")
}
func newOCSPResponderBadDelegateIntermediateCA1(t *testing.T) *http.Server {
t.Helper()
// UserA2 is a cert issued by intermediate1, but intermediate1 did not add OCSP signing extension
respCertPEM := "configs/certs/ocsp_peer/mini-ca/client1/UserA2_bundle.pem"
respKeyPEM := "configs/certs/ocsp_peer/mini-ca/client1/private/UserA2_keypair.pem"
issuerCertPEM := "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem"
return newOCSPResponderDesignatedCustomAddress(t, issuerCertPEM, respCertPEM, respKeyPEM, "127.0.0.1:18888")
}
func newOCSPResponderIntermediateCA2(t *testing.T) *http.Server {
t.Helper()
respCertPEM := "configs/certs/ocsp_peer/mini-ca/ocsp2/ocsp2_bundle.pem"
respKeyPEM := "configs/certs/ocsp_peer/mini-ca/ocsp2/private/ocsp2_keypair.pem"
issuerCertPEM := "configs/certs/ocsp_peer/mini-ca/intermediate2/intermediate2_cert.pem"
return newOCSPResponderDesignatedCustomAddress(t, issuerCertPEM, respCertPEM, respKeyPEM, "127.0.0.1:28888")
}
// TestOCSPPeerGoodClients is test of two NATS client (AIA enabled at leaf and cert) under good path (different intermediates)
// and default ocsp_cache implementation and oscp_cache=false configuration
func TestOCSPPeerGoodClients(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate2/intermediate2_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Good)
intermediateCA2Responder := newOCSPResponderIntermediateCA2(t)
intermediateCA2ResponderURL := fmt.Sprintf("http://%s", intermediateCA2Responder.Addr)
defer intermediateCA2Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA2ResponderURL, "configs/certs/ocsp_peer/mini-ca/client2/UserB1_cert.pem", ocsp.Good)
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
configure func()
}{
{
"Default cache: mTLS OCSP peer check on inbound client connection, client of intermediate CA 1",
`
port: -1
# default ocsp_cache since omitted
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration, non-default ca_timeout
ocsp_peer: {
verify: true
ca_timeout: 5
allowed_clockskew: 30
}
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
{
"Default cache: mTLS OCSP peer check on inbound client connection, client of intermediate CA 2",
`
port: -1
# default ocsp_cache since omitted
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Short form configuration
ocsp_peer: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client2/UserB1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client2/private/UserB1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
{
"Explicit true cache: mTLS OCSP peer check on inbound client connection, client of intermediate CA 1",
`
port: -1
# Short form configuration
ocsp_cache: true
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
ca_timeout: 5
allowed_clockskew: 30
}
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
nc.Subscribe("ping", func(m *nats.Msg) {
m.Respond([]byte("pong"))
})
nc.Flush()
_, err = nc.Request("ping", []byte("ping"), 250*time.Millisecond)
if test.rerr != nil && err == nil {
t.Errorf("Expected error getting response")
} else if test.rerr == nil && err != nil {
t.Errorf("Expected response")
}
})
}
}
// TestOCSPPeerUnknownClient is test of NATS client that is OCSP status Unknown from its OCSP Responder
func TestOCSPPeerUnknownClient(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
defer intermediateCA1Responder.Shutdown(ctx)
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
configure func()
}{
{
"Default cache, mTLS OCSP peer check on inbound client connection, client unknown to intermediate CA 1",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Short form configuration
ocsp_peer: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
errors.New("remote error: tls: bad certificate"),
errors.New("expect error"),
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
t.Errorf("Expected connection error, fell through")
})
}
}
// TestOCSPPeerRevokedClient is test of NATS client that is OCSP status Revoked from its OCSP Responder
func TestOCSPPeerRevokedClient(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Revoked)
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
configure func()
}{
{
"mTLS OCSP peer check on inbound client connection, client revoked by intermediate CA 1",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Turn on CA OCSP check so this revoked client should NOT be able to connect
ocsp_peer: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
errors.New("remote error: tls: bad certificate"),
errors.New("expect error"),
func() {},
},
{
"Explicit disable, mTLS OCSP peer check on inbound client connection, client revoked by intermediate CA 1 but no OCSP check",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Explicit disable of OCSP peer check
ocsp_peer: false
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
{
"Implicit disable, mTLS OCSP peer check on inbound client connection, client revoked by intermediate CA 1 but no OCSP check",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Implicit disable of OCSP peer check (i.e. not configured)
# ocsp_peer: false
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
{
"Explicit disable (long form), mTLS OCSP peer check on inbound client connection, client revoked by intermediate CA 1 but no OCSP check",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Explicit disable of OCSP peer check, long form
ocsp_peer: { verify: false }
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
})
}
}
// TestOCSPPeerUnknownAndRevokedIntermediate test of NATS client that is OCSP good but either its intermediate is unknown or revoked
func TestOCSPPeerUnknownAndRevokedIntermediate(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Revoked)
// No test OCSP status set on intermediate2, so unknown
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Good)
intermediateCA2Responder := newOCSPResponderIntermediateCA2(t)
intermediateCA2ResponderURL := fmt.Sprintf("http://%s", intermediateCA2Responder.Addr)
defer intermediateCA2Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA2ResponderURL, "configs/certs/ocsp_peer/mini-ca/client2/UserB1_cert.pem", ocsp.Good)
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
configure func()
}{
{
"mTLS OCSP peer check on inbound client connection, client's intermediate is revoked",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Short form configuration
ocsp_peer: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
errors.New("remote error: tls: bad certificate"),
errors.New("expect error"),
func() {},
},
{
"mTLS OCSP peer check on inbound client connection, client's intermediate is unknown'",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Short form configuration
ocsp_peer: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client2/UserB1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client2/private/UserB1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
errors.New("remote error: tls: bad certificate"),
errors.New("expect error"),
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
t.Errorf("Expected connection error, fell through")
})
}
}
// TestOCSPPeerLeafGood tests Leaf Spoke peer checking Leaf Hub, Leaf Hub peer checking Leaf Spoke, and both peer checking
func TestOCSPPeerLeafGood(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_cert.pem", ocsp.Good)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/server1/TestServer2_cert.pem", ocsp.Good)
for _, test := range []struct {
name string
hubconfig string
spokeconfig string
expected int
}{
{
"OCSP peer check on Leaf Hub by Leaf Spoke (TLS client OCSP verification of TLS server)",
`
port: -1
# Cache configuration is default
leaf: {
listen: 127.0.0.1:7444
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
}
}
`,
`
port: -1
leaf: {
remotes: [
{
url: "nats://127.0.0.1:7444",
tls: {
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
# Short form configuration
ocsp_peer: true
}
}
]
}
`,
1,
},
{
"OCSP peer check on Leaf Spoke by Leaf Hub (TLS server OCSP verification of TLS client)",
`
port: -1
# Cache configuration is default
leaf: {
listen: 127.0.0.1:7444
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Short form configuration
ocsp_peer: true
}
}
`,
`
port: -1
leaf: {
remotes: [
{
url: "nats://127.0.0.1:7444",
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer2_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer2_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
}
}
]
}
`,
1,
},
{
"OCSP peer check bi-directionally",
`
port: -1
# Cache configuration is default
leaf: {
listen: 127.0.0.1:7444
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Short form configuration
ocsp_peer: true
}
}
`,
`
port: -1
leaf: {
remotes: [
{
url: "nats://127.0.0.1:7444",
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer2_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer2_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
# Short form configuration
ocsp_peer: true
}
}
]
}
`,
1,
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
hubcontent := test.hubconfig
hubconf := createConfFile(t, []byte(hubcontent))
hub, _ := RunServerWithConfig(hubconf)
defer hub.Shutdown()
spokecontent := test.spokeconfig
spokeconf := createConfFile(t, []byte(spokecontent))
spoke, _ := RunServerWithConfig(spokeconf)
defer spoke.Shutdown()
checkLeafNodeConnectedCount(t, hub, test.expected)
})
}
}
// TestOCSPPeerLeafRejects tests rejected Leaf Hub, rejected Leaf Spoke, and both rejecting each other
func TestOCSPPeerLeafReject(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_cert.pem", ocsp.Revoked)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/server1/TestServer2_cert.pem", ocsp.Revoked)
for _, test := range []struct {
name string
hubconfig string
spokeconfig string
expected int
}{
{
"OCSP peer check on Leaf Hub by Leaf Spoke (TLS client OCSP verification of TLS server)",
`
port: -1
# Cache configuration is default
leaf: {
listen: 127.0.0.1:7444
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
}
}
`,
`
port: -1
leaf: {
remotes: [
{
url: "nats://127.0.0.1:7444",
tls: {
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
# Short form configuration
ocsp_peer: true
}
}
]
}
`,
0,
},
{
"OCSP peer check on Leaf Spoke by Leaf Hub (TLS server OCSP verification of TLS client)",
`
port: -1
leaf: {
listen: 127.0.0.1:7444
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Short form configuration
ocsp_peer: true
}
}
`,
`
port: -1
leaf: {
remotes: [
{
url: "nats://127.0.0.1:7444",
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer2_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer2_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
}
}
]
}
`,
0,
},
{
"OCSP peer check bi-directionally",
`
port: -1
leaf: {
listen: 127.0.0.1:7444
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Short form configuration
ocsp_peer: true
}
}
`,
`
port: -1
leaf: {
remotes: [
{
url: "nats://127.0.0.1:7444",
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer2_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer2_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
# Short form configuration
ocsp_peer: true
}
}
]
}
`,
0,
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
hubcontent := test.hubconfig
hubconf := createConfFile(t, []byte(hubcontent))
hub, _ := RunServerWithConfig(hubconf)
defer hub.Shutdown()
spokecontent := test.spokeconfig
spokeconf := createConfFile(t, []byte(spokecontent))
spoke, _ := RunServerWithConfig(spokeconf)
defer spoke.Shutdown()
// Need to inject some time for leaf connection attempts to complete, could refine this to better
// negative test
time.Sleep(2000 * time.Millisecond)
checkLeafNodeConnectedCount(t, hub, test.expected)
})
}
}
func checkLeafNodeConnectedCount(t testing.TB, s *server.Server, lnCons int) {
t.Helper()
checkFor(t, 5*time.Second, 15*time.Millisecond, func() error {
if nln := s.NumLeafNodes(); nln != lnCons {
return fmt.Errorf("expected %d connected leafnode(s) for server %q, got %d",
lnCons, s.ID(), nln)
}
return nil
})
}
// TestOCSPPeerGoodClientsNoneCache is test of two NATS client (AIA enabled at leaf and cert) under good path (different intermediates)
// and ocsp cache type of none (no-op)
func TestOCSPPeerGoodClientsNoneCache(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate2/intermediate2_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Good)
intermediateCA2Responder := newOCSPResponderIntermediateCA2(t)
intermediateCA2ResponderURL := fmt.Sprintf("http://%s", intermediateCA2Responder.Addr)
defer intermediateCA2Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA2ResponderURL, "configs/certs/ocsp_peer/mini-ca/client2/UserB1_cert.pem", ocsp.Good)
deleteLocalStore(t, "")
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
configure func()
}{
{
"None cache explicit long form: mTLS OCSP peer check on inbound client connection, client of intermediate CA 1",
`
port: -1
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
ca_timeout: 5
allowed_clockskew: 30
}
}
# Long form configuration
ocsp_cache: {
type: none
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
{
"None cache explicit short form: mTLS OCSP peer check on inbound client connection, client of intermediate CA 1",
`
port: -1
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
ca_timeout: 5
allowed_clockskew: 30
}
}
# Short form configuration
ocsp_cache: false
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
nc.Subscribe("ping", func(m *nats.Msg) {
m.Respond([]byte("pong"))
})
nc.Flush()
_, err = nc.Request("ping", []byte("ping"), 250*time.Millisecond)
if test.rerr != nil && err == nil {
t.Errorf("Expected error getting response")
} else if test.rerr == nil && err != nil {
t.Errorf("Expected response")
}
})
}
}
// TestOCSPPeerGoodClientsLocalCache is test of two NATS client (AIA enabled at leaf and cert) under good path (different intermediates)
// and leveraging the local ocsp cache type
func TestOCSPPeerGoodClientsLocalCache(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate2/intermediate2_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Good)
intermediateCA2Responder := newOCSPResponderIntermediateCA2(t)
intermediateCA2ResponderURL := fmt.Sprintf("http://%s", intermediateCA2Responder.Addr)
defer intermediateCA2Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA2ResponderURL, "configs/certs/ocsp_peer/mini-ca/client2/UserB1_cert.pem", ocsp.Good)
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
configure func()
}{
{
"Default cache, short form: mTLS OCSP peer check on inbound client connection, UserA1 client of intermediate CA 1",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
ca_timeout: 5
allowed_clockskew: 30
}
}
# Short form configuration, local as default
ocsp_cache: true
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
{
"Local cache long form: mTLS OCSP peer check on inbound client connection, UserB1 client of intermediate CA 2",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Short form configuration
ocsp_peer: true
}
# Long form configuration
ocsp_cache: {
type: local
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client2/UserB1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client2/private/UserB1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
// Cleanup any previous test that saved a local cache
deleteLocalStore(t, "")
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
nc.Close()
v := monitorGetVarzHelper(t, 8222)
if v.OCSPResponseCache.Misses != 2 || v.OCSPResponseCache.Responses != 2 {
t.Errorf("Expected cache misses and cache items to be 2, got %d and %d", v.OCSPResponseCache.Misses, v.OCSPResponseCache.Responses)
}
// Should get a cache hit now
nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
nc.Subscribe("ping", func(m *nats.Msg) {
m.Respond([]byte("pong"))
})
nc.Flush()
_, err = nc.Request("ping", []byte("ping"), 250*time.Millisecond)
if test.rerr != nil && err == nil {
t.Errorf("Expected error getting response")
} else if test.rerr == nil && err != nil {
t.Errorf("Expected response")
}
v = monitorGetVarzHelper(t, 8222)
if v.OCSPResponseCache.Misses != 2 || v.OCSPResponseCache.Hits != 2 || v.OCSPResponseCache.Responses != 2 {
t.Errorf("Expected cache misses, hits and cache items to be 2, got %d and %d and %d", v.OCSPResponseCache.Misses, v.OCSPResponseCache.Hits, v.OCSPResponseCache.Responses)
}
})
}
}
func TestOCSPPeerMonitor(t *testing.T) {
for _, test := range []struct {
name string
config string
NATSClient bool
WSClient bool
MQTTClient bool
LeafClient bool
LeafRemotes bool
NumTrueLeafRemotes int
}{
{
"Monitor peer config setting on NATS client",
`
port: -1
http_port: 8222
# Default cache configuration
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
}
}
`,
true,
false,
false,
false,
false,
0,
},
{
"Monitor peer config setting on Websockets client",
`
port: -1
http_port: 8222
# Default cache configuration
websocket: {
port: 8443
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
}
}
}
`,
false,
true,
false,
false,
false,
0,
},
{
"Monitor peer config setting on MQTT client",
`
port: -1
http_port: 8222
# Default cache configuration
# Required for MQTT
server_name: "my_mqtt_server"
jetstream: {
enabled: true
}
mqtt: {
port: 1883
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
}
}
}
`,
false,
false,
true,
false,
false,
0,
},
{
"Monitor peer config setting on Leaf client",
`
port: -1
http_port: 8222
# Default cache configuration
leaf: {
port: 7422
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
}
}
}
`,
false,
false,
false,
true,
false,
0,
},
{
"Monitor peer config on some Leaf Remotes as well as Leaf client",
`
port: -1
http_port: 8222
# Default cache configuration
leaf: {
port: 7422
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
}
}
remotes: [
{
url: "nats-leaf://bogus:7422"
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
# Long form configuration
ocsp_peer: {
verify: true
}
}
},
{
url: "nats-leaf://anotherbogus:7422"
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
# Short form configuration
ocsp_peer: true
}
},
{
url: "nats-leaf://yetanotherbogus:7422"
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
# Peer not configured (default false)
}
}
]
}
`,
false,
false,
false,
true,
true,
2,
},
} {
t.Run(test.name, func(t *testing.T) {
content := test.config
conf := createConfFile(t, []byte(content))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
v := monitorGetVarzHelper(t, 8222)
if test.NATSClient {
if !v.TLSOCSPPeerVerify {
t.Fatalf("Expected NATS Client TLSOCSPPeerVerify to be true, got false")
}
}
if test.WSClient {
if !v.Websocket.TLSOCSPPeerVerify {
t.Fatalf("Expected WS Client TLSOCSPPeerVerify to be true, got false")
}
}
if test.LeafClient {
if !v.LeafNode.TLSOCSPPeerVerify {
t.Fatalf("Expected Leaf Client TLSOCSPPeerVerify to be true, got false")
}
}
if test.LeafRemotes {
cnt := 0
for _, r := range v.LeafNode.Remotes {
if r.TLSOCSPPeerVerify {
cnt++
}
}
if cnt != test.NumTrueLeafRemotes {
t.Fatalf("Expected %d Leaf Remotes with TLSOCSPPeerVerify true, got %d", test.NumTrueLeafRemotes, cnt)
}
}
})
}
}
func TestOCSPResponseCacheMonitor(t *testing.T) {
for _, test := range []struct {
name string
config string
expect string
}{
{
"Monitor local cache enabled, explicit cache true",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
}
}
# Short form configuration
ocsp_cache: true
`,
"local",
},
{
"Monitor local cache enabled, explicit cache type local",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
}
}
# Long form configuration
ocsp_cache: {
type: local
}
`,
"local",
},
{
"Monitor local cache enabled, implicit default",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
}
}
# Short form configuration
# ocsp_cache: true
`,
"local",
},
{
"Monitor none cache enabled, explicit cache false (short)",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
}
}
# Short form configuration
ocsp_cache: false
`,
"",
},
{
"Monitor none cache enabled, explicit cache false (long)",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
}
}
# Long form configuration
ocsp_cache: {
type: none
}
`,
"",
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
content := test.config
conf := createConfFile(t, []byte(content))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
v := monitorGetVarzHelper(t, 8222)
if v.OCSPResponseCache.Type != test.expect {
t.Fatalf("Expected OCSP Response Cache to be %s, got %s", test.expect, v.OCSPResponseCache.Type)
}
})
}
}
func TestOCSPResponseCacheChangeAndReload(t *testing.T) {
deleteLocalStore(t, "")
// Start with ocsp cache set to none
content := `
port: -1
http_port: 8222
tls {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Short form configuration
ocsp_peer: true
}
# Long form configuration
ocsp_cache: {
type: none
}
`
conf := createConfFile(t, []byte(content))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
v := monitorGetVarzHelper(t, 8222)
if v.OCSPResponseCache.Type != "" {
t.Fatalf("Expected OCSP Response Cache to have empty type in varz indicating none")
}
// Change to local cache
content = `
port: -1
http_port: 8222
tls {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Short form configuration
ocsp_peer: true
}
# Long form configuration
ocsp_cache: {
type: local
}
`
if err := os.WriteFile(conf, []byte(content), 0666); err != nil {
t.Fatalf("Error writing config: %v", err)
}
if err := s.Reload(); err != nil {
t.Fatal(err)
}
time.Sleep(2 * time.Second)
v = monitorGetVarzHelper(t, 8222)
if v.OCSPResponseCache.Type != "local" {
t.Fatalf("Expected OCSP Response Cache type to be local, got %q", v.OCSPResponseCache.Type)
}
}
func deleteLocalStore(t *testing.T, dir string) {
t.Helper()
if dir == "" {
// default
dir = "_rc_"
}
if err := os.RemoveAll(dir); err != nil {
t.Fatalf("Error cleaning up local store: %v", err)
}
}
func monitorGetVarzHelper(t *testing.T, httpPort int) *server.Varz {
t.Helper()
url := fmt.Sprintf("http://127.0.0.1:%d/", httpPort)
resp, err := http.Get(url + "varz")
if err != nil {
t.Fatalf("Expected no error: Got %v\n", err)
}
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Got an error reading the body: %v\n", err)
}
v := server.Varz{}
if err := json.Unmarshal(body, &v); err != nil {
t.Fatalf("Got an error unmarshalling the body: %v\n", err)
}
return &v
}
func writeCacheFile(dir string, content []byte) error {
if dir == "" {
dir = "_rc_"
}
err := os.MkdirAll(filepath.Join(dir), os.ModePerm)
if err != nil {
return err
}
return os.WriteFile(filepath.Join(dir, "cache.json"), content, os.ModePerm)
}
// TestOCSPPeerPreserveRevokedCacheItem is test of the preserve_revoked cache policy
func TestOCSPPeerPreserveRevokedCacheItem(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
for _, test := range []struct {
name string
config string
opts []nats.Option
responses int64
revokes int64
goods int64
unknowns int64
err error
rerr error
clean bool
}{
{
"Test expired revoked cert not actually deleted",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Turn on CA OCSP check so this revoked client should NOT be able to connect
ocsp_peer: {
verify: true
ca_timeout: 0.5
}
}
# preserve revoked true
ocsp_cache: {
type: local
preserve_revoked: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
1,
1,
0,
0,
errors.New("remote error: tls: bad certificate"),
errors.New("expect error"),
true,
},
{
"Test expired revoked cert replaced by current good cert",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Turn on CA OCSP check so this revoked client should NOT be able to connect
ocsp_peer: true
}
# preserve revoked true
ocsp_cache: {
type: local
preserve_revoked: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
2,
0,
2,
0,
nil,
nil,
false,
},
} {
t.Run(test.name, func(t *testing.T) {
var intermediateCA1Responder *http.Server
// clean slate starting the test and start the leaf CA responder for first run
if test.clean {
deleteLocalStore(t, "")
// establish the revoked item (expired) in cache
c := []byte(`
{
"5xL/SuHl6JN0OmxrNMpzVMTA73JVYcRfGX8+HvJinEI=": {
"subject": "CN=UserA1,O=Testnats,L=Tacoma,ST=WA,C=US",
"cached_at": "2023-05-29T17:56:45Z",
"resp_status": "revoked",
"resp_expires": "2023-05-29T17:56:49Z",
"resp": "/wYAAFMyc1R3TwBSBQBao1Qr1QzUMIIGUQoBAKCCBkowggZGBgkrBgEFBQcwAQEEggY3MIIGMzCB46FZMFcxCzAJBgNVBAYTAlVTEQ0gCAwCV0ExDzANAQ0wBwwGVGFjb21hMREwDwEROAoMCFRlc3RuYXRzMRcwFQET8HQDDA5PQ1NQIFJlc3BvbmRlchgPMjAyMzA1MjkxNzU2MDBaMHUwczBNMAkGBSsOAwIaBQAEFKgwn5fplwQy+DsulBg5SRpx0iaYBBS1kW5PZLcWhHb5tL6ZzmCVmBqOnQIUXKGv1Xy7Fu/Cx+ZT/JQa7SS7tBc2ZAAQNDVaoBE2dwD0QQE0OVowDQYJKoZIhvcNAQELBQADggEBAGAax/vkv3SBFNbxp2utc/N6Rje4E0ceC972sWgqYjzYrH0oc/acg+OAXaxUjwqoQWaT+dHaI4D5qoTkMx7XlWATjI2L72IUTf6Luo92jPzyDFwb10CdeFHtRtEYD54Qbi/nD4oxQ8cSoLKC3wft2l3E/mK/1I4Mxwq15CioK4MhfzTISoeGZbjDXPKgloJOG3rn9v64vFGV6dosbLgaXEs+MPcCsPQYkwhOOyazuewRmIDOBp5QSsKPhqsT8Rs20t8LGTMkvjZniFWJs90l9QL9F1m3obq5nyuxrGt+7Rf5zoj4T+0XCOGtE+b7cRCLg43tFuTbaAQG8Z+qkPzpza+gggQ1MIIEMTCCBC0wggMVoAMCAQICFCnhUo39pSqH6x3kHUds4YpYaXOrOj8BBDBaUSLaLwIIGjAYSS+oEUludGVybWVkaWF0ZSBDQSAxMB4XDTIzMDUwMTE5MjgzOVoXDTMzMDQyOA0PUasVAEkMMIIBIi4nAgABBQD0QAEPADCCAQoCggEBAKMMyuuA66EOHnGb07P5Zc5wwiEGPDHBBn6lqErhIaN0VJ9XzlDWwyk8Q7CdPlSU7o36DXFs316eATB5bLuXXa+7WwV3cp9V5mZF9OLCz3sOWNYUanYprOMwKA3uvcqqrh8e70Dzw6sX8tfsDeH7aJoJg5kRWEKU+A3Umm+fO+hW8Km3GBqRQXxD49uxAfGtCznXZZjmFbAXqVZu+4R6wMxndfz2dYQxeMVtUY/QGdMWT4fvWzO5et3+X6hq/URUAPOkplv9O2U4T4JPucS9yZpW/FTxWC/L7vQI/bfsrSgIZpv4eJgy27FW3Q4xusbjVvUCL/t2KLvEi/Nr2qodOCECAwEAAaOB7TCB6jAdBgNVHQ4EFgQUy15QYHqrL6k7HiSrAkKN7IFgSBMwHwYDVR0jBBgwFoBSyQNQMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQ4YBAMCB4AwFgEeACUBEBAMMAoGCIm0sAMJMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly8xMjcuMC4wLjE6MTg4ODgvaV1WKDFfY3JsLmRlcjAzEUscAQEEJzAlMCMRWwwwAYYXWkoALhICkTnw/0hlzm2RRjA3tvJ2wELj9e7pMg5GtdWdrLDyI/U1qBxhZoHADbyku7W+R1iL8dFfc4PSmdo+owsygZakvahXjv49xJNX7wV3YMmIHC4lfurIlY2mSnPlu2zEOwEDkI0S9WkTxXmHrkXLSciQJDkwzye6MR5fW+APk4JmKDPc46Go/K1A0EgxY/ugahMYsYtZu++W+IOYbEoYNxoCrcJCHX4c3Ep3t/Wulz4X6DWWhaDkMMUDC2JVE8E/3xUbw0X3adZe9Xf8T+goOz7wLCAigXKj1hvRUmOGISIGelv0KsfluZesG1a1TGLp+W9JX0M9nOaFOvjJTDP96aqIjs8oXGk="
}
}`)
err := writeCacheFile("", c)
if err != nil {
t.Fatal(err)
}
} else {
intermediateCA1Responder = newOCSPResponderIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Good)
defer intermediateCA1Responder.Shutdown(ctx)
}
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
v := monitorGetVarzHelper(t, 8222)
responses := v.OCSPResponseCache.Responses
revokes := v.OCSPResponseCache.Revokes
goods := v.OCSPResponseCache.Goods
unknowns := v.OCSPResponseCache.Unknowns
if !(responses == test.responses && revokes == test.revokes && goods == test.goods && unknowns == test.unknowns) {
t.Fatalf("Expected %d response, %d revoked, %d good, %d unknown; got [%d] and [%d] and [%d] and [%d]", test.responses, test.revokes, test.goods, test.unknowns, responses, revokes, goods, unknowns)
}
})
}
}
// TestOCSPStapleFeatureInterop is a test of a NATS client (AIA enabled at leaf and cert) connecting to a NATS Server
// in which both ocsp_peer is enabled on NATS client connections (verify client) and the ocsp staple is enabled such
// that the NATS Server will staple its own OCSP response and make available to the NATS client during handshake.
func TestOCSPStapleFeatureInterop(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_cert.pem", ocsp.Good)
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
configure func()
}{
{
"Interop: Both Good: mTLS OCSP peer check on inbound client connection and server's OCSP staple validated at client",
`
port: -1
ocsp_cache: true
ocsp: {
mode: always
}
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration, non-default ca_timeout
ocsp_peer: {
verify: true
ca_timeout: 5
allowed_clockskew: 30
}
}
`,
[]nats.Option{
nats.Secure(&tls.Config{
VerifyConnection: func(s tls.ConnectionState) error {
if s.OCSPResponse == nil {
return fmt.Errorf("expected OCSP staple to be present")
}
resp, err := ocsp.ParseResponse(s.OCSPResponse, s.VerifiedChains[0][1])
if err != nil || resp.Status != ocsp.Good {
return fmt.Errorf("expected a valid GOOD stapled response")
}
return nil
},
}),
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Good)
},
},
{
"Interop: Bad Client: mTLS OCSP peer check on inbound client connection and server's OCSP staple validated at client",
`
port: -1
ocsp_cache: true
ocsp: {
mode: always
}
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration, non-default ca_timeout
ocsp_peer: {
verify: true
ca_timeout: 5
allowed_clockskew: 30
}
}
`,
[]nats.Option{
nats.Secure(&tls.Config{
VerifyConnection: func(s tls.ConnectionState) error {
if s.OCSPResponse == nil {
return fmt.Errorf("expected OCSP staple to be present")
}
resp, err := ocsp.ParseResponse(s.OCSPResponse, s.VerifiedChains[0][1])
if err != nil || resp.Status != ocsp.Good {
return fmt.Errorf("expected a valid GOOD stapled response")
}
return nil
},
}),
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
fmt.Errorf("remote error: tls: bad certificate"),
nil,
func() {
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Revoked)
},
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
nc.Subscribe("ping", func(m *nats.Msg) {
m.Respond([]byte("pong"))
})
nc.Flush()
_, err = nc.Request("ping", []byte("ping"), 250*time.Millisecond)
if test.rerr != nil && err == nil {
t.Errorf("Expected error getting response")
} else if test.rerr == nil && err != nil {
t.Errorf("Expected response")
}
})
}
}
// TestOCSPPeerWarnOnlyOption is test of NATS client that is OCSP Revoked status but allowed to pass with warn_only option
func TestOCSPPeerWarnOnlyOption(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Revoked)
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
configure func()
}{
{
"Revoked NATS client with warn_only explicitly set to false",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Enable OCSP peer but with warn_only option set to false
ocsp_peer: {
verify: true
warn_only: false
}
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
errors.New("remote error: tls: bad certificate"),
errors.New("expect error"),
func() {},
},
{
"Revoked NATS client with warn_only explicitly set to true",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Enable OCSP peer but with warn_only option set to true
ocsp_peer: {
verify: true
warn_only: true
}
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
})
}
}
// TestOCSPPeerUnknownIsGoodOption is test of NATS client that is OCSP status Unknown from its OCSP Responder but we treat
// status Unknown as "Good"
func TestOCSPPeerUnknownIsGoodOption(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
defer intermediateCA1Responder.Shutdown(ctx)
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
configure func()
}{
{
"Unknown NATS client with no unknown_is_good option set (default false)",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Short form configuration
ocsp_peer: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
errors.New("remote error: tls: bad certificate"),
errors.New("expect error"),
func() {},
},
{
"Unknown NATS client with unknown_is_good set to true",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
unknown_is_good: true
}
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
})
}
}
// TestOCSPPeerAllowWhenCAUnreachableOption is test of the allow_when_ca_unreachable peer option
func TestOCSPPeerAllowWhenCAUnreachableOption(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
for _, test := range []struct {
name string
config string
opts []nats.Option
cachedResponse string
err error
rerr error
}{
{
"Expired Revoked response in cache for UserA1 -- should be rejected connection (expired revoke honored)",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Turn on CA OCSP check but allow when CA is unreachable
ocsp_peer: {
verify: true
ca_timeout: 0.5
allow_when_ca_unreachable: true
}
}
# preserve revoked true
ocsp_cache: {
type: local
preserve_revoked: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
`
{
"5xL/SuHl6JN0OmxrNMpzVMTA73JVYcRfGX8+HvJinEI=": {
"subject": "CN=UserA1,O=Testnats,L=Tacoma,ST=WA,C=US",
"cached_at": "2023-05-29T17:56:45Z",
"resp_status": "revoked",
"resp_expires": "2023-05-29T17:56:49Z",
"resp": "/wYAAFMyc1R3TwBSBQBao1Qr1QzUMIIGUQoBAKCCBkowggZGBgkrBgEFBQcwAQEEggY3MIIGMzCB46FZMFcxCzAJBgNVBAYTAlVTEQ0gCAwCV0ExDzANAQ0wBwwGVGFjb21hMREwDwEROAoMCFRlc3RuYXRzMRcwFQET8HQDDA5PQ1NQIFJlc3BvbmRlchgPMjAyMzA1MjkxNzU2MDBaMHUwczBNMAkGBSsOAwIaBQAEFKgwn5fplwQy+DsulBg5SRpx0iaYBBS1kW5PZLcWhHb5tL6ZzmCVmBqOnQIUXKGv1Xy7Fu/Cx+ZT/JQa7SS7tBc2ZAAQNDVaoBE2dwD0QQE0OVowDQYJKoZIhvcNAQELBQADggEBAGAax/vkv3SBFNbxp2utc/N6Rje4E0ceC972sWgqYjzYrH0oc/acg+OAXaxUjwqoQWaT+dHaI4D5qoTkMx7XlWATjI2L72IUTf6Luo92jPzyDFwb10CdeFHtRtEYD54Qbi/nD4oxQ8cSoLKC3wft2l3E/mK/1I4Mxwq15CioK4MhfzTISoeGZbjDXPKgloJOG3rn9v64vFGV6dosbLgaXEs+MPcCsPQYkwhOOyazuewRmIDOBp5QSsKPhqsT8Rs20t8LGTMkvjZniFWJs90l9QL9F1m3obq5nyuxrGt+7Rf5zoj4T+0XCOGtE+b7cRCLg43tFuTbaAQG8Z+qkPzpza+gggQ1MIIEMTCCBC0wggMVoAMCAQICFCnhUo39pSqH6x3kHUds4YpYaXOrOj8BBDBaUSLaLwIIGjAYSS+oEUludGVybWVkaWF0ZSBDQSAxMB4XDTIzMDUwMTE5MjgzOVoXDTMzMDQyOA0PUasVAEkMMIIBIi4nAgABBQD0QAEPADCCAQoCggEBAKMMyuuA66EOHnGb07P5Zc5wwiEGPDHBBn6lqErhIaN0VJ9XzlDWwyk8Q7CdPlSU7o36DXFs316eATB5bLuXXa+7WwV3cp9V5mZF9OLCz3sOWNYUanYprOMwKA3uvcqqrh8e70Dzw6sX8tfsDeH7aJoJg5kRWEKU+A3Umm+fO+hW8Km3GBqRQXxD49uxAfGtCznXZZjmFbAXqVZu+4R6wMxndfz2dYQxeMVtUY/QGdMWT4fvWzO5et3+X6hq/URUAPOkplv9O2U4T4JPucS9yZpW/FTxWC/L7vQI/bfsrSgIZpv4eJgy27FW3Q4xusbjVvUCL/t2KLvEi/Nr2qodOCECAwEAAaOB7TCB6jAdBgNVHQ4EFgQUy15QYHqrL6k7HiSrAkKN7IFgSBMwHwYDVR0jBBgwFoBSyQNQMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQ4YBAMCB4AwFgEeACUBEBAMMAoGCIm0sAMJMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly8xMjcuMC4wLjE6MTg4ODgvaV1WKDFfY3JsLmRlcjAzEUscAQEEJzAlMCMRWwwwAYYXWkoALhICkTnw/0hlzm2RRjA3tvJ2wELj9e7pMg5GtdWdrLDyI/U1qBxhZoHADbyku7W+R1iL8dFfc4PSmdo+owsygZakvahXjv49xJNX7wV3YMmIHC4lfurIlY2mSnPlu2zEOwEDkI0S9WkTxXmHrkXLSciQJDkwzye6MR5fW+APk4JmKDPc46Go/K1A0EgxY/ugahMYsYtZu++W+IOYbEoYNxoCrcJCHX4c3Ep3t/Wulz4X6DWWhaDkMMUDC2JVE8E/3xUbw0X3adZe9Xf8T+goOz7wLCAigXKj1hvRUmOGISIGelv0KsfluZesG1a1TGLp+W9JX0M9nOaFOvjJTDP96aqIjs8oXGk="
}
}`,
errors.New("remote error: tls: bad certificate"),
errors.New("expect error"),
},
{
"Expired Good response in cache for UserA1 -- should be allowed connection (cached item irrelevant)",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Turn on CA OCSP check but allow when CA is unreachable
ocsp_peer: {
verify: true
ca_timeout: 0.5
allow_when_ca_unreachable: true
}
}
# preserve revoked true
ocsp_cache: {
type: local
preserve_revoked: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
`
{
"5xL/SuHl6JN0OmxrNMpzVMTA73JVYcRfGX8+HvJinEI=": {
"subject": "CN=UserA1,O=Testnats,L=Tacoma,ST=WA,C=US",
"cached_at": "2023-06-05T16:33:52Z",
"resp_status": "good",
"resp_expires": "2023-06-05T16:33:55Z",
"resp": "/wYAAFMyc1R3TwBYBQBgpzMn1wzUMIIGUwoBAKCCBkwwggZIBgkrBgEFBQcwAQEEggY5MIIGNTCB5aFZMFcxCzAJBgNVBAYTAlVTEQ0gCAwCV0ExDzANAQ0wBwwGVGFjb21hMREwDwEROAoMCFRlc3RuYXRzMRcwFQET8HYDDA5PQ1NQIFJlc3BvbmRlchgPMjAyMzA2MDUxNjMzMDBaMHcwdTBNMAkGBSsOAwIaBQAEFKgwn5fplwQy+DsulBg5SRpx0iaYBBS1kW5PZLcWhHb5tL6ZzmCVmBqOnQIUXKGv1Xy7Fu/Cx+ZT/JQa7SS7tBeAADZmABA1MVqgEToTAPRAATVaMA0GCSqGSIb3DQEBCwUAA4IBAQB9csJxA2VcpQYmC5lzI0dJJnEC1zcaP1oFbBm5VsZAbWh4mIGTqyEjMkucBqANTypnhWFRYcZE5nJE8tN8Aen0EBYkhk1wfEIhincaF3zs6x5RzigxQOqTPAanO55keKH5exYnfBcyCwAQiBbQaTXLJJdjerfqiRjqgsnLW8yl2IC8+kxTCfR4GHTK34mvqVWYYhP/xbaxTLJTp35t1vf1o78ct9R+z8W5sCSO4TsMNnExZlu4Ejeon/KivMR22j7nelTEaDCuaOl03WxKh9yhw2ix8V7lvR74wh5f4fjLZria6Y0+lUjwvvrZyBRwA62W/ihe3778q8KhLECFPQSaoIIENTCCBDEwggQtMIIDFaADAgECAhQp4VKN/aUqh+sd5B1HbOGKWGlzqzo/AQQwWlEk2jECCBowGEkxqBFJbnRlcm1lZGlhdGUgQ0EgMTAeFw0yMzA1MDExOTI4MzlaFw0zMzA0MjgND1GtFQBJDDCCASIuJwIAAQUA9EABDwAwggEKAoIBAQCjDMrrgOuhDh5xm9Oz+WXOcMIhBjwxwQZ+pahK4SGjdFSfV85Q1sMpPEOwnT5UlO6N+g1xbN9engEweWy7l12vu1sFd3KfVeZmRfTiws97DljWFGp2KazjMCgN7r3Kqq4fHu9A88OrF/LX7A3h+2iaCYOZEVhClPgN1JpvnzvoVvCptxgakUF8Q+PbsQHxrQs512WY5hWwF6lWbvuEesDMZ3X89nWEMXjFbVGP0BnTFk+H71szuXrd/l+oav1EVADzpKZb/TtlOE+CT7nEvcmaVvxU8Vgvy+70CP237K0oCGab+HiYMtuxVt0OMbrG41b1Ai/7dii7xIvza9qqHTghAgMBAAGjge0wgeowHQYDVR0OBBYEFMteUGB6qy+pOx4kqwJCjeyBYEgTMB8GA1UdIwQYMBaAUssDUDAMBgNVHRMBAf8EAjAAMA4GA1UdDwEOGAQDAgeAMBYBHgAlARAQDDAKBgiJtrADCTA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vMTI3LjAuMC4xOjE4ODg4L2ldVigxX2NybC5kZXIwMxFLHAEBBCcwJTAjEVsMMAGGF1pKAC4SAgALBQD0AQEBAEhlzm2RRjA3tvJ2wELj9e7pMg5GtdWdrLDyI/U1qBxhZoHADbyku7W+R1iL8dFfc4PSmdo+owsygZakvahXjv49xJNX7wV3YMmIHC4lfurIlY2mSnPlu2zEOwEDkI0S9WkTxXmHrkXLSciQJDkwzye6MR5fW+APk4JmKDPc46Go/K1A0EgxY/ugahMYsYtZu++W+IOYbEoYNxoCrcJCHX4c3Ep3t/Wulz4X6DWWhaDkMMUDC2JVE8E/3xUbw0X3adZe9Xf8T+goOz7wLCAigXKj1hvRUmOGISIGelv0KsfluZesG1a1TGLp+W9JX0M9nOaFOvjJTDP96aqIjs8oXGk="
}
}`,
nil,
nil,
},
{
"Expired Unknown response in cache for UserA1 -- should be allowed connection (cached item irrelevant)",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Turn on CA OCSP check but allow when CA is unreachable
ocsp_peer: {
verify: true
ca_timeout: 0.5
allow_when_ca_unreachable: true
}
}
# preserve revoked true
ocsp_cache: {
type: local
preserve_revoked: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
`
{
"5xL/SuHl6JN0OmxrNMpzVMTA73JVYcRfGX8+HvJinEI=": {
"subject": "CN=UserA1,O=Testnats,L=Tacoma,ST=WA,C=US",
"cached_at": "2023-06-05T16:45:01Z",
"resp_status": "unknown",
"resp_expires": "2023-06-05T16:45:05Z",
"resp": "/wYAAFMyc1R3TwBSBQBH1aW01wzUMIIGUwoBAKCCBkwwggZIBgkrBgEFBQcwAQEEggY5MIIGNTCB5aFZMFcxCzAJBgNVBAYTAlVTEQ0gCAwCV0ExDzANAQ0wBwwGVGFjb21hMREwDwEROAoMCFRlc3RuYXRzMRcwFQET8HYDDA5PQ1NQIFJlc3BvbmRlchgPMjAyMzA2MDUxNjQ1MDBaMHcwdTBNMAkGBSsOAwIaBQAEFKgwn5fplwQy+DsulBg5SRpx0iaYBBS1kW5PZLcWhHb5tL6ZzmCVmBqOnQIUXKGv1Xy7Fu/Cx+ZT/JQa7SS7tBeCADpmAAwxWqAROhMA9EABNVowDQYJKoZIhvcNAQELBQADggEBAAzwED88ngiFj3hLzIejZ8DE/ppticX0vgAjUM8oXjDjwNXpSQ5xdXHsDk4RAYAVpNyiAfL2fapwz91g3JuZot6npp8smtZC5D0YMKK8iMjrx2aMqVyv+ai/33WG8PRWpBNzSTYaLhlBFjhUrx8HDu97ozNbmfgDWzRS1LqkJRa5YXvkyppqYTFSX73DV9R9tOVOwZ0x5WEKst9IJ+88mXsOBGyuye2Gh9RK6KsLgaOwiD9FBf18WRKIixeVM1Y/xHc/iwFPi8k3Z6hZ6gHX8NboQ/djCyzYVSWsUedTo/62uuagHPRWoYci4HQl4bSFXfcEO/EkWGnqkWBHfYZ4soigggQ1MIIEMTCCBC0wggMVoAMCAQICFCnhUo39pSqH6x3kHUds4YpYaXOrOj8BBDBaUSTaMQIIGjAYSTGoEUludGVybWVkaWF0ZSBDQSAxMB4XDTIzMDUwMTE5MjgzOVoXDTMzMDQyOA0PUa0VAEkMMIIBIi4nAgABBQD0QAEPADCCAQoCggEBAKMMyuuA66EOHnGb07P5Zc5wwiEGPDHBBn6lqErhIaN0VJ9XzlDWwyk8Q7CdPlSU7o36DXFs316eATB5bLuXXa+7WwV3cp9V5mZF9OLCz3sOWNYUanYprOMwKA3uvcqqrh8e70Dzw6sX8tfsDeH7aJoJg5kRWEKU+A3Umm+fO+hW8Km3GBqRQXxD49uxAfGtCznXZZjmFbAXqVZu+4R6wMxndfz2dYQxeMVtUY/QGdMWT4fvWzO5et3+X6hq/URUAPOkplv9O2U4T4JPucS9yZpW/FTxWC/L7vQI/bfsrSgIZpv4eJgy27FW3Q4xusbjVvUCL/t2KLvEi/Nr2qodOCECAwEAAaOB7TCB6jAdBgNVHQ4EFgQUy15QYHqrL6k7HiSrAkKN7IFgSBMwHwYDVR0jBBgwFoBSywNQMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQ4YBAMCB4AwFgEeACUBEBAMMAoGCIm2sAMJMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly8xMjcuMC4wLjE6MTg4ODgvaV1WKDFfY3JsLmRlcjAzEUscAQEEJzAlMCMRWwwwAYYXWkoALhICkTnw/0hlzm2RRjA3tvJ2wELj9e7pMg5GtdWdrLDyI/U1qBxhZoHADbyku7W+R1iL8dFfc4PSmdo+owsygZakvahXjv49xJNX7wV3YMmIHC4lfurIlY2mSnPlu2zEOwEDkI0S9WkTxXmHrkXLSciQJDkwzye6MR5fW+APk4JmKDPc46Go/K1A0EgxY/ugahMYsYtZu++W+IOYbEoYNxoCrcJCHX4c3Ep3t/Wulz4X6DWWhaDkMMUDC2JVE8E/3xUbw0X3adZe9Xf8T+goOz7wLCAigXKj1hvRUmOGISIGelv0KsfluZesG1a1TGLp+W9JX0M9nOaFOvjJTDP96aqIjs8oXGk="
}
}`,
nil,
nil,
},
{
"No response in cache for UserA1 -- should be allowed connection",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Turn on CA OCSP check but allow when CA is unreachable
ocsp_peer: {
verify: true
ca_timeout: 0.5
allow_when_ca_unreachable: true
}
}
# preserve revoked true
ocsp_cache: {
type: local
preserve_revoked: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
"",
nil,
nil,
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
c := []byte(test.cachedResponse)
err := writeCacheFile("", c)
if err != nil {
t.Fatal(err)
}
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
})
}
}
// TestOCSPResponseCacheLocalStoreOption is test of default and non-default local_store option
func TestOCSPResponseCacheLocalStoreOpt(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
for _, test := range []struct {
name string
config string
opts []nats.Option
cachedResponse string
err error
rerr error
storeLocation string
}{
{
"Test load from non-default local store _custom_; connect will reject only if cache file found and loaded",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Turn on CA OCSP check but allow when CA is unreachable
ocsp_peer: {
verify: true
ca_timeout: 0.5
allow_when_ca_unreachable: true
}
}
# preserve revoked true
ocsp_cache: {
type: local
local_store: "_custom_"
preserve_revoked: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
`
{
"5xL/SuHl6JN0OmxrNMpzVMTA73JVYcRfGX8+HvJinEI=": {
"subject": "CN=UserA1,O=Testnats,L=Tacoma,ST=WA,C=US",
"cached_at": "2023-05-29T17:56:45Z",
"resp_status": "revoked",
"resp_expires": "2023-05-29T17:56:49Z",
"resp": "/wYAAFMyc1R3TwBSBQBao1Qr1QzUMIIGUQoBAKCCBkowggZGBgkrBgEFBQcwAQEEggY3MIIGMzCB46FZMFcxCzAJBgNVBAYTAlVTEQ0gCAwCV0ExDzANAQ0wBwwGVGFjb21hMREwDwEROAoMCFRlc3RuYXRzMRcwFQET8HQDDA5PQ1NQIFJlc3BvbmRlchgPMjAyMzA1MjkxNzU2MDBaMHUwczBNMAkGBSsOAwIaBQAEFKgwn5fplwQy+DsulBg5SRpx0iaYBBS1kW5PZLcWhHb5tL6ZzmCVmBqOnQIUXKGv1Xy7Fu/Cx+ZT/JQa7SS7tBc2ZAAQNDVaoBE2dwD0QQE0OVowDQYJKoZIhvcNAQELBQADggEBAGAax/vkv3SBFNbxp2utc/N6Rje4E0ceC972sWgqYjzYrH0oc/acg+OAXaxUjwqoQWaT+dHaI4D5qoTkMx7XlWATjI2L72IUTf6Luo92jPzyDFwb10CdeFHtRtEYD54Qbi/nD4oxQ8cSoLKC3wft2l3E/mK/1I4Mxwq15CioK4MhfzTISoeGZbjDXPKgloJOG3rn9v64vFGV6dosbLgaXEs+MPcCsPQYkwhOOyazuewRmIDOBp5QSsKPhqsT8Rs20t8LGTMkvjZniFWJs90l9QL9F1m3obq5nyuxrGt+7Rf5zoj4T+0XCOGtE+b7cRCLg43tFuTbaAQG8Z+qkPzpza+gggQ1MIIEMTCCBC0wggMVoAMCAQICFCnhUo39pSqH6x3kHUds4YpYaXOrOj8BBDBaUSLaLwIIGjAYSS+oEUludGVybWVkaWF0ZSBDQSAxMB4XDTIzMDUwMTE5MjgzOVoXDTMzMDQyOA0PUasVAEkMMIIBIi4nAgABBQD0QAEPADCCAQoCggEBAKMMyuuA66EOHnGb07P5Zc5wwiEGPDHBBn6lqErhIaN0VJ9XzlDWwyk8Q7CdPlSU7o36DXFs316eATB5bLuXXa+7WwV3cp9V5mZF9OLCz3sOWNYUanYprOMwKA3uvcqqrh8e70Dzw6sX8tfsDeH7aJoJg5kRWEKU+A3Umm+fO+hW8Km3GBqRQXxD49uxAfGtCznXZZjmFbAXqVZu+4R6wMxndfz2dYQxeMVtUY/QGdMWT4fvWzO5et3+X6hq/URUAPOkplv9O2U4T4JPucS9yZpW/FTxWC/L7vQI/bfsrSgIZpv4eJgy27FW3Q4xusbjVvUCL/t2KLvEi/Nr2qodOCECAwEAAaOB7TCB6jAdBgNVHQ4EFgQUy15QYHqrL6k7HiSrAkKN7IFgSBMwHwYDVR0jBBgwFoBSyQNQMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQ4YBAMCB4AwFgEeACUBEBAMMAoGCIm0sAMJMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly8xMjcuMC4wLjE6MTg4ODgvaV1WKDFfY3JsLmRlcjAzEUscAQEEJzAlMCMRWwwwAYYXWkoALhICkTnw/0hlzm2RRjA3tvJ2wELj9e7pMg5GtdWdrLDyI/U1qBxhZoHADbyku7W+R1iL8dFfc4PSmdo+owsygZakvahXjv49xJNX7wV3YMmIHC4lfurIlY2mSnPlu2zEOwEDkI0S9WkTxXmHrkXLSciQJDkwzye6MR5fW+APk4JmKDPc46Go/K1A0EgxY/ugahMYsYtZu++W+IOYbEoYNxoCrcJCHX4c3Ep3t/Wulz4X6DWWhaDkMMUDC2JVE8E/3xUbw0X3adZe9Xf8T+goOz7wLCAigXKj1hvRUmOGISIGelv0KsfluZesG1a1TGLp+W9JX0M9nOaFOvjJTDP96aqIjs8oXGk="
}
}`,
errors.New("remote error: tls: bad certificate"),
errors.New("expect error"),
"_custom_",
},
{
"Test load from default local store when \"\" set; connect will reject only if cache file found and loaded",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Turn on CA OCSP check but allow when CA is unreachable
ocsp_peer: {
verify: true
ca_timeout: 0.5
allow_when_ca_unreachable: true
}
}
# preserve revoked true
ocsp_cache: {
type: local
local_store: ""
preserve_revoked: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
`
{
"5xL/SuHl6JN0OmxrNMpzVMTA73JVYcRfGX8+HvJinEI=": {
"subject": "CN=UserA1,O=Testnats,L=Tacoma,ST=WA,C=US",
"cached_at": "2023-05-29T17:56:45Z",
"resp_status": "revoked",
"resp_expires": "2023-05-29T17:56:49Z",
"resp": "/wYAAFMyc1R3TwBSBQBao1Qr1QzUMIIGUQoBAKCCBkowggZGBgkrBgEFBQcwAQEEggY3MIIGMzCB46FZMFcxCzAJBgNVBAYTAlVTEQ0gCAwCV0ExDzANAQ0wBwwGVGFjb21hMREwDwEROAoMCFRlc3RuYXRzMRcwFQET8HQDDA5PQ1NQIFJlc3BvbmRlchgPMjAyMzA1MjkxNzU2MDBaMHUwczBNMAkGBSsOAwIaBQAEFKgwn5fplwQy+DsulBg5SRpx0iaYBBS1kW5PZLcWhHb5tL6ZzmCVmBqOnQIUXKGv1Xy7Fu/Cx+ZT/JQa7SS7tBc2ZAAQNDVaoBE2dwD0QQE0OVowDQYJKoZIhvcNAQELBQADggEBAGAax/vkv3SBFNbxp2utc/N6Rje4E0ceC972sWgqYjzYrH0oc/acg+OAXaxUjwqoQWaT+dHaI4D5qoTkMx7XlWATjI2L72IUTf6Luo92jPzyDFwb10CdeFHtRtEYD54Qbi/nD4oxQ8cSoLKC3wft2l3E/mK/1I4Mxwq15CioK4MhfzTISoeGZbjDXPKgloJOG3rn9v64vFGV6dosbLgaXEs+MPcCsPQYkwhOOyazuewRmIDOBp5QSsKPhqsT8Rs20t8LGTMkvjZniFWJs90l9QL9F1m3obq5nyuxrGt+7Rf5zoj4T+0XCOGtE+b7cRCLg43tFuTbaAQG8Z+qkPzpza+gggQ1MIIEMTCCBC0wggMVoAMCAQICFCnhUo39pSqH6x3kHUds4YpYaXOrOj8BBDBaUSLaLwIIGjAYSS+oEUludGVybWVkaWF0ZSBDQSAxMB4XDTIzMDUwMTE5MjgzOVoXDTMzMDQyOA0PUasVAEkMMIIBIi4nAgABBQD0QAEPADCCAQoCggEBAKMMyuuA66EOHnGb07P5Zc5wwiEGPDHBBn6lqErhIaN0VJ9XzlDWwyk8Q7CdPlSU7o36DXFs316eATB5bLuXXa+7WwV3cp9V5mZF9OLCz3sOWNYUanYprOMwKA3uvcqqrh8e70Dzw6sX8tfsDeH7aJoJg5kRWEKU+A3Umm+fO+hW8Km3GBqRQXxD49uxAfGtCznXZZjmFbAXqVZu+4R6wMxndfz2dYQxeMVtUY/QGdMWT4fvWzO5et3+X6hq/URUAPOkplv9O2U4T4JPucS9yZpW/FTxWC/L7vQI/bfsrSgIZpv4eJgy27FW3Q4xusbjVvUCL/t2KLvEi/Nr2qodOCECAwEAAaOB7TCB6jAdBgNVHQ4EFgQUy15QYHqrL6k7HiSrAkKN7IFgSBMwHwYDVR0jBBgwFoBSyQNQMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQ4YBAMCB4AwFgEeACUBEBAMMAoGCIm0sAMJMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly8xMjcuMC4wLjE6MTg4ODgvaV1WKDFfY3JsLmRlcjAzEUscAQEEJzAlMCMRWwwwAYYXWkoALhICkTnw/0hlzm2RRjA3tvJ2wELj9e7pMg5GtdWdrLDyI/U1qBxhZoHADbyku7W+R1iL8dFfc4PSmdo+owsygZakvahXjv49xJNX7wV3YMmIHC4lfurIlY2mSnPlu2zEOwEDkI0S9WkTxXmHrkXLSciQJDkwzye6MR5fW+APk4JmKDPc46Go/K1A0EgxY/ugahMYsYtZu++W+IOYbEoYNxoCrcJCHX4c3Ep3t/Wulz4X6DWWhaDkMMUDC2JVE8E/3xUbw0X3adZe9Xf8T+goOz7wLCAigXKj1hvRUmOGISIGelv0KsfluZesG1a1TGLp+W9JX0M9nOaFOvjJTDP96aqIjs8oXGk="
}
}`,
errors.New("remote error: tls: bad certificate"),
errors.New("expect error"),
"_rc_",
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, test.storeLocation)
c := []byte(test.cachedResponse)
err := writeCacheFile(test.storeLocation, c)
if err != nil {
t.Fatal(err)
}
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
})
}
}
// TestOCSPPeerIncrementalSaveLocalCache is test of timer-based response cache save as new entries added
func TestOCSPPeerIncrementalSaveLocalCache(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate2/intermediate2_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Good)
intermediateCA2Responder := newOCSPResponderIntermediateCA2(t)
intermediateCA2ResponderURL := fmt.Sprintf("http://%s", intermediateCA2Responder.Addr)
defer intermediateCA2Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA2ResponderURL, "configs/certs/ocsp_peer/mini-ca/client2/UserB1_cert.pem", ocsp.Good)
var fi os.FileInfo
var err error
for _, test := range []struct {
name string
config string
opts [][]nats.Option
err error
rerr error
configure func()
}{
{
"Default cache, short form: mTLS OCSP peer check on inbound client connection, UserA1 client of intermediate CA 1",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
ca_timeout: 5
allowed_clockskew: 30
}
}
# Local cache with custom save_interval for testability
ocsp_cache: {
type: local
# Save if dirty ever 1 second
save_interval: 1
}
`,
[][]nats.Option{
{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client2/UserB1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client2/private/UserB1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
},
nil,
nil,
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
// Cleanup any previous test that saved a local cache
deleteLocalStore(t, "")
fi, err = statCacheFile("")
if err != nil && fi != nil && fi.Size() != 0 {
t.Fatalf("Expected no local cache file, got a FileInfo with size %d", fi.Size())
}
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
// Connect with UserA1 client and get a CA Response
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts[0]...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
nc.Close()
time.Sleep(2 * time.Second)
fi, err = statCacheFile("")
if err == nil && fi != nil && fi.Size() > 0 {
// good
} else {
if err != nil {
t.Fatalf("Expected an extant local cache file, got error: %v", err)
}
if fi != nil {
t.Fatalf("Expected non-zero size local cache file, got a FileInfo with size %d", fi.Size())
}
}
firstFi := fi
// Connect with UserB1 client and get another CA Response
nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts[1]...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
nc.Close()
time.Sleep(2 * time.Second)
fi, err = statCacheFile("")
if err == nil && fi != nil && fi.Size() > firstFi.Size() {
// good
} else {
if err != nil {
t.Fatalf("Expected an extant local cache file, got error: %v", err)
}
if fi != nil {
t.Fatalf("Expected non-zero size local cache file with more bytes, got a FileInfo with size %d", fi.Size())
}
}
})
}
}
func statCacheFile(dir string) (os.FileInfo, error) {
if dir == "" {
dir = "_rc_"
}
return os.Stat(filepath.Join(dir, "cache.json"))
}
// TestOCSPPeerUndelegatedCAResponseSigner
func TestOCSPPeerUndelegatedCAResponseSigner(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1Undelegated(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Good)
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
configure func()
}{
{
"mTLS OCSP peer check on inbound client connection, responder is CA (undelegated)",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Turn on CA OCSP check so unvalidated clients can't connect
ocsp_peer: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
})
}
}
// TestOCSPPeerDelegatedCAResponseSigner
func TestOCSPPeerDelegatedCAResponseSigner(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Good)
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
configure func()
}{
{
"mTLS OCSP peer check on inbound client connection, responder is CA (undelegated)",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Turn on CA OCSP check so unvalidated clients can't connect
ocsp_peer: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
})
}
}
// TestOCSPPeerBadDelegatedCAResponseSigner
func TestOCSPPeerBadDelegatedCAResponseSigner(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
intermediateCA1Responder := newOCSPResponderBadDelegateIntermediateCA1(t)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Good)
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
configure func()
}{
{
"mTLS OCSP peer check on inbound client connection, responder is not a legal delegate",
`
port: -1
# Cache configuration is default
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Turn on CA OCSP check so unvalidated clients can't connect
ocsp_peer: true
}
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
errors.New("remote error: tls: bad certificate"),
errors.New("expect error"),
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
deleteLocalStore(t, "")
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
})
}
}
// TestOCSPPeerNextUpdateUnset is test of scenario when responder does not set NextUpdate and cache TTL option is used
func TestOCSPPeerNextUpdateUnset(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rootCAResponder := newOCSPResponderRootCA(t)
rootCAResponderURL := fmt.Sprintf("http://%s", rootCAResponder.Addr)
defer rootCAResponder.Shutdown(ctx)
setOCSPStatus(t, rootCAResponderURL, "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem", ocsp.Good)
respCertPEM := "configs/certs/ocsp_peer/mini-ca/ocsp1/ocsp1_bundle.pem"
respKeyPEM := "configs/certs/ocsp_peer/mini-ca/ocsp1/private/ocsp1_keypair.pem"
issuerCertPEM := "configs/certs/ocsp_peer/mini-ca/intermediate1/intermediate1_cert.pem"
intermediateCA1Responder := newOCSPResponderBase(t, issuerCertPEM, respCertPEM, respKeyPEM, true, "127.0.0.1:18888", 0)
intermediateCA1ResponderURL := fmt.Sprintf("http://%s", intermediateCA1Responder.Addr)
defer intermediateCA1Responder.Shutdown(ctx)
setOCSPStatus(t, intermediateCA1ResponderURL, "configs/certs/ocsp_peer/mini-ca/client1/UserA1_cert.pem", ocsp.Good)
for _, test := range []struct {
name string
config string
opts []nats.Option
err error
rerr error
expectedMisses int64
configure func()
}{
{
"TTL set to 4 seconds with second client connection leveraging cache from first client connect",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
ca_timeout: 5
allowed_clockskew: 0
cache_ttl_when_next_update_unset: 4
}
}
# Short form configuration, local as default
ocsp_cache: true
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
2,
func() {},
},
{
"TTL set to 1 seconds with second client connection not leveraging cache items from first client connect",
`
port: -1
http_port: 8222
tls: {
cert_file: "configs/certs/ocsp_peer/mini-ca/server1/TestServer1_bundle.pem"
key_file: "configs/certs/ocsp_peer/mini-ca/server1/private/TestServer1_keypair.pem"
ca_file: "configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"
timeout: 5
verify: true
# Long form configuration
ocsp_peer: {
verify: true
ca_timeout: 5
allowed_clockskew: 0
cache_ttl_when_next_update_unset: 1
}
}
# Short form configuration, local as default
ocsp_cache: true
`,
[]nats.Option{
nats.ClientCert("./configs/certs/ocsp_peer/mini-ca/client1/UserA1_bundle.pem", "./configs/certs/ocsp_peer/mini-ca/client1/private/UserA1_keypair.pem"),
nats.RootCAs("./configs/certs/ocsp_peer/mini-ca/root/root_cert.pem"),
nats.ErrorHandler(noOpErrHandler),
},
nil,
nil,
3,
func() {},
},
} {
t.Run(test.name, func(t *testing.T) {
// Cleanup any previous test that saved a local cache
deleteLocalStore(t, "")
test.configure()
content := test.config
conf := createConfFile(t, []byte(content))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
nc.Close()
// Wait interval shorter than first test, and longer than second test
time.Sleep(2 * time.Second)
nc, err = nats.Connect(fmt.Sprintf("tls://localhost:%d", opts.Port), test.opts...)
if test.err == nil && err != nil {
t.Errorf("Expected to connect, got %v", err)
} else if test.err != nil && err == nil {
t.Errorf("Expected error on connect")
} else if test.err != nil && err != nil {
// Error on connect was expected
if test.err.Error() != err.Error() {
t.Errorf("Expected error %s, got: %s", test.err, err)
}
return
}
defer nc.Close()
v := monitorGetVarzHelper(t, 8222)
if v.OCSPResponseCache.Misses != test.expectedMisses || v.OCSPResponseCache.Responses != 2 {
t.Errorf("Expected cache misses to be %d and cache items to be 2, got %d and %d", test.expectedMisses, v.OCSPResponseCache.Misses, v.OCSPResponseCache.Responses)
}
})
}
}