Skip to content

Instantly share code, notes, and snippets.

@alexrudd
Last active November 17, 2023 07:44
Show Gist options
  • Save alexrudd/ca792642c1f7923ee344b3645a420006 to your computer and use it in GitHub Desktop.
Save alexrudd/ca792642c1f7923ee344b3645a420006 to your computer and use it in GitHub Desktop.
OCSP Request in Go

OCSP Request in Go

This came out of debugging the error net/http: TLS handshake timeout which seemed to only be happening for certain endpoints. The thing that these endpoints all had in common was that they used Let's Encrypt as their CA. Some googling lead me to think it might have something to do with OCSP, so I wrote a small application that could make an OCSP request for a specified certificate.

The issue ended up being that the server experience the TLS handshake timeout had port 80 blocked. Port 80 was required to make the OCSP HTTP request to check that the certificate wasn't revoked.

go run ocsp.go ./cert.cer
package main
import (
"bytes"
"context"
"crypto"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"golang.org/x/crypto/ocsp"
)
func main() {
// read x509 certificate from DER encoded file
cert, err := readCert(os.Args[1])
if err != nil {
log.Fatal(err)
}
// Perform OCSP Check
err = CheckOCSPStatus(context.Background(), cert)
if err != nil {
log.Fatal(err)
}
log.Println("OCSP check was successful")
}
// CheckOCSPStatus will make an OCSP request for the provided certificate.
// If the status of the certificate is not good, then an error is returned.
func CheckOCSPStatus(ctx context.Context, cert *x509.Certificate) error {
var (
ocspURL = cert.OCSPServer[0]
issuerCertURL = cert.IssuingCertificateURL[0]
)
// download the issuer certificate
issuer, err := getCert(ctx, issuerCertURL)
if err != nil {
return fmt.Errorf("getting issuer certificate: %w", err)
}
// Build OCSP request
buffer, err := ocsp.CreateRequest(cert, issuer, &ocsp.RequestOptions{
Hash: crypto.SHA1,
})
if err != nil {
return fmt.Errorf("creating ocsp request body: %w", err)
}
req, err := http.NewRequest(http.MethodPost, ocspURL, bytes.NewBuffer(buffer))
if err != nil {
return fmt.Errorf("creating http request: %w", err)
}
ocspUrl, err := url.Parse(ocspURL)
if err != nil {
return fmt.Errorf("parsing ocsp url: %w", err)
}
req.Header.Add("Content-Type", "application/ocsp-request")
req.Header.Add("Accept", "application/ocsp-response")
req.Header.Add("host", ocspUrl.Host)
req = req.WithContext(ctx)
// Make OCSP request
httpResponse, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("making ocsp request: %w", err)
}
defer httpResponse.Body.Close()
output, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return fmt.Errorf("reading response body: %w", err)
}
// Parse response
ocspResponse, err := ocsp.ParseResponse(output, issuer)
if err != nil {
return fmt.Errorf("parsing ocsp response: %w", err)
}
// check status
if ocspResponse.Status != ocsp.Good {
return fmt.Errorf("invalid cert status: %v", ocspResponse.Status)
}
return nil
}
func getCert(ctx context.Context, url string) (*x509.Certificate, error) {
req, _ := http.NewRequest(http.MethodGet, url, nil)
req = req.WithContext(ctx)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("getting cert from %s: %w", url, err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading response body: %w", err)
}
cert, err := x509.ParseCertificate(body)
if err != nil {
return nil, fmt.Errorf("parsing certificate: %w", err)
}
return cert, nil
}
func readCert(path string) (*x509.Certificate, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("opening %s: %w", path, err)
}
defer file.Close()
b, err := io.ReadAll(file)
if err != nil {
return nil, fmt.Errorf("reading %s: %w", path, err)
}
cert, err := x509.ParseCertificate(b)
if err != nil {
return nil, fmt.Errorf("parsing certificate %s: %w", path, err)
}
return cert, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment