Initial QSfera import
This commit is contained in:
+312
@@ -0,0 +1,312 @@
|
||||
// Copyright 2023-2024 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 certidp
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultAllowedClockSkew = 30 * time.Second
|
||||
DefaultOCSPResponderTimeout = 2 * time.Second
|
||||
DefaultTTLUnsetNextUpdate = 1 * time.Hour
|
||||
)
|
||||
|
||||
type StatusAssertion int
|
||||
|
||||
var (
|
||||
StatusAssertionStrToVal = map[string]StatusAssertion{
|
||||
"good": ocsp.Good,
|
||||
"revoked": ocsp.Revoked,
|
||||
"unknown": ocsp.Unknown,
|
||||
}
|
||||
StatusAssertionValToStr = map[StatusAssertion]string{
|
||||
ocsp.Good: "good",
|
||||
ocsp.Revoked: "revoked",
|
||||
ocsp.Unknown: "unknown",
|
||||
}
|
||||
StatusAssertionIntToVal = map[int]StatusAssertion{
|
||||
0: ocsp.Good,
|
||||
1: ocsp.Revoked,
|
||||
2: ocsp.Unknown,
|
||||
}
|
||||
)
|
||||
|
||||
// GetStatusAssertionStr returns the corresponding string representation of the StatusAssertion.
|
||||
func GetStatusAssertionStr(sa int) string {
|
||||
// If the provided status assertion value is not found in the map (StatusAssertionIntToVal),
|
||||
// the function defaults to "unknown" to avoid defaulting to "good," which is the default iota value
|
||||
// for the ocsp.StatusAssertion enumeration (https://pkg.go.dev/golang.org/x/crypto/ocsp#pkg-constants).
|
||||
// This ensures that we don't unintentionally default to "good" when there's no map entry.
|
||||
v, ok := StatusAssertionIntToVal[sa]
|
||||
if !ok {
|
||||
// set unknown as fallback
|
||||
v = ocsp.Unknown
|
||||
}
|
||||
|
||||
return StatusAssertionValToStr[v]
|
||||
}
|
||||
|
||||
func (sa StatusAssertion) MarshalJSON() ([]byte, error) {
|
||||
// This ensures that we don't unintentionally default to "good" when there's no map entry.
|
||||
// (see more details in the GetStatusAssertionStr() comment)
|
||||
str, ok := StatusAssertionValToStr[sa]
|
||||
if !ok {
|
||||
// set unknown as fallback
|
||||
str = StatusAssertionValToStr[ocsp.Unknown]
|
||||
}
|
||||
return json.Marshal(str)
|
||||
}
|
||||
|
||||
func (sa *StatusAssertion) UnmarshalJSON(in []byte) error {
|
||||
// This ensures that we don't unintentionally default to "good" when there's no map entry.
|
||||
// (see more details in the GetStatusAssertionStr() comment)
|
||||
v, ok := StatusAssertionStrToVal[strings.ReplaceAll(string(in), "\"", "")]
|
||||
if !ok {
|
||||
// set unknown as fallback
|
||||
v = StatusAssertionStrToVal["unknown"]
|
||||
}
|
||||
*sa = v
|
||||
return nil
|
||||
}
|
||||
|
||||
type ChainLink struct {
|
||||
Leaf *x509.Certificate
|
||||
Issuer *x509.Certificate
|
||||
OCSPWebEndpoints *[]*url.URL
|
||||
}
|
||||
|
||||
// OCSPPeerConfig holds the parsed OCSP peer configuration section of TLS configuration
|
||||
type OCSPPeerConfig struct {
|
||||
Verify bool
|
||||
Timeout float64
|
||||
ClockSkew float64
|
||||
WarnOnly bool
|
||||
UnknownIsGood bool
|
||||
AllowWhenCAUnreachable bool
|
||||
TTLUnsetNextUpdate float64
|
||||
}
|
||||
|
||||
func NewOCSPPeerConfig() *OCSPPeerConfig {
|
||||
return &OCSPPeerConfig{
|
||||
Verify: false,
|
||||
Timeout: DefaultOCSPResponderTimeout.Seconds(),
|
||||
ClockSkew: DefaultAllowedClockSkew.Seconds(),
|
||||
WarnOnly: false,
|
||||
UnknownIsGood: false,
|
||||
AllowWhenCAUnreachable: false,
|
||||
TTLUnsetNextUpdate: DefaultTTLUnsetNextUpdate.Seconds(),
|
||||
}
|
||||
}
|
||||
|
||||
// Log is a neutral method of passing server loggers to plugins
|
||||
type Log struct {
|
||||
Debugf func(format string, v ...any)
|
||||
Noticef func(format string, v ...any)
|
||||
Warnf func(format string, v ...any)
|
||||
Errorf func(format string, v ...any)
|
||||
Tracef func(format string, v ...any)
|
||||
}
|
||||
|
||||
type CertInfo struct {
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Issuer string `json:"issuer,omitempty"`
|
||||
Fingerprint string `json:"fingerprint,omitempty"`
|
||||
Raw []byte `json:"raw,omitempty"`
|
||||
}
|
||||
|
||||
var OCSPPeerUsage = `
|
||||
For client, leaf spoke (remotes), and leaf hub connections, you may enable OCSP peer validation:
|
||||
|
||||
tls {
|
||||
...
|
||||
# mTLS must be enabled (with exception of Leaf remotes)
|
||||
verify: true
|
||||
...
|
||||
# short form enables peer verify and takes option defaults
|
||||
ocsp_peer: true
|
||||
|
||||
# long form includes settable options
|
||||
ocsp_peer {
|
||||
# Enable OCSP peer validation (default false)
|
||||
verify: true
|
||||
|
||||
# OCSP responder timeout in seconds (may be fractional, default 2 seconds)
|
||||
ca_timeout: 2
|
||||
|
||||
# Allowed skew between server and OCSP responder time in seconds (may be fractional, default 30 seconds)
|
||||
allowed_clockskew: 30
|
||||
|
||||
# Warn-only and never reject connections (default false)
|
||||
warn_only: false
|
||||
|
||||
# Treat response Unknown status as valid certificate (default false)
|
||||
unknown_is_good: false
|
||||
|
||||
# Warn-only if no CA response can be obtained and no cached revocation exists (default false)
|
||||
allow_when_ca_unreachable: false
|
||||
|
||||
# If response NextUpdate unset by CA, set a default cache TTL in seconds from ThisUpdate (default 1 hour)
|
||||
cache_ttl_when_next_update_unset: 3600
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
Note: OCSP validation for route and gateway connections is enabled using the 'ocsp' configuration option.
|
||||
`
|
||||
|
||||
// GenerateFingerprint returns a base64-encoded SHA256 hash of the raw certificate
|
||||
func GenerateFingerprint(cert *x509.Certificate) string {
|
||||
data := sha256.Sum256(cert.Raw)
|
||||
return base64.StdEncoding.EncodeToString(data[:])
|
||||
}
|
||||
|
||||
func getWebEndpoints(uris []string) []*url.URL {
|
||||
var urls []*url.URL
|
||||
for _, uri := range uris {
|
||||
endpoint, err := url.ParseRequestURI(uri)
|
||||
if err != nil {
|
||||
// skip invalid URLs
|
||||
continue
|
||||
}
|
||||
if endpoint.Scheme != "http" && endpoint.Scheme != "https" {
|
||||
// skip non-web URLs
|
||||
continue
|
||||
}
|
||||
urls = append(urls, endpoint)
|
||||
}
|
||||
return urls
|
||||
}
|
||||
|
||||
// GetSubjectDNForm returns RDN sequence concatenation of the certificate's subject to be
|
||||
// used in logs, events, etc. Should never be used for reliable cache matching or other crypto purposes.
|
||||
func GetSubjectDNForm(cert *x509.Certificate) string {
|
||||
if cert == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSuffix(fmt.Sprintf("%s+", cert.Subject.ToRDNSequence()), "+")
|
||||
}
|
||||
|
||||
// GetIssuerDNForm returns RDN sequence concatenation of the certificate's issuer to be
|
||||
// used in logs, events, etc. Should never be used for reliable cache matching or other crypto purposes.
|
||||
func GetIssuerDNForm(cert *x509.Certificate) string {
|
||||
if cert == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSuffix(fmt.Sprintf("%s+", cert.Issuer.ToRDNSequence()), "+")
|
||||
}
|
||||
|
||||
// CertOCSPEligible checks if the certificate's issuer has populated AIA with OCSP responder endpoint(s)
|
||||
// and is thus eligible for OCSP validation
|
||||
func CertOCSPEligible(link *ChainLink) bool {
|
||||
if link == nil || link.Leaf.Raw == nil || len(link.Leaf.Raw) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(link.Leaf.OCSPServer) == 0 {
|
||||
return false
|
||||
}
|
||||
urls := getWebEndpoints(link.Leaf.OCSPServer)
|
||||
if len(urls) == 0 {
|
||||
return false
|
||||
}
|
||||
link.OCSPWebEndpoints = &urls
|
||||
return true
|
||||
}
|
||||
|
||||
// GetLeafIssuerCert returns the issuer certificate of the leaf (positional) certificate in the chain
|
||||
func GetLeafIssuerCert(chain []*x509.Certificate, leafPos int) *x509.Certificate {
|
||||
if len(chain) == 0 || leafPos < 0 {
|
||||
return nil
|
||||
}
|
||||
// self-signed certificate or too-big leafPos
|
||||
if leafPos >= len(chain)-1 {
|
||||
return nil
|
||||
}
|
||||
// returns pointer to issuer cert or nil
|
||||
return (chain)[leafPos+1]
|
||||
}
|
||||
|
||||
// OCSPResponseCurrent checks if the OCSP response is current (i.e. not expired and not future effective)
|
||||
func OCSPResponseCurrent(ocspr *ocsp.Response, opts *OCSPPeerConfig, log *Log) bool {
|
||||
skew := time.Duration(opts.ClockSkew * float64(time.Second))
|
||||
if skew < 0*time.Second {
|
||||
skew = DefaultAllowedClockSkew
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
// Typical effectivity check based on CA response ThisUpdate and NextUpdate semantics
|
||||
if !ocspr.NextUpdate.IsZero() && ocspr.NextUpdate.Before(now.Add(-1*skew)) {
|
||||
t := ocspr.NextUpdate.Format(time.RFC3339Nano)
|
||||
nt := now.Format(time.RFC3339Nano)
|
||||
log.Debugf(DbgResponseExpired, t, nt, skew)
|
||||
return false
|
||||
}
|
||||
// CA responder can assert NextUpdate unset, in which case use config option to set a default cache TTL
|
||||
if ocspr.NextUpdate.IsZero() {
|
||||
ttl := time.Duration(opts.TTLUnsetNextUpdate * float64(time.Second))
|
||||
if ttl < 0*time.Second {
|
||||
ttl = DefaultTTLUnsetNextUpdate
|
||||
}
|
||||
expiryTime := ocspr.ThisUpdate.Add(ttl)
|
||||
if expiryTime.Before(now.Add(-1 * skew)) {
|
||||
t := expiryTime.Format(time.RFC3339Nano)
|
||||
nt := now.Format(time.RFC3339Nano)
|
||||
log.Debugf(DbgResponseTTLExpired, t, nt, skew)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if ocspr.ThisUpdate.After(now.Add(skew)) {
|
||||
t := ocspr.ThisUpdate.Format(time.RFC3339Nano)
|
||||
nt := now.Format(time.RFC3339Nano)
|
||||
log.Debugf(DbgResponseFutureDated, t, nt, skew)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ValidDelegationCheck checks if the CA OCSP Response was signed by a valid CA Issuer delegate as per (RFC 6960, section 4.2.2.2)
|
||||
// If a valid delegate or direct-signed by CA Issuer, true returned.
|
||||
func ValidDelegationCheck(iss *x509.Certificate, ocspr *ocsp.Response) bool {
|
||||
// This call assumes prior successful parse and signature validation of the OCSP response
|
||||
// The Go OCSP library (as of x/crypto/ocsp v0.9) will detect and perform a 1-level delegate signature check but does not
|
||||
// implement the additional criteria for delegation specified in RFC 6960, section 4.2.2.2.
|
||||
if iss == nil || ocspr == nil {
|
||||
return false
|
||||
}
|
||||
// not a delegation, no-op
|
||||
if ocspr.Certificate == nil {
|
||||
return true
|
||||
}
|
||||
// delegate is self-same with CA Issuer, not a delegation although response issued in that form
|
||||
if ocspr.Certificate.Equal(iss) {
|
||||
return true
|
||||
}
|
||||
// we need to verify CA Issuer stamped id-kp-OCSPSigning on delegate
|
||||
delegatedSigner := false
|
||||
for _, keyUseExt := range ocspr.Certificate.ExtKeyUsage {
|
||||
if keyUseExt == x509.ExtKeyUsageOCSPSigning {
|
||||
delegatedSigner = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return delegatedSigner
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// 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 certidp
|
||||
|
||||
var (
|
||||
// Returned errors
|
||||
ErrIllegalPeerOptsConfig = "expected map to define OCSP peer options, got [%T]"
|
||||
ErrIllegalCacheOptsConfig = "expected map to define OCSP peer cache options, got [%T]"
|
||||
ErrParsingPeerOptFieldGeneric = "error parsing tls peer config, unknown field [%q]"
|
||||
ErrParsingPeerOptFieldTypeConversion = "error parsing tls peer config, conversion error: %s"
|
||||
ErrParsingCacheOptFieldTypeConversion = "error parsing OCSP peer cache config, conversion error: %s"
|
||||
ErrUnableToPlugTLSEmptyConfig = "unable to plug TLS verify connection, config is nil"
|
||||
ErrMTLSRequired = "OCSP peer verification for client connections requires TLS verify (mTLS) to be enabled"
|
||||
ErrUnableToPlugTLSClient = "unable to register client OCSP verification"
|
||||
ErrUnableToPlugTLSServer = "unable to register server OCSP verification"
|
||||
ErrCannotWriteCompressed = "error writing to compression writer: %w"
|
||||
ErrCannotReadCompressed = "error reading compression reader: %w"
|
||||
ErrTruncatedWrite = "short write on body (%d != %d)"
|
||||
ErrCannotCloseWriter = "error closing compression writer: %w"
|
||||
ErrParsingCacheOptFieldGeneric = "error parsing OCSP peer cache config, unknown field [%q]"
|
||||
ErrUnknownCacheType = "error parsing OCSP peer cache config, unknown type [%s]"
|
||||
ErrInvalidChainlink = "invalid chain link"
|
||||
ErrBadResponderHTTPStatus = "bad OCSP responder http status: [%d]"
|
||||
ErrNoAvailOCSPServers = "no available OCSP servers"
|
||||
ErrFailedWithAllRequests = "exhausted OCSP responders: %w"
|
||||
|
||||
// Direct logged errors
|
||||
ErrLoadCacheFail = "Unable to load OCSP peer cache: %s"
|
||||
ErrSaveCacheFail = "Unable to save OCSP peer cache: %s"
|
||||
ErrBadCacheTypeConfig = "Unimplemented OCSP peer cache type [%v]"
|
||||
ErrResponseCompressFail = "Unable to compress OCSP response for key [%s]: %s"
|
||||
ErrResponseDecompressFail = "Unable to decompress OCSP response for key [%s]: %s"
|
||||
ErrPeerEmptyNoEvent = "Peer certificate is nil, cannot send OCSP peer reject event"
|
||||
ErrPeerEmptyAutoReject = "Peer certificate is nil, rejecting OCSP peer"
|
||||
|
||||
// Debug information
|
||||
DbgPlugTLSForKind = "Plugging TLS OCSP peer for [%s]"
|
||||
DbgNumServerChains = "Peer OCSP enabled: %d TLS server chain(s) will be evaluated"
|
||||
DbgNumClientChains = "Peer OCSP enabled: %d TLS client chain(s) will be evaluated"
|
||||
DbgLinksInChain = "Chain [%d]: %d total link(s)"
|
||||
DbgSelfSignedValid = "Chain [%d] is self-signed, thus peer is valid"
|
||||
DbgValidNonOCSPChain = "Chain [%d] has no OCSP eligible links, thus peer is valid"
|
||||
DbgChainIsOCSPEligible = "Chain [%d] has %d OCSP eligible link(s)"
|
||||
DbgChainIsOCSPValid = "Chain [%d] is OCSP valid for all eligible links, thus peer is valid"
|
||||
DbgNoOCSPValidChains = "No OCSP valid chains, thus peer is invalid"
|
||||
DbgCheckingCacheForCert = "Checking OCSP peer cache for [%s], key [%s]"
|
||||
DbgCurrentResponseCached = "Cached OCSP response is current, status [%s]"
|
||||
DbgExpiredResponseCached = "Cached OCSP response is expired, status [%s]"
|
||||
DbgOCSPValidPeerLink = "OCSP verify pass for [%s]"
|
||||
DbgCachingResponse = "Caching OCSP response for [%s], key [%s]"
|
||||
DbgAchievedCompression = "OCSP response compression ratio: [%f]"
|
||||
DbgCacheHit = "OCSP peer cache hit for key [%s]"
|
||||
DbgCacheMiss = "OCSP peer cache miss for key [%s]"
|
||||
DbgPreservedRevocation = "Revoked OCSP response for key [%s] preserved by cache policy"
|
||||
DbgDeletingCacheResponse = "Deleting OCSP peer cached response for key [%s]"
|
||||
DbgStartingCache = "Starting OCSP peer cache"
|
||||
DbgStoppingCache = "Stopping OCSP peer cache"
|
||||
DbgLoadingCache = "Loading OCSP peer cache [%s]"
|
||||
DbgNoCacheFound = "No OCSP peer cache found, starting with empty cache"
|
||||
DbgSavingCache = "Saving OCSP peer cache [%s]"
|
||||
DbgCacheSaved = "Saved OCSP peer cache successfully (%d bytes)"
|
||||
DbgMakingCARequest = "Trying OCSP responder url [%s]"
|
||||
DbgResponseExpired = "OCSP response NextUpdate [%s] is before now [%s] with clockskew [%s]"
|
||||
DbgResponseTTLExpired = "OCSP response cache expiry [%s] is before now [%s] with clockskew [%s]"
|
||||
DbgResponseFutureDated = "OCSP response ThisUpdate [%s] is before now [%s] with clockskew [%s]"
|
||||
DbgCacheSaveTimerExpired = "OCSP peer cache save timer expired"
|
||||
DbgCacheDirtySave = "OCSP peer cache is dirty, saving"
|
||||
|
||||
// Returned to peer as TLS reject reason
|
||||
MsgTLSClientRejectConnection = "client not OCSP valid"
|
||||
MsgTLSServerRejectConnection = "server not OCSP valid"
|
||||
|
||||
// Expected runtime errors (direct logged)
|
||||
ErrCAResponderCalloutFail = "Attempt to obtain OCSP response from CA responder for [%s] failed: %s"
|
||||
ErrNewCAResponseNotCurrent = "New OCSP CA response obtained for [%s] but not current"
|
||||
ErrCAResponseParseFailed = "Could not parse OCSP CA response for [%s]: %s"
|
||||
ErrOCSPInvalidPeerLink = "OCSP verify fail for [%s] with CA status [%s]"
|
||||
|
||||
// Policy override warnings (direct logged)
|
||||
MsgAllowWhenCAUnreachableOccurred = "Failed to obtain OCSP CA response for [%s] but AllowWhenCAUnreachable set; no cached revocation so allowing"
|
||||
MsgAllowWhenCAUnreachableOccurredCachedRevoke = "Failed to obtain OCSP CA response for [%s] but AllowWhenCAUnreachable set; cached revocation exists so rejecting"
|
||||
MsgAllowWarnOnlyOccurred = "OCSP verify fail for [%s] but WarnOnly is true so allowing"
|
||||
|
||||
// Info (direct logged)
|
||||
MsgCacheOnline = "OCSP peer cache online, type [%s]"
|
||||
MsgCacheOffline = "OCSP peer cache offline, type [%s]"
|
||||
|
||||
// OCSP cert invalid reasons (debug and event reasons)
|
||||
MsgFailedOCSPResponseFetch = "Failed OCSP response fetch"
|
||||
MsgOCSPResponseNotEffective = "OCSP response not in effectivity window"
|
||||
MsgFailedOCSPResponseParse = "Failed OCSP response parse"
|
||||
MsgOCSPResponseInvalidStatus = "Invalid OCSP response status: %s"
|
||||
MsgOCSPResponseDelegationInvalid = "Invalid OCSP response delegation: %s"
|
||||
MsgCachedOCSPResponseInvalid = "Invalid cached OCSP response for [%s] with fingerprint [%s]"
|
||||
)
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
// Copyright 2023-2025 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 certidp
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
func FetchOCSPResponse(link *ChainLink, opts *OCSPPeerConfig, log *Log) ([]byte, error) {
|
||||
if link == nil || link.Leaf == nil || link.Issuer == nil || opts == nil || log == nil {
|
||||
return nil, errors.New(ErrInvalidChainlink)
|
||||
}
|
||||
|
||||
timeout := time.Duration(opts.Timeout * float64(time.Second))
|
||||
if timeout <= 0*time.Second {
|
||||
timeout = DefaultOCSPResponderTimeout
|
||||
}
|
||||
|
||||
getRequestBytes := func(u string, hc *http.Client) ([]byte, error) {
|
||||
resp, err := hc.Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf(ErrBadResponderHTTPStatus, resp.StatusCode)
|
||||
}
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
// Request documentation:
|
||||
// https://tools.ietf.org/html/rfc6960#appendix-A.1
|
||||
|
||||
reqDER, err := ocsp.CreateRequest(link.Leaf, link.Issuer, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqEnc := encodeOCSPRequest(reqDER)
|
||||
|
||||
responders := *link.OCSPWebEndpoints
|
||||
|
||||
if len(responders) == 0 {
|
||||
return nil, errors.New(ErrNoAvailOCSPServers)
|
||||
}
|
||||
|
||||
var raw []byte
|
||||
hc := &http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
for _, u := range responders {
|
||||
responderURL := u.String()
|
||||
log.Debugf(DbgMakingCARequest, responderURL)
|
||||
responderURL = strings.TrimSuffix(responderURL, "/")
|
||||
raw, err = getRequestBytes(fmt.Sprintf("%s/%s", responderURL, reqEnc), hc)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(ErrFailedWithAllRequests, err)
|
||||
}
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
// encodeOCSPRequest encodes the OCSP request in base64 and URL-encodes it.
|
||||
// This is needed to fulfill the OCSP responder's requirements for the request format. (X.690)
|
||||
func encodeOCSPRequest(reqDER []byte) string {
|
||||
reqEnc := base64.StdEncoding.EncodeToString(reqDER)
|
||||
return url.QueryEscape(reqEnc)
|
||||
}
|
||||
Reference in New Issue
Block a user