2018-12-11 02:59:03 +10:00
|
|
|
// Copyright 2015 Matthew Holt
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package certmagic
|
|
|
|
|
2019-02-03 04:10:17 +10:00
|
|
|
import (
|
|
|
|
"crypto/tls"
|
2021-08-31 04:48:23 +10:00
|
|
|
"crypto/x509"
|
Significant refactoring to improve correctness and flexibility (#39)
* Significant refactor
This refactoring expands the capabilities of the library for advanced
use cases, as well as improving the overall architecture, including
possible memory leak fixes if used over a long period with many certs
loaded into memory. This refactor enables using different configs
depending on the certificate.
The public API has changed slightly, however, and arguably it is
slightly less convenient/elegant. I have never quite found the perfect
design for this package, and this certainly isn't it, but I think it's
better than what we had before.
There is still work to be done, but this is a good step forward. I've
decoupled Storage from Cache, and made it easier and more correct for
Configs (and Storage values) to be short-lived. Cache is the only value
that should be long-lived.
Note that CertMagic no longer automatically takes care of storage (i.e.
it used to delete old OCSP staples, but now it doesn't). The functions
to do this are still there and even exported, and now we expect the
application to call the cleanup functions when it wants to.
* Fix little oopsies
* Create Manager abstraction so obtain/renew isn't limited to ACME
2019-04-21 02:44:55 +10:00
|
|
|
"reflect"
|
2019-02-03 04:10:17 +10:00
|
|
|
"testing"
|
2021-08-31 04:48:23 +10:00
|
|
|
"time"
|
2019-02-03 04:10:17 +10:00
|
|
|
)
|
2018-12-11 02:59:03 +10:00
|
|
|
|
|
|
|
func TestUnexportedGetCertificate(t *testing.T) {
|
2022-09-27 02:19:28 +10:00
|
|
|
certCache := &Cache{cache: make(map[string]Certificate), cacheIndex: make(map[string][]string), logger: defaultTestLogger}
|
|
|
|
cfg := &Config{Logger: defaultTestLogger, certCache: certCache}
|
2018-12-11 02:59:03 +10:00
|
|
|
|
|
|
|
// When cache is empty
|
2022-02-18 07:37:50 +10:00
|
|
|
if _, matched, defaulted := cfg.getCertificateFromCache(&tls.ClientHelloInfo{ServerName: "example.com"}); matched || defaulted {
|
2018-12-11 02:59:03 +10:00
|
|
|
t.Errorf("Got a certificate when cache was empty; matched=%v, defaulted=%v", matched, defaulted)
|
|
|
|
}
|
|
|
|
|
|
|
|
// When cache has one certificate in it
|
|
|
|
firstCert := Certificate{Names: []string{"example.com"}}
|
|
|
|
certCache.cache["0xdeadbeef"] = firstCert
|
Significant refactoring to improve correctness and flexibility (#39)
* Significant refactor
This refactoring expands the capabilities of the library for advanced
use cases, as well as improving the overall architecture, including
possible memory leak fixes if used over a long period with many certs
loaded into memory. This refactor enables using different configs
depending on the certificate.
The public API has changed slightly, however, and arguably it is
slightly less convenient/elegant. I have never quite found the perfect
design for this package, and this certainly isn't it, but I think it's
better than what we had before.
There is still work to be done, but this is a good step forward. I've
decoupled Storage from Cache, and made it easier and more correct for
Configs (and Storage values) to be short-lived. Cache is the only value
that should be long-lived.
Note that CertMagic no longer automatically takes care of storage (i.e.
it used to delete old OCSP staples, but now it doesn't). The functions
to do this are still there and even exported, and now we expect the
application to call the cleanup functions when it wants to.
* Fix little oopsies
* Create Manager abstraction so obtain/renew isn't limited to ACME
2019-04-21 02:44:55 +10:00
|
|
|
certCache.cacheIndex["example.com"] = []string{"0xdeadbeef"}
|
2022-02-18 07:37:50 +10:00
|
|
|
if cert, matched, defaulted := cfg.getCertificateFromCache(&tls.ClientHelloInfo{ServerName: "example.com"}); !matched || defaulted || cert.Names[0] != "example.com" {
|
2018-12-11 02:59:03 +10:00
|
|
|
t.Errorf("Didn't get a cert for 'example.com' or got the wrong one: %v, matched=%v, defaulted=%v", cert, matched, defaulted)
|
|
|
|
}
|
|
|
|
|
|
|
|
// When retrieving wildcard certificate
|
|
|
|
certCache.cache["0xb01dface"] = Certificate{Names: []string{"*.example.com"}}
|
Significant refactoring to improve correctness and flexibility (#39)
* Significant refactor
This refactoring expands the capabilities of the library for advanced
use cases, as well as improving the overall architecture, including
possible memory leak fixes if used over a long period with many certs
loaded into memory. This refactor enables using different configs
depending on the certificate.
The public API has changed slightly, however, and arguably it is
slightly less convenient/elegant. I have never quite found the perfect
design for this package, and this certainly isn't it, but I think it's
better than what we had before.
There is still work to be done, but this is a good step forward. I've
decoupled Storage from Cache, and made it easier and more correct for
Configs (and Storage values) to be short-lived. Cache is the only value
that should be long-lived.
Note that CertMagic no longer automatically takes care of storage (i.e.
it used to delete old OCSP staples, but now it doesn't). The functions
to do this are still there and even exported, and now we expect the
application to call the cleanup functions when it wants to.
* Fix little oopsies
* Create Manager abstraction so obtain/renew isn't limited to ACME
2019-04-21 02:44:55 +10:00
|
|
|
certCache.cacheIndex["*.example.com"] = []string{"0xb01dface"}
|
2022-02-18 07:37:50 +10:00
|
|
|
if cert, matched, defaulted := cfg.getCertificateFromCache(&tls.ClientHelloInfo{ServerName: "sub.example.com"}); !matched || defaulted || cert.Names[0] != "*.example.com" {
|
2018-12-11 02:59:03 +10:00
|
|
|
t.Errorf("Didn't get wildcard cert for 'sub.example.com' or got the wrong one: %v, matched=%v, defaulted=%v", cert, matched, defaulted)
|
|
|
|
}
|
|
|
|
|
|
|
|
// When no certificate matches and SNI is provided, return no certificate (should be TLS alert)
|
2022-02-18 07:37:50 +10:00
|
|
|
if cert, matched, defaulted := cfg.getCertificateFromCache(&tls.ClientHelloInfo{ServerName: "nomatch"}); matched || defaulted {
|
2018-12-11 02:59:03 +10:00
|
|
|
t.Errorf("Expected matched=false, defaulted=false; but got matched=%v, defaulted=%v (cert: %v)", matched, defaulted, cert)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCacheCertificate(t *testing.T) {
|
2022-09-27 02:19:28 +10:00
|
|
|
certCache := &Cache{cache: make(map[string]Certificate), cacheIndex: make(map[string][]string), logger: defaultTestLogger}
|
2018-12-11 02:59:03 +10:00
|
|
|
|
2021-08-31 04:48:23 +10:00
|
|
|
certCache.cacheCertificate(Certificate{Names: []string{"example.com", "sub.example.com"}, hash: "foobar", Certificate: tls.Certificate{Leaf: &x509.Certificate{NotAfter: time.Now()}}})
|
2018-12-11 02:59:03 +10:00
|
|
|
if len(certCache.cache) != 1 {
|
|
|
|
t.Errorf("Expected length of certificate cache to be 1")
|
|
|
|
}
|
|
|
|
if _, ok := certCache.cache["foobar"]; !ok {
|
|
|
|
t.Error("Expected first cert to be cached by key 'foobar', but it wasn't")
|
|
|
|
}
|
Significant refactoring to improve correctness and flexibility (#39)
* Significant refactor
This refactoring expands the capabilities of the library for advanced
use cases, as well as improving the overall architecture, including
possible memory leak fixes if used over a long period with many certs
loaded into memory. This refactor enables using different configs
depending on the certificate.
The public API has changed slightly, however, and arguably it is
slightly less convenient/elegant. I have never quite found the perfect
design for this package, and this certainly isn't it, but I think it's
better than what we had before.
There is still work to be done, but this is a good step forward. I've
decoupled Storage from Cache, and made it easier and more correct for
Configs (and Storage values) to be short-lived. Cache is the only value
that should be long-lived.
Note that CertMagic no longer automatically takes care of storage (i.e.
it used to delete old OCSP staples, but now it doesn't). The functions
to do this are still there and even exported, and now we expect the
application to call the cleanup functions when it wants to.
* Fix little oopsies
* Create Manager abstraction so obtain/renew isn't limited to ACME
2019-04-21 02:44:55 +10:00
|
|
|
if _, ok := certCache.cacheIndex["example.com"]; !ok {
|
2018-12-11 02:59:03 +10:00
|
|
|
t.Error("Expected first cert to be keyed by 'example.com', but it wasn't")
|
|
|
|
}
|
Significant refactoring to improve correctness and flexibility (#39)
* Significant refactor
This refactoring expands the capabilities of the library for advanced
use cases, as well as improving the overall architecture, including
possible memory leak fixes if used over a long period with many certs
loaded into memory. This refactor enables using different configs
depending on the certificate.
The public API has changed slightly, however, and arguably it is
slightly less convenient/elegant. I have never quite found the perfect
design for this package, and this certainly isn't it, but I think it's
better than what we had before.
There is still work to be done, but this is a good step forward. I've
decoupled Storage from Cache, and made it easier and more correct for
Configs (and Storage values) to be short-lived. Cache is the only value
that should be long-lived.
Note that CertMagic no longer automatically takes care of storage (i.e.
it used to delete old OCSP staples, but now it doesn't). The functions
to do this are still there and even exported, and now we expect the
application to call the cleanup functions when it wants to.
* Fix little oopsies
* Create Manager abstraction so obtain/renew isn't limited to ACME
2019-04-21 02:44:55 +10:00
|
|
|
if _, ok := certCache.cacheIndex["sub.example.com"]; !ok {
|
2018-12-11 02:59:03 +10:00
|
|
|
t.Error("Expected first cert to be keyed by 'sub.example.com', but it wasn't")
|
|
|
|
}
|
|
|
|
|
Significant refactoring to improve correctness and flexibility (#39)
* Significant refactor
This refactoring expands the capabilities of the library for advanced
use cases, as well as improving the overall architecture, including
possible memory leak fixes if used over a long period with many certs
loaded into memory. This refactor enables using different configs
depending on the certificate.
The public API has changed slightly, however, and arguably it is
slightly less convenient/elegant. I have never quite found the perfect
design for this package, and this certainly isn't it, but I think it's
better than what we had before.
There is still work to be done, but this is a good step forward. I've
decoupled Storage from Cache, and made it easier and more correct for
Configs (and Storage values) to be short-lived. Cache is the only value
that should be long-lived.
Note that CertMagic no longer automatically takes care of storage (i.e.
it used to delete old OCSP staples, but now it doesn't). The functions
to do this are still there and even exported, and now we expect the
application to call the cleanup functions when it wants to.
* Fix little oopsies
* Create Manager abstraction so obtain/renew isn't limited to ACME
2019-04-21 02:44:55 +10:00
|
|
|
// using same cache; and has cert with overlapping name, but different hash
|
2021-08-31 04:48:23 +10:00
|
|
|
certCache.cacheCertificate(Certificate{Names: []string{"example.com"}, hash: "barbaz", Certificate: tls.Certificate{Leaf: &x509.Certificate{NotAfter: time.Now()}}})
|
2018-12-11 02:59:03 +10:00
|
|
|
if _, ok := certCache.cache["barbaz"]; !ok {
|
|
|
|
t.Error("Expected second cert to be cached by key 'barbaz.com', but it wasn't")
|
|
|
|
}
|
Significant refactoring to improve correctness and flexibility (#39)
* Significant refactor
This refactoring expands the capabilities of the library for advanced
use cases, as well as improving the overall architecture, including
possible memory leak fixes if used over a long period with many certs
loaded into memory. This refactor enables using different configs
depending on the certificate.
The public API has changed slightly, however, and arguably it is
slightly less convenient/elegant. I have never quite found the perfect
design for this package, and this certainly isn't it, but I think it's
better than what we had before.
There is still work to be done, but this is a good step forward. I've
decoupled Storage from Cache, and made it easier and more correct for
Configs (and Storage values) to be short-lived. Cache is the only value
that should be long-lived.
Note that CertMagic no longer automatically takes care of storage (i.e.
it used to delete old OCSP staples, but now it doesn't). The functions
to do this are still there and even exported, and now we expect the
application to call the cleanup functions when it wants to.
* Fix little oopsies
* Create Manager abstraction so obtain/renew isn't limited to ACME
2019-04-21 02:44:55 +10:00
|
|
|
if hashes, ok := certCache.cacheIndex["example.com"]; !ok {
|
2018-12-11 02:59:03 +10:00
|
|
|
t.Error("Expected second cert to be keyed by 'example.com', but it wasn't")
|
Significant refactoring to improve correctness and flexibility (#39)
* Significant refactor
This refactoring expands the capabilities of the library for advanced
use cases, as well as improving the overall architecture, including
possible memory leak fixes if used over a long period with many certs
loaded into memory. This refactor enables using different configs
depending on the certificate.
The public API has changed slightly, however, and arguably it is
slightly less convenient/elegant. I have never quite found the perfect
design for this package, and this certainly isn't it, but I think it's
better than what we had before.
There is still work to be done, but this is a good step forward. I've
decoupled Storage from Cache, and made it easier and more correct for
Configs (and Storage values) to be short-lived. Cache is the only value
that should be long-lived.
Note that CertMagic no longer automatically takes care of storage (i.e.
it used to delete old OCSP staples, but now it doesn't). The functions
to do this are still there and even exported, and now we expect the
application to call the cleanup functions when it wants to.
* Fix little oopsies
* Create Manager abstraction so obtain/renew isn't limited to ACME
2019-04-21 02:44:55 +10:00
|
|
|
} else if !reflect.DeepEqual(hashes, []string{"foobar", "barbaz"}) {
|
|
|
|
t.Errorf("Expected second cert to map to 'barbaz' but it was %v instead", hashes)
|
2018-12-11 02:59:03 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-14 11:03:46 +10:00
|
|
|
func TestSubjectQualifiesForCert(t *testing.T) {
|
|
|
|
for i, test := range []struct {
|
|
|
|
host string
|
|
|
|
expect bool
|
|
|
|
}{
|
|
|
|
{"hostname", true},
|
|
|
|
{"example.com", true},
|
|
|
|
{"sub.example.com", true},
|
|
|
|
{"Sub.Example.COM", true},
|
|
|
|
{"127.0.0.1", true},
|
|
|
|
{"127.0.1.5", true},
|
|
|
|
{"69.123.43.94", true},
|
|
|
|
{"::1", true},
|
|
|
|
{"::", true},
|
|
|
|
{"0.0.0.0", true},
|
|
|
|
{"", false},
|
|
|
|
{" ", false},
|
|
|
|
{"*.example.com", true},
|
|
|
|
{"*.*.example.com", true},
|
|
|
|
{"sub.*.example.com", false},
|
|
|
|
{"*sub.example.com", false},
|
2021-01-20 07:53:32 +10:00
|
|
|
{"**.tld", false},
|
|
|
|
{"*", true},
|
|
|
|
{"*.tld", true},
|
|
|
|
{".tld", false},
|
2020-03-14 11:03:46 +10:00
|
|
|
{"example.com.", false},
|
|
|
|
{"localhost", true},
|
|
|
|
{"foo.localhost", true},
|
|
|
|
{"local", true},
|
|
|
|
{"192.168.1.3", true},
|
|
|
|
{"10.0.2.1", true},
|
|
|
|
{"169.112.53.4", true},
|
|
|
|
{"$hostname", false},
|
|
|
|
{"%HOSTNAME%", false},
|
|
|
|
{"{hostname}", false},
|
|
|
|
{"hostname!", false},
|
|
|
|
{"<hostname>", false},
|
|
|
|
{"# hostname", false},
|
|
|
|
{"// hostname", false},
|
|
|
|
{"user@hostname", false},
|
|
|
|
{"hostname;", false},
|
|
|
|
{`"hostname"`, false},
|
|
|
|
} {
|
|
|
|
actual := SubjectQualifiesForCert(test.host)
|
|
|
|
if actual != test.expect {
|
|
|
|
t.Errorf("Test %d: Expected SubjectQualifiesForCert(%s)=%v, but got %v",
|
|
|
|
i, test.host, test.expect, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-13 08:02:48 +10:00
|
|
|
func TestSubjectQualifiesForPublicCert(t *testing.T) {
|
2018-12-11 02:59:03 +10:00
|
|
|
for i, test := range []struct {
|
|
|
|
host string
|
|
|
|
expect bool
|
|
|
|
}{
|
2019-10-15 05:58:44 +10:00
|
|
|
{"hostname", true},
|
2018-12-11 02:59:03 +10:00
|
|
|
{"example.com", true},
|
|
|
|
{"sub.example.com", true},
|
|
|
|
{"Sub.Example.COM", true},
|
|
|
|
{"127.0.0.1", false},
|
|
|
|
{"127.0.1.5", false},
|
2024-04-12 04:23:53 +10:00
|
|
|
{"1.2.3.4", true},
|
|
|
|
{"69.123.43.94", true},
|
2018-12-11 02:59:03 +10:00
|
|
|
{"::1", false},
|
|
|
|
{"::", false},
|
|
|
|
{"0.0.0.0", false},
|
|
|
|
{"", false},
|
|
|
|
{" ", false},
|
|
|
|
{"*.example.com", true},
|
|
|
|
{"*.*.example.com", false},
|
|
|
|
{"sub.*.example.com", false},
|
|
|
|
{"*sub.example.com", false},
|
2021-01-20 07:53:32 +10:00
|
|
|
{"*", false}, // won't be trusted by browsers
|
|
|
|
{"*.tld", false}, // won't be trusted by browsers
|
|
|
|
{".tld", false},
|
2018-12-11 02:59:03 +10:00
|
|
|
{"example.com.", false},
|
|
|
|
{"localhost", false},
|
2020-03-08 11:56:16 +10:00
|
|
|
{"foo.localhost", false},
|
2018-12-11 02:59:03 +10:00
|
|
|
{"local", true},
|
2020-03-24 05:34:23 +10:00
|
|
|
{"foo.local", false},
|
|
|
|
{"foo.bar.local", false},
|
2024-08-10 10:24:33 +10:00
|
|
|
{"foo.internal", false},
|
|
|
|
{"foo.bar.internal", false},
|
2023-09-06 01:38:56 +10:00
|
|
|
{"foo.home.arpa", false},
|
|
|
|
{"foo.bar.home.arpa", false},
|
2018-12-11 02:59:03 +10:00
|
|
|
{"192.168.1.3", false},
|
|
|
|
{"10.0.2.1", false},
|
2024-04-12 04:23:53 +10:00
|
|
|
{"169.112.53.4", true},
|
2019-10-15 05:58:44 +10:00
|
|
|
{"$hostname", false},
|
|
|
|
{"%HOSTNAME%", false},
|
|
|
|
{"{hostname}", false},
|
|
|
|
{"hostname!", false},
|
|
|
|
{"<hostname>", false},
|
|
|
|
{"# hostname", false},
|
|
|
|
{"// hostname", false},
|
|
|
|
{"user@hostname", false},
|
|
|
|
{"hostname;", false},
|
|
|
|
{`"hostname"`, false},
|
2018-12-11 02:59:03 +10:00
|
|
|
} {
|
2020-03-13 08:02:48 +10:00
|
|
|
actual := SubjectQualifiesForPublicCert(test.host)
|
2018-12-11 02:59:03 +10:00
|
|
|
if actual != test.expect {
|
2020-03-13 08:02:48 +10:00
|
|
|
t.Errorf("Test %d: Expected SubjectQualifiesForPublicCert(%s)=%v, but got %v",
|
2018-12-11 02:59:03 +10:00
|
|
|
i, test.host, test.expect, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-27 05:58:05 +10:00
|
|
|
|
|
|
|
func TestMatchWildcard(t *testing.T) {
|
|
|
|
for i, test := range []struct {
|
|
|
|
subject, wildcard string
|
|
|
|
expect bool
|
|
|
|
}{
|
|
|
|
{"hostname", "hostname", true},
|
2022-02-18 07:37:50 +10:00
|
|
|
{"HOSTNAME", "hostname", true},
|
|
|
|
{"hostname", "HOSTNAME", true},
|
2020-03-27 05:58:05 +10:00
|
|
|
{"foo.localhost", "foo.localhost", true},
|
|
|
|
{"foo.localhost", "bar.localhost", false},
|
|
|
|
{"foo.localhost", "*.localhost", true},
|
|
|
|
{"bar.localhost", "*.localhost", true},
|
2022-02-18 07:37:50 +10:00
|
|
|
{"FOO.LocalHost", "*.localhost", true},
|
|
|
|
{"Bar.localhost", "*.LOCALHOST", true},
|
2020-03-27 05:58:05 +10:00
|
|
|
{"foo.bar.localhost", "*.localhost", false},
|
|
|
|
{".localhost", "*.localhost", false},
|
2021-02-11 07:36:08 +10:00
|
|
|
{"foo.localhost", "foo.*", false},
|
2020-03-27 05:58:05 +10:00
|
|
|
{"foo.bar.local", "foo.*.local", false},
|
2021-02-11 07:36:08 +10:00
|
|
|
{"foo.bar.local", "foo.bar.*", false},
|
2020-03-27 05:58:05 +10:00
|
|
|
{"foo.bar.local", "*.bar.local", true},
|
|
|
|
{"1.2.3.4.5.6", "*.2.3.4.5.6", true},
|
|
|
|
{"1.2.3.4.5.6", "*.*.3.4.5.6", true},
|
|
|
|
{"1.2.3.4.5.6", "*.*.*.4.5.6", true},
|
|
|
|
{"1.2.3.4.5.6", "*.*.*.*.5.6", true},
|
|
|
|
{"1.2.3.4.5.6", "*.*.*.*.*.6", true},
|
|
|
|
{"1.2.3.4.5.6", "*.*.*.*.*.*", true},
|
|
|
|
{"0.1.2.3.4.5.6", "*.*.*.*.*.*", false},
|
2021-02-11 07:36:08 +10:00
|
|
|
{"1.2.3.4", "1.2.3.*", false}, // https://tools.ietf.org/html/rfc2818#section-3.1
|
2020-03-27 05:58:05 +10:00
|
|
|
} {
|
|
|
|
actual := MatchWildcard(test.subject, test.wildcard)
|
|
|
|
if actual != test.expect {
|
|
|
|
t.Errorf("Test %d: Expected MatchWildcard(%s, %s)=%v, but got %v",
|
|
|
|
i, test.subject, test.wildcard, test.expect, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|