github.com/StackExchange/DNSControl@v0.2.8/pkg/acme/directoryStorage.go (about) 1 package acme 2 3 import ( 4 "crypto/x509" 5 "encoding/json" 6 "encoding/pem" 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 12 "github.com/xenolf/lego/acme" 13 ) 14 15 // directoryStorage implements storage in a local file directory 16 type directoryStorage string 17 18 // filename for certificate / key / json file 19 func (d directoryStorage) certFile(name, ext string) string { 20 return filepath.Join(d.certDir(name), name+"."+ext) 21 } 22 func (d directoryStorage) certDir(name string) string { 23 return filepath.Join(string(d), "certificates", name) 24 } 25 26 func (d directoryStorage) accountDirectory(acmeHost string) string { 27 return filepath.Join(string(d), ".letsencrypt", acmeHost) 28 } 29 30 func (d directoryStorage) accountFile(acmeHost string) string { 31 return filepath.Join(d.accountDirectory(acmeHost), "account.json") 32 } 33 func (d directoryStorage) accountKeyFile(acmeHost string) string { 34 return filepath.Join(d.accountDirectory(acmeHost), "account.key") 35 } 36 37 // TODO: probably lock these down more 38 const perms os.FileMode = 0644 39 const dirPerms os.FileMode = 0700 40 41 func (d directoryStorage) GetCertificate(name string) (*acme.CertificateResource, error) { 42 f, err := os.Open(d.certFile(name, "json")) 43 if err != nil && os.IsNotExist(err) { 44 // if json does not exist, nothing does 45 return nil, nil 46 } 47 if err != nil { 48 return nil, err 49 } 50 defer f.Close() 51 dec := json.NewDecoder(f) 52 cr := &acme.CertificateResource{} 53 if err = dec.Decode(cr); err != nil { 54 return nil, err 55 } 56 // load cert 57 crtBytes, err := ioutil.ReadFile(d.certFile(name, "crt")) 58 if err != nil { 59 return nil, err 60 } 61 cr.Certificate = crtBytes 62 return cr, nil 63 } 64 65 func (d directoryStorage) StoreCertificate(name string, cert *acme.CertificateResource) error { 66 // make sure actual cert data never gets into metadata json 67 if err := os.MkdirAll(d.certDir(name), dirPerms); err != nil { 68 return err 69 } 70 pub := cert.Certificate 71 cert.Certificate = nil 72 priv := cert.PrivateKey 73 cert.PrivateKey = nil 74 jDAt, err := json.MarshalIndent(cert, "", " ") 75 if err != nil { 76 return err 77 } 78 if err = ioutil.WriteFile(d.certFile(name, "json"), jDAt, perms); err != nil { 79 return err 80 } 81 if err = ioutil.WriteFile(d.certFile(name, "crt"), pub, perms); err != nil { 82 return err 83 } 84 return ioutil.WriteFile(d.certFile(name, "key"), priv, perms) 85 } 86 87 func (d directoryStorage) GetAccount(acmeHost string) (*Account, error) { 88 f, err := os.Open(d.accountFile(acmeHost)) 89 if err != nil && os.IsNotExist(err) { 90 return nil, nil 91 } 92 if err != nil { 93 return nil, err 94 } 95 defer f.Close() 96 dec := json.NewDecoder(f) 97 acct := &Account{} 98 if err = dec.Decode(acct); err != nil { 99 return nil, err 100 } 101 keyBytes, err := ioutil.ReadFile(d.accountKeyFile(acmeHost)) 102 if err != nil { 103 return nil, err 104 } 105 keyBlock, _ := pem.Decode(keyBytes) 106 if keyBlock == nil { 107 return nil, fmt.Errorf("Error decoding account private key") 108 } 109 acct.key, err = x509.ParseECPrivateKey(keyBlock.Bytes) 110 if err != nil { 111 return nil, err 112 } 113 return acct, nil 114 } 115 116 func (d directoryStorage) StoreAccount(acmeHost string, account *Account) error { 117 if err := os.MkdirAll(d.accountDirectory(acmeHost), dirPerms); err != nil { 118 return err 119 } 120 acctBytes, err := json.MarshalIndent(account, "", " ") 121 if err != nil { 122 return err 123 } 124 if err = ioutil.WriteFile(d.accountFile(acmeHost), acctBytes, perms); err != nil { 125 return err 126 } 127 keyBytes, err := x509.MarshalECPrivateKey(account.key) 128 if err != nil { 129 return err 130 } 131 pemKey := &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes} 132 pemBytes := pem.EncodeToMemory(pemKey) 133 return ioutil.WriteFile(d.accountKeyFile(acmeHost), pemBytes, perms) 134 }