func loadKey(data []byte, password []byte) (*rsa.PrivateKey, error) {
	pemBlock, _ := pem.Decode(data)
	if pemBlock == nil {
		return nil, errors.New("PEM decode failed")
	}
	if pemBlock.Type != rsaPrivateKeyPEMBlockType {
		return nil, errors.New("unmatched type or headers")
	}
	b, err := x509.DecryptPEMBlock(pemBlock, password)
	if err != nil {
		return nil, err
	}
	return x509.ParsePKCS1PrivateKey(b)
}
load an encrypted private key from disk
package file
import (
	"bufio"
	"bytes"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math/big"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)
func NewFileDepot(path string) (*fileDepot, error) {
	f, err := os.OpenFile(fmt.Sprintf("%s/index.txt", path),
		os.O_RDONLY|os.O_CREATE, 0666)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return &fileDepot{dirPath: path}, nil
}
type fileDepot struct {
	dirPath string
}
func (d *fileDepot) CA(pass []byte) ([]*x509.Certificate, *rsa.PrivateKey, error) {
	caPEM, err := d.getFile("ca.pem")
	if err != nil {
		return nil, nil, err
	}
	cert, err := loadCert(caPEM.Data)
	if err != nil {
		return nil, nil, err
	}
	keyPEM, err := d.getFile("ca.key")
	if err != nil {
		return nil, nil, err
	}
	key, err := loadKey(keyPEM.Data, pass)
	if err != nil {
		return nil, nil, err
	}
	return []*x509.Certificate{cert}, key, nil
}
const (
	certPerm   = 0444
	serialPerm = 0400
	dbPerm     = 0600
)
func (d *fileDepot) Put(cn string, crt *x509.Certificate) error {
	if crt == nil {
		return errors.New("crt is nil")
	}
	if crt.Raw == nil {
		return errors.New("data is nil")
	}
	data := crt.Raw
	if err := os.MkdirAll(d.dirPath, 0755); err != nil {
		return err
	}
	serial, err := d.Serial()
	if err != nil {
		return err
	}
	if crt.Subject.CommonName == "" {
		cn = fmt.Sprintf("%x", sha256.Sum256(crt.Raw))
	}
	filename := fmt.Sprintf("%s.%s.pem", cn, serial.String())
	filepath := d.path(filename)
	file, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, certPerm)
	if err != nil {
		return err
	}
	defer file.Close()
	if _, err := file.Write(pemCert(data)); err != nil {
		os.Remove(filepath)
		return err
	}
	if err := d.writeDB(cn, serial, filename, crt); err != nil {
		return err
	}
	if err := d.incrementSerial(serial); err != nil {
		return err
	}
	return nil
}
func (d *fileDepot) Serial() (*big.Int, error) {
	name := d.path("serial")
	s := big.NewInt(2)
	if err := d.check("serial"); err != nil {
		if err := d.writeSerial(s); err != nil {
			return nil, err
		}
		return s, nil
	}
	file, err := os.Open(name)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	r := bufio.NewReader(file)
	data, err := r.ReadString('\r')
	if err != nil && err != io.EOF {
		return nil, err
	}
	data = strings.TrimSuffix(data, "\r")
	data = strings.TrimSuffix(data, "\n")
	serial, ok := s.SetString(data, 16)
	if !ok {
		return nil, errors.New("could not convert " + string(data) + " to serial number")
	}
	return serial, nil
}
func makeOpenSSLTime(t time.Time) string {
	y := (int(t.Year()) % 100)
	validDate := fmt.Sprintf("%02d%02d%02d%02d%02d%02dZ", y, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())
	return validDate
}
func makeDn(cert *x509.Certificate) string {
	var dn bytes.Buffer
	if len(cert.Subject.Country) > 0 && len(cert.Subject.Country[0]) > 0 {
		dn.WriteString("/C=" + cert.Subject.Country[0])
	}
	if len(cert.Subject.Province) > 0 && len(cert.Subject.Province[0]) > 0 {
		dn.WriteString("/ST=" + cert.Subject.Province[0])
	}
	if len(cert.Subject.Locality) > 0 && len(cert.Subject.Locality[0]) > 0 {
		dn.WriteString("/L=" + cert.Subject.Locality[0])
	}
	if len(cert.Subject.Organization) > 0 && len(cert.Subject.Organization[0]) > 0 {
		dn.WriteString("/O=" + cert.Subject.Organization[0])
	}
	if len(cert.Subject.OrganizationalUnit) > 0 && len(cert.Subject.OrganizationalUnit[0]) > 0 {
		dn.WriteString("/OU=" + cert.Subject.OrganizationalUnit[0])
	}
	if len(cert.Subject.CommonName) > 0 {
		dn.WriteString("/CN=" + cert.Subject.CommonName)
	}
	if len(cert.EmailAddresses) > 0 {
		dn.WriteString("/emailAddress=" + cert.EmailAddresses[0])
	}
	return dn.String()
}
func (d *fileDepot) HasCN(_ string, allowTime int, cert *x509.Certificate, revokeOldCertificate bool) (bool, error) {
	var addDB bytes.Buffer
	candidates := make(map[string]string)
	dn := makeDn(cert)
	if err := os.MkdirAll(d.dirPath, 0755); err != nil {
		return false, err
	}
	name := d.path("index.txt")
	file, err := os.Open(name)
	if err != nil {
		return false, err
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasSuffix(line, dn) {
			if strings.HasPrefix(line, "R\t") {
				entries := strings.Split(line, "\t")
				serial := strings.ToUpper(entries[3])
				candidates[serial] = line
				delete(candidates, serial)
				addDB.WriteString(line + "\n")
			} else if strings.HasPrefix(line, "V\t") {
				issueDate, err := strconv.ParseInt(strings.Replace(strings.Split(line, "\t")[1], "Z", "", 1), 10, 64)
				if err != nil {
					return false, errors.New("Could not get expiry date from ca db")
				}
				minimalRenewDate, err := strconv.ParseInt(strings.Replace(makeOpenSSLTime(time.Now().AddDate(0, 0, allowTime).UTC()), "Z", "", 1), 10, 64)
				if err != nil {
					return false, errors.New("Could not calculate expiry date")
				}
				entries := strings.Split(line, "\t")
				serial := strings.ToUpper(entries[3])
				if minimalRenewDate < issueDate && allowTime > 0 {
					candidates[serial] = "no"
				} else {
					candidates[serial] = line
				}
			}
		} else {
			addDB.WriteString(line + "\n")
		}
	}
	file.Close()
	for key, value := range candidates {
		if value == "no" {
			return false, errors.New("DN " + dn + " already exists")
		}
		if revokeOldCertificate {
			fmt.Println("Revoking certificate with serial " + key + " from DB. Recreation of CRL needed.")
			entries := strings.Split(value, "\t")
			addDB.WriteString("R\t" + entries[1] + "\t" + makeOpenSSLTime(time.Now()) + "\t" + strings.ToUpper(entries[3]) + "\t" + entries[4] + "\t" + entries[5] + "\n")
		}
	}
	if err := scanner.Err(); err != nil {
		return false, err
	}
	if revokeOldCertificate {
		file, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR, dbPerm)
		if err != nil {
			return false, err
		}
		if _, err := file.Write(addDB.Bytes()); err != nil {
			return false, err
		}
	}
	return true, nil
}
func (d *fileDepot) writeDB(cn string, serial *big.Int, filename string, cert *x509.Certificate) error {
	var dbEntry bytes.Buffer
	if _, err := d.HasCN(cn, 0, cert, true); err != nil {
		return err
	}
	if err := os.MkdirAll(d.dirPath, 0755); err != nil {
		return err
	}
	name := d.path("index.txt")
	file, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_APPEND, dbPerm)
	if err != nil {
		return fmt.Errorf("could not append to "+name+" : %q\n", err.Error())
	}
	defer file.Close()
	serialHex := fmt.Sprintf("%X", cert.SerialNumber)
	if len(serialHex)%2 == 1 {
		serialHex = fmt.Sprintf("0%s", serialHex)
	}
	validDate := makeOpenSSLTime(cert.NotAfter)
	dn := makeDn(cert)
	dbEntry.WriteString("V\t")
	dbEntry.WriteString(validDate + "\t")
	dbEntry.WriteString("\t")
	dbEntry.WriteString(serialHex + "\t")
	dbEntry.WriteString(filename + "\t")
	dbEntry.WriteString(dn)
	dbEntry.WriteString("\n")
	if _, err := file.Write(dbEntry.Bytes()); err != nil {
		return err
	}
	return nil
}
func (d *fileDepot) writeSerial(serial *big.Int) error {
	if err := os.MkdirAll(d.dirPath, 0755); err != nil {
		return err
	}
	name := d.path("serial")
	os.Remove(name)
	file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, serialPerm)
	if err != nil {
		return err
	}
	defer file.Close()
	if _, err := file.WriteString(fmt.Sprintf("%x\n", serial.Bytes())); err != nil {
		os.Remove(name)
		return err
	}
	return nil
}
func (d *fileDepot) incrementSerial(s *big.Int) error {
	serial := s.Add(s, big.NewInt(1))
	if err := d.writeSerial(serial); err != nil {
		return err
	}
	return nil
}
type file struct {
	Info os.FileInfo
	Data []byte
}
func (d *fileDepot) check(path string) error {
	name := d.path(path)
	_, err := os.Stat(name)
	if err != nil {
		return err
	}
	return nil
}
func (d *fileDepot) getFile(path string) (*file, error) {
	if err := d.check(path); err != nil {
		return nil, err
	}
	fi, err := os.Stat(d.path(path))
	if err != nil {
		return nil, err
	}
	b, err := ioutil.ReadFile(d.path(path))
	return &file{fi, b}, err
}
func (d *fileDepot) path(name string) string {
	return filepath.Join(d.dirPath, name)
}
const (
	rsaPrivateKeyPEMBlockType = "RSA PRIVATE KEY"
	certificatePEMBlockType   = "CERTIFICATE"
)