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 }