// moveStorage moves the old certificate storage location by // renaming the "letsencrypt" folder to the hostname of the // CA URL. This is TEMPORARY until most users have upgraded to 0.9+. func moveStorage() { oldPath := filepath.Join(caddy.AssetsPath(), "letsencrypt") _, err := os.Stat(oldPath) if os.IsNotExist(err) { return } newPath, err := caddytls.StorageFor(caddytls.DefaultCAUrl) if err != nil { log.Fatalf("[ERROR] Unable to get new path for certificate storage: %v", err) } err = os.MkdirAll(string(newPath), 0700) if err != nil { log.Fatalf("[ERROR] Unable to make new certificate storage path: %v\n\nPlease follow instructions at:\nhttps://github.com/mholt/caddy/issues/902#issuecomment-228876011", err) } err = os.Rename(oldPath, string(newPath)) if err != nil { log.Fatalf("[ERROR] Unable to migrate certificate storage: %v\n\nPlease follow instructions at:\nhttps://github.com/mholt/caddy/issues/902#issuecomment-228876011", err) } // convert mixed case folder and file names to lowercase filepath.Walk(string(newPath), func(path string, info os.FileInfo, err error) error { // must be careful to only lowercase the base of the path, not the whole thing!! base := filepath.Base(path) if lowerBase := strings.ToLower(base); base != lowerBase { lowerPath := filepath.Join(filepath.Dir(path), lowerBase) err = os.Rename(path, lowerPath) if err != nil { log.Fatalf("[ERROR] Unable to lower-case: %v\n\nPlease follow instructions at:\nhttps://github.com/mholt/caddy/issues/902#issuecomment-228876011", err) } } return nil }) }
// moveStorage moves the old certificate storage location by // renaming the "letsencrypt" folder to the hostname of the // CA URL. This is TEMPORARY until most users have upgraded to 0.9+. func moveStorage() { oldPath := filepath.Join(caddy.AssetsPath(), "letsencrypt") _, err := os.Stat(oldPath) if os.IsNotExist(err) { return } // Just use a default config to get default (file) storage fileStorage, err := new(caddytls.Config).StorageFor(caddytls.DefaultCAUrl) if err != nil { log.Fatalf("[ERROR] Unable to get new path for certificate storage: %v", err) } newPath := string(fileStorage.(caddytls.FileStorage)) err = os.MkdirAll(string(newPath), 0700) if err != nil { log.Fatalf("[ERROR] Unable to make new certificate storage path: %v\n\nPlease follow instructions at:\nhttps://github.com/mholt/caddy/issues/902#issuecomment-228876011", err) } err = os.Rename(oldPath, string(newPath)) if err != nil { log.Fatalf("[ERROR] Unable to migrate certificate storage: %v\n\nPlease follow instructions at:\nhttps://github.com/mholt/caddy/issues/902#issuecomment-228876011", err) } // convert mixed case folder and file names to lowercase var done bool // walking is recursive and preloads the file names, so we must restart walk after a change until no changes for !done { done = true filepath.Walk(string(newPath), func(path string, info os.FileInfo, err error) error { // must be careful to only lowercase the base of the path, not the whole thing!! base := filepath.Base(path) if lowerBase := strings.ToLower(base); base != lowerBase { lowerPath := filepath.Join(filepath.Dir(path), lowerBase) err = os.Rename(path, lowerPath) if err != nil { log.Fatalf("[ERROR] Unable to lower-case: %v\n\nPlease follow instructions at:\nhttps://github.com/mholt/caddy/issues/902#issuecomment-228876011", err) } // terminate traversal and restart since Walk needs the updated file list with new file names done = false return errors.New("start over") } return nil }) } }
import ( "github.com/mholt/caddy" "io/ioutil" "net/url" "os" "path/filepath" "strings" ) func init() { RegisterStorageProvider("file", FileStorageCreator) } // storageBasePath is the root path in which all TLS/ACME assets are // stored. Do not change this value during the lifetime of the program. var storageBasePath = filepath.Join(caddy.AssetsPath(), "acme") // FileStorageCreator creates a new Storage instance backed by the local // disk. The resulting Storage instance is guaranteed to be non-nil if // there is no error. This can be used by "middleware" implementations that // may want to proxy the disk storage. func FileStorageCreator(caURL *url.URL) (Storage, error) { return FileStorage(filepath.Join(storageBasePath, caURL.Host)), nil } // FileStorage is a root directory and facilitates forming file paths derived // from it. It is used to get file paths in a consistent, cross- platform way // for persisting ACME assets on the file system. type FileStorage string // sites gets the directory that stores site certificate and keys.
continue } resp, err := ocsp.ParseResponse(ocspBytes, nil) if err != nil { // contents are invalid; delete it err = os.Remove(stapleFile) if err != nil { log.Printf("[ERROR] Purging corrupt staple file %s: %v", stapleFile, err) } } if time.Now().After(resp.NextUpdate) { // response has expired; delete it err = os.Remove(stapleFile) if err != nil { log.Printf("[ERROR] Purging expired staple file %s: %v", stapleFile, err) } } } } // 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 { // start checking OCSP staple about halfway through validity period for good measure refreshTime := resp.ThisUpdate.Add(resp.NextUpdate.Sub(resp.ThisUpdate) / 2) return time.Now().Before(refreshTime) } var ocspFolder = filepath.Join(caddy.AssetsPath(), "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. func stapleOCSP(cert *Certificate, pemBundle []byte) error { if pemBundle == nil { // The function in the acme package that gets OCSP requires a PEM-encoded cert 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. // TODO: Use Storage interface instead of disk directly var ocspFileNamePrefix string if len(cert.Names) > 0 { ocspFileNamePrefix = cert.Names[0] + "-" } ocspFileName := ocspFileNamePrefix + fastHash(pemBundle) ocspCachePath := filepath.Join(ocspFolder, ocspFileName) cachedOCSP, err := ioutil.ReadFile(ocspCachePath) 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 := os.Remove(ocspCachePath) 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 = acme.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 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 { cert.Certificate.OCSPStaple = ocspBytes cert.OCSP = ocspResp if gotNewOCSP { err := os.MkdirAll(filepath.Join(caddy.AssetsPath(), "ocsp"), 0700) if err != nil { return fmt.Errorf("unable to make OCSP staple path for %v: %v", cert.Names, err) } err = ioutil.WriteFile(ocspCachePath, ocspBytes, 0644) if err != nil { return fmt.Errorf("unable to write OCSP staple file for %v: %v", cert.Names, err) } } } return nil }