OCSP Peer Verification (#4258)

New security feature [ADR-38: OCSP Peer
Verification](https://github.com/nats-io/nats-architecture-and-design/pull/226/files#diff-575a9545de9d498a48d2889972b0cb57dbadebde3b4328b65ab02bb43f557935)
providing fine-grain certificate status check via OCSP verification; for
inbound NATS, MQTT, WebSocket, and Leaf client connections (mTLS) as
well as outbound Leaf connections to another NATS System.
This commit is contained in:
Todd Beets
2023-08-01 09:17:27 -07:00
committed by GitHub
52 changed files with 8463 additions and 137 deletions

View File

@@ -17,6 +17,7 @@ import (
"bytes"
"compress/gzip"
"crypto/sha256"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
@@ -30,7 +31,9 @@ import (
"time"
"github.com/klauspost/compress/s2"
"github.com/nats-io/jwt/v2"
"github.com/nats-io/nats-server/v2/server/certidp"
"github.com/nats-io/nats-server/v2/server/pse"
)
@@ -84,6 +87,9 @@ const (
accReqTokens = 5
accReqAccIndex = 3
ocspPeerRejectEventSubj = "$SYS.SERVER.%s.OCSP.PEER.CONN.REJECT"
ocspPeerChainlinkInvalidEventSubj = "$SYS.SERVER.%s.OCSP.PEER.LINK.INVALID"
)
// FIXME(dlc) - make configurable.
@@ -157,6 +163,34 @@ type DisconnectEventMsg struct {
// DisconnectEventMsgType is the schema type for DisconnectEventMsg
const DisconnectEventMsgType = "io.nats.server.advisory.v1.client_disconnect"
// OCSPPeerRejectEventMsg is sent when a peer TLS handshake is ultimately rejected due to OCSP invalidation.
// A "peer" can be an inbound client connection or a leaf connection to a remote server. Peer in event payload
// is always the peer's (TLS) leaf cert, which may or may be the invalid cert (See also OCSPPeerChainlinkInvalidEventMsg)
type OCSPPeerRejectEventMsg struct {
TypedEvent
Kind string `json:"kind"`
Peer certidp.CertInfo `json:"peer"`
Server ServerInfo `json:"server"`
Reason string `json:"reason"`
}
// OCSPPeerRejectEventMsgType is the schema type for OCSPPeerRejectEventMsg
const OCSPPeerRejectEventMsgType = "io.nats.server.advisory.v1.ocsp_peer_reject"
// OCSPPeerChainlinkInvalidEventMsg is sent when a certificate (link) in a valid TLS chain is found to be OCSP invalid
// during a peer TLS handshake. A "peer" can be an inbound client connection or a leaf connection to a remote server.
// Peer and Link may be the same if the invalid cert was the peer's leaf cert
type OCSPPeerChainlinkInvalidEventMsg struct {
TypedEvent
Link certidp.CertInfo `json:"link"`
Peer certidp.CertInfo `json:"peer"`
Server ServerInfo `json:"server"`
Reason string `json:"reason"`
}
// OCSPPeerChainlinkInvalidEventMsgType is the schema type for OCSPPeerChainlinkInvalidEventMsg
const OCSPPeerChainlinkInvalidEventMsgType = "io.nats.server.advisory.v1.ocsp_peer_link_invalid"
// AccountNumConns is an event that will be sent from a server that is tracking
// a given account when the number of connections changes. It will also HB
// updates in the absence of any changes.
@@ -2719,3 +2753,74 @@ func (s *Server) wrapChk(f func()) func() {
s.mu.Unlock()
}
}
// sendOCSPPeerRejectEvent sends a system level event to system account when a peer connection is
// rejected due to OCSP invalid status of its trust chain(s).
func (s *Server) sendOCSPPeerRejectEvent(kind string, peer *x509.Certificate, reason string) {
s.mu.Lock()
defer s.mu.Unlock()
if !s.eventsEnabled() {
return
}
if peer == nil {
s.Errorf(certidp.ErrPeerEmptyNoEvent)
return
}
eid := s.nextEventID()
now := time.Now().UTC()
m := OCSPPeerRejectEventMsg{
TypedEvent: TypedEvent{
Type: OCSPPeerRejectEventMsgType,
ID: eid,
Time: now,
},
Kind: kind,
Peer: certidp.CertInfo{
Subject: certidp.GetSubjectDNForm(peer),
Issuer: certidp.GetIssuerDNForm(peer),
Fingerprint: certidp.GenerateFingerprint(peer),
Raw: peer.Raw,
},
Reason: reason,
}
subj := fmt.Sprintf(ocspPeerRejectEventSubj, s.info.ID)
s.sendInternalMsg(subj, _EMPTY_, &m.Server, &m)
}
// sendOCSPPeerChainlinkInvalidEvent sends a system level event to system account when a link in a peer's trust chain
// is OCSP invalid.
func (s *Server) sendOCSPPeerChainlinkInvalidEvent(peer *x509.Certificate, link *x509.Certificate, reason string) {
s.mu.Lock()
defer s.mu.Unlock()
if !s.eventsEnabled() {
return
}
if peer == nil || link == nil {
s.Errorf(certidp.ErrPeerEmptyNoEvent)
return
}
eid := s.nextEventID()
now := time.Now().UTC()
m := OCSPPeerChainlinkInvalidEventMsg{
TypedEvent: TypedEvent{
Type: OCSPPeerChainlinkInvalidEventMsgType,
ID: eid,
Time: now,
},
Link: certidp.CertInfo{
Subject: certidp.GetSubjectDNForm(link),
Issuer: certidp.GetIssuerDNForm(link),
Fingerprint: certidp.GenerateFingerprint(link),
Raw: link.Raw,
},
Peer: certidp.CertInfo{
Subject: certidp.GetSubjectDNForm(peer),
Issuer: certidp.GetIssuerDNForm(peer),
Fingerprint: certidp.GenerateFingerprint(peer),
Raw: peer.Raw,
},
Reason: reason,
}
subj := fmt.Sprintf(ocspPeerChainlinkInvalidEventSubj, s.info.ID)
s.sendInternalMsg(subj, _EMPTY_, &m.Server, &m)
}