summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/caddyserver/certmagic/ocsp.go
diff options
context:
space:
mode:
authortechknowlogick <techknowlogick@gitea.io>2021-01-24 18:37:35 -0500
committerGitHub <noreply@github.com>2021-01-25 01:37:35 +0200
commitd2ea21d0d8103986b2ce53c17b7b99b1ce6828b0 (patch)
tree802ea1a787b1f6ef08b18524d3818115a750f0eb /vendor/github.com/caddyserver/certmagic/ocsp.go
parentbc05ddc0ebd6fdc826ef2beec99304bac60ddd8a (diff)
downloadgitea-d2ea21d0d8103986b2ce53c17b7b99b1ce6828b0.tar.gz
gitea-d2ea21d0d8103986b2ce53c17b7b99b1ce6828b0.zip
Use caddy's certmagic library for extensible/robust ACME handling (#14177)
* use certmagic for more extensible/robust ACME cert handling * accept TOS based on config option Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv>
Diffstat (limited to 'vendor/github.com/caddyserver/certmagic/ocsp.go')
-rw-r--r--vendor/github.com/caddyserver/certmagic/ocsp.go212
1 files changed, 212 insertions, 0 deletions
diff --git a/vendor/github.com/caddyserver/certmagic/ocsp.go b/vendor/github.com/caddyserver/certmagic/ocsp.go
new file mode 100644
index 0000000000..fedc70e264
--- /dev/null
+++ b/vendor/github.com/caddyserver/certmagic/ocsp.go
@@ -0,0 +1,212 @@
+// Copyright 2015 Matthew Holt
+//
+// 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 certmagic
+
+import (
+ "bytes"
+ "crypto/x509"
+ "encoding/pem"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "time"
+
+ "golang.org/x/crypto/ocsp"
+)
+
+// stapleOCSP staples OCSP information to cert for hostname name.
+// If you have it handy, you should pass in the PEM-encoded certificate
+// bundle; otherwise the DER-encoded cert will have to be PEM-encoded.
+// If you don't have the PEM blocks already, just pass in nil.
+//
+// Errors here are not necessarily fatal, it could just be that the
+// certificate doesn't have an issuer URL.
+//
+// If a status was received, it returns that status. Note that the
+// returned status is not always stapled to the certificate.
+func stapleOCSP(storage Storage, cert *Certificate, pemBundle []byte) (*ocsp.Response, error) {
+ if pemBundle == nil {
+ // we need a PEM encoding only for some function calls below
+ bundle := new(bytes.Buffer)
+ for _, derBytes := range cert.Certificate.Certificate {
+ pem.Encode(bundle, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
+ }
+ pemBundle = bundle.Bytes()
+ }
+
+ var ocspBytes []byte
+ var ocspResp *ocsp.Response
+ var ocspErr error
+ var gotNewOCSP bool
+
+ // First try to load OCSP staple from storage and see if
+ // we can still use it.
+ ocspStapleKey := StorageKeys.OCSPStaple(cert, pemBundle)
+ cachedOCSP, err := storage.Load(ocspStapleKey)
+ if err == nil {
+ resp, err := ocsp.ParseResponse(cachedOCSP, nil)
+ if err == nil {
+ if freshOCSP(resp) {
+ // staple is still fresh; use it
+ ocspBytes = cachedOCSP
+ ocspResp = resp
+ }
+ } else {
+ // invalid contents; delete the file
+ // (we do this independently of the maintenance routine because
+ // in this case we know for sure this should be a staple file
+ // because we loaded it by name, whereas the maintenance routine
+ // just iterates the list of files, even if somehow a non-staple
+ // file gets in the folder. in this case we are sure it is corrupt.)
+ err := storage.Delete(ocspStapleKey)
+ if err != nil {
+ log.Printf("[WARNING] Unable to delete invalid OCSP staple file: %v", err)
+ }
+ }
+ }
+
+ // If we couldn't get a fresh staple by reading the cache,
+ // then we need to request it from the OCSP responder
+ if ocspResp == nil || len(ocspBytes) == 0 {
+ ocspBytes, ocspResp, ocspErr = getOCSPForCert(pemBundle)
+ if ocspErr != nil {
+ // An error here is not a problem because a certificate may simply
+ // not contain a link to an OCSP server. But we should log it anyway.
+ // There's nothing else we can do to get OCSP for this certificate,
+ // so we can return here with the error.
+ return nil, fmt.Errorf("no OCSP stapling for %v: %v", cert.Names, ocspErr)
+ }
+ gotNewOCSP = true
+ }
+
+ // By now, we should have a response. If good, staple it to
+ // the certificate. If the OCSP response was not loaded from
+ // storage, we persist it for next time.
+ if ocspResp.Status == ocsp.Good {
+ if ocspResp.NextUpdate.After(cert.Leaf.NotAfter) {
+ // uh oh, this OCSP response expires AFTER the certificate does, that's kinda bogus.
+ // it was the reason a lot of Symantec-validated sites (not Caddy) went down
+ // in October 2017. https://twitter.com/mattiasgeniar/status/919432824708648961
+ return ocspResp, fmt.Errorf("invalid: OCSP response for %v valid after certificate expiration (%s)",
+ cert.Names, cert.Leaf.NotAfter.Sub(ocspResp.NextUpdate))
+ }
+ cert.Certificate.OCSPStaple = ocspBytes
+ cert.ocsp = ocspResp
+ if gotNewOCSP {
+ err := storage.Store(ocspStapleKey, ocspBytes)
+ if err != nil {
+ return ocspResp, fmt.Errorf("unable to write OCSP staple file for %v: %v", cert.Names, err)
+ }
+ }
+ }
+
+ return ocspResp, nil
+}
+
+// getOCSPForCert takes a PEM encoded cert or cert bundle returning the raw OCSP response,
+// the parsed response, and an error, if any. The returned []byte can be passed directly
+// into the OCSPStaple property of a tls.Certificate. If the bundle only contains the
+// issued certificate, this function will try to get the issuer certificate from the
+// IssuingCertificateURL in the certificate. If the []byte and/or ocsp.Response return
+// values are nil, the OCSP status may be assumed OCSPUnknown.
+//
+// Borrowed from xenolf.
+func getOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) {
+ // TODO: Perhaps this should be synchronized too, with a Locker?
+
+ certificates, err := parseCertsFromPEMBundle(bundle)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // We expect the certificate slice to be ordered downwards the chain.
+ // SRV CRT -> CA. We need to pull the leaf and issuer certs out of it,
+ // which should always be the first two certificates. If there's no
+ // OCSP server listed in the leaf cert, there's nothing to do. And if
+ // we have only one certificate so far, we need to get the issuer cert.
+ issuedCert := certificates[0]
+ if len(issuedCert.OCSPServer) == 0 {
+ return nil, nil, fmt.Errorf("no OCSP server specified in certificate")
+ }
+ if len(certificates) == 1 {
+ if len(issuedCert.IssuingCertificateURL) == 0 {
+ return nil, nil, fmt.Errorf("no URL to issuing certificate")
+ }
+
+ resp, err := http.Get(issuedCert.IssuingCertificateURL[0])
+ if err != nil {
+ return nil, nil, fmt.Errorf("getting issuer certificate: %v", err)
+ }
+ defer resp.Body.Close()
+
+ issuerBytes, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024*1024))
+ if err != nil {
+ return nil, nil, fmt.Errorf("reading issuer certificate: %v", err)
+ }
+
+ issuerCert, err := x509.ParseCertificate(issuerBytes)
+ if err != nil {
+ return nil, nil, fmt.Errorf("parsing issuer certificate: %v", err)
+ }
+
+ // insert it into the slice on position 0;
+ // we want it ordered right SRV CRT -> CA
+ certificates = append(certificates, issuerCert)
+ }
+
+ issuerCert := certificates[1]
+
+ ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil)
+ if err != nil {
+ return nil, nil, fmt.Errorf("creating OCSP request: %v", err)
+ }
+
+ reader := bytes.NewReader(ocspReq)
+ req, err := http.Post(issuedCert.OCSPServer[0], "application/ocsp-request", reader)
+ if err != nil {
+ return nil, nil, fmt.Errorf("making OCSP request: %v", err)
+ }
+ defer req.Body.Close()
+
+ ocspResBytes, err := ioutil.ReadAll(io.LimitReader(req.Body, 1024*1024))
+ if err != nil {
+ return nil, nil, fmt.Errorf("reading OCSP response: %v", err)
+ }
+
+ ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
+ if err != nil {
+ return nil, nil, fmt.Errorf("parsing OCSP response: %v", err)
+ }
+
+ return ocspResBytes, ocspRes, nil
+}
+
+// freshOCSP returns true if resp is still fresh,
+// meaning that it is not expedient to get an
+// updated response from the OCSP server.
+func freshOCSP(resp *ocsp.Response) bool {
+ nextUpdate := resp.NextUpdate
+ // If there is an OCSP responder certificate, and it expires before the
+ // OCSP response, use its expiration date as the end of the OCSP
+ // response's validity period.
+ if resp.Certificate != nil && resp.Certificate.NotAfter.Before(nextUpdate) {
+ nextUpdate = resp.Certificate.NotAfter
+ }
+ // start checking OCSP staple about halfway through validity period for good measure
+ refreshTime := resp.ThisUpdate.Add(nextUpdate.Sub(resp.ThisUpdate) / 2)
+ return time.Now().Before(refreshTime)
+}