snapshot of https://github.com/nats-io/nats-server.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
NATS Server Tests / Lint (push) Failing after 3s
NATS Server Tests / Build (Latest Go) (push) Has been skipped
NATS Server Tests / Build (Minimum Go) (push) Has been skipped
NATS Server Tests / Build (Minimum Go, windows-2022) (push) Has been skipped
NATS Server Tests / Build (Minimum Go, windows-2025) (push) Has been skipped
NATS Server Tests / Test Stores (push) Has been skipped
NATS Server Tests / Test JetStream (push) Has been skipped
NATS Server Tests / Test Raft (push) Has been skipped
NATS Server Tests / Test JetStream Consumers (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 1 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 2 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 3 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 4 (push) Has been skipped
NATS Server Tests / Test JetStream Supercluster (push) Has been skipped
NATS Server Tests / Test No-Race 1 (push) Has been skipped
NATS Server Tests / Test No-Race 2 (push) Has been skipped
NATS Server Tests / Test MQTT (push) Has been skipped
NATS Server Tests / Test Message Tracing (push) Has been skipped
NATS Server Tests / Test JWT (push) Has been skipped
NATS Server Tests / Test Remaining Server Tests (push) Has been skipped
NATS Server Tests / Test Other Packages (push) Has been skipped
Vulnerability Scan / govulncheck (push) Failing after 12m2s
Some checks failed
NATS Server Tests / Lint (push) Failing after 3s
NATS Server Tests / Build (Latest Go) (push) Has been skipped
NATS Server Tests / Build (Minimum Go) (push) Has been skipped
NATS Server Tests / Build (Minimum Go, windows-2022) (push) Has been skipped
NATS Server Tests / Build (Minimum Go, windows-2025) (push) Has been skipped
NATS Server Tests / Test Stores (push) Has been skipped
NATS Server Tests / Test JetStream (push) Has been skipped
NATS Server Tests / Test Raft (push) Has been skipped
NATS Server Tests / Test JetStream Consumers (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 1 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 2 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 3 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 4 (push) Has been skipped
NATS Server Tests / Test JetStream Supercluster (push) Has been skipped
NATS Server Tests / Test No-Race 1 (push) Has been skipped
NATS Server Tests / Test No-Race 2 (push) Has been skipped
NATS Server Tests / Test MQTT (push) Has been skipped
NATS Server Tests / Test Message Tracing (push) Has been skipped
NATS Server Tests / Test JWT (push) Has been skipped
NATS Server Tests / Test Remaining Server Tests (push) Has been skipped
NATS Server Tests / Test Other Packages (push) Has been skipped
Vulnerability Scan / govulncheck (push) Failing after 12m2s
This commit is contained in:
276
internal/ocsp/ocsp.go
Normal file
276
internal/ocsp/ocsp.go
Normal file
@@ -0,0 +1,276 @@
|
||||
// Copyright 2019-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 testhelper
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultResponseTTL = 4 * time.Second
|
||||
defaultAddress = "127.0.0.1:8888"
|
||||
)
|
||||
|
||||
func NewOCSPResponderCustomAddress(t *testing.T, issuerCertPEM, issuerKeyPEM string, addr string) *http.Server {
|
||||
t.Helper()
|
||||
return NewOCSPResponderBase(t, issuerCertPEM, issuerCertPEM, issuerKeyPEM, false, addr, defaultResponseTTL, "")
|
||||
}
|
||||
|
||||
func NewOCSPResponder(t *testing.T, issuerCertPEM, issuerKeyPEM string) *http.Server {
|
||||
t.Helper()
|
||||
return NewOCSPResponderBase(t, issuerCertPEM, issuerCertPEM, issuerKeyPEM, false, defaultAddress, defaultResponseTTL, "")
|
||||
}
|
||||
|
||||
func NewOCSPResponderDesignatedCustomAddress(t *testing.T, issuerCertPEM, respCertPEM, respKeyPEM string, addr string) *http.Server {
|
||||
t.Helper()
|
||||
return NewOCSPResponderBase(t, issuerCertPEM, respCertPEM, respKeyPEM, true, addr, defaultResponseTTL, "")
|
||||
}
|
||||
|
||||
func NewOCSPResponderPreferringHTTPMethod(t *testing.T, issuerCertPEM, issuerKeyPEM, method string) *http.Server {
|
||||
t.Helper()
|
||||
return NewOCSPResponderBase(t, issuerCertPEM, issuerCertPEM, issuerKeyPEM, false, defaultAddress, defaultResponseTTL, method)
|
||||
}
|
||||
|
||||
func NewOCSPResponderCustomTimeout(t *testing.T, issuerCertPEM, issuerKeyPEM string, responseTTL time.Duration) *http.Server {
|
||||
t.Helper()
|
||||
return NewOCSPResponderBase(t, issuerCertPEM, issuerCertPEM, issuerKeyPEM, false, defaultAddress, responseTTL, "")
|
||||
}
|
||||
|
||||
func NewOCSPResponderBase(t *testing.T, issuerCertPEM, respCertPEM, respKeyPEM string, embed bool, addr string, responseTTL time.Duration, method string) *http.Server {
|
||||
t.Helper()
|
||||
var mu sync.Mutex
|
||||
status := make(map[string]int)
|
||||
|
||||
issuerCert := parseCertPEM(t, issuerCertPEM)
|
||||
respCert := parseCertPEM(t, respCertPEM)
|
||||
respKey := parseKeyPEM(t, respKeyPEM)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
// The "/statuses/" endpoint is for directly setting a key-value pair in
|
||||
// the CA's status database.
|
||||
mux.HandleFunc("/statuses/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
key := r.URL.Path[len("/statuses/"):]
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
mu.Lock()
|
||||
n, ok := status[key]
|
||||
if !ok {
|
||||
n = ocsp.Unknown
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
fmt.Fprintf(rw, "%s %d", key, n)
|
||||
case "POST":
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
n, err := strconv.Atoi(string(data))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
status[key] = n
|
||||
mu.Unlock()
|
||||
|
||||
fmt.Fprintf(rw, "%s %d", key, n)
|
||||
default:
|
||||
http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
})
|
||||
// The "/" endpoint is for normal OCSP requests. This actually parses an
|
||||
// OCSP status request and signs a response with a CA. Lightly based off:
|
||||
// https://www.ietf.org/rfc/rfc2560.txt
|
||||
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
var reqData []byte
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case r.Method == "GET":
|
||||
if method != "" && r.Method != method {
|
||||
http.Error(rw, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
reqData, err = base64.StdEncoding.DecodeString(r.URL.Path[1:])
|
||||
case r.Method == "POST":
|
||||
if method != "" && r.Method != method {
|
||||
http.Error(rw, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
reqData, err = io.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
default:
|
||||
http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ocspReq, err := ocsp.ParseRequest(reqData)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
n, ok := status[ocspReq.SerialNumber.String()]
|
||||
if !ok {
|
||||
n = ocsp.Unknown
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
tmpl := ocsp.Response{
|
||||
Status: n,
|
||||
SerialNumber: ocspReq.SerialNumber,
|
||||
ThisUpdate: time.Now(),
|
||||
}
|
||||
if responseTTL != 0 {
|
||||
tmpl.NextUpdate = tmpl.ThisUpdate.Add(responseTTL)
|
||||
}
|
||||
if embed {
|
||||
tmpl.Certificate = respCert
|
||||
}
|
||||
respData, err := ocsp.CreateResponse(issuerCert, respCert, tmpl, respKey)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/ocsp-response")
|
||||
rw.Header().Set("Content-Length", fmt.Sprint(len(respData)))
|
||||
|
||||
fmt.Fprint(rw, string(respData))
|
||||
})
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
ReadTimeout: time.Second * 5,
|
||||
}
|
||||
go srv.ListenAndServe()
|
||||
time.Sleep(1 * time.Second)
|
||||
return srv
|
||||
}
|
||||
|
||||
func parseCertPEM(t *testing.T, certPEM string) *x509.Certificate {
|
||||
t.Helper()
|
||||
block := parsePEM(t, certPEM)
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse cert '%s': %s", certPEM, err)
|
||||
}
|
||||
return cert
|
||||
}
|
||||
|
||||
func parseKeyPEM(t *testing.T, keyPEM string) crypto.Signer {
|
||||
t.Helper()
|
||||
block := parsePEM(t, keyPEM)
|
||||
|
||||
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse ikey %s: %s", keyPEM, err)
|
||||
}
|
||||
}
|
||||
keyc := key.(crypto.Signer)
|
||||
return keyc
|
||||
}
|
||||
|
||||
func parsePEM(t *testing.T, pemPath string) *pem.Block {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(pemPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil {
|
||||
t.Fatalf("failed to decode PEM %s", pemPath)
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
func GetOCSPStatus(s tls.ConnectionState) (*ocsp.Response, error) {
|
||||
if len(s.VerifiedChains) == 0 {
|
||||
return nil, fmt.Errorf("missing TLS verified chains")
|
||||
}
|
||||
chain := s.VerifiedChains[0]
|
||||
|
||||
if got, want := len(chain), 2; got < want {
|
||||
return nil, fmt.Errorf("incomplete cert chain, got %d, want at least %d", got, want)
|
||||
}
|
||||
leaf, issuer := chain[0], chain[1]
|
||||
|
||||
resp, err := ocsp.ParseResponseForCert(s.OCSPResponse, leaf, issuer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse OCSP response: %w", err)
|
||||
}
|
||||
if err := resp.CheckSignatureFrom(issuer); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func SetOCSPStatus(t *testing.T, ocspURL, certPEM string, status int) {
|
||||
t.Helper()
|
||||
|
||||
cert := parseCertPEM(t, certPEM)
|
||||
|
||||
hc := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := hc.Post(
|
||||
fmt.Sprintf("%s/statuses/%s", ocspURL, cert.SerialNumber),
|
||||
"",
|
||||
strings.NewReader(fmt.Sprint(status)),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read OCSP HTTP response body: %s", err)
|
||||
}
|
||||
|
||||
if got, want := resp.Status, "200 OK"; got != want {
|
||||
t.Error(strings.TrimSpace(string(data)))
|
||||
t.Fatalf("unexpected OCSP HTTP set status, got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user