get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/server/certidp/certidp.go (about)

     1  // Copyright 2023 The NATS Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package certidp
    15  
    16  import (
    17  	"crypto/sha256"
    18  	"crypto/x509"
    19  	"encoding/base64"
    20  	"encoding/json"
    21  	"fmt"
    22  	"net/url"
    23  	"strings"
    24  	"time"
    25  
    26  	"golang.org/x/crypto/ocsp"
    27  )
    28  
    29  const (
    30  	DefaultAllowedClockSkew     = 30 * time.Second
    31  	DefaultOCSPResponderTimeout = 2 * time.Second
    32  	DefaultTTLUnsetNextUpdate   = 1 * time.Hour
    33  )
    34  
    35  type StatusAssertion int
    36  
    37  var (
    38  	StatusAssertionStrToVal = map[string]StatusAssertion{
    39  		"good":    ocsp.Good,
    40  		"revoked": ocsp.Revoked,
    41  		"unknown": ocsp.Unknown,
    42  	}
    43  	StatusAssertionValToStr = map[StatusAssertion]string{
    44  		ocsp.Good:    "good",
    45  		ocsp.Revoked: "revoked",
    46  		ocsp.Unknown: "unknown",
    47  	}
    48  	StatusAssertionIntToVal = map[int]StatusAssertion{
    49  		0: ocsp.Good,
    50  		1: ocsp.Revoked,
    51  		2: ocsp.Unknown,
    52  	}
    53  )
    54  
    55  // GetStatusAssertionStr returns the corresponding string representation of the StatusAssertion.
    56  func GetStatusAssertionStr(sa int) string {
    57  	// If the provided status assertion value is not found in the map (StatusAssertionIntToVal),
    58  	// the function defaults to "unknown" to avoid defaulting to "good," which is the default iota value
    59  	// for the ocsp.StatusAssertion enumeration (https://pkg.go.dev/golang.org/x/crypto/ocsp#pkg-constants).
    60  	// This ensures that we don't unintentionally default to "good" when there's no map entry.
    61  	v, ok := StatusAssertionIntToVal[sa]
    62  	if !ok {
    63  		// set unknown as fallback
    64  		v = ocsp.Unknown
    65  	}
    66  
    67  	return StatusAssertionValToStr[v]
    68  }
    69  
    70  func (sa StatusAssertion) MarshalJSON() ([]byte, error) {
    71  	// This ensures that we don't unintentionally default to "good" when there's no map entry.
    72  	// (see more details in the GetStatusAssertionStr() comment)
    73  	str, ok := StatusAssertionValToStr[sa]
    74  	if !ok {
    75  		// set unknown as fallback
    76  		str = StatusAssertionValToStr[ocsp.Unknown]
    77  	}
    78  	return json.Marshal(str)
    79  }
    80  
    81  func (sa *StatusAssertion) UnmarshalJSON(in []byte) error {
    82  	// This ensures that we don't unintentionally default to "good" when there's no map entry.
    83  	// (see more details in the GetStatusAssertionStr() comment)
    84  	v, ok := StatusAssertionStrToVal[strings.ReplaceAll(string(in), "\"", "")]
    85  	if !ok {
    86  		// set unknown as fallback
    87  		v = StatusAssertionStrToVal["unknown"]
    88  	}
    89  	*sa = v
    90  	return nil
    91  }
    92  
    93  type ChainLink struct {
    94  	Leaf             *x509.Certificate
    95  	Issuer           *x509.Certificate
    96  	OCSPWebEndpoints *[]*url.URL
    97  }
    98  
    99  // OCSPPeerConfig holds the parsed OCSP peer configuration section of TLS configuration
   100  type OCSPPeerConfig struct {
   101  	Verify                 bool
   102  	Timeout                float64
   103  	ClockSkew              float64
   104  	WarnOnly               bool
   105  	UnknownIsGood          bool
   106  	AllowWhenCAUnreachable bool
   107  	TTLUnsetNextUpdate     float64
   108  }
   109  
   110  func NewOCSPPeerConfig() *OCSPPeerConfig {
   111  	return &OCSPPeerConfig{
   112  		Verify:                 false,
   113  		Timeout:                DefaultOCSPResponderTimeout.Seconds(),
   114  		ClockSkew:              DefaultAllowedClockSkew.Seconds(),
   115  		WarnOnly:               false,
   116  		UnknownIsGood:          false,
   117  		AllowWhenCAUnreachable: false,
   118  		TTLUnsetNextUpdate:     DefaultTTLUnsetNextUpdate.Seconds(),
   119  	}
   120  }
   121  
   122  // Log is a neutral method of passing server loggers to plugins
   123  type Log struct {
   124  	Debugf  func(format string, v ...interface{})
   125  	Noticef func(format string, v ...interface{})
   126  	Warnf   func(format string, v ...interface{})
   127  	Errorf  func(format string, v ...interface{})
   128  	Tracef  func(format string, v ...interface{})
   129  }
   130  
   131  type CertInfo struct {
   132  	Subject     string `json:"subject,omitempty"`
   133  	Issuer      string `json:"issuer,omitempty"`
   134  	Fingerprint string `json:"fingerprint,omitempty"`
   135  	Raw         []byte `json:"raw,omitempty"`
   136  }
   137  
   138  var OCSPPeerUsage = `
   139  For client, leaf spoke (remotes), and leaf hub connections, you may enable OCSP peer validation:
   140  
   141      tls {
   142          ...
   143          # mTLS must be enabled (with exception of Leaf remotes)
   144          verify: true
   145          ...
   146          # short form enables peer verify and takes option defaults
   147          ocsp_peer: true
   148          
   149          # long form includes settable options
   150          ocsp_peer {
   151             # Enable OCSP peer validation (default false)
   152             verify: true
   153  
   154             # OCSP responder timeout in seconds (may be fractional, default 2 seconds)
   155             ca_timeout: 2
   156  
   157             # Allowed skew between server and OCSP responder time in seconds (may be fractional, default 30 seconds)
   158             allowed_clockskew: 30
   159  
   160             # Warn-only and never reject connections (default false)
   161             warn_only: false
   162  
   163             # Treat response Unknown status as valid certificate (default false)
   164             unknown_is_good: false
   165  
   166             # Warn-only if no CA response can be obtained and no cached revocation exists (default false)
   167             allow_when_ca_unreachable: false
   168  
   169             # If response NextUpdate unset by CA, set a default cache TTL in seconds from ThisUpdate (default 1 hour)
   170             cache_ttl_when_next_update_unset: 3600
   171          }
   172          ...
   173      }
   174  
   175  Note: OCSP validation for route and gateway connections is enabled using the 'ocsp' configuration option.
   176  `
   177  
   178  // GenerateFingerprint returns a base64-encoded SHA256 hash of the raw certificate
   179  func GenerateFingerprint(cert *x509.Certificate) string {
   180  	data := sha256.Sum256(cert.Raw)
   181  	return base64.StdEncoding.EncodeToString(data[:])
   182  }
   183  
   184  func getWebEndpoints(uris []string) []*url.URL {
   185  	var urls []*url.URL
   186  	for _, uri := range uris {
   187  		endpoint, err := url.ParseRequestURI(uri)
   188  		if err != nil {
   189  			// skip invalid URLs
   190  			continue
   191  		}
   192  		if endpoint.Scheme != "http" && endpoint.Scheme != "https" {
   193  			// skip non-web URLs
   194  			continue
   195  		}
   196  		urls = append(urls, endpoint)
   197  	}
   198  	return urls
   199  }
   200  
   201  // GetSubjectDNForm returns RDN sequence concatenation of the certificate's subject to be
   202  // used in logs, events, etc. Should never be used for reliable cache matching or other crypto purposes.
   203  func GetSubjectDNForm(cert *x509.Certificate) string {
   204  	if cert == nil {
   205  		return ""
   206  	}
   207  	return strings.TrimSuffix(fmt.Sprintf("%s+", cert.Subject.ToRDNSequence()), "+")
   208  }
   209  
   210  // GetIssuerDNForm returns RDN sequence concatenation of the certificate's issuer to be
   211  // used in logs, events, etc. Should never be used for reliable cache matching or other crypto purposes.
   212  func GetIssuerDNForm(cert *x509.Certificate) string {
   213  	if cert == nil {
   214  		return ""
   215  	}
   216  	return strings.TrimSuffix(fmt.Sprintf("%s+", cert.Issuer.ToRDNSequence()), "+")
   217  }
   218  
   219  // CertOCSPEligible checks if the certificate's issuer has populated AIA with OCSP responder endpoint(s)
   220  // and is thus eligible for OCSP validation
   221  func CertOCSPEligible(link *ChainLink) bool {
   222  	if link == nil || link.Leaf.Raw == nil || len(link.Leaf.Raw) == 0 {
   223  		return false
   224  	}
   225  	if link.Leaf.OCSPServer == nil || len(link.Leaf.OCSPServer) == 0 {
   226  		return false
   227  	}
   228  	urls := getWebEndpoints(link.Leaf.OCSPServer)
   229  	if len(urls) == 0 {
   230  		return false
   231  	}
   232  	link.OCSPWebEndpoints = &urls
   233  	return true
   234  }
   235  
   236  // GetLeafIssuerCert returns the issuer certificate of the leaf (positional) certificate in the chain
   237  func GetLeafIssuerCert(chain []*x509.Certificate, leafPos int) *x509.Certificate {
   238  	if len(chain) == 0 || leafPos < 0 {
   239  		return nil
   240  	}
   241  	// self-signed certificate or too-big leafPos
   242  	if leafPos >= len(chain)-1 {
   243  		return nil
   244  	}
   245  	// returns pointer to issuer cert or nil
   246  	return (chain)[leafPos+1]
   247  }
   248  
   249  // OCSPResponseCurrent checks if the OCSP response is current (i.e. not expired and not future effective)
   250  func OCSPResponseCurrent(ocspr *ocsp.Response, opts *OCSPPeerConfig, log *Log) bool {
   251  	skew := time.Duration(opts.ClockSkew * float64(time.Second))
   252  	if skew < 0*time.Second {
   253  		skew = DefaultAllowedClockSkew
   254  	}
   255  	now := time.Now().UTC()
   256  	// Typical effectivity check based on CA response ThisUpdate and NextUpdate semantics
   257  	if !ocspr.NextUpdate.IsZero() && ocspr.NextUpdate.Before(now.Add(-1*skew)) {
   258  		t := ocspr.NextUpdate.Format(time.RFC3339Nano)
   259  		nt := now.Format(time.RFC3339Nano)
   260  		log.Debugf(DbgResponseExpired, t, nt, skew)
   261  		return false
   262  	}
   263  	// CA responder can assert NextUpdate unset, in which case use config option to set a default cache TTL
   264  	if ocspr.NextUpdate.IsZero() {
   265  		ttl := time.Duration(opts.TTLUnsetNextUpdate * float64(time.Second))
   266  		if ttl < 0*time.Second {
   267  			ttl = DefaultTTLUnsetNextUpdate
   268  		}
   269  		expiryTime := ocspr.ThisUpdate.Add(ttl)
   270  		if expiryTime.Before(now.Add(-1 * skew)) {
   271  			t := expiryTime.Format(time.RFC3339Nano)
   272  			nt := now.Format(time.RFC3339Nano)
   273  			log.Debugf(DbgResponseTTLExpired, t, nt, skew)
   274  			return false
   275  		}
   276  	}
   277  	if ocspr.ThisUpdate.After(now.Add(skew)) {
   278  		t := ocspr.ThisUpdate.Format(time.RFC3339Nano)
   279  		nt := now.Format(time.RFC3339Nano)
   280  		log.Debugf(DbgResponseFutureDated, t, nt, skew)
   281  		return false
   282  	}
   283  	return true
   284  }
   285  
   286  // ValidDelegationCheck checks if the CA OCSP Response was signed by a valid CA Issuer delegate as per (RFC 6960, section 4.2.2.2)
   287  // If a valid delegate or direct-signed by CA Issuer, true returned.
   288  func ValidDelegationCheck(iss *x509.Certificate, ocspr *ocsp.Response) bool {
   289  	// This call assumes prior successful parse and signature validation of the OCSP response
   290  	// The Go OCSP library (as of x/crypto/ocsp v0.9) will detect and perform a 1-level delegate signature check but does not
   291  	// implement the additional criteria for delegation specified in RFC 6960, section 4.2.2.2.
   292  	if iss == nil || ocspr == nil {
   293  		return false
   294  	}
   295  	// not a delegation, no-op
   296  	if ocspr.Certificate == nil {
   297  		return true
   298  	}
   299  	// delegate is self-same with CA Issuer, not a delegation although response issued in that form
   300  	if ocspr.Certificate.Equal(iss) {
   301  		return true
   302  	}
   303  	// we need to verify CA Issuer stamped id-kp-OCSPSigning on delegate
   304  	delegatedSigner := false
   305  	for _, keyUseExt := range ocspr.Certificate.ExtKeyUsage {
   306  		if keyUseExt == x509.ExtKeyUsageOCSPSigning {
   307  			delegatedSigner = true
   308  			break
   309  		}
   310  	}
   311  	return delegatedSigner
   312  }