diff options
| author | Xe Iaso <me@xeiaso.net> | 2023-06-19 12:56:31 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2023-06-19 12:56:31 -0400 |
| commit | c592b6d195aedcd6ec86e8c60d3ba91d524e293b (patch) | |
| tree | fbdb9fe9331ce491d606402d4b62ba5999ce6122 /localca | |
| parent | 84e8f57b98fd1038e6f2fc401277d936ef45522a (diff) | |
| download | x-c592b6d195aedcd6ec86e8c60d3ba91d524e293b.tar.xz x-c592b6d195aedcd6ec86e8c60d3ba91d524e293b.zip | |
second reshuffling
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'localca')
| -rw-r--r-- | localca/LICENSE | 21 | ||||
| -rw-r--r-- | localca/doc.go | 10 | ||||
| -rw-r--r-- | localca/legal.go | 27 | ||||
| -rw-r--r-- | localca/localca.go | 121 | ||||
| -rw-r--r-- | localca/localca_test.go | 83 | ||||
| -rw-r--r-- | localca/minica.go | 234 | ||||
| -rw-r--r-- | localca/utils.go | 83 |
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") -} |
