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 }