diff options
| author | Christine Dodrill <me@christine.website> | 2018-10-05 10:02:55 -0700 |
|---|---|---|
| committer | Christine Dodrill <me@christine.website> | 2018-10-05 14:31:22 -0700 |
| commit | f84de97aac5865fc17ee28cb1af7f25005ee321b (patch) | |
| tree | 92c767bacd170ba8fbff07794abb5f0951af5eb2 /localca/localca.go | |
| parent | d0007f122fe353a6d3796b12f8c7dbdb753811ac (diff) | |
| download | x-f84de97aac5865fc17ee28cb1af7f25005ee321b.tar.xz x-f84de97aac5865fc17ee28cb1af7f25005ee321b.zip | |
import libraries
Diffstat (limited to 'localca/localca.go')
| -rw-r--r-- | localca/localca.go | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/localca/localca.go b/localca/localca.go new file mode 100644 index 0000000..abc0f35 --- /dev/null +++ b/localca/localca.go @@ -0,0 +1,127 @@ +package localca + +import ( + "context" + "crypto/tls" + "encoding/pem" + "errors" + "strings" + "time" + + "github.com/Xe/ln" + "github.com/Xe/ln/opname" + "golang.org/x/crypto/acme/autocert" +) + +var ( + ErrBadData = errors.New("localca: certificate data is bad") + ErrDomainDoesntHaveSuffix = errors.New("localca: domain doesn't have the given suffix") +) + +// Manager automatically provisions and caches TLS certificates in a given +// autocert Cache. If it cannot fetch a certificate on demand, the certificate +// is dynamically generated with a lifetime of 100 years, which should be good +// enough. +type Manager struct { + Cache autocert.Cache + DomainSuffix string + + *issuer +} + +// New creates a new Manager with the given key filename, certificate filename, +// allowed domain suffix and autocert cache. All given certificates will be +// created if they don't already exist. +func New(keyFile, certFile, suffix string, cache autocert.Cache) (Manager, error) { + iss, err := getIssuer(keyFile, certFile, true) + + if err != nil { + return Manager{}, err + } + + result := Manager{ + DomainSuffix: suffix, + Cache: cache, + issuer: iss, + } + + return result, nil +} + +func (m Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + name := hello.ServerName + if !strings.Contains(strings.Trim(name, "."), ".") { + return nil, errors.New("localca: server name component count invalid") + } + + if !strings.HasSuffix(name, m.DomainSuffix) { + return nil, ErrDomainDoesntHaveSuffix + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + ctx = opname.With(ctx, "localca.Manager.GetCertificate") + ctx = ln.WithF(ctx, ln.F{"server_name": name}) + + data, err := m.Cache.Get(ctx, name) + if err != nil && err != autocert.ErrCacheMiss { + return nil, err + } + + if err == autocert.ErrCacheMiss { + data, _, err = m.issuer.sign([]string{name}, nil) + if err != nil { + return nil, err + } + err = m.Cache.Put(ctx, name, data) + if err != nil { + return nil, err + } + } + + cert, err := loadCertificate(name, data) + if err != nil { + return nil, err + } + + ln.Log(ctx, ln.Info("returned cert successfully")) + + return cert, nil +} + +func loadCertificate(name string, data []byte) (*tls.Certificate, error) { + priv, pub := pem.Decode(data) + if priv == nil || !strings.Contains(priv.Type, "PRIVATE") { + return nil, ErrBadData + } + privKey, err := parsePrivateKey(priv.Bytes) + if err != nil { + return nil, err + } + + // public + var pubDER [][]byte + for len(pub) > 0 { + var b *pem.Block + b, pub = pem.Decode(pub) + if b == nil { + break + } + pubDER = append(pubDER, b.Bytes) + } + if len(pub) > 0 { + return nil, ErrBadData + } + + // verify and create TLS cert + leaf, err := validCert(name, pubDER, privKey, time.Now()) + if err != nil { + return nil, err + } + tlscert := &tls.Certificate{ + Certificate: pubDER, + PrivateKey: privKey, + Leaf: leaf, + } + return tlscert, nil +} |
