Newer
Older
package acmeserver
import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"git.autistici.org/ai3/replds"
)
type dirStorage struct {
root string
}
func (d *dirStorage) GetCert(cn string) ([][]byte, crypto.Signer, error) {
certPath := filepath.Join(d.root, cn, "fullchain.pem")
der, err := parseCertsFromFile(certPath)
if err != nil {
return nil, nil, err
}
priv, err := parsePrivateKeyFromFile(keyPath)
if err != nil {
return nil, nil, err
}
return der, priv, nil
}
func (d *dirStorage) PutCert(cn string, der [][]byte, key crypto.Signer) error {
filemap, err := dumpCertsAndKey(cn, der, key)
if err != nil {
return err
}
dir := filepath.Join(d.root, cn)
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
return err
}
}
for path, data := range filemap {
var mode os.FileMode = 0644
log.Printf("writing %s (%03o)", path, mode)
if err := ioutil.WriteFile(filepath.Join(d.root, path), data, mode); err != nil {
return err
}
}
return nil
}
func dumpCertsAndKey(cn string, der [][]byte, key crypto.Signer) (map[string][]byte, error) {
m := make(map[string][]byte)
data, err := encodeCerts(der)
if err != nil {
return nil, err
}
m[filepath.Join(cn, "fullchain.pem")] = data
data, err = encodeCerts(der[:1])
if err != nil {
return nil, err
}
m[filepath.Join(cn, "cert.pem")] = data
data, err = encodePrivateKey(key)
if err != nil {
return nil, err
}
return m, nil
}
// The replStorage overrides the PutCert method and writes the
// certificates to replds instead.
type replStorage struct {
*dirStorage
replClient replds.PublicClient
}
func (d *replStorage) PutCert(cn string, der [][]byte, key crypto.Signer) error {
filemap, err := dumpCertsAndKey(cn, der, key)
if err != nil {
return err
}
now := time.Now()
var req replds.SetNodesRequest
for path, data := range filemap {
req.Nodes = append(req.Nodes, replds.Node{
Path: path,
Value: data,
Timestamp: now,
})
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
resp, err := d.replClient.SetNodes(ctx, &req)
if err != nil {
return err
}
if resp.HostsOk < 1 {
return errors.New("not enough successful replds writes")
}
return nil
}
func parseCertsFromFile(path string) ([][]byte, error) {
if err != nil {
return nil, err
}
var der [][]byte
for {
block, rest := pem.Decode(data)
if block == nil {
break
}
der = append(der, block.Bytes)
data = rest
}
return der, nil
}
func parsePrivateKeyFromFile(path string) (crypto.Signer, error) {
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
if err != nil {
return nil, err
}
priv, _ := pem.Decode(data)
if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
return nil, errors.New("invalid account key")
}
return parsePrivateKey(priv.Bytes)
}
func encodeCerts(der [][]byte) ([]byte, error) {
var buf bytes.Buffer
for _, b := range der {
pb := &pem.Block{Type: "CERTIFICATE", Bytes: b}
if err := pem.Encode(&buf, pb); err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}
func encodePrivateKey(key crypto.Signer) ([]byte, error) {
var pb *pem.Block
switch priv := key.(type) {
case *rsa.PrivateKey:
pb = &pem.Block{
Bytes: x509.MarshalPKCS1PrivateKey(priv),
}
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, err
}
pb = &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: b,
}
default:
return nil, errors.New("unknown private key type")
}
var buf bytes.Buffer
if err := pem.Encode(&buf, pb); err != nil {
return nil, err
}
return buf.Bytes(), nil
}