package certificates import ( "crypto/tls" "crypto/x509" "encoding/pem" "errors" "fmt" "os" "path/filepath" ) // storage manages certificate/key material on disk. Private keys are // written with 0600 permissions and their directories with 0700; nothing // under here is ever written to a log. type storage struct { root string } func newStorage(root string) (*storage, error) { if err := os.MkdirAll(filepath.Join(root, "accounts"), 0o700); err != nil { return nil, err } if err := os.MkdirAll(filepath.Join(root, "certs"), 0o700); err != nil { return nil, err } return &storage{root: root}, nil } func (s *storage) certDir(name string) string { return filepath.Join(s.root, "certs", safeName(name)) } func safeName(name string) string { out := make([]rune, 0, len(name)) for _, r := range name { switch { case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '-', r == '_', r == '.': out = append(out, r) default: out = append(out, '_') } } if len(out) == 0 { return "cert" } return string(out) } // SavePair writes fullchain.pem and privkey.pem for the given logical // certificate name and returns their paths. func (s *storage) SavePair(name string, certPEM, keyPEM []byte) (certPath, keyPath string, err error) { dir := s.certDir(name) if err = os.MkdirAll(dir, 0o700); err != nil { return } certPath = filepath.Join(dir, "fullchain.pem") keyPath = filepath.Join(dir, "privkey.pem") if err = os.WriteFile(certPath, certPEM, 0o644); err != nil { return } if err = os.WriteFile(keyPath, keyPEM, 0o600); err != nil { return } return } func (s *storage) accountKeyPath() string { return filepath.Join(s.root, "accounts", "account.key") } func loadTLSCertificate(certPath, keyPath string) (*tls.Certificate, error) { cert, err := tls.LoadX509KeyPair(certPath, keyPath) if err != nil { return nil, err } if len(cert.Certificate) == 0 { return nil, errors.New("empty certificate chain") } leaf, err := x509.ParseCertificate(cert.Certificate[0]) if err != nil { return nil, err } cert.Leaf = leaf return &cert, nil } // ValidateKeyPair checks that certPEM/keyPEM parse and form a matching // pair before anything is written to disk. func ValidateKeyPair(certPEM, keyPEM []byte) (*x509.Certificate, error) { tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) if err != nil { return nil, fmt.Errorf("certificate/key do not match: %w", err) } block, _ := pem.Decode(certPEM) if block == nil { return nil, errors.New("no PEM data found in certificate") } leaf, err := x509.ParseCertificate(tlsCert.Certificate[0]) if err != nil { return nil, err } return leaf, nil }