1 // Copyright 2016 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 5 package autocert
6 7 import (
8 "context"
9 "errors"
10 "os"
11 "path/filepath"
12 )
13 14 // ErrCacheMiss is returned when a certificate is not found in cache.
15 var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
16 17 // Cache is used by Manager to store and retrieve previously obtained certificates
18 // and other account data as opaque blobs.
19 //
20 // Cache implementations should not rely on the key naming pattern. Keys can
21 // include any printable ASCII characters, except the following: \/:*?"<>|
22 type Cache interface {
23 // Get returns a certificate data for the specified key.
24 // If there's no such key, Get returns ErrCacheMiss.
25 Get(ctx context.Context, key string) ([]byte, error)
26 27 // Put stores the data in the cache under the specified key.
28 // Underlying implementations may use any data storage format,
29 // as long as the reverse operation, Get, results in the original data.
30 Put(ctx context.Context, key string, data []byte) error
31 32 // Delete removes a certificate data from the cache under the specified key.
33 // If there's no such key in the cache, Delete returns nil.
34 Delete(ctx context.Context, key string) error
35 }
36 37 // DirCache implements Cache using a directory on the local filesystem.
38 // If the directory does not exist, it will be created with 0700 permissions.
39 type DirCache string
40 41 // Get reads a certificate data from the specified file name.
42 func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
43 name = filepath.Join(string(d), filepath.Clean("/"+name))
44 var (
45 data []byte
46 err error
47 done = make(chan struct{})
48 )
49 go func() {
50 data, err = os.ReadFile(name)
51 close(done)
52 }()
53 select {
54 case <-ctx.Done():
55 return nil, ctx.Err()
56 case <-done:
57 }
58 if os.IsNotExist(err) {
59 return nil, ErrCacheMiss
60 }
61 return data, err
62 }
63 64 // Put writes the certificate data to the specified file name.
65 // The file will be created with 0600 permissions.
66 func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
67 if err := os.MkdirAll(string(d), 0700); err != nil {
68 return err
69 }
70 71 done := make(chan struct{})
72 var err error
73 go func() {
74 defer close(done)
75 var tmp string
76 if tmp, err = d.writeTempFile(name, data); err != nil {
77 return
78 }
79 defer os.Remove(tmp)
80 select {
81 case <-ctx.Done():
82 // Don't overwrite the file if the context was canceled.
83 default:
84 newName := filepath.Join(string(d), filepath.Clean("/"+name))
85 err = os.Rename(tmp, newName)
86 }
87 }()
88 select {
89 case <-ctx.Done():
90 return ctx.Err()
91 case <-done:
92 }
93 return err
94 }
95 96 // Delete removes the specified file name.
97 func (d DirCache) Delete(ctx context.Context, name string) error {
98 name = filepath.Join(string(d), filepath.Clean("/"+name))
99 var (
100 err error
101 done = make(chan struct{})
102 )
103 go func() {
104 err = os.Remove(name)
105 close(done)
106 }()
107 select {
108 case <-ctx.Done():
109 return ctx.Err()
110 case <-done:
111 }
112 if err != nil && !os.IsNotExist(err) {
113 return err
114 }
115 return nil
116 }
117 118 // writeTempFile writes b to a temporary file, closes the file and returns its path.
119 func (d DirCache) writeTempFile(prefix string, b []byte) (name string, reterr error) {
120 // TempFile uses 0600 permissions
121 f, err := os.CreateTemp(string(d), prefix)
122 if err != nil {
123 return "", err
124 }
125 defer func() {
126 if reterr != nil {
127 os.Remove(f.Name())
128 }
129 }()
130 if _, err := f.Write(b); err != nil {
131 f.Close()
132 return "", err
133 }
134 return f.Name(), f.Close()
135 }
136