github.com/letsencrypt/boulder@v0.20251208.0/test/ocsp/checkari/main.go (about)

     1  package main
     2  
     3  import (
     4  	"crypto"
     5  	_ "crypto/sha256"
     6  	"crypto/x509"
     7  	"crypto/x509/pkix"
     8  	"encoding/asn1"
     9  	"encoding/base64"
    10  	"encoding/json"
    11  	"flag"
    12  	"fmt"
    13  	"io"
    14  	"math/big"
    15  	"net/http"
    16  	"os"
    17  
    18  	"github.com/letsencrypt/boulder/core"
    19  )
    20  
    21  // certID matches the ASN.1 structure of the CertID sequence defined by RFC6960.
    22  type certID struct {
    23  	HashAlgorithm  pkix.AlgorithmIdentifier
    24  	IssuerNameHash []byte
    25  	IssuerKeyHash  []byte
    26  	SerialNumber   *big.Int
    27  }
    28  
    29  func createRequest(cert *x509.Certificate) ([]byte, error) {
    30  	if !crypto.SHA256.Available() {
    31  		return nil, x509.ErrUnsupportedAlgorithm
    32  	}
    33  	h := crypto.SHA256.New()
    34  
    35  	h.Write(cert.RawIssuer)
    36  	issuerNameHash := h.Sum(nil)
    37  
    38  	req := certID{
    39  		pkix.AlgorithmIdentifier{ // SHA256
    40  			Algorithm:  asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1},
    41  			Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */},
    42  		},
    43  		issuerNameHash,
    44  		cert.AuthorityKeyId,
    45  		cert.SerialNumber,
    46  	}
    47  
    48  	return asn1.Marshal(req)
    49  }
    50  
    51  func parseResponse(resp *http.Response) (*core.RenewalInfo, error) {
    52  	body, err := io.ReadAll(resp.Body)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	var res core.RenewalInfo
    58  	err = json.Unmarshal(body, &res)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	return &res, nil
    64  }
    65  
    66  func checkARI(baseURL string, certPath string) (*core.RenewalInfo, error) {
    67  	cert, err := core.LoadCert(certPath)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	req, err := createRequest(cert)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	url := fmt.Sprintf("%s/%s", baseURL, base64.RawURLEncoding.EncodeToString(req))
    78  	resp, err := http.Get(url)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	ri, err := parseResponse(resp)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	return ri, nil
    89  }
    90  
    91  func getARIURL(directory string) (string, error) {
    92  	resp, err := http.Get(directory)
    93  	if err != nil {
    94  		return "", err
    95  	}
    96  
    97  	body, err := io.ReadAll(resp.Body)
    98  	if err != nil {
    99  		return "", err
   100  	}
   101  
   102  	var dir struct {
   103  		RenewalInfo string `json:"renewalInfo"`
   104  	}
   105  	err = json.Unmarshal(body, &dir)
   106  	if err != nil {
   107  		return "", err
   108  	}
   109  
   110  	return dir.RenewalInfo, nil
   111  }
   112  
   113  func main() {
   114  	flag.Usage = func() {
   115  		fmt.Fprintf(os.Stderr, `
   116  checkari [-url https://acme.api/directory] FILE [FILE]...
   117  
   118  Tool for querying ARI. Provide a list of filenames for certificates in PEM
   119  format, and this tool will query for and output the suggested renewal window
   120  for each certificate.
   121  
   122  `)
   123  		flag.PrintDefaults()
   124  	}
   125  	directory := flag.String("url", "https://acme-v02.api.letsencrypt.org/directory", "ACME server's Directory URL")
   126  	flag.Parse()
   127  	if len(flag.Args()) == 0 {
   128  		flag.Usage()
   129  		os.Exit(1)
   130  	}
   131  
   132  	ariPath, err := getARIURL(*directory)
   133  	if err != nil {
   134  		fmt.Println(err.Error())
   135  		os.Exit(1)
   136  	}
   137  
   138  	for _, cert := range flag.Args() {
   139  		fmt.Printf("%s:\n", cert)
   140  		window, err := checkARI(ariPath, cert)
   141  		if err != nil {
   142  			fmt.Printf("\t%s\n", err)
   143  		} else {
   144  			fmt.Printf("\tRenew after : %s\n", window.SuggestedWindow.Start)
   145  			fmt.Printf("\tRenew before: %s\n", window.SuggestedWindow.End)
   146  		}
   147  	}
   148  }