aboutsummaryrefslogtreecommitdiff
path: root/localca
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2023-06-19 12:56:31 -0400
committerXe Iaso <me@xeiaso.net>2023-06-19 12:56:31 -0400
commitc592b6d195aedcd6ec86e8c60d3ba91d524e293b (patch)
treefbdb9fe9331ce491d606402d4b62ba5999ce6122 /localca
parent84e8f57b98fd1038e6f2fc401277d936ef45522a (diff)
downloadx-c592b6d195aedcd6ec86e8c60d3ba91d524e293b.tar.xz
x-c592b6d195aedcd6ec86e8c60d3ba91d524e293b.zip
second reshuffling
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'localca')
-rw-r--r--localca/LICENSE21
-rw-r--r--localca/doc.go10
-rw-r--r--localca/legal.go27
-rw-r--r--localca/localca.go121
-rw-r--r--localca/localca_test.go83
-rw-r--r--localca/minica.go234
-rw-r--r--localca/utils.go83
7 files changed, 0 insertions, 579 deletions
diff --git a/localca/LICENSE b/localca/LICENSE
deleted file mode 100644
index 6aa3d70..0000000
--- a/localca/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2016 Jacob Hoffman-Andrews
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE. \ No newline at end of file
diff --git a/localca/doc.go b/localca/doc.go
deleted file mode 100644
index fb4e829..0000000
--- a/localca/doc.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// Package localca uses an autocert.Cache to store and generate TLS certificates
-// for domains on demand.
-//
-// This is kind of powerful, and as such it is limited to only generate
-// certificates as subdomains of a given domain.
-//
-// The design and implementation of this is kinda stolen from minica[1].
-//
-// [1]: https://github.com/jsha/minica
-package localca
diff --git a/localca/legal.go b/localca/legal.go
deleted file mode 100644
index 9d35318..0000000
--- a/localca/legal.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package localca
-
-import "go4.org/legal"
-
-func init() {
- legal.RegisterLicense(`MIT License
-
-Copyright (c) 2016 Jacob Hoffman-Andrews
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.`)
-}
diff --git a/localca/localca.go b/localca/localca.go
deleted file mode 100644
index 360b53f..0000000
--- a/localca/localca.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package localca
-
-import (
- "context"
- "crypto/tls"
- "encoding/pem"
- "errors"
- "strings"
- "time"
-
- "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()
-
- 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
- }
-
- 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
-}
diff --git a/localca/localca_test.go b/localca/localca_test.go
deleted file mode 100644
index 0c85fba..0000000
--- a/localca/localca_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package localca
-
-import (
- "context"
- "crypto/tls"
- "io"
- "io/ioutil"
- "os"
- "path"
- "testing"
- "time"
-
- "golang.org/x/crypto/acme/autocert"
-)
-
-func TestLocalCA(t *testing.T) {
- dir, err := ioutil.TempDir("", "localca-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
- cache := autocert.DirCache(dir)
-
- keyFile := path.Join(dir, "key.pem")
- certFile := path.Join(dir, "cert.pem")
- const suffix = "club"
-
- m, err := New(keyFile, certFile, suffix, cache)
- if err != nil {
- t.Fatal(err)
- }
-
- t.Run("local", func(t *testing.T) {
- _, err = m.GetCertificate(&tls.ClientHelloInfo{
- ServerName: "foo.local.cetacean.club",
- })
- if err != nil {
- t.Fatal(err)
- }
- })
-
- t.Run("network", func(t *testing.T) {
- t.Skip("no")
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
- tc := &tls.Config{
- GetCertificate: m.GetCertificate,
- }
-
- go func() {
- lis, err := tls.Listen("tcp", ":9293", tc)
- if err != nil {
- t.Fatal(err)
- }
- defer lis.Close()
-
- for {
- select {
- case <-ctx.Done():
- return
- default:
- }
-
- cli, err := lis.Accept()
- if err != nil {
- t.Fatal(err)
- }
- defer cli.Close()
-
- go io.Copy(cli, cli)
- }
- }()
-
- time.Sleep(130 * time.Millisecond)
- cli, err := tls.Dial("tcp", "localhost:9293", &tls.Config{InsecureSkipVerify: true})
- if err != nil {
- t.Fatal(err)
- }
- defer cli.Close()
-
- cli.Write([]byte("butts"))
- })
-}
diff --git a/localca/minica.go b/localca/minica.go
deleted file mode 100644
index d47330b..0000000
--- a/localca/minica.go
+++ /dev/null
@@ -1,234 +0,0 @@
-package localca
-
-import (
- "bytes"
- "crypto"
- "crypto/rand"
- "crypto/rsa"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/hex"
- "encoding/pem"
- "fmt"
- "io/ioutil"
- "math"
- "math/big"
- "net"
- "os"
- "strings"
- "time"
-)
-
-type issuer struct {
- key crypto.Signer
- cert *x509.Certificate
-}
-
-func getIssuer(keyFile, certFile string, autoCreate bool) (*issuer, error) {
- keyContents, keyErr := ioutil.ReadFile(keyFile)
- certContents, certErr := ioutil.ReadFile(certFile)
- if os.IsNotExist(keyErr) && os.IsNotExist(certErr) {
- err := makeIssuer(keyFile, certFile)
- if err != nil {
- return nil, err
- }
- return getIssuer(keyFile, certFile, false)
- } else if keyErr != nil {
- return nil, fmt.Errorf("%s (but %s exists)", keyErr, certFile)
- } else if certErr != nil {
- return nil, fmt.Errorf("%s (but %s exists)", certErr, keyFile)
- }
- key, err := readPrivateKey(keyContents)
- if err != nil {
- return nil, fmt.Errorf("reading private key from %s: %s", keyFile, err)
- }
-
- cert, err := readCert(certContents)
- if err != nil {
- return nil, fmt.Errorf("reading CA certificate from %s: %s", certFile, err)
- }
-
- equal, err := publicKeysEqual(key.Public(), cert.PublicKey)
- if err != nil {
- return nil, fmt.Errorf("comparing public keys: %s", err)
- } else if !equal {
- return nil, fmt.Errorf("public key in CA certificate %s doesn't match private key in %s",
- certFile, keyFile)
- }
- return &issuer{key, cert}, nil
-}
-
-func readPrivateKey(keyContents []byte) (crypto.Signer, error) {
- block, _ := pem.Decode(keyContents)
- if block == nil {
- return nil, fmt.Errorf("no PEM found")
- } else if block.Type != "RSA PRIVATE KEY" && block.Type != "ECDSA PRIVATE KEY" {
- return nil, fmt.Errorf("incorrect PEM type %s", block.Type)
- }
- return x509.ParsePKCS1PrivateKey(block.Bytes)
-}
-
-func readCert(certContents []byte) (*x509.Certificate, error) {
- block, _ := pem.Decode(certContents)
- if block == nil {
- return nil, fmt.Errorf("no PEM found")
- } else if block.Type != "CERTIFICATE" {
- return nil, fmt.Errorf("incorrect PEM type %s", block.Type)
- }
- return x509.ParseCertificate(block.Bytes)
-}
-
-func makeIssuer(keyFile, certFile string) error {
- keyData, key, err := makeKey()
- if err != nil {
- return err
- }
- ioutil.WriteFile(keyFile, keyData, 0600)
- certData, _, err := makeRootCert(key, certFile)
- if err != nil {
- return err
- }
- ioutil.WriteFile(certFile, certData, 0600)
- return nil
-}
-
-func makeKey() ([]byte, *rsa.PrivateKey, error) {
- key, err := rsa.GenerateKey(rand.Reader, 2048)
- if err != nil {
- return nil, nil, err
- }
- der := x509.MarshalPKCS1PrivateKey(key)
- if err != nil {
- return nil, nil, err
- }
- buf := bytes.NewBuffer([]byte{})
- err = pem.Encode(buf, &pem.Block{
- Type: "RSA PRIVATE KEY",
- Bytes: der,
- })
- if err != nil {
- return nil, nil, err
- }
- return buf.Bytes(), key, nil
-}
-
-func makeRootCert(key crypto.Signer, filename string) ([]byte, *x509.Certificate, error) {
- serial, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
- if err != nil {
- return nil, nil, err
- }
- template := &x509.Certificate{
- Subject: pkix.Name{
- CommonName: "localca root ca " + hex.EncodeToString(serial.Bytes()[:3]),
- },
- SerialNumber: serial,
- NotBefore: time.Now(),
- NotAfter: time.Now().AddDate(100, 0, 0),
-
- KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
- ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
- BasicConstraintsValid: true,
- IsCA: true,
- MaxPathLenZero: true,
- }
-
- der, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
- if err != nil {
- return nil, nil, err
- }
- buf := bytes.NewBuffer([]byte{})
- err = pem.Encode(buf, &pem.Block{
- Type: "CERTIFICATE",
- Bytes: der,
- })
- if err != nil {
- return nil, nil, err
- }
- result, err := x509.ParseCertificate(der)
- return buf.Bytes(), result, err
-}
-
-func parseIPs(ipAddresses []string) ([]net.IP, error) {
- var parsed []net.IP
- for _, s := range ipAddresses {
- p := net.ParseIP(s)
- if p == nil {
- return nil, fmt.Errorf("invalid IP address %s", s)
- }
- parsed = append(parsed, p)
- }
- return parsed, nil
-}
-
-func publicKeysEqual(a, b interface{}) (bool, error) {
- aBytes, err := x509.MarshalPKIXPublicKey(a)
- if err != nil {
- return false, err
- }
- bBytes, err := x509.MarshalPKIXPublicKey(b)
- if err != nil {
- return false, err
- }
- return bytes.Compare(aBytes, bBytes) == 0, nil
-}
-
-func (iss *issuer) sign(domains []string, ipAddresses []string) ([]byte, *x509.Certificate, error) {
- var cn string
- if len(domains) > 0 {
- cn = domains[0]
- } else if len(ipAddresses) > 0 {
- cn = ipAddresses[0]
- } else {
- return nil, nil, fmt.Errorf("must specify at least one domain name or IP address")
- }
- keyData, key, err := makeKey()
- if err != nil {
- return nil, nil, err
- }
- buf := bytes.NewBuffer([]byte{})
- buf.Write(keyData)
-
- parsedIPs, err := parseIPs(ipAddresses)
- if err != nil {
- return nil, nil, err
- }
- serial, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
- if err != nil {
- return nil, nil, err
- }
- template := &x509.Certificate{
- DNSNames: domains,
- IPAddresses: parsedIPs,
- Subject: pkix.Name{
- CommonName: cn,
- },
- SerialNumber: serial,
- NotBefore: time.Now(),
- NotAfter: time.Now().AddDate(90, 0, 0),
-
- KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
- ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
- BasicConstraintsValid: true,
- IsCA: false,
- }
- der, err := x509.CreateCertificate(rand.Reader, template, iss.cert, key.Public(), iss.key)
- if err != nil {
- return nil, nil, err
- }
- err = pem.Encode(buf, &pem.Block{
- Type: "CERTIFICATE",
- Bytes: der,
- })
- if err != nil {
- return nil, nil, err
- }
- result, err := x509.ParseCertificate(der)
- return buf.Bytes(), result, err
-}
-
-func split(s string) (results []string) {
- if len(s) > 0 {
- return strings.Split(s, ",")
- }
- return nil
-}
diff --git a/localca/utils.go b/localca/utils.go
deleted file mode 100644
index efc06f3..0000000
--- a/localca/utils.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package localca
-
-import (
- "crypto"
- "crypto/ecdsa"
- "crypto/rsa"
- "crypto/x509"
- "errors"
- "time"
-)
-
-// validCert parses a cert chain provided as der argument and verifies the leaf and der[0]
-// correspond to the private key, the domain and key type match, and expiration dates
-// are valid. It doesn't do any revocation checking.
-//
-// The returned value is the verified leaf cert.
-func validCert(name string, der [][]byte, key crypto.Signer, now time.Time) (leaf *x509.Certificate, err error) {
- // parse public part(s)
- var n int
- for _, b := range der {
- n += len(b)
- }
- pub := make([]byte, n)
- n = 0
- for _, b := range der {
- n += copy(pub[n:], b)
- }
- x509Cert, err := x509.ParseCertificates(pub)
- if err != nil || len(x509Cert) == 0 {
- return nil, errors.New("localca: no public key found")
- }
- // verify the leaf is not expired and matches the domain name
- leaf = x509Cert[0]
- if now.Before(leaf.NotBefore) {
- return nil, errors.New("localca: certificate is not valid yet")
- }
- if now.After(leaf.NotAfter) {
- return nil, errors.New("localca: expired certificate")
- }
- if err := leaf.VerifyHostname(name); err != nil {
- return nil, err
- }
- // ensure the leaf corresponds to the private key and matches the certKey type
- switch pub := leaf.PublicKey.(type) {
- case *rsa.PublicKey:
- prv, ok := key.(*rsa.PrivateKey)
- if !ok {
- return nil, errors.New("localca: private key type does not match public key type")
- }
- if pub.N.Cmp(prv.N) != 0 {
- return nil, errors.New("localca: private key does not match public key")
- }
- default:
- return nil, errors.New("localca: unknown public key algorithm")
- }
- return leaf, nil
-}
-
-// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
-// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
-// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
-//
-// Inspired by parsePrivateKey in crypto/tls/tls.go.
-func parsePrivateKey(der []byte) (crypto.Signer, error) {
- if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
- return key, nil
- }
- if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
- switch key := key.(type) {
- case *rsa.PrivateKey:
- return key, nil
- case *ecdsa.PrivateKey:
- return key, nil
- default:
- return nil, errors.New("localca: unknown private key type in PKCS#8 wrapping")
- }
- }
- if key, err := x509.ParseECPrivateKey(der); err == nil {
- return key, nil
- }
-
- return nil, errors.New("localca: failed to parse private key")
-}