added support to use custom tls certificates, updated certmagic

This commit is contained in:
Kuba Gretzky
2024-03-01 15:42:19 +01:00
parent d8f7d44e14
commit 3b0f5c9971
690 changed files with 58154 additions and 16527 deletions
+2
View File
@@ -1,3 +1,5 @@
release/
build/
private/
/phishlets
!/phishlets/example.yaml
+2
View File
@@ -1,4 +1,6 @@
# Unreleased
- Feature: Added support to load custom TLS certificates from a public certificate file and a private key file stored in `~/.evilginx/crt/sites/<hostname>/`. Will load `fullchain.pem` and `privkey.pem` pair or a combination of a `.pem`/`.crt` (public certificate) and a `.key` (private key) file. Make sure to run without `-developer` flag and disable autocert retrieval with `config autocert off`.
- Feature: Added ability to disable automated TLS certificate retrieval from LetsEncrypt with `config autocert <on/off>`.
- Fixed: Added support for exported cookies with names prefixed with `__Host-` and `__Secure-`.
- Fixed: Global `unauth_url` can now be set to an empty string to have the server return `403` on unauthorized requests.
- Fixed: Unauthorized redirects and blacklisting would be ignored for `proxy_hosts` with `session: false` (default) making it easy to detect evilginx by external scanners.
+85 -1
View File
@@ -40,10 +40,13 @@ func NewCertDb(cache_dir string, cfg *Config, ns *Nameserver) (*CertDb, error) {
tlsCache: make(map[string]*tls.Certificate),
}
if err := os.MkdirAll(filepath.Join(cache_dir, "sites"), 0700); err != nil {
return nil, err
}
certmagic.DefaultACME.Agreed = true
certmagic.DefaultACME.Email = o.GetEmail()
err := o.generateCertificates()
if err != nil {
return nil, err
@@ -166,6 +169,87 @@ func (o *CertDb) setManagedSync(hosts []string, t time.Duration) error {
return err
}
func (o *CertDb) setUnmanagedSync(verbose bool) error {
sitesDir := filepath.Join(o.cache_dir, "sites")
files, err := os.ReadDir(sitesDir)
if err != nil {
return fmt.Errorf("failed to list certificates in directory '%s': %v", sitesDir, err)
}
for _, f := range files {
if f.IsDir() {
certDir := filepath.Join(sitesDir, f.Name())
certFiles, err := os.ReadDir(certDir)
if err != nil {
return fmt.Errorf("failed to list certificate directory '%s': %v", certDir, err)
}
var certPath, keyPath string
var pemCnt, crtCnt, keyCnt int
for _, cf := range certFiles {
//log.Debug("%s", cf.Name())
if !cf.IsDir() {
switch strings.ToLower(filepath.Ext(cf.Name())) {
case ".pem":
pemCnt += 1
if certPath == "" {
certPath = filepath.Join(certDir, cf.Name())
}
if cf.Name() == "fullchain.pem" {
certPath = filepath.Join(certDir, cf.Name())
}
if cf.Name() == "privkey.pem" {
keyPath = filepath.Join(certDir, cf.Name())
}
case ".crt":
crtCnt += 1
if certPath == "" {
certPath = filepath.Join(certDir, cf.Name())
}
case ".key":
keyCnt += 1
if keyPath == "" {
keyPath = filepath.Join(certDir, cf.Name())
}
}
}
}
if pemCnt > 0 && crtCnt > 0 {
if verbose {
log.Warning("cert_db: found multiple .crt and .pem files in the same directory: %s", certDir)
}
continue
}
if certPath == "" {
if verbose {
log.Warning("cert_db: not a single public certificate found in directory: %s", certDir)
}
continue
}
if keyPath == "" {
if verbose {
log.Warning("cert_db: not a single private key found in directory: %s", certDir)
}
continue
}
log.Debug("caching certificate: cert:%s key:%s", certPath, keyPath)
ctx := context.Background()
_, err = o.magic.CacheUnmanagedCertificatePEMFile(ctx, certPath, keyPath, []string{})
if err != nil {
if verbose {
log.Error("cert_db: failed to load certificate key-pair: %v", err)
}
continue
}
}
}
return nil
}
func (o *CertDb) reloadCertificates() error {
// TODO: load private certificates from disk
return nil
+25
View File
@@ -67,6 +67,7 @@ type GeneralConfig struct {
UnauthUrl string `mapstructure:"unauth_url" json:"unauth_url" yaml:"unauth_url"`
HttpsPort int `mapstructure:"https_port" json:"https_port" yaml:"https_port"`
DnsPort int `mapstructure:"dns_port" json:"dns_port" yaml:"dns_port"`
Autocert bool `mapstructure:"autocert" json:"autocert" yaml:"autocert"`
}
type Config struct {
@@ -134,6 +135,11 @@ func NewConfig(cfg_dir string, path string) (*Config, error) {
}
c.cfg.UnmarshalKey(CFG_GENERAL, &c.general)
if c.cfg.Get("general.autocert") == nil {
c.cfg.Set("general.autocert", true)
c.general.Autocert = true
}
c.cfg.UnmarshalKey(CFG_BLACKLIST, &c.blacklistConfig)
if c.general.OldIpv4 != "" {
@@ -156,6 +162,9 @@ func NewConfig(cfg_dir string, path string) (*Config, error) {
if c.general.DnsPort == 0 {
c.SetDnsPort(53)
}
if created_cfg {
c.EnableAutocert(true)
}
c.lures = []*Lure{}
c.cfg.UnmarshalKey(CFG_LURES, &c.lures)
@@ -168,6 +177,7 @@ func NewConfig(cfg_dir string, path string) (*Config, error) {
c.lureIds = append(c.lureIds, GenRandomToken())
}
c.cfg.WriteConfig()
return c, nil
}
@@ -437,6 +447,17 @@ func (c *Config) SetUnauthUrl(_url string) {
c.cfg.WriteConfig()
}
func (c *Config) EnableAutocert(enabled bool) {
c.general.Autocert = enabled
if enabled {
log.Info("autocert is now enabled")
} else {
log.Info("autocert is now disabled")
}
c.cfg.Set(CFG_GENERAL, c.general)
c.cfg.WriteConfig()
}
func (c *Config) refreshActiveHostnames() {
c.activeHostnames = []string{}
sites := c.GetEnabledSites()
@@ -743,3 +764,7 @@ func (c *Config) GetRedirectorsDir() string {
func (c *Config) GetBlacklistMode() string {
return c.blacklistConfig.Mode
}
func (c *Config) IsAutocertEnabled() bool {
return c.general.Autocert
}
+44 -18
View File
@@ -182,8 +182,13 @@ func (t *Terminal) DoWork() {
func (t *Terminal) handleConfig(args []string) error {
pn := len(args)
if pn == 0 {
keys := []string{"domain", "external_ipv4", "bind_ipv4", "https_port", "dns_port", "unauth_url"}
vals := []string{t.cfg.general.Domain, t.cfg.general.ExternalIpv4, t.cfg.general.BindIpv4, strconv.Itoa(t.cfg.general.HttpsPort), strconv.Itoa(t.cfg.general.DnsPort), t.cfg.general.UnauthUrl}
autocertOnOff := "off"
if t.cfg.IsAutocertEnabled() {
autocertOnOff = "on"
}
keys := []string{"domain", "external_ipv4", "bind_ipv4", "https_port", "dns_port", "unauth_url", "autocert"}
vals := []string{t.cfg.general.Domain, t.cfg.general.ExternalIpv4, t.cfg.general.BindIpv4, strconv.Itoa(t.cfg.general.HttpsPort), strconv.Itoa(t.cfg.general.DnsPort), t.cfg.general.UnauthUrl, autocertOnOff}
log.Printf("\n%s\n", AsRows(keys, vals))
return nil
} else if pn == 2 {
@@ -205,6 +210,17 @@ func (t *Terminal) handleConfig(args []string) error {
}
t.cfg.SetUnauthUrl(args[1])
return nil
case "autocert":
switch args[1] {
case "on":
t.cfg.EnableAutocert(true)
t.manageCertificates(true)
return nil
case "off":
t.cfg.EnableAutocert(false)
t.manageCertificates(true)
return nil
}
}
} else if pn == 3 {
switch args[0] {
@@ -1109,13 +1125,14 @@ func (t *Terminal) monitorLurePause() {
func (t *Terminal) createHelp() {
h, _ := NewHelp()
h.AddCommand("config", "general", "manage general configuration", "Shows values of all configuration variables and allows to change them.", LAYER_TOP,
readline.PcItem("config", readline.PcItem("domain"), readline.PcItem("ipv4", readline.PcItem("external"), readline.PcItem("bind")), readline.PcItem("unauth_url"), readline.PcItem("wildcards")))
readline.PcItem("config", readline.PcItem("domain"), readline.PcItem("ipv4", readline.PcItem("external"), readline.PcItem("bind")), readline.PcItem("unauth_url"), readline.PcItem("autocert", readline.PcItem("on"), readline.PcItem("off"))))
h.AddSubCommand("config", nil, "", "show all configuration variables")
h.AddSubCommand("config", []string{"domain"}, "domain <domain>", "set base domain for all phishlets (e.g. evilsite.com)")
h.AddSubCommand("config", []string{"ipv4"}, "ipv4 <ipv4_address>", "set ipv4 external address of the current server")
h.AddSubCommand("config", []string{"ipv4", "external"}, "ipv4 external <ipv4_address>", "set ipv4 external address of the current server")
h.AddSubCommand("config", []string{"ipv4", "bind"}, "ipv4 bind <ipv4_address>", "set ipv4 bind address of the current server")
h.AddSubCommand("config", []string{"unauth_url"}, "unauth_url <url>", "change the url where all unauthorized requests will be redirected to")
h.AddSubCommand("config", []string{"autocert"}, "autocert <on|off>", "enable or disable the automated certificate retrieval from letsencrypt")
h.AddCommand("proxy", "general", "manage proxy configuration", "Configures proxy which will be used to proxy the connection to remote website", LAYER_TOP,
readline.PcItem("proxy", readline.PcItem("enable"), readline.PcItem("disable"), readline.PcItem("type"), readline.PcItem("address"), readline.PcItem("port"), readline.PcItem("username"), readline.PcItem("password")))
@@ -1262,21 +1279,30 @@ func (t *Terminal) checkStatus() {
func (t *Terminal) manageCertificates(verbose bool) {
if !t.p.developer {
hosts := t.p.cfg.GetActiveHostnames("")
//wc_host := t.p.cfg.GetWildcardHostname()
//hosts := []string{wc_host}
//hosts = append(hosts, t.p.cfg.GetActiveHostnames("")...)
if verbose {
log.Info("obtaining and setting up %d TLS certificates - please wait up to 60 seconds...", len(hosts))
}
err := t.p.crt_db.setManagedSync(hosts, 60*time.Second)
if err != nil {
log.Error("failed to set up TLS certificates: %s", err)
log.Error("run 'test-certs' command to retry")
return
}
if verbose {
log.Info("successfully set up all TLS certificates")
if t.cfg.IsAutocertEnabled() {
hosts := t.p.cfg.GetActiveHostnames("")
//wc_host := t.p.cfg.GetWildcardHostname()
//hosts := []string{wc_host}
//hosts = append(hosts, t.p.cfg.GetActiveHostnames("")...)
if verbose {
log.Info("obtaining and setting up %d TLS certificates - please wait up to 60 seconds...", len(hosts))
}
err := t.p.crt_db.setManagedSync(hosts, 60*time.Second)
if err != nil {
log.Error("failed to set up TLS certificates: %s", err)
log.Error("run 'test-certs' command to retry")
return
}
if verbose {
log.Info("successfully set up all TLS certificates")
}
} else {
err := t.p.crt_db.setUnmanagedSync(verbose)
if err != nil {
log.Error("failed to set up TLS certificates: %s", err)
log.Error("run 'test-certs' command to retry")
return
}
}
}
}
+14 -13
View File
@@ -3,30 +3,30 @@ module github.com/kgretzky/evilginx2
go 1.18
require (
github.com/caddyserver/certmagic v0.16.1
github.com/caddyserver/certmagic v0.20.0
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/elazarl/goproxy v0.0.0-20220529153421-8ea89ba92021
github.com/fatih/color v1.13.0
github.com/go-acme/lego/v3 v3.1.0
github.com/gorilla/mux v1.7.3
github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b
github.com/miekg/dns v1.1.50
github.com/miekg/dns v1.1.58
github.com/mwitkow/go-http-dialer v0.0.0-20161116154839-378f744fb2b8
github.com/spf13/viper v1.10.1
github.com/tidwall/buntdb v1.1.0
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462
golang.org/x/net v0.21.0
)
require (
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/libdns/libdns v0.2.1 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mholt/acmez v1.0.3 // indirect
github.com/mholt/acmez v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/spf13/afero v1.8.1 // indirect
@@ -41,14 +41,15 @@ require (
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e // indirect
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.20.0 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.18.0 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
+33
View File
@@ -70,6 +70,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/caddyserver/certmagic v0.16.1 h1:rdSnjcUVJojmL4M0efJ+yHXErrrijS4YYg3FuwRdJkI=
github.com/caddyserver/certmagic v0.16.1/go.mod h1:jKQ5n+ViHAr6DbPwEGLTSM2vDwTO6EvCKBblBRUvvuQ=
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c=
github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -218,8 +220,11 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/kgretzky/goproxy v0.0.0-20220622134552-7d0e0c658440 h1:2B7/pxomcOdEXRg1b40AkROGPkSn+uu31aAgoeKQtlQ=
github.com/kgretzky/goproxy v0.0.0-20220622134552-7d0e0c658440/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -251,9 +256,13 @@ github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4f
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mholt/acmez v1.0.3 h1:mDgRxGYk6TKlfydYNMfX0HXXJh9i73YL+swPjYCADU8=
github.com/mholt/acmez v1.0.3/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY=
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@@ -366,6 +375,10 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -378,14 +391,21 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -400,6 +420,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -436,6 +458,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -479,6 +503,8 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I=
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -554,6 +580,9 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -565,6 +594,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -622,6 +653,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+1 -2
View File
@@ -3,7 +3,6 @@ package log
import (
"fmt"
"io"
"io/ioutil"
"log"
"sync"
"time"
@@ -54,7 +53,7 @@ func GetOutput() io.Writer {
}
func NullLogger() *log.Logger {
return log.New(ioutil.Discard, "", 0)
return log.New(io.Discard, "", 0)
}
func refreshReadline() {
+8 -7
View File
@@ -3,15 +3,17 @@ package main
import (
"flag"
"fmt"
"io/ioutil"
_log "log"
"os"
"os/user"
"path/filepath"
"regexp"
"github.com/caddyserver/certmagic"
"github.com/kgretzky/evilginx2/core"
"github.com/kgretzky/evilginx2/database"
"github.com/kgretzky/evilginx2/log"
"go.uber.org/zap"
"github.com/fatih/color"
)
@@ -55,6 +57,10 @@ func main() {
core.Banner()
showAd()
_log.SetOutput(log.NullLogger().Writer())
certmagic.Default.Logger = zap.NewNop()
certmagic.DefaultACME.Logger = zap.NewNop()
if *phishlets_dir == "" {
*phishlets_dir = joinPath(exe_dir, "./phishlets")
if _, err := os.Stat(*phishlets_dir); os.IsNotExist(err) {
@@ -110,11 +116,6 @@ func main() {
crt_path := joinPath(*cfg_dir, "./crt")
if err := core.CreateDir(crt_path, 0700); err != nil {
log.Fatal("mkdir: %v", err)
return
}
cfg, err := core.NewConfig(*cfg_dir, "")
if err != nil {
log.Fatal("config: %v", err)
@@ -134,7 +135,7 @@ func main() {
return
}
files, err := ioutil.ReadDir(phishlets_path)
files, err := os.ReadDir(phishlets_path)
if err != nil {
log.Fatal("failed to list phishlets directory '%s': %v", phishlets_path, err)
return
+60 -15
View File
@@ -75,10 +75,11 @@ CertMagic - Automatic HTTPS using Let's Encrypt
## Features
- Fully automated certificate management including issuance and renewal
- One-liner, fully managed HTTPS servers
- One-line, fully managed HTTPS servers
- Full control over almost every aspect of the system
- HTTP->HTTPS redirects
- Solves all 3 ACME challenges: HTTP, TLS-ALPN, and DNS
- Multiple issuers supported: get certificates from multiple sources/CAs for redundancy and resiliency
- Solves all 3 common ACME challenges: HTTP, TLS-ALPN, and DNS (and capable of others)
- Most robust error handling of _any_ ACME client
- Challenges are randomized to avoid accidental dependence
- Challenges are rotated to overcome certain network blockages
@@ -88,7 +89,8 @@ CertMagic - Automatic HTTPS using Let's Encrypt
- Written in Go, a language with memory-safety guarantees
- Powered by [ACMEz](https://github.com/mholt/acmez), _the_ premier ACME client library for Go
- All [libdns](https://github.com/libdns) DNS providers work out-of-the-box
- Pluggable storage implementations (default: file system)
- Pluggable storage backends (default: file system)
- Pluggable key sources
- Wildcard certificates
- Automatic OCSP stapling ([done right](https://gist.github.com/sleevi/5efe9ef98961ecfb4da8#gistcomment-2336055)) [keeps your sites online!](https://twitter.com/caddyserver/status/1234874273724084226)
- Will [automatically attempt](https://twitter.com/mholt6/status/1235577699541762048) to replace [revoked certificates](https://community.letsencrypt.org/t/2020-02-29-caa-rechecking-bug/114591/3?u=mholt)!
@@ -101,7 +103,8 @@ CertMagic - Automatic HTTPS using Let's Encrypt
- Caddy / CertMagic pioneered this technology
- Custom decision functions to regulate and throttle on-demand behavior
- Optional event hooks for observation
- Works with any certificate authority (CA) compliant with the ACME specification
- One-time private keys by default (new key for each cert) to discourage pinning and reduce scope of key compromise
- Works with any certificate authority (CA) compliant with the ACME specification RFC 8555
- Certificate revocation (please, only if private key is compromised)
- Must-Staple (optional; not default)
- Cross-platform support! Mac, Windows, Linux, BSD, Android...
@@ -230,6 +233,9 @@ tlsConfig, err := certmagic.TLS([]string{"example.com"})
if err != nil {
return err
}
// be sure to customize NextProtos if serving a specific
// application protocol after the TLS handshake, for example:
tlsConfig.NextProtos = append([]string{"h2", "http/1.1"}, tlsConfig.NextProtos...)
```
@@ -238,16 +244,17 @@ if err != nil {
For more control (particularly, if you need a different way of managing each certificate), you'll make and use a `Cache` and a `Config` like so:
```go
cache := certmagic.NewCache(certmagic.CacheOptions{
// First make a pointer to a Cache as we need to reference the same Cache in
// GetConfigForCert below.
var cache *certmagic.Cache
cache = certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
// do whatever you need to do to get the right
// configuration for this certificate; keep in
// mind that this config value is used as a
// template, and will be completed with any
// defaults that are set in the Default config
return &certmagic.Config{
// Here we use New to get a valid Config associated with the same cache.
// The provided Config is used as a template and will be completed with
// any defaults that are set in the Default config.
return certmagic.New(cache, certmagic.Config{
// ...
}, nil
}), nil
},
...
})
@@ -263,7 +270,7 @@ myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
// plus any other customizations you need
})
magic.Issuer = myACME
magic.Issuers = []certmagic.Issuer{myACME}
// this obtains certificates or renews them if necessary
err := magic.ManageSync(context.TODO(), []string{"example.com", "sub.example.com"})
@@ -445,7 +452,7 @@ By default, CertMagic stores assets on the local file system in `$HOME/.local/sh
The notion of a "cluster" or "fleet" of instances that may be serving the same site and sharing certificates, etc, is tied to storage. Simply, any instances that use the same storage facilities are considered part of the cluster. So if you deploy 100 instances of CertMagic behind a load balancer, they are all part of the same cluster if they share the same storage configuration. Sharing storage could be mounting a shared folder, or implementing some other distributed storage system such as a database server or KV store.
The easiest way to change the storage being used is to set `certmagic.DefaultStorage` to a value that satisfies the [Storage interface](https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc#Storage). Keep in mind that a valid `Storage` must be able to implement some operations atomically in order to provide locking and synchronization.
The easiest way to change the storage being used is to set `certmagic.Default.Storage` to a value that satisfies the [Storage interface](https://pkg.go.dev/github.com/caddyserver/certmagic?tab=doc#Storage). Keep in mind that a valid `Storage` must be able to implement some operations atomically in order to provide locking and synchronization.
If you write a Storage implementation, please add it to the [project wiki](https://github.com/caddyserver/certmagic/wiki/Storage-Implementations) so people can find it!
@@ -454,10 +461,48 @@ If you write a Storage implementation, please add it to the [project wiki](https
All of the certificates in use are de-duplicated and cached in memory for optimal performance at handshake-time. This cache must be backed by persistent storage as described above.
Most applications will not need to interact with certificate caches directly. Usually, the closest you will come is to set the package-wide `certmagic.DefaultStorage` variable (before attempting to create any Configs). However, if your use case requires using different storage facilities for different Configs (that's highly unlikely and NOT recommended! Even Caddy doesn't get that crazy), you will need to call `certmagic.NewCache()` and pass in the storage you want to use, then get new `Config` structs with `certmagic.NewWithCache()` and pass in the cache.
Most applications will not need to interact with certificate caches directly. Usually, the closest you will come is to set the package-wide `certmagic.Default.Storage` variable (before attempting to create any Configs) which defines how the cache is persisted. However, if your use case requires using different storage facilities for different Configs (that's highly unlikely and NOT recommended! Even Caddy doesn't get that crazy), you will need to call `certmagic.NewCache()` and pass in the storage you want to use, then get new `Config` structs with `certmagic.NewWithCache()` and pass in the cache.
Again, if you're needing to do this, you've probably over-complicated your application design.
## Events
(Events are new and still experimental, so they may change.)
CertMagic emits events when possible things of interest happen. Set the [`OnEvent` field of your `Config`](https://pkg.go.dev/github.com/caddyserver/certmagic#Config.OnEvent) to subscribe to events; ignore the ones you aren't interested in. Here are the events currently emitted along with their metadata you can use:
- **`cached_unmanaged_cert`** An unmanaged certificate was cached
- `sans`: The subject names on the certificate
- **`cert_obtaining`** A certificate is about to be obtained
- `renewal`: Whether this is a renewal
- `identifier`: The name on the certificate
- `forced`: Whether renewal is being forced (if renewal)
- `remaining`: Time left on the certificate (if renewal)
- `issuer`: The previous or current issuer
- **`cert_obtained`** A certificate was successfully obtained
- `renewal`: Whether this is a renewal
- `identifier`: The name on the certificate
- `remaining`: Time left on the certificate (if renewal)
- `issuer`: The previous or current issuer
- `storage_path`: The path to the folder containing the cert resources within storage
- `private_key_path`: The path to the private key file in storage
- `certificate_path`: The path to the public key file in storage
- `metadata_path`: The path to the metadata file in storage
- **`cert_failed`** An attempt to obtain a certificate failed
- `renewal`: Whether this is a renewal
- `identifier`: The name on the certificate
- `remaining`: Time left on the certificate (if renewal)
- `issuers`: The issuer(s) tried
- `error`: The (final) error message
- **`tls_get_certificate`** The GetCertificate phase of a TLS handshake is under way
- `client_hello`: The tls.ClientHelloInfo struct
- **`cert_ocsp_revoked`** A certificate's OCSP indicates it has been revoked
- `subjects`: The subject names on the certificate
- `certificate`: The Certificate struct
- `reason`: The OCSP revocation reason
- `revoked_at`: When the certificate was revoked
`OnEvent` can return an error. Some events may be aborted by returning an error. For example, returning an error from `cert_obtained` can cancel obtaining the certificate. Only return an error from `OnEvent` if you want to abort program flow.
## FAQ
+12 -5
View File
@@ -171,14 +171,14 @@ func (am *ACMEIssuer) saveAccount(ctx context.Context, ca string, account acme.A
return storeTx(ctx, am.config.Storage, all)
}
// getEmail does everything it can to obtain an email address
// setEmail does everything it can to obtain an email address
// from the user within the scope of memory and storage to use
// for ACME TLS. If it cannot get an email address, it does nothing
// (If user is prompted, it will warn the user of
// the consequences of an empty email.) This function MAY prompt
// the user for input. If allowPrompts is false, the user
// will NOT be prompted and an empty email may be returned.
func (am *ACMEIssuer) getEmail(ctx context.Context, allowPrompts bool) error {
func (am *ACMEIssuer) setEmail(ctx context.Context, allowPrompts bool) error {
leEmail := am.Email
// First try package default email, or a discovered email address
@@ -206,10 +206,12 @@ func (am *ACMEIssuer) getEmail(ctx context.Context, allowPrompts bool) error {
}
// User might have just signified their agreement
am.Agreed = DefaultACME.Agreed
am.mu.Lock()
am.agreed = DefaultACME.Agreed
am.mu.Unlock()
}
// save the email for later and ensure it is consistent
// Save the email for later and ensure it is consistent
// for repeated use; then update cfg with the email
leEmail = strings.TrimSpace(strings.ToLower(leEmail))
discoveredEmailMu.Lock()
@@ -217,7 +219,12 @@ func (am *ACMEIssuer) getEmail(ctx context.Context, allowPrompts bool) error {
discoveredEmail = leEmail
}
discoveredEmailMu.Unlock()
am.Email = leEmail
// The unexported email field is the one we use
// because we have thread-safe control over it
am.mu.Lock()
am.email = leEmail
am.mu.Unlock()
return nil
}
+29 -69
View File
@@ -16,12 +16,10 @@ package certmagic
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
weakrand "math/rand"
"net"
"net/http"
"net/url"
"strconv"
"strings"
@@ -61,7 +59,7 @@ func (iss *ACMEIssuer) newACMEClientWithAccount(ctx context.Context, useTestCA,
if iss.AccountKeyPEM != "" {
account, err = iss.GetAccount(ctx, []byte(iss.AccountKeyPEM))
} else {
account, err = iss.getAccount(ctx, client.Directory, iss.Email)
account, err = iss.getAccount(ctx, client.Directory, iss.getEmail())
}
if err != nil {
return nil, fmt.Errorf("getting ACME account: %v", err)
@@ -70,7 +68,10 @@ func (iss *ACMEIssuer) newACMEClientWithAccount(ctx context.Context, useTestCA,
// register account if it is new
if account.Status == "" {
if iss.NewAccountFunc != nil {
// obtain lock here, since NewAccountFunc calls happen concurrently and they typically read and change the issuer
iss.mu.Lock()
account, err = iss.NewAccountFunc(ctx, iss, account)
iss.mu.Unlock()
if err != nil {
return nil, fmt.Errorf("account pre-registration callback: %v", err)
}
@@ -78,7 +79,7 @@ func (iss *ACMEIssuer) newACMEClientWithAccount(ctx context.Context, useTestCA,
// agree to terms
if interactive {
if !iss.Agreed {
if !iss.isAgreed() {
var termsURL string
dir, err := client.GetDirectory(ctx)
if err != nil {
@@ -88,18 +89,23 @@ func (iss *ACMEIssuer) newACMEClientWithAccount(ctx context.Context, useTestCA,
termsURL = dir.Meta.TermsOfService
}
if termsURL != "" {
iss.Agreed = iss.askUserAgreement(termsURL)
if !iss.Agreed {
agreed := iss.askUserAgreement(termsURL)
if !agreed {
return nil, fmt.Errorf("user must agree to CA terms")
}
iss.mu.Lock()
iss.agreed = agreed
iss.mu.Unlock()
}
}
} else {
// can't prompt a user who isn't there; they should
// have reviewed the terms beforehand
iss.Agreed = true
iss.mu.Lock()
iss.agreed = true
iss.mu.Unlock()
}
account.TermsOfServiceAgreed = iss.Agreed
account.TermsOfServiceAgreed = iss.isAgreed()
// associate account with external binding, if configured
if iss.ExternalAccount != nil {
@@ -163,60 +169,16 @@ func (iss *ACMEIssuer) newACMEClient(useTestCA bool) (*acmez.Client, error) {
return nil, fmt.Errorf("%s: insecure CA URL (HTTPS required)", caURL)
}
// set up the dialers and resolver for the ACME client's HTTP client
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 2 * time.Minute,
}
if iss.Resolver != "" {
dialer.Resolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, _ string) (net.Conn, error) {
return (&net.Dialer{
Timeout: 15 * time.Second,
}).DialContext(ctx, network, iss.Resolver)
},
}
}
// TODO: we could potentially reuse the HTTP transport and client
hc := iss.httpClient // TODO: is this racey?
if iss.httpClient == nil {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
TLSHandshakeTimeout: 30 * time.Second, // increase to 30s requested in #175
ResponseHeaderTimeout: 30 * time.Second, // increase to 30s requested in #175
ExpectContinueTimeout: 2 * time.Second,
ForceAttemptHTTP2: true,
}
if iss.TrustedRoots != nil {
transport.TLSClientConfig = &tls.Config{
RootCAs: iss.TrustedRoots,
}
}
hc = &http.Client{
Transport: transport,
Timeout: HTTPTimeout,
}
iss.httpClient = hc
}
client := &acmez.Client{
Client: &acme.Client{
Directory: caURL,
PollTimeout: certObtainTimeout,
UserAgent: buildUAString(),
HTTPClient: hc,
HTTPClient: iss.httpClient,
},
ChallengeSolvers: make(map[string]acmez.Solver),
}
if iss.Logger != nil {
l := iss.Logger.Named("acme_client")
client.Client.Logger, client.Logger = l, l
}
client.Logger = iss.Logger.Named("acme_client")
// configure challenges (most of the time, DNS challenge is
// exclusive of other ones because it is usually only used
@@ -287,8 +249,10 @@ func (iss *ACMEIssuer) newACMEClient(useTestCA bool) (*acmez.Client, error) {
}
func (c *acmeClient) throttle(ctx context.Context, names []string) error {
email := c.iss.getEmail()
// throttling is scoped to CA + account email
rateLimiterKey := c.acmeClient.Directory + "," + c.iss.Email
rateLimiterKey := c.acmeClient.Directory + "," + email
rateLimitersMu.Lock()
rl, ok := rateLimiters[rateLimiterKey]
if !ok {
@@ -297,24 +261,20 @@ func (c *acmeClient) throttle(ctx context.Context, names []string) error {
// TODO: stop rate limiter when it is garbage-collected...
}
rateLimitersMu.Unlock()
if c.iss.Logger != nil {
c.iss.Logger.Info("waiting on internal rate limiter",
zap.Strings("identifiers", names),
zap.String("ca", c.acmeClient.Directory),
zap.String("account", c.iss.Email),
)
}
c.iss.Logger.Info("waiting on internal rate limiter",
zap.Strings("identifiers", names),
zap.String("ca", c.acmeClient.Directory),
zap.String("account", email),
)
err := rl.Wait(ctx)
if err != nil {
return err
}
if c.iss.Logger != nil {
c.iss.Logger.Info("done waiting on internal rate limiter",
zap.Strings("identifiers", names),
zap.String("ca", c.acmeClient.Directory),
zap.String("account", c.iss.Email),
)
}
c.iss.Logger.Info("done waiting on internal rate limiter",
zap.Strings("identifiers", names),
zap.String("ca", c.acmeClient.Directory),
zap.String("account", email),
)
return nil
}
+99 -24
View File
@@ -2,13 +2,16 @@ package certmagic
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"sort"
"strings"
"sync"
"time"
"github.com/mholt/acmez"
@@ -107,11 +110,29 @@ type ACMEIssuer struct {
// certificate chains
PreferredChains ChainPreference
// Set a logger to enable logging
// Set a logger to configure logging; a default
// logger must always be set; if no logging is
// desired, set this to zap.NewNop().
Logger *zap.Logger
// Set a http proxy to use when issuing a certificate.
// Default is http.ProxyFromEnvironment
HTTPProxy func(*http.Request) (*url.URL, error)
config *Config
httpClient *http.Client
// Some fields are changed on-the-fly during
// certificate management. For example, the
// email might be implicitly discovered if not
// explicitly configured, and agreement might
// happen during the flow. Changing the exported
// fields field is racey (issue #195) so we
// control unexported fields that we can
// synchronize properly.
email string
agreed bool
mu *sync.Mutex // protects the above grouped fields, as well as entire struct during NewAccountFunc calls
}
// NewACMEIssuer constructs a valid ACMEIssuer based on a template
@@ -181,7 +202,55 @@ func NewACMEIssuer(cfg *Config, template ACMEIssuer) *ACMEIssuer {
if template.Logger == nil {
template.Logger = DefaultACME.Logger
}
// absolutely do not allow a nil logger; that would panic
if template.Logger == nil {
template.Logger = defaultLogger
}
if template.HTTPProxy == nil {
template.HTTPProxy = DefaultACME.HTTPProxy
}
if template.HTTPProxy == nil {
template.HTTPProxy = http.ProxyFromEnvironment
}
template.config = cfg
template.mu = new(sync.Mutex)
// set up the dialer and transport / HTTP client
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 2 * time.Minute,
}
if template.Resolver != "" {
dialer.Resolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, _ string) (net.Conn, error) {
return (&net.Dialer{
Timeout: 15 * time.Second,
}).DialContext(ctx, network, template.Resolver)
},
}
}
transport := &http.Transport{
Proxy: template.HTTPProxy,
DialContext: dialer.DialContext,
TLSHandshakeTimeout: 30 * time.Second, // increase to 30s requested in #175
ResponseHeaderTimeout: 30 * time.Second, // increase to 30s requested in #175
ExpectContinueTimeout: 2 * time.Second,
ForceAttemptHTTP2: true,
}
if template.TrustedRoots != nil {
transport.TLSClientConfig = &tls.Config{
RootCAs: template.TrustedRoots,
}
}
template.httpClient = &http.Client{
Transport: transport,
Timeout: HTTPTimeout,
}
return &template
}
@@ -213,12 +282,24 @@ func (*ACMEIssuer) issuerKey(ca string) string {
return key
}
func (iss *ACMEIssuer) getEmail() string {
iss.mu.Lock()
defer iss.mu.Unlock()
return iss.email
}
func (iss *ACMEIssuer) isAgreed() bool {
iss.mu.Lock()
defer iss.mu.Unlock()
return iss.agreed
}
// PreCheck performs a few simple checks before obtaining or
// renewing a certificate with ACME, and returns whether this
// batch is eligible for certificates if using Let's Encrypt.
// It also ensures that an email address is available.
func (am *ACMEIssuer) PreCheck(ctx context.Context, names []string, interactive bool) error {
publicCA := strings.Contains(am.CA, "api.letsencrypt.org") || strings.Contains(am.CA, "acme.zerossl.com")
publicCA := strings.Contains(am.CA, "api.letsencrypt.org") || strings.Contains(am.CA, "acme.zerossl.com") || strings.Contains(am.CA, "api.pki.goog")
if publicCA {
for _, name := range names {
if !SubjectQualifiesForPublicCert(name) {
@@ -226,7 +307,7 @@ func (am *ACMEIssuer) PreCheck(ctx context.Context, names []string, interactive
}
}
}
return am.getEmail(ctx, interactive)
return am.setEmail(ctx, interactive)
}
// Issue implements the Issuer interface. It obtains a certificate for the given csr using
@@ -335,7 +416,7 @@ func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest,
// processing. If there are no matches, the first chain is returned.
func (am *ACMEIssuer) selectPreferredChain(certChains []acme.Certificate) acme.Certificate {
if len(certChains) == 1 {
if am.Logger != nil && (len(am.PreferredChains.AnyCommonName) > 0 || len(am.PreferredChains.RootCommonName) > 0) {
if len(am.PreferredChains.AnyCommonName) > 0 || len(am.PreferredChains.RootCommonName) > 0 {
am.Logger.Debug("there is only one chain offered; selecting it regardless of preferences",
zap.String("chain_url", certChains[0].URL))
}
@@ -360,11 +441,9 @@ func (am *ACMEIssuer) selectPreferredChain(certChains []acme.Certificate) acme.C
for i, chain := range certChains {
certs, err := parseCertsFromPEMBundle(chain.ChainPEM)
if err != nil {
if am.Logger != nil {
am.Logger.Error("unable to parse PEM certificate chain",
zap.Int("chain", i),
zap.Error(err))
}
am.Logger.Error("unable to parse PEM certificate chain",
zap.Int("chain", i),
zap.Error(err))
continue
}
decodedChains[i] = certs
@@ -375,11 +454,9 @@ func (am *ACMEIssuer) selectPreferredChain(certChains []acme.Certificate) acme.C
for i, chain := range decodedChains {
for _, cert := range chain {
if cert.Issuer.CommonName == prefAnyCN {
if am.Logger != nil {
am.Logger.Debug("found preferred certificate chain by issuer common name",
zap.String("preference", prefAnyCN),
zap.Int("chain", i))
}
am.Logger.Debug("found preferred certificate chain by issuer common name",
zap.String("preference", prefAnyCN),
zap.Int("chain", i))
return certChains[i]
}
}
@@ -391,20 +468,16 @@ func (am *ACMEIssuer) selectPreferredChain(certChains []acme.Certificate) acme.C
for _, prefRootCN := range am.PreferredChains.RootCommonName {
for i, chain := range decodedChains {
if chain[len(chain)-1].Issuer.CommonName == prefRootCN {
if am.Logger != nil {
am.Logger.Debug("found preferred certificate chain by root common name",
zap.String("preference", prefRootCN),
zap.Int("chain", i))
}
am.Logger.Debug("found preferred certificate chain by root common name",
zap.String("preference", prefRootCN),
zap.Int("chain", i))
return certChains[i]
}
}
}
}
if am.Logger != nil {
am.Logger.Warn("did not find chain matching preferences; using first")
}
am.Logger.Warn("did not find chain matching preferences; using first")
}
return certChains[0]
@@ -444,8 +517,10 @@ type ChainPreference struct {
// DefaultACME specifies default settings to use for ACMEIssuers.
// Using this value is optional but can be convenient.
var DefaultACME = ACMEIssuer{
CA: LetsEncryptProductionCA,
TestCA: LetsEncryptStagingCA,
CA: LetsEncryptProductionCA,
TestCA: LetsEncryptStagingCA,
Logger: defaultLogger,
HTTPProxy: http.ProxyFromEnvironment,
}
// Some well-known CA endpoints available to use.
+32 -24
View File
@@ -71,9 +71,7 @@ func (jm *jobManager) worker() {
jm.queue = jm.queue[1:]
jm.mu.Unlock()
if err := next.job(); err != nil {
if next.logger != nil {
next.logger.Error("job failed", zap.Error(err))
}
next.logger.Error("job failed", zap.Error(err))
}
if next.name != "" {
jm.mu.Lock()
@@ -116,22 +114,19 @@ func doWithRetry(ctx context.Context, log *zap.Logger, f func(context.Context) e
intervalIndex++
}
if time.Since(start) < maxRetryDuration {
if log != nil {
log.Error("will retry",
zap.Error(err),
zap.Int("attempt", attempts),
zap.Duration("retrying_in", retryIntervals[intervalIndex]),
zap.Duration("elapsed", time.Since(start)),
zap.Duration("max_duration", maxRetryDuration))
}
log.Error("will retry",
zap.Error(err),
zap.Int("attempt", attempts),
zap.Duration("retrying_in", retryIntervals[intervalIndex]),
zap.Duration("elapsed", time.Since(start)),
zap.Duration("max_duration", maxRetryDuration))
} else {
if log != nil {
log.Error("final attempt; giving up",
zap.Error(err),
zap.Int("attempt", attempts),
zap.Duration("elapsed", time.Since(start)),
zap.Duration("max_duration", maxRetryDuration))
}
log.Error("final attempt; giving up",
zap.Error(err),
zap.Int("attempt", attempts),
zap.Duration("elapsed", time.Since(start)),
zap.Duration("max_duration", maxRetryDuration))
return nil
}
}
@@ -160,8 +155,8 @@ var AttemptsCtxKey retryStateCtxKey
// front. We figure that intermittent errors would be
// resolved after the first retry, but any errors after
// that would probably require at least a few minutes
// to clear up: either for DNS to propagate, for the
// administrator to fix their DNS or network properties,
// or hours to clear up: either for DNS to propagate, for
// the administrator to fix their DNS or network config,
// or some other external factor needs to change. We
// chose intervals that we think will be most useful
// without introducing unnecessary delay. The last
@@ -173,13 +168,26 @@ var retryIntervals = []time.Duration{
2 * time.Minute,
5 * time.Minute, // elapsed: 10 min
10 * time.Minute,
20 * time.Minute,
10 * time.Minute,
10 * time.Minute,
20 * time.Minute, // elapsed: 1 hr
20 * time.Minute,
20 * time.Minute,
20 * time.Minute, // elapsed: 2 hr
30 * time.Minute,
30 * time.Minute, // elapsed: 2 hr
30 * time.Minute, // elapsed: 3 hr
30 * time.Minute,
30 * time.Minute, // elapsed: 4 hr
30 * time.Minute,
30 * time.Minute, // elapsed: 5 hr
1 * time.Hour, // elapsed: 6 hr
1 * time.Hour,
3 * time.Hour, // elapsed: 6 hr
6 * time.Hour, // for up to maxRetryDuration
1 * time.Hour, // elapsed: 8 hr
2 * time.Hour,
2 * time.Hour, // elapsed: 12 hr
3 * time.Hour,
3 * time.Hour, // elapsed: 18 hr
6 * time.Hour, // repeat for up to maxRetryDuration
}
// maxRetryDuration is the maximum duration to try
+112 -49
View File
@@ -48,7 +48,8 @@ import (
// differently.
type Cache struct {
// User configuration of the cache
options CacheOptions
options CacheOptions
optionsMu sync.RWMutex
// The cache is keyed by certificate hash
cache map[string]Certificate
@@ -56,7 +57,7 @@ type Cache struct {
// cacheIndex is a map of SAN to cache key (cert hash)
cacheIndex map[string][]string
// Protects the cache and index maps
// Protects the cache and cacheIndex maps
mu sync.RWMutex
// Close this channel to cancel asset maintenance
@@ -118,11 +119,22 @@ func NewCache(opts CacheOptions) *Cache {
logger: opts.Logger,
}
// absolutely do not allow a nil logger; panics galore
if c.logger == nil {
c.logger = defaultLogger
}
go c.maintainAssets(0)
return c
}
func (certCache *Cache) SetOptions(opts CacheOptions) {
certCache.optionsMu.Lock()
certCache.options = opts
certCache.optionsMu.Unlock()
}
// Stop stops the maintenance goroutine for
// certificates in certCache. It blocks until
// stopping is complete. Once a cache is
@@ -140,7 +152,8 @@ type CacheOptions struct {
// used for managing a certificate, or for accessing
// that certificate's asset storage (e.g. for
// OCSP staples, etc). The returned Config MUST
// be associated with the same Cache as the caller.
// be associated with the same Cache as the caller,
// use New to obtain a valid Config.
//
// The reason this is a callback function, dynamically
// returning a Config (instead of attaching a static
@@ -192,22 +205,39 @@ func (certCache *Cache) cacheCertificate(cert Certificate) {
// This function is NOT safe for concurrent use. Callers MUST acquire
// a write lock on certCache.mu first.
func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
// no-op if this certificate already exists in the cache
if _, ok := certCache.cache[cert.hash]; ok {
if certCache.logger != nil {
certCache.logger.Debug("certificate already cached",
zap.Strings("subjects", cert.Names),
zap.Time("expiration", cert.Leaf.NotAfter),
zap.Bool("managed", cert.managed),
zap.String("issuer_key", cert.issuerKey),
zap.String("hash", cert.hash))
// if this certificate already exists in the cache, this is basically
// a no-op so we reuse existing cert (prevent duplication), but we do
// modify the cert to add tags it may be missing (see issue #211)
if existingCert, ok := certCache.cache[cert.hash]; ok {
logMsg := "certificate already cached"
if len(cert.Tags) > 0 {
for _, tag := range cert.Tags {
if !existingCert.HasTag(tag) {
existingCert.Tags = append(existingCert.Tags, tag)
}
}
certCache.cache[cert.hash] = existingCert
logMsg += "; appended any missing tags to cert"
}
certCache.logger.Debug(logMsg,
zap.Strings("subjects", cert.Names),
zap.Time("expiration", expiresAt(cert.Leaf)),
zap.Bool("managed", cert.managed),
zap.String("issuer_key", cert.issuerKey),
zap.String("hash", cert.hash),
zap.Strings("tags", cert.Tags))
return
}
// if the cache is at capacity, make room for new cert
cacheSize := len(certCache.cache)
if certCache.options.Capacity > 0 && cacheSize >= certCache.options.Capacity {
certCache.optionsMu.RLock()
atCapacity := certCache.options.Capacity > 0 && cacheSize >= certCache.options.Capacity
certCache.optionsMu.RUnlock()
if atCapacity {
// Go maps are "nondeterministic" but not actually random,
// so although we could just chop off the "front" of the
// map with less code, that is a heavily skewed eviction
@@ -217,13 +247,11 @@ func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
i := 0
for _, randomCert := range certCache.cache {
if i == rnd {
if certCache.logger != nil {
certCache.logger.Debug("cache full; evicting random certificate",
zap.Strings("removing_subjects", randomCert.Names),
zap.String("removing_hash", randomCert.hash),
zap.Strings("inserting_subjects", cert.Names),
zap.String("inserting_hash", cert.hash))
}
certCache.logger.Debug("cache full; evicting random certificate",
zap.Strings("removing_subjects", randomCert.Names),
zap.String("removing_hash", randomCert.hash),
zap.Strings("inserting_subjects", cert.Names),
zap.String("inserting_hash", cert.hash))
certCache.removeCertificate(randomCert)
break
}
@@ -239,16 +267,16 @@ func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
certCache.cacheIndex[name] = append(certCache.cacheIndex[name], cert.hash)
}
if certCache.logger != nil {
certCache.logger.Debug("added certificate to cache",
zap.Strings("subjects", cert.Names),
zap.Time("expiration", cert.Leaf.NotAfter),
zap.Bool("managed", cert.managed),
zap.String("issuer_key", cert.issuerKey),
zap.String("hash", cert.hash),
zap.Int("cache_size", len(certCache.cache)),
zap.Int("cache_capacity", certCache.options.Capacity))
}
certCache.optionsMu.RLock()
certCache.logger.Debug("added certificate to cache",
zap.Strings("subjects", cert.Names),
zap.Time("expiration", expiresAt(cert.Leaf)),
zap.Bool("managed", cert.managed),
zap.String("issuer_key", cert.issuerKey),
zap.String("hash", cert.hash),
zap.Int("cache_size", len(certCache.cache)),
zap.Int("cache_capacity", certCache.options.Capacity))
certCache.optionsMu.RUnlock()
}
// removeCertificate removes cert from the cache.
@@ -275,16 +303,16 @@ func (certCache *Cache) removeCertificate(cert Certificate) {
// delete the actual cert from the cache
delete(certCache.cache, cert.hash)
if certCache.logger != nil {
certCache.logger.Debug("removed certificate from cache",
zap.Strings("subjects", cert.Names),
zap.Time("expiration", cert.Leaf.NotAfter),
zap.Bool("managed", cert.managed),
zap.String("issuer_key", cert.issuerKey),
zap.String("hash", cert.hash),
zap.Int("cache_size", len(certCache.cache)),
zap.Int("cache_capacity", certCache.options.Capacity))
}
certCache.optionsMu.RLock()
certCache.logger.Debug("removed certificate from cache",
zap.Strings("subjects", cert.Names),
zap.Time("expiration", expiresAt(cert.Leaf)),
zap.Bool("managed", cert.managed),
zap.String("issuer_key", cert.issuerKey),
zap.String("hash", cert.hash),
zap.Int("cache_size", len(certCache.cache)),
zap.Int("cache_capacity", certCache.options.Capacity))
certCache.optionsMu.RUnlock()
}
// replaceCertificate atomically replaces oldCert with newCert in
@@ -296,18 +324,18 @@ func (certCache *Cache) replaceCertificate(oldCert, newCert Certificate) {
certCache.removeCertificate(oldCert)
certCache.unsyncedCacheCertificate(newCert)
certCache.mu.Unlock()
if certCache.logger != nil {
certCache.logger.Info("replaced certificate in cache",
zap.Strings("subjects", newCert.Names),
zap.Time("new_expiration", newCert.Leaf.NotAfter))
}
certCache.logger.Info("replaced certificate in cache",
zap.Strings("subjects", newCert.Names),
zap.Time("new_expiration", expiresAt(newCert.Leaf)))
}
func (certCache *Cache) getAllMatchingCerts(name string) []Certificate {
// getAllMatchingCerts returns all certificates with exactly this subject
// (wildcards are NOT expanded).
func (certCache *Cache) getAllMatchingCerts(subject string) []Certificate {
certCache.mu.RLock()
defer certCache.mu.RUnlock()
allCertKeys := certCache.cacheIndex[name]
allCertKeys := certCache.cacheIndex[subject]
certs := make([]Certificate, len(allCertKeys))
for i := range allCertKeys {
@@ -328,11 +356,19 @@ func (certCache *Cache) getAllCerts() []Certificate {
}
func (certCache *Cache) getConfig(cert Certificate) (*Config, error) {
cfg, err := certCache.options.GetConfigForCert(cert)
certCache.optionsMu.RLock()
getCert := certCache.options.GetConfigForCert
certCache.optionsMu.RUnlock()
cfg, err := getCert(cert)
if err != nil {
return nil, err
}
if cfg.certCache != nil && cfg.certCache != certCache {
if cfg.certCache == nil {
return nil, fmt.Errorf("config returned for certificate %v has nil cache; expected %p (this one)",
cert.Names, certCache)
}
if cfg.certCache != certCache {
return nil, fmt.Errorf("config returned for certificate %v is not nil and points to different cache; got %p, expected %p (this one)",
cert.Names, cfg.certCache, certCache)
}
@@ -358,6 +394,33 @@ func (certCache *Cache) AllMatchingCertificates(name string) []Certificate {
return certs
}
// RemoveManaged removes managed certificates for the given subjects from the cache.
// This effectively stops maintenance of those certificates.
func (certCache *Cache) RemoveManaged(subjects []string) {
deleteQueue := make([]string, 0, len(subjects))
for _, subject := range subjects {
certs := certCache.getAllMatchingCerts(subject) // does NOT expand wildcards; exact matches only
for _, cert := range certs {
if !cert.managed {
continue
}
deleteQueue = append(deleteQueue, cert.hash)
}
}
certCache.Remove(deleteQueue)
}
// Remove removes certificates with the given hashes from the cache.
// This is effectively used to unload manually-loaded certificates.
func (certCache *Cache) Remove(hashes []string) {
certCache.mu.Lock()
for _, h := range hashes {
cert := certCache.cache[h]
certCache.removeCertificate(cert)
}
certCache.mu.Unlock()
}
var (
defaultCache *Cache
defaultCacheMu sync.Mutex
+42 -26
View File
@@ -48,7 +48,7 @@ type Certificate struct {
// most recent OCSP response we have for this certificate.
ocsp *ocsp.Response
// The hex-encoded hash of this cert's chain's bytes.
// The hex-encoded hash of this cert's chain's DER bytes.
hash string
// Whether this certificate is under our management.
@@ -64,10 +64,13 @@ func (cert Certificate) Empty() bool {
return len(cert.Certificate.Certificate) == 0
}
// Hash returns a checksum of the certificate chain's DER-encoded bytes.
func (cert Certificate) Hash() string { return cert.hash }
// NeedsRenewal returns true if the certificate is
// expiring soon (according to cfg) or has expired.
func (cert Certificate) NeedsRenewal(cfg *Config) bool {
return currentlyInRenewalWindow(cert.Leaf.NotBefore, cert.Leaf.NotAfter, cfg.RenewalWindowRatio)
return currentlyInRenewalWindow(cert.Leaf.NotBefore, expiresAt(cert.Leaf), cfg.RenewalWindowRatio)
}
// Expired returns true if the certificate has expired.
@@ -79,7 +82,7 @@ func (cert Certificate) Expired() bool {
// tls.X509KeyPair() discards the leaf; oh well
return false
}
return time.Now().After(cert.Leaf.NotAfter)
return time.Now().After(expiresAt(cert.Leaf))
}
// currentlyInRenewalWindow returns true if the current time is
@@ -109,6 +112,16 @@ func (cert Certificate) HasTag(tag string) bool {
return false
}
// expiresAt return the time that a certificate expires. Account for the 1s
// resolution of ASN.1 UTCTime/GeneralizedTime by including the extra fraction
// of a second of certificate validity beyond the NotAfter value.
func expiresAt(cert *x509.Certificate) time.Time {
if cert == nil {
return time.Time{}
}
return cert.NotAfter.Truncate(time.Second).Add(1 * time.Second)
}
// CacheManagedCertificate loads the certificate for domain into the
// cache, from the TLS storage for managed certificates. It returns a
// copy of the Certificate that was put into the cache.
@@ -122,7 +135,7 @@ func (cfg *Config) CacheManagedCertificate(ctx context.Context, domain string) (
return cert, err
}
cfg.certCache.cacheCertificate(cert)
cfg.emit("cached_managed_cert", cert.Names)
cfg.emit(ctx, "cached_managed_cert", map[string]any{"sans": cert.Names})
return cert, nil
}
@@ -145,53 +158,57 @@ func (cfg *Config) loadManagedCertificate(ctx context.Context, domain string) (C
// CacheUnmanagedCertificatePEMFile loads a certificate for host using certFile
// and keyFile, which must be in PEM format. It stores the certificate in
// the in-memory cache.
// the in-memory cache and returns the hash, useful for removing from the cache.
//
// This method is safe for concurrent use.
func (cfg *Config) CacheUnmanagedCertificatePEMFile(ctx context.Context, certFile, keyFile string, tags []string) error {
func (cfg *Config) CacheUnmanagedCertificatePEMFile(ctx context.Context, certFile, keyFile string, tags []string) (string, error) {
cert, err := cfg.makeCertificateFromDiskWithOCSP(ctx, cfg.Storage, certFile, keyFile)
if err != nil {
return err
return "", err
}
cert.Tags = tags
cfg.certCache.cacheCertificate(cert)
cfg.emit("cached_unmanaged_cert", cert.Names)
return nil
cfg.emit(ctx, "cached_unmanaged_cert", map[string]any{"sans": cert.Names})
return cert.hash, nil
}
// CacheUnmanagedTLSCertificate adds tlsCert to the certificate cache.
// CacheUnmanagedTLSCertificate adds tlsCert to the certificate cache
//
// and returns the hash, useful for removing from the cache.
//
// It staples OCSP if possible.
//
// This method is safe for concurrent use.
func (cfg *Config) CacheUnmanagedTLSCertificate(ctx context.Context, tlsCert tls.Certificate, tags []string) error {
func (cfg *Config) CacheUnmanagedTLSCertificate(ctx context.Context, tlsCert tls.Certificate, tags []string) (string, error) {
var cert Certificate
err := fillCertFromLeaf(&cert, tlsCert)
if err != nil {
return err
return "", err
}
err = stapleOCSP(ctx, cfg.OCSP, cfg.Storage, &cert, nil)
if err != nil && cfg.Logger != nil {
if err != nil {
cfg.Logger.Warn("stapling OCSP", zap.Error(err))
}
cfg.emit("cached_unmanaged_cert", cert.Names)
cfg.emit(ctx, "cached_unmanaged_cert", map[string]any{"sans": cert.Names})
cert.Tags = tags
cfg.certCache.cacheCertificate(cert)
return nil
return cert.hash, nil
}
// CacheUnmanagedCertificatePEMBytes makes a certificate out of the PEM bytes
// of the certificate and key, then caches it in memory.
// of the certificate and key, then caches it in memory, and returns the hash,
// which is useful for removing from the cache.
//
// This method is safe for concurrent use.
func (cfg *Config) CacheUnmanagedCertificatePEMBytes(ctx context.Context, certBytes, keyBytes []byte, tags []string) error {
func (cfg *Config) CacheUnmanagedCertificatePEMBytes(ctx context.Context, certBytes, keyBytes []byte, tags []string) (string, error) {
cert, err := cfg.makeCertificateWithOCSP(ctx, certBytes, keyBytes)
if err != nil {
return err
return "", err
}
cert.Tags = tags
cfg.certCache.cacheCertificate(cert)
cfg.emit("cached_unmanaged_cert", cert.Names)
return nil
cfg.emit(ctx, "cached_unmanaged_cert", map[string]any{"sans": cert.Names})
return cert.hash, nil
}
// makeCertificateFromDiskWithOCSP makes a Certificate by loading the
@@ -218,7 +235,7 @@ func (cfg Config) makeCertificateWithOCSP(ctx context.Context, certPEMBlock, key
return cert, err
}
err = stapleOCSP(ctx, cfg.OCSP, cfg.Storage, &cert, certPEMBlock)
if err != nil && cfg.Logger != nil {
if err != nil {
cfg.Logger.Warn("stapling OCSP", zap.Error(err), zap.Strings("identifiers", cert.Names))
}
return cert, nil
@@ -326,9 +343,7 @@ func (cfg *Config) managedCertInStorageExpiresSoon(ctx context.Context, cert Cer
// to the new cert. It assumes that the new certificate for oldCert.Names[0] is
// already in storage. It returns the newly-loaded certificate if successful.
func (cfg *Config) reloadManagedCertificate(ctx context.Context, oldCert Certificate) (Certificate, error) {
if cfg.Logger != nil {
cfg.Logger.Info("reloading managed certificate", zap.Strings("identifiers", oldCert.Names))
}
cfg.Logger.Info("reloading managed certificate", zap.Strings("identifiers", oldCert.Names))
newCert, err := cfg.loadManagedCertificate(ctx, oldCert.Names[0])
if err != nil {
return Certificate{}, fmt.Errorf("loading managed certificate for %v from storage: %v", oldCert.Names, err)
@@ -341,7 +356,7 @@ func (cfg *Config) reloadManagedCertificate(ctx context.Context, oldCert Certifi
// as a quick sanity check, looks like it could be the subject
// of a certificate. Requirements are:
// - must not be empty
// - must not start or end with a dot (RFC 1034)
// - must not start or end with a dot (RFC 1034; RFC 6066 section 3)
// - must not contain common accidental special characters
func SubjectQualifiesForCert(subj string) bool {
// must not be empty
@@ -395,7 +410,8 @@ func SubjectIsIP(subj string) bool {
func SubjectIsInternal(subj string) bool {
return subj == "localhost" ||
strings.HasSuffix(subj, ".localhost") ||
strings.HasSuffix(subj, ".local")
strings.HasSuffix(subj, ".local") ||
strings.HasSuffix(subj, ".home.arpa")
}
// MatchWildcard returns true if subject (a candidate DNS name)
+52 -19
View File
@@ -43,10 +43,14 @@ import (
"log"
"net"
"net/http"
"os"
"sort"
"strings"
"sync"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// HTTPS serves mux for all domainNames using the HTTP
@@ -264,9 +268,18 @@ type OnDemandConfig struct {
// whether a certificate can be obtained or renewed
// for the given name. If an error is returned, the
// request will be denied.
DecisionFunc func(name string) error
DecisionFunc func(ctx context.Context, name string) error
// List of whitelisted hostnames (SNI values) for
// Sources for getting new, unmanaged certificates.
// They will be invoked only during TLS handshakes
// before on-demand certificate management occurs,
// for certificates that are not already loaded into
// the in-memory cache.
//
// TODO: EXPERIMENTAL: subject to change and/or removal.
Managers []Manager
// List of allowed hostnames (SNI values) for
// deferred (on-demand) obtaining of certificates.
// Used only by higher-level functions in this
// package to persist the list of hostnames that
@@ -278,20 +291,15 @@ type OnDemandConfig struct {
// for higher-level convenience functions to be
// able to retain their convenience (alternative
// is: the user manually creates a DecisionFunc
// that whitelists the same names it already
// passed into Manage) and without letting clients
// have their run of any domain names they want.
// Only enforced if len > 0.
hostWhitelist []string
}
func (o *OnDemandConfig) whitelistContains(name string) bool {
for _, n := range o.hostWhitelist {
if strings.EqualFold(n, name) {
return true
}
}
return false
// that allows the same names it already passed
// into Manage) and without letting clients have
// their run of any domain names they want.
// Only enforced if len > 0. (This is a map to
// avoid O(n^2) performance; when it was a slice,
// we saw a 30s CPU profile for a config managing
// 110K names where 29s was spent checking for
// duplicates. Order is not important here.)
hostAllowlist map[string]struct{}
}
// isLoopback returns true if the hostname of addr looks
@@ -398,6 +406,23 @@ type KeyGenerator interface {
GenerateKey() (crypto.PrivateKey, error)
}
// IssuerPolicy is a type that enumerates how to
// choose which issuer to use. EXPERIMENTAL and
// subject to change.
type IssuerPolicy string
// Supported issuer policies. These are subject to change.
const (
// UseFirstIssuer uses the first issuer that
// successfully returns a certificate.
UseFirstIssuer = "first"
// UseFirstRandomIssuer shuffles the list of
// configured issuers, then uses the first one
// that successfully returns a certificate.
UseFirstRandomIssuer = "first_random"
)
// IssuedCertificate represents a certificate that was just issued.
type IssuedCertificate struct {
// The PEM-encoding of DER-encoded ASN.1 data.
@@ -405,7 +430,7 @@ type IssuedCertificate struct {
// Any extra information to serialize alongside the
// certificate in storage.
Metadata interface{}
Metadata any
}
// CertificateResource associates a certificate with its private
@@ -425,11 +450,11 @@ type CertificateResource struct {
// Any extra information associated with the certificate,
// usually provided by the issuer implementation.
IssuerData interface{} `json:"issuer_data,omitempty"`
IssuerData any `json:"issuer_data,omitempty"`
// The unique string identifying the issuer of the
// certificate; internally useful for storage access.
issuerKey string `json:"-"`
issuerKey string
}
// NamesKey returns the list of SANs as a single string,
@@ -468,8 +493,16 @@ var Default = Config{
RenewalWindowRatio: DefaultRenewalWindowRatio,
Storage: defaultFileStorage,
KeySource: DefaultKeyGenerator,
Logger: defaultLogger,
}
// defaultLogger is guaranteed to be a non-nil fallback logger.
var defaultLogger = zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
os.Stderr,
zap.InfoLevel,
))
const (
// HTTPChallengePort is the officially-designated port for
// the HTTP challenge according to the ACME spec.
+232 -176
View File
@@ -56,13 +56,29 @@ type Config struct {
// to subscribe to certain things happening
// internally by this config; invocations are
// synchronous, so make them return quickly!
OnEvent func(event string, data interface{})
// Functions should honor context cancellation.
//
// An error should only be returned to advise
// the emitter to abort or cancel an upcoming
// event. Some events, especially those that have
// already happened, cannot be aborted. For example,
// cert_obtaining can be canceled, but
// cert_obtained cannot. Emitters may choose to
// ignore returned errors.
OnEvent func(ctx context.Context, event string, data map[string]any) error
// DefaultServerName specifies a server name
// to use when choosing a certificate if the
// ClientHello's ServerName field is empty.
DefaultServerName string
// FallbackServerName specifies a server name
// to use when choosing a certificate if the
// ClientHello's ServerName field doesn't match
// any available certificate.
// EXPERIMENTAL: Subject to change or removal.
FallbackServerName string
// The state needed to operate on-demand TLS;
// if non-nil, on-demand TLS is enabled and
// certificate operations are deferred to
@@ -79,14 +95,16 @@ type Config struct {
// turn until one succeeds.
Issuers []Issuer
// Sources for getting new, unmanaged certificates.
// They will be invoked only during TLS handshakes
// before on-demand certificate management occurs,
// for certificates that are not already loaded into
// the in-memory cache.
//
// TODO: EXPERIMENTAL: subject to change and/or removal.
Managers []Manager
// How to select which issuer to use.
// Default: UseFirstIssuer (subject to change).
IssuerPolicy IssuerPolicy
// If true, private keys already existing in storage
// will be reused. Otherwise, a new key will be
// created for every new certificate to mitigate
// pinning and reduce the scope of key compromise.
// Default: false (do not reuse keys).
ReusePrivateKeys bool
// The source of new private keys for certificates;
// the default KeySource is StandardKeyGenerator.
@@ -110,7 +128,18 @@ type Config struct {
// TLS assets. Default is the local file system.
Storage Storage
// Set a logger to enable logging.
// CertMagic will verify the storage configuration
// is acceptable before obtaining a certificate
// to avoid information loss after an expensive
// operation. If you are absolutely 100% sure your
// storage is properly configured and has sufficient
// space, you can disable this check to reduce I/O
// if that is expensive for you.
// EXPERIMENTAL: Option subject to change or removal.
DisableStorageCheck bool
// Set a logger to enable logging. If not set,
// a default logger will be created.
Logger *zap.Logger
// required pointer to the in-memory cert cache
@@ -152,6 +181,7 @@ func NewDefault() *Config {
GetConfigForCert: func(Certificate) (*Config, error) {
return NewDefault(), nil
},
Logger: Default.Logger,
})
}
certCache := defaultCache
@@ -179,7 +209,10 @@ func New(certCache *Cache, cfg Config) *Config {
if certCache == nil {
panic("a certificate cache is required")
}
if certCache.options.GetConfigForCert == nil {
certCache.optionsMu.RLock()
getConfigForCert := certCache.options.GetConfigForCert
defer certCache.optionsMu.RUnlock()
if getConfigForCert == nil {
panic("cache must have GetConfigForCert set in its options")
}
return newWithCache(certCache, cfg)
@@ -196,6 +229,16 @@ func newWithCache(certCache *Cache, cfg Config) *Config {
if cfg.OnDemand == nil {
cfg.OnDemand = Default.OnDemand
}
if !cfg.MustStaple {
cfg.MustStaple = Default.MustStaple
}
if cfg.Issuers == nil {
cfg.Issuers = Default.Issuers
if cfg.Issuers == nil {
// at least one issuer is absolutely required if not nil
cfg.Issuers = []Issuer{NewACMEIssuer(&cfg, DefaultACME)}
}
}
if cfg.RenewalWindowRatio == 0 {
cfg.RenewalWindowRatio = Default.RenewalWindowRatio
}
@@ -208,18 +251,14 @@ func newWithCache(certCache *Cache, cfg Config) *Config {
if cfg.DefaultServerName == "" {
cfg.DefaultServerName = Default.DefaultServerName
}
if !cfg.MustStaple {
cfg.MustStaple = Default.MustStaple
if cfg.FallbackServerName == "" {
cfg.FallbackServerName = Default.FallbackServerName
}
if cfg.Storage == nil {
cfg.Storage = Default.Storage
}
if len(cfg.Issuers) == 0 {
cfg.Issuers = Default.Issuers
if len(cfg.Issuers) == 0 {
// at least one issuer is absolutely required
cfg.Issuers = []Issuer{NewACMEIssuer(&cfg, DefaultACME)}
}
if cfg.Logger == nil {
cfg.Logger = Default.Logger
}
// absolutely don't allow a nil storage,
@@ -229,6 +268,12 @@ func newWithCache(certCache *Cache, cfg Config) *Config {
cfg.Storage = defaultFileStorage
}
// absolutely don't allow a nil logger either,
// because that would result in panics
if cfg.Logger == nil {
cfg.Logger = defaultLogger
}
cfg.certCache = certCache
return &cfg
@@ -236,17 +281,20 @@ func newWithCache(certCache *Cache, cfg Config) *Config {
// ManageSync causes the certificates for domainNames to be managed
// according to cfg. If cfg.OnDemand is not nil, then this simply
// whitelists the domain names and defers the certificate operations
// allowlists the domain names and defers the certificate operations
// to when they are needed. Otherwise, the certificates for each
// name are loaded from storage or obtained from the CA. If loaded
// from storage, they are renewed if they are expiring or expired.
// It then caches the certificate in memory and is prepared to serve
// them up during TLS handshakes.
// name are loaded from storage or obtained from the CA if not already
// in the cache associated with the Config. If loaded from storage,
// they are renewed if they are expiring or expired. It then caches
// the certificate in memory and is prepared to serve them up during
// TLS handshakes. To change how an already-loaded certificate is
// managed, update the cache options relating to getting a config for
// a cert.
//
// Note that name whitelisting for on-demand management only takes
// Note that name allowlisting for on-demand management only takes
// effect if cfg.OnDemand.DecisionFunc is not set (is nil); it will
// not overwrite an existing DecisionFunc, nor will it overwrite
// its decision; i.e. the implicit whitelist is only used if no
// its decision; i.e. the implicit allowlist is only used if no
// DecisionFunc is set.
//
// This method is synchronous, meaning that certificates for all
@@ -306,16 +354,18 @@ func (cfg *Config) manageAll(ctx context.Context, domainNames []string, async bo
if ctx == nil {
ctx = context.Background()
}
if cfg.OnDemand != nil && cfg.OnDemand.hostAllowlist == nil {
cfg.OnDemand.hostAllowlist = make(map[string]struct{})
}
for _, domainName := range domainNames {
// if on-demand is configured, defer obtain and renew operations
if cfg.OnDemand != nil {
if !cfg.OnDemand.whitelistContains(domainName) {
cfg.OnDemand.hostWhitelist = append(cfg.OnDemand.hostWhitelist, domainName)
}
cfg.OnDemand.hostAllowlist[normalizedName(domainName)] = struct{}{}
continue
}
// TODO: consider doing this in a goroutine if async, to utilize multiple cores while loading certs
// otherwise, begin management immediately
err := cfg.manageOne(ctx, domainName, async)
if err != nil {
@@ -327,6 +377,14 @@ func (cfg *Config) manageAll(ctx context.Context, domainNames []string, async bo
}
func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool) error {
// if certificate is already being managed, nothing to do; maintenance will continue
certs := cfg.certCache.getAllMatchingCerts(domainName)
for _, cert := range certs {
if cert.managed {
return nil
}
}
// first try loading existing certificate from storage
cert, err := cfg.CacheManagedCertificate(ctx, domainName)
if err != nil {
@@ -406,28 +464,6 @@ func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool)
return renew()
}
// Unmanage causes the certificates for domainNames to stop being managed.
// If there are certificates for the supplied domain names in the cache, they
// are evicted from the cache.
func (cfg *Config) Unmanage(domainNames []string) {
var deleteQueue []Certificate
for _, domainName := range domainNames {
certs := cfg.certCache.AllMatchingCertificates(domainName)
for _, cert := range certs {
if !cert.managed {
continue
}
deleteQueue = append(deleteQueue, cert)
}
}
cfg.certCache.mu.Lock()
for _, cert := range deleteQueue {
cfg.certCache.removeCertificate(cert)
}
cfg.certCache.mu.Unlock()
}
// ObtainCertSync generates a new private key and obtains a certificate for
// name using cfg in the foreground; i.e. interactively and without retries.
// It stows the renewed certificate and its assets in storage if successful.
@@ -460,11 +496,9 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
return fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err)
}
log := loggerNamed(cfg.Logger, "obtain")
log := cfg.Logger.Named("obtain")
if log != nil {
log.Info("acquiring lock", zap.String("identifier", name))
}
log.Info("acquiring lock", zap.String("identifier", name))
// ensure idempotency of the obtain operation for this name
lockKey := cfg.lockKey(certIssueLockOp, name)
@@ -473,36 +507,48 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
return fmt.Errorf("unable to acquire lock '%s': %v", lockKey, err)
}
defer func() {
if log != nil {
log.Info("releasing lock", zap.String("identifier", name))
}
log.Info("releasing lock", zap.String("identifier", name))
if err := releaseLock(ctx, cfg.Storage, lockKey); err != nil {
if log != nil {
log.Error("unable to unlock",
zap.String("identifier", name),
zap.String("lock_key", lockKey),
zap.Error(err))
}
log.Error("unable to unlock",
zap.String("identifier", name),
zap.String("lock_key", lockKey),
zap.Error(err))
}
}()
if log != nil {
log.Info("lock acquired", zap.String("identifier", name))
}
log.Info("lock acquired", zap.String("identifier", name))
f := func(ctx context.Context) error {
// check if obtain is still needed -- might have been obtained during lock
if cfg.storageHasCertResourcesAnyIssuer(ctx, name) {
if log != nil {
log.Info("certificate already exists in storage", zap.String("identifier", name))
}
log.Info("certificate already exists in storage", zap.String("identifier", name))
return nil
}
// if storage has a private key already, use it; otherwise,
// we'll generate our own
privKey, privKeyPEM, issuers, err := cfg.reusePrivateKey(ctx, name)
if err != nil {
return err
log.Info("obtaining certificate", zap.String("identifier", name))
if err := cfg.emit(ctx, "cert_obtaining", map[string]any{"identifier": name}); err != nil {
return fmt.Errorf("obtaining certificate aborted by event handler: %w", err)
}
// If storage has a private key already, use it; otherwise we'll generate our own.
// Also create the slice of issuers we will try using according to any issuer
// selection policy (it must be a copy of the slice so we don't mutate original).
var privKey crypto.PrivateKey
var privKeyPEM []byte
var issuers []Issuer
if cfg.ReusePrivateKeys {
privKey, privKeyPEM, issuers, err = cfg.reusePrivateKey(ctx, name)
if err != nil {
return err
}
} else {
issuers = make([]Issuer, len(cfg.Issuers))
copy(issuers, cfg.Issuers)
}
if cfg.IssuerPolicy == UseFirstRandomIssuer {
weakrand.Shuffle(len(issuers), func(i, j int) {
issuers[i], issuers[j] = issuers[j], issuers[i]
})
}
if privKey == nil {
privKey, err = cfg.KeySource.GenerateKey()
@@ -523,11 +569,12 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
// try to obtain from each issuer until we succeed
var issuedCert *IssuedCertificate
var issuerUsed Issuer
var issuerKeys []string
for i, issuer := range issuers {
if log != nil {
log.Debug(fmt.Sprintf("trying issuer %d/%d", i+1, len(cfg.Issuers)),
zap.String("issuer", issuer.IssuerKey()))
}
issuerKeys = append(issuerKeys, issuer.IssuerKey())
log.Debug(fmt.Sprintf("trying issuer %d/%d", i+1, len(cfg.Issuers)),
zap.String("issuer", issuer.IssuerKey()))
if prechecker, ok := issuer.(PreChecker); ok {
err = prechecker.PreCheck(ctx, []string{name}, interactive)
@@ -549,17 +596,23 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
if errors.As(err, &problem) {
errToLog = problem
}
if log != nil {
log.Error("could not get certificate from issuer",
zap.String("identifier", name),
zap.String("issuer", issuer.IssuerKey()),
zap.Error(errToLog))
}
log.Error("could not get certificate from issuer",
zap.String("identifier", name),
zap.String("issuer", issuer.IssuerKey()),
zap.Error(errToLog))
}
if err != nil {
cfg.emit(ctx, "cert_failed", map[string]any{
"renewal": false,
"identifier": name,
"issuers": issuerKeys,
"error": err,
})
// only the error from the last issuer will be returned, but we logged the others
return fmt.Errorf("[%s] Obtain: %w", name, err)
}
issuerKey := issuerUsed.IssuerKey()
// success - immediately save the certificate resource
certRes := CertificateResource{
@@ -567,21 +620,26 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
CertificatePEM: issuedCert.Certificate,
PrivateKeyPEM: privKeyPEM,
IssuerData: issuedCert.Metadata,
issuerKey: issuerUsed.IssuerKey(),
}
err = cfg.saveCertResource(ctx, issuerUsed, certRes)
if err != nil {
return fmt.Errorf("[%s] Obtain: saving assets: %v", name, err)
}
cfg.emit("cert_obtained", CertificateEventData{
Name: name,
IssuerKey: issuerUsed.IssuerKey(),
StorageKey: certRes.NamesKey(),
})
log.Info("certificate obtained successfully", zap.String("identifier", name))
if log != nil {
log.Info("certificate obtained successfully", zap.String("identifier", name))
}
certKey := certRes.NamesKey()
cfg.emit(ctx, "cert_obtained", map[string]any{
"renewal": false,
"identifier": name,
"issuer": issuerUsed.IssuerKey(),
"storage_path": StorageKeys.CertsSitePrefix(issuerKey, certKey),
"private_key_path": StorageKeys.SitePrivateKey(issuerKey, certKey),
"certificate_path": StorageKeys.SiteCert(issuerKey, certKey),
"metadata_path": StorageKeys.SiteMeta(issuerKey, certKey),
})
return nil
}
@@ -650,9 +708,6 @@ func (cfg *Config) storageHasCertResourcesAnyIssuer(ctx context.Context, name st
// and its assets in storage if successful. It DOES NOT update the in-memory
// cache with the new certificate. The certificate will not be renewed if it
// is not close to expiring unless force is true.
//
// Renewing a certificate is the same as obtaining a certificate, except that
// the existing private key already in storage is reused.
func (cfg *Config) RenewCertSync(ctx context.Context, name string, force bool) error {
return cfg.renewCert(ctx, name, force, true)
}
@@ -675,11 +730,9 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
return fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err)
}
log := loggerNamed(cfg.Logger, "renew")
log := cfg.Logger.Named("renew")
if log != nil {
log.Info("acquiring lock", zap.String("identifier", name))
}
log.Info("acquiring lock", zap.String("identifier", name))
// ensure idempotency of the renew operation for this name
lockKey := cfg.lockKey(certIssueLockOp, name)
@@ -688,21 +741,16 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
return fmt.Errorf("unable to acquire lock '%s': %v", lockKey, err)
}
defer func() {
if log != nil {
log.Info("releasing lock", zap.String("identifier", name))
}
log.Info("releasing lock", zap.String("identifier", name))
if err := releaseLock(ctx, cfg.Storage, lockKey); err != nil {
if log != nil {
log.Error("unable to unlock",
zap.String("identifier", name),
zap.String("lock_key", lockKey),
zap.Error(err))
}
log.Error("unable to unlock",
zap.String("identifier", name),
zap.String("lock_key", lockKey),
zap.Error(err))
}
}()
if log != nil {
log.Info("lock acquired", zap.String("identifier", name))
}
log.Info("lock acquired", zap.String("identifier", name))
f := func(ctx context.Context) error {
// prepare for renewal (load PEM cert, key, and meta)
@@ -715,31 +763,50 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
timeLeft, needsRenew := cfg.managedCertNeedsRenewal(certRes)
if !needsRenew {
if force {
if log != nil {
log.Info("certificate does not need to be renewed, but renewal is being forced",
zap.String("identifier", name),
zap.Duration("remaining", timeLeft))
}
log.Info("certificate does not need to be renewed, but renewal is being forced",
zap.String("identifier", name),
zap.Duration("remaining", timeLeft))
} else {
if log != nil {
log.Info("certificate appears to have been renewed already",
zap.String("identifier", name),
zap.Duration("remaining", timeLeft))
}
log.Info("certificate appears to have been renewed already",
zap.String("identifier", name),
zap.Duration("remaining", timeLeft))
return nil
}
}
if log != nil {
log.Info("renewing certificate",
zap.String("identifier", name),
zap.Duration("remaining", timeLeft))
log.Info("renewing certificate",
zap.String("identifier", name),
zap.Duration("remaining", timeLeft))
if err := cfg.emit(ctx, "cert_obtaining", map[string]any{
"renewal": true,
"identifier": name,
"forced": force,
"remaining": timeLeft,
"issuer": certRes.issuerKey, // previous/current issuer
}); err != nil {
return fmt.Errorf("renewing certificate aborted by event handler: %w", err)
}
privateKey, err := PEMDecodePrivateKey(certRes.PrivateKeyPEM)
// reuse or generate new private key for CSR
var privateKey crypto.PrivateKey
if cfg.ReusePrivateKeys {
privateKey, err = PEMDecodePrivateKey(certRes.PrivateKeyPEM)
} else {
privateKey, err = cfg.KeySource.GenerateKey()
}
if err != nil {
return err
}
// if we generated a new key, make sure to replace its PEM encoding too!
if !cfg.ReusePrivateKeys {
certRes.PrivateKeyPEM, err = PEMEncodePrivateKey(privateKey)
if err != nil {
return err
}
}
csr, err := cfg.generateCSR(privateKey, []string{name})
if err != nil {
return err
@@ -748,7 +815,9 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
// try to obtain from each issuer until we succeed
var issuedCert *IssuedCertificate
var issuerUsed Issuer
var issuerKeys []string
for _, issuer := range cfg.Issuers {
issuerKeys = append(issuerKeys, issuer.IssuerKey())
if prechecker, ok := issuer.(PreChecker); ok {
err = prechecker.PreCheck(ctx, []string{name}, interactive)
if err != nil {
@@ -769,17 +838,24 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
if errors.As(err, &problem) {
errToLog = problem
}
if log != nil {
log.Error("could not get certificate from issuer",
zap.String("identifier", name),
zap.String("issuer", issuer.IssuerKey()),
zap.Error(errToLog))
}
log.Error("could not get certificate from issuer",
zap.String("identifier", name),
zap.String("issuer", issuer.IssuerKey()),
zap.Error(errToLog))
}
if err != nil {
cfg.emit(ctx, "cert_failed", map[string]any{
"renewal": true,
"identifier": name,
"remaining": timeLeft,
"issuers": issuerKeys,
"error": err,
})
// only the error from the last issuer will be returned, but we logged the others
return fmt.Errorf("[%s] Renew: %w", name, err)
}
issuerKey := issuerUsed.IssuerKey()
// success - immediately save the renewed certificate resource
newCertRes := CertificateResource{
@@ -787,21 +863,27 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
CertificatePEM: issuedCert.Certificate,
PrivateKeyPEM: certRes.PrivateKeyPEM,
IssuerData: issuedCert.Metadata,
issuerKey: issuerKey,
}
err = cfg.saveCertResource(ctx, issuerUsed, newCertRes)
if err != nil {
return fmt.Errorf("[%s] Renew: saving assets: %v", name, err)
}
cfg.emit("cert_renewed", CertificateEventData{
Name: name,
IssuerKey: issuerUsed.IssuerKey(),
StorageKey: certRes.NamesKey(),
})
log.Info("certificate renewed successfully", zap.String("identifier", name))
if log != nil {
log.Info("certificate renewed successfully", zap.String("identifier", name))
}
certKey := newCertRes.NamesKey()
cfg.emit(ctx, "cert_obtained", map[string]any{
"renewal": true,
"remaining": timeLeft,
"identifier": name,
"issuer": issuerKey,
"storage_path": StorageKeys.CertsSitePrefix(issuerKey, certKey),
"private_key_path": StorageKeys.SitePrivateKey(issuerKey, certKey),
"certificate_path": StorageKeys.SiteCert(issuerKey, certKey),
"metadata_path": StorageKeys.SiteMeta(issuerKey, certKey),
})
return nil
}
@@ -876,12 +958,6 @@ func (cfg *Config) RevokeCert(ctx context.Context, domain string, reason int, in
return fmt.Errorf("issuer %d (%s): %v", i, issuerKey, err)
}
cfg.emit("cert_revoked", CertificateEventData{
Name: domain,
IssuerKey: issuerKey,
StorageKey: certRes.NamesKey(),
})
err = cfg.deleteSiteAssets(ctx, issuerKey, domain)
if err != nil {
return fmt.Errorf("certificate revoked, but unable to fully clean up assets from issuer %s: %v", issuerKey, err)
@@ -981,6 +1057,9 @@ func (cfg *Config) getChallengeInfo(ctx context.Context, identifier string) (Cha
// comparing the loaded value. If this fails, the provided
// cfg.Storage mechanism should not be used.
func (cfg *Config) checkStorage(ctx context.Context) error {
if cfg.DisableStorageCheck {
return nil
}
key := fmt.Sprintf("rw_test_%d", weakrand.Int())
contents := make([]byte, 1024*10) // size sufficient for one or two ACME resources
_, err := weakrand.Read(contents)
@@ -994,10 +1073,8 @@ func (cfg *Config) checkStorage(ctx context.Context) error {
defer func() {
deleteErr := cfg.Storage.Delete(ctx, key)
if deleteErr != nil {
if cfg.Logger != nil {
cfg.Logger.Error("deleting test key from storage",
zap.String("key", key), zap.Error(err))
}
cfg.Logger.Error("deleting test key from storage",
zap.String("key", key), zap.Error(err))
}
// if there was no other error, make sure
// to return any error returned from Delete
@@ -1065,23 +1142,16 @@ func (cfg *Config) managedCertNeedsRenewal(certRes CertificateResource) (time.Du
if err != nil {
return 0, true
}
remaining := time.Until(certChain[0].NotAfter)
needsRenew := currentlyInRenewalWindow(certChain[0].NotBefore, certChain[0].NotAfter, cfg.RenewalWindowRatio)
remaining := time.Until(expiresAt(certChain[0]))
needsRenew := currentlyInRenewalWindow(certChain[0].NotBefore, expiresAt(certChain[0]), cfg.RenewalWindowRatio)
return remaining, needsRenew
}
func (cfg *Config) emit(eventName string, data interface{}) {
func (cfg *Config) emit(ctx context.Context, eventName string, data map[string]any) error {
if cfg.OnEvent == nil {
return
}
cfg.OnEvent(eventName, data)
}
func loggerNamed(l *zap.Logger, name string) *zap.Logger {
if l == nil {
return nil
}
return l.Named(name)
return cfg.OnEvent(ctx, eventName, data)
}
// CertificateSelector is a type which can select a certificate to use given multiple choices.
@@ -1105,20 +1175,6 @@ type OCSPConfig struct {
ResponderOverrides map[string]string
}
// CertificateEventData contains contextual information for
// an obtained, renewed or revoked certificate.
// EXPERIMENTAL: subject to change.
type CertificateEventData struct {
// Domain or subject name of the certificate.
Name string
// Storage key for the issuer used for this certificate.
IssuerKey string
// Location in storage at which the certificate could be found.
StorageKey string
}
// certIssueLockOp is the name of the operation used
// when naming a lock to make it mutually exclusive
// with other certificate issuance operations for a
+8 -10
View File
@@ -22,7 +22,6 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/json"
@@ -35,6 +34,7 @@ import (
"strings"
"github.com/klauspost/cpuid/v2"
"github.com/zeebo/blake3"
"go.uber.org/zap"
"golang.org/x/net/idna"
)
@@ -226,14 +226,12 @@ func (cfg *Config) loadCertResourceAnyIssuer(ctx context.Context, certNamesKey s
return certResources[j].decoded.NotBefore.Before(certResources[i].decoded.NotBefore)
})
if cfg.Logger != nil {
cfg.Logger.Debug("loading managed certificate",
zap.String("domain", certNamesKey),
zap.Time("expiration", certResources[0].decoded.NotAfter),
zap.String("issuer_key", certResources[0].issuer.IssuerKey()),
zap.Any("storage", cfg.Storage),
)
}
cfg.Logger.Debug("loading managed certificate",
zap.String("domain", certNamesKey),
zap.Time("expiration", expiresAt(certResources[0].decoded)),
zap.String("issuer_key", certResources[0].issuer.IssuerKey()),
zap.Any("storage", cfg.Storage),
)
return certResources[0].CertificateResource, nil
}
@@ -273,7 +271,7 @@ func (cfg *Config) loadCertResource(ctx context.Context, issuer Issuer, certName
// which is the chain of DER-encoded bytes. It returns the
// hex encoding of the hash.
func hashCertificateChain(certChain [][]byte) string {
h := sha256.New()
h := blake3.New()
for _, certInChain := range certChain {
h.Write(certInChain)
}
+5 -3
View File
@@ -133,7 +133,9 @@ func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
m := new(dns.Msg)
m.SetQuestion(fqdn, rtype)
m.SetEdns0(4096, false)
// See: https://caddy.community/t/hard-time-getting-a-response-on-a-dns-01-challenge/15721/16
m.SetEdns0(1232, false)
if !recursive {
m.RecursionDesired = false
}
@@ -235,13 +237,13 @@ func checkDNSPropagation(fqdn, value string, resolvers []string) (bool, error) {
// checkAuthoritativeNss queries each of the given nameservers for the expected TXT record.
func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) {
for _, ns := range nameservers {
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{net.JoinHostPort(ns, "53")}, false)
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{net.JoinHostPort(ns, "53")}, true)
if err != nil {
return false, err
}
if r.Rcode != dns.RcodeSuccess {
if r.Rcode == dns.RcodeNameError {
if r.Rcode == dns.RcodeNameError || r.Rcode == dns.RcodeServerFailure {
// if Present() succeeded, then it must show up eventually, or else
// something is really broken in the DNS provider or their API;
// no need for error here, simply have the caller try again
+35 -10
View File
@@ -149,10 +149,15 @@ func (s *FileStorage) Filename(key string) string {
return filepath.Join(s.Path, filepath.FromSlash(key))
}
// Lock obtains a lock named by the given key. It blocks
// Lock obtains a lock named by the given name. It blocks
// until the lock can be obtained or an error is returned.
func (s *FileStorage) Lock(ctx context.Context, key string) error {
filename := s.lockFilename(key)
func (s *FileStorage) Lock(ctx context.Context, name string) error {
filename := s.lockFilename(name)
// sometimes the lockfiles read as empty (size 0) - this is either a stale lock or it
// is currently being written; we can retry a few times in this case, as it has been
// shown to help (issue #232)
var emptyCount int
for {
err := createLockfile(filename)
@@ -172,7 +177,25 @@ func (s *FileStorage) Lock(ctx context.Context, key string) error {
if err == nil {
err2 := json.NewDecoder(f).Decode(&meta)
f.Close()
if err2 != nil {
if errors.Is(err2, io.EOF) {
emptyCount++
if emptyCount < 8 {
// wait for brief time and retry; could be that the file is in the process
// of being written or updated (which involves truncating) - see issue #232
select {
case <-time.After(250 * time.Millisecond):
case <-ctx.Done():
return ctx.Err()
}
continue
} else {
// lockfile is empty or truncated multiple times; I *think* we can assume
// the previous acquirer either crashed or had some sort of failure that
// caused them to be unable to fully acquire or retain the lock, therefore
// we should treat it as if the lockfile did not exist
log.Printf("[INFO][%s] %s: Empty lockfile (%v) - likely previous process crashed or storage medium failure; treating as stale", s, filename, err2)
}
} else if err2 != nil {
return fmt.Errorf("decoding lockfile contents: %w", err2)
}
}
@@ -193,10 +216,10 @@ func (s *FileStorage) Lock(ctx context.Context, key string) error {
// or must give up on perfect mutual exclusivity; however, these cases are rare,
// so we prefer the simpler solution that avoids infinite loops)
log.Printf("[INFO][%s] Lock for '%s' is stale (created: %s, last update: %s); removing then retrying: %s",
s, key, meta.Created, meta.Updated, filename)
s, name, meta.Created, meta.Updated, filename)
if err = os.Remove(filename); err != nil { // hopefully we can replace the lock file quickly!
if !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("unable to delete stale lock; deadlocked: %w", err)
return fmt.Errorf("unable to delete stale lockfile; deadlocked: %w", err)
}
}
continue
@@ -215,16 +238,16 @@ func (s *FileStorage) Lock(ctx context.Context, key string) error {
}
// Unlock releases the lock for name.
func (s *FileStorage) Unlock(_ context.Context, key string) error {
return os.Remove(s.lockFilename(key))
func (s *FileStorage) Unlock(_ context.Context, name string) error {
return os.Remove(s.lockFilename(name))
}
func (s *FileStorage) String() string {
return "FileStorage:" + s.Path
}
func (s *FileStorage) lockFilename(key string) string {
return filepath.Join(s.lockDir(), StorageKeys.Safe(key)+".lock")
func (s *FileStorage) lockFilename(name string) string {
return filepath.Join(s.lockDir(), StorageKeys.Safe(name)+".lock")
}
func (s *FileStorage) lockDir() string {
@@ -305,6 +328,8 @@ func updateLockfileFreshness(filename string) (bool, error) {
}
var meta lockMeta
if err := json.Unmarshal(metaBytes, &meta); err != nil {
// see issue #232: this can error if the file is empty,
// which happens sometimes when the disk is REALLY slow
return true, err
}
+373 -258
View File
@@ -42,8 +42,27 @@ import (
// 5. Issuers (if on-demand is enabled)
//
// This method is safe for use as a tls.Config.GetCertificate callback.
//
// GetCertificate will run in a new context, use GetCertificateWithContext to provide
// a context.
func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
cfg.emit("tls_handshake_started", clientHello)
return cfg.GetCertificateWithContext(clientHello.Context(), clientHello)
}
func (cfg *Config) GetCertificateWithContext(ctx context.Context, clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
if err := cfg.emit(ctx, "tls_get_certificate", map[string]any{"client_hello": clientHelloWithoutConn(clientHello)}); err != nil {
cfg.Logger.Error("TLS handshake aborted by event handler",
zap.String("server_name", clientHello.ServerName),
zap.String("remote", clientHello.Conn.RemoteAddr().String()),
zap.Error(err))
return nil, fmt.Errorf("handshake aborted by event handler: %w", err)
}
if ctx == nil {
// tests can't set context on a tls.ClientHelloInfo because it's unexported :(
ctx = context.Background()
}
ctx = context.WithValue(ctx, ClientHelloInfoCtxKey, clientHello)
// special case: serve up the certificate for a TLS-ALPN ACME challenge
// (https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05)
@@ -51,29 +70,24 @@ func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certif
if proto == acmez.ACMETLS1Protocol {
challengeCert, distributed, err := cfg.getTLSALPNChallengeCert(clientHello)
if err != nil {
if cfg.Logger != nil {
cfg.Logger.Error("tls-alpn challenge",
zap.String("server_name", clientHello.ServerName),
zap.Error(err))
}
cfg.Logger.Error("tls-alpn challenge",
zap.String("remote_addr", clientHello.Conn.RemoteAddr().String()),
zap.String("server_name", clientHello.ServerName),
zap.Error(err))
return nil, err
}
if cfg.Logger != nil {
cfg.Logger.Info("served key authentication certificate",
zap.String("server_name", clientHello.ServerName),
zap.String("challenge", "tls-alpn-01"),
zap.String("remote", clientHello.Conn.RemoteAddr().String()),
zap.Bool("distributed", distributed))
}
cfg.Logger.Info("served key authentication certificate",
zap.String("server_name", clientHello.ServerName),
zap.String("challenge", "tls-alpn-01"),
zap.String("remote", clientHello.Conn.RemoteAddr().String()),
zap.Bool("distributed", distributed))
return challengeCert, nil
}
}
// get the certificate and serve it up
cert, err := cfg.getCertDuringHandshake(clientHello, true, true)
if err == nil {
cfg.emit("tls_handshake_completed", clientHello)
}
cert, err := cfg.getCertDuringHandshake(ctx, clientHello, true)
return &cert.Certificate, err
}
@@ -134,6 +148,20 @@ func (cfg *Config) getCertificateFromCache(hello *tls.ClientHelloInfo) (cert Cer
}
}
// a fallback server name can be tried in the very niche
// case where a client sends one SNI value but expects or
// accepts a different one in return (this is sometimes
// the case with CDNs like Cloudflare that send the
// downstream ServerName in the handshake but accept
// the backend origin's true hostname in a cert).
if cfg.FallbackServerName != "" {
normFallback := normalizedName(cfg.FallbackServerName)
cert, defaulted = cfg.selectCert(hello, normFallback)
if defaulted {
return
}
}
// otherwise, we're bingo on ammo; see issues
// caddyserver/caddy#2035 and caddyserver/caddy#1303 (any
// change to certificate matching behavior must
@@ -152,48 +180,44 @@ func (cfg *Config) getCertificateFromCache(hello *tls.ClientHelloInfo) (cert Cer
// then all certificates in the cache will be passed in
// for the cfg.CertSelection to make the final decision.
func (cfg *Config) selectCert(hello *tls.ClientHelloInfo, name string) (Certificate, bool) {
logger := loggerNamed(cfg.Logger, "handshake")
logger := cfg.Logger.Named("handshake")
choices := cfg.certCache.getAllMatchingCerts(name)
if len(choices) == 0 {
if cfg.CertSelection == nil {
if logger != nil {
logger.Debug("no matching certificates and no custom selection logic", zap.String("identifier", name))
}
logger.Debug("no matching certificates and no custom selection logic", zap.String("identifier", name))
return Certificate{}, false
}
if logger != nil {
logger.Debug("no matching certificate; will choose from all certificates", zap.String("identifier", name))
}
logger.Debug("no matching certificate; will choose from all certificates", zap.String("identifier", name))
choices = cfg.certCache.getAllCerts()
}
if logger != nil {
logger.Debug("choosing certificate",
zap.String("identifier", name),
zap.Int("num_choices", len(choices)))
}
logger.Debug("choosing certificate",
zap.String("identifier", name),
zap.Int("num_choices", len(choices)))
if cfg.CertSelection == nil {
cert, err := DefaultCertificateSelector(hello, choices)
if logger != nil {
logger.Debug("default certificate selection results",
zap.Error(err),
zap.String("identifier", name),
zap.Strings("subjects", cert.Names),
zap.Bool("managed", cert.managed),
zap.String("issuer_key", cert.issuerKey),
zap.String("hash", cert.hash))
}
return cert, err == nil
}
cert, err := cfg.CertSelection.SelectCertificate(hello, choices)
if logger != nil {
logger.Debug("custom certificate selection results",
logger.Debug("default certificate selection results",
zap.Error(err),
zap.String("identifier", name),
zap.Strings("subjects", cert.Names),
zap.Bool("managed", cert.managed),
zap.String("issuer_key", cert.issuerKey),
zap.String("hash", cert.hash))
return cert, err == nil
}
cert, err := cfg.CertSelection.SelectCertificate(hello, choices)
logger.Debug("custom certificate selection results",
zap.Error(err),
zap.String("identifier", name),
zap.Strings("subjects", cert.Names),
zap.Bool("managed", cert.managed),
zap.String("issuer_key", cert.issuerKey),
zap.String("hash", cert.hash))
return cert, err == nil
}
@@ -214,7 +238,7 @@ func DefaultCertificateSelector(hello *tls.ClientHelloInfo, choices []Certificat
continue
}
best = choice // at least the client supports it...
if now.After(choice.Leaf.NotBefore) && now.Before(choice.Leaf.NotAfter) {
if now.After(choice.Leaf.NotBefore) && now.Before(expiresAt(choice.Leaf)) {
return choice, nil // ...and unexpired, great! "Certificate, I choose you!"
}
}
@@ -234,33 +258,74 @@ func DefaultCertificateSelector(hello *tls.ClientHelloInfo, choices []Certificat
// An error will be returned if and only if no certificate is available.
//
// This function is safe for concurrent use.
func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNecessary, obtainIfNecessary bool) (Certificate, error) {
log := loggerNamed(cfg.Logger, "handshake")
ctx := context.TODO() // TODO: get a proper context? from somewhere...
func (cfg *Config) getCertDuringHandshake(ctx context.Context, hello *tls.ClientHelloInfo, loadOrObtainIfNecessary bool) (Certificate, error) {
logger := logWithRemote(cfg.Logger.Named("handshake"), hello)
// First check our in-memory cache to see if we've already loaded it
cert, matched, defaulted := cfg.getCertificateFromCache(hello)
if matched {
if log != nil {
log.Debug("matched certificate in cache",
zap.Strings("subjects", cert.Names),
zap.Bool("managed", cert.managed),
zap.Time("expiration", cert.Leaf.NotAfter),
zap.String("hash", cert.hash))
}
if cert.managed && cfg.OnDemand != nil && obtainIfNecessary {
logger.Debug("matched certificate in cache",
zap.Strings("subjects", cert.Names),
zap.Bool("managed", cert.managed),
zap.Time("expiration", expiresAt(cert.Leaf)),
zap.String("hash", cert.hash))
if cert.managed && cfg.OnDemand != nil && loadOrObtainIfNecessary {
// On-demand certificates are maintained in the background, but
// maintenance is triggered by handshakes instead of by a timer
// as in maintain.go.
return cfg.optionalMaintenance(ctx, loggerNamed(cfg.Logger, "on_demand"), cert, hello)
return cfg.optionalMaintenance(ctx, cfg.Logger.Named("on_demand"), cert, hello)
}
return cert, nil
}
name := cfg.getNameFromClientHello(hello)
// By this point, we need to load or obtain a certificate. If a swarm of requests comes in for the same
// domain, avoid pounding manager or storage thousands of times simultaneously. We use a similar sync
// strategy for obtaining certificate during handshake.
certLoadWaitChansMu.Lock()
wait, ok := certLoadWaitChans[name]
if ok {
// another goroutine is already loading the cert; just wait and we'll get it from the in-memory cache
certLoadWaitChansMu.Unlock()
timeout := time.NewTimer(2 * time.Minute)
select {
case <-timeout.C:
return Certificate{}, fmt.Errorf("timed out waiting to load certificate for %s", name)
case <-ctx.Done():
timeout.Stop()
return Certificate{}, ctx.Err()
case <-wait:
timeout.Stop()
}
return cfg.getCertDuringHandshake(ctx, hello, false)
} else {
// no other goroutine is currently trying to load this cert
wait = make(chan struct{})
certLoadWaitChans[name] = wait
certLoadWaitChansMu.Unlock()
// unblock others and clean up when we're done
defer func() {
certLoadWaitChansMu.Lock()
close(wait)
delete(certLoadWaitChans, name)
certLoadWaitChansMu.Unlock()
}()
}
// Make sure a certificate is allowed for the given name. If not, it doesn't
// make sense to try loading one from storage (issue #185), getting it from a
// certificate manager, or obtaining one from an issuer.
if err := cfg.checkIfCertShouldBeObtained(ctx, name, false); err != nil {
return Certificate{}, fmt.Errorf("certificate is not allowed for server name %s: %w", name, err)
}
// If an external Manager is configured, try to get it from them.
// Only continue to use our own logic if it returns empty+nil.
externalCert, err := cfg.getCertFromAnyCertManager(ctx, hello, log)
externalCert, err := cfg.getCertFromAnyCertManager(ctx, hello, logger)
if err != nil {
return Certificate{}, err
}
@@ -268,8 +333,6 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece
return externalCert, nil
}
name := cfg.getNameFromClientHello(hello)
// We might be able to load or obtain a needed certificate. Load from
// storage if OnDemand is enabled, or if there is the possibility that
// a statically-managed cert was evicted from a full cache.
@@ -284,74 +347,78 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece
// perfectly full while still being able to load needed certs from storage.
// See https://caddy.community/t/error-tls-alert-internal-error-592-again/13272
// and caddyserver/caddy#4320.
cfg.certCache.optionsMu.RLock()
cacheCapacity := float64(cfg.certCache.options.Capacity)
cfg.certCache.optionsMu.RUnlock()
cacheAlmostFull := cacheCapacity > 0 && float64(cacheSize) >= cacheCapacity*.9
loadDynamically := cfg.OnDemand != nil || cacheAlmostFull
if loadDynamically && loadIfNecessary {
// Then check to see if we have one on disk
// TODO: As suggested here, https://caddy.community/t/error-tls-alert-internal-error-592-again/13272/30?u=matt,
// it might be a good idea to check with the DecisionFunc or allowlist first before even loading the certificate
// from storage, since if we can't renew it, why should we even try serving it (it will just get evicted after
// we get a return value of false anyway)? See issue #174
loadedCert, err := cfg.CacheManagedCertificate(ctx, name)
if errors.Is(err, fs.ErrNotExist) {
// If no exact match, try a wildcard variant, which is something we can still use
labels := strings.Split(name, ".")
labels[0] = "*"
loadedCert, err = cfg.CacheManagedCertificate(ctx, strings.Join(labels, "."))
}
if loadDynamically && loadOrObtainIfNecessary {
// Check to see if we have one on disk
loadedCert, err := cfg.loadCertFromStorage(ctx, logger, hello)
if err == nil {
if log != nil {
log.Debug("loaded certificate from storage",
zap.Strings("subjects", loadedCert.Names),
zap.Bool("managed", loadedCert.managed),
zap.Time("expiration", loadedCert.Leaf.NotAfter),
zap.String("hash", loadedCert.hash))
}
loadedCert, err = cfg.handshakeMaintenance(ctx, hello, loadedCert)
if err != nil {
if log != nil {
log.Error("maintaining newly-loaded certificate",
zap.String("server_name", name),
zap.Error(err))
}
}
return loadedCert, nil
}
if cfg.OnDemand != nil && obtainIfNecessary {
logger.Debug("did not load cert from storage",
zap.String("server_name", hello.ServerName),
zap.Error(err))
if cfg.OnDemand != nil {
// By this point, we need to ask the CA for a certificate
return cfg.obtainOnDemandCertificate(ctx, hello)
}
return loadedCert, nil
}
// Fall back to the default certificate if there is one
// Fall back to another certificate if there is one (either DefaultServerName or FallbackServerName)
if defaulted {
if log != nil {
log.Debug("fell back to default certificate",
zap.Strings("subjects", cert.Names),
zap.Bool("managed", cert.managed),
zap.Time("expiration", cert.Leaf.NotAfter),
zap.String("hash", cert.hash))
}
logger.Debug("fell back to default certificate",
zap.Strings("subjects", cert.Names),
zap.Bool("managed", cert.managed),
zap.Time("expiration", expiresAt(cert.Leaf)),
zap.String("hash", cert.hash))
return cert, nil
}
if log != nil {
log.Debug("no certificate matching TLS ClientHello",
zap.String("server_name", hello.ServerName),
zap.String("remote", hello.Conn.RemoteAddr().String()),
zap.String("identifier", name),
zap.Uint16s("cipher_suites", hello.CipherSuites),
zap.Float64("cert_cache_fill", float64(cacheSize)/cacheCapacity), // may be approximate! because we are not within the lock
zap.Bool("load_if_necessary", loadIfNecessary),
zap.Bool("obtain_if_necessary", obtainIfNecessary),
zap.Bool("on_demand", cfg.OnDemand != nil))
}
logger.Debug("no certificate matching TLS ClientHello",
zap.String("server_name", hello.ServerName),
zap.String("remote", hello.Conn.RemoteAddr().String()),
zap.String("identifier", name),
zap.Uint16s("cipher_suites", hello.CipherSuites),
zap.Float64("cert_cache_fill", float64(cacheSize)/cacheCapacity), // may be approximate! because we are not within the lock
zap.Bool("load_or_obtain_if_necessary", loadOrObtainIfNecessary),
zap.Bool("on_demand", cfg.OnDemand != nil))
return Certificate{}, fmt.Errorf("no certificate available for '%s'", name)
}
// loadCertFromStorage loads the certificate for name from storage and maintains it
// (as this is only called with on-demand TLS enabled).
func (cfg *Config) loadCertFromStorage(ctx context.Context, logger *zap.Logger, hello *tls.ClientHelloInfo) (Certificate, error) {
name := cfg.getNameFromClientHello(hello)
loadedCert, err := cfg.CacheManagedCertificate(ctx, name)
if errors.Is(err, fs.ErrNotExist) {
// If no exact match, try a wildcard variant, which is something we can still use
labels := strings.Split(name, ".")
labels[0] = "*"
loadedCert, err = cfg.CacheManagedCertificate(ctx, strings.Join(labels, "."))
}
if err != nil {
return Certificate{}, fmt.Errorf("no matching certificate to load for %s: %w", name, err)
}
logger.Debug("loaded certificate from storage",
zap.Strings("subjects", loadedCert.Names),
zap.Bool("managed", loadedCert.managed),
zap.Time("expiration", expiresAt(loadedCert.Leaf)),
zap.String("hash", loadedCert.hash))
loadedCert, err = cfg.handshakeMaintenance(ctx, hello, loadedCert)
if err != nil {
logger.Error("maintaining newly-loaded certificate",
zap.String("server_name", name),
zap.Error(err))
}
return loadedCert, nil
}
// optionalMaintenance will perform maintenance on the certificate (if necessary) and
// will return the resulting certificate. This should only be done if the certificate
// is managed, OnDemand is enabled, and the scope is allowed to obtain certificates.
@@ -361,12 +428,10 @@ func (cfg *Config) optionalMaintenance(ctx context.Context, log *zap.Logger, cer
return newCert, nil
}
if log != nil {
log.Error("renewing certificate on-demand failed",
zap.Strings("subjects", cert.Names),
zap.Time("not_after", cert.Leaf.NotAfter),
zap.Error(err))
}
log.Error("renewing certificate on-demand failed",
zap.Strings("subjects", cert.Names),
zap.Time("not_after", expiresAt(cert.Leaf)),
zap.Error(err))
if cert.Expired() {
return cert, err
@@ -379,19 +444,25 @@ func (cfg *Config) optionalMaintenance(ctx context.Context, log *zap.Logger, cer
// checkIfCertShouldBeObtained checks to see if an on-demand TLS certificate
// should be obtained for a given domain based upon the config settings. If
// a non-nil error is returned, do not issue a new certificate for name.
func (cfg *Config) checkIfCertShouldBeObtained(name string) error {
if cfg.OnDemand == nil {
func (cfg *Config) checkIfCertShouldBeObtained(ctx context.Context, name string, requireOnDemand bool) error {
if requireOnDemand && cfg.OnDemand == nil {
return fmt.Errorf("not configured for on-demand certificate issuance")
}
if !SubjectQualifiesForCert(name) {
return fmt.Errorf("subject name does not qualify for certificate: %s", name)
}
if cfg.OnDemand.DecisionFunc != nil {
return cfg.OnDemand.DecisionFunc(name)
}
if len(cfg.OnDemand.hostWhitelist) > 0 &&
!cfg.OnDemand.whitelistContains(name) {
return fmt.Errorf("certificate for '%s' is not managed", name)
if cfg.OnDemand != nil {
if cfg.OnDemand.DecisionFunc != nil {
if err := cfg.OnDemand.DecisionFunc(ctx, name); err != nil {
return fmt.Errorf("decision func: %w", err)
}
return nil
}
if len(cfg.OnDemand.hostAllowlist) > 0 {
if _, ok := cfg.OnDemand.hostAllowlist[name]; !ok {
return fmt.Errorf("certificate for '%s' is not managed", name)
}
}
}
return nil
}
@@ -402,15 +473,10 @@ func (cfg *Config) checkIfCertShouldBeObtained(name string) error {
//
// This function is safe for use by multiple concurrent goroutines.
func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.ClientHelloInfo) (Certificate, error) {
log := loggerNamed(cfg.Logger, "on_demand")
log := logWithRemote(cfg.Logger.Named("on_demand"), hello)
name := cfg.getNameFromClientHello(hello)
getCertWithoutReobtaining := func() (Certificate, error) {
// very important to set the obtainIfNecessary argument to false, so we don't repeat this infinitely
return cfg.getCertDuringHandshake(hello, true, false)
}
// We must protect this process from happening concurrently, so synchronize.
obtainCertWaitChansMu.Lock()
wait, ok := obtainCertWaitChans[name]
@@ -419,6 +485,9 @@ func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.Cli
// wait for it to finish obtaining the cert and then we'll use it.
obtainCertWaitChansMu.Unlock()
log.Debug("new certificate is needed, but is already being obtained; waiting for that issuance to complete",
zap.String("subject", name))
// TODO: see if we can get a proper context in here, for true cancellation
timeout := time.NewTimer(2 * time.Minute)
select {
@@ -428,7 +497,9 @@ func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.Cli
timeout.Stop()
}
return getCertWithoutReobtaining()
// it should now be loaded in the cache, ready to go; if not,
// the goroutine in charge of that probably had an error
return cfg.getCertDuringHandshake(ctx, hello, false)
}
// looks like it's up to us to do all the work and obtain the cert.
@@ -444,39 +515,30 @@ func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.Cli
obtainCertWaitChansMu.Unlock()
}
// Make sure the certificate should be obtained based on config
err := cfg.checkIfCertShouldBeObtained(name)
if err != nil {
unblockWaiters()
return Certificate{}, err
}
log.Info("obtaining new certificate", zap.String("server_name", name))
if log != nil {
log.Info("obtaining new certificate", zap.String("server_name", name))
}
// TODO: we are only adding a timeout because we don't know if the context passed in is actually cancelable...
// set a timeout so we don't inadvertently hold a client handshake open too long
// (timeout duration is based on https://caddy.community/t/zerossl-dns-challenge-failing-often-route53-plugin/13822/24?u=matt)
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, 180*time.Second)
defer cancel()
// Obtain the certificate
err = cfg.ObtainCertAsync(ctx, name)
// immediately unblock anyone waiting for it; doing this in
// a defer would risk deadlock because of the recursive call
// to getCertDuringHandshake below when we return!
unblockWaiters()
if err != nil {
// shucks; failed to solve challenge on-demand
return Certificate{}, err
// obtain the certificate (this puts it in storage) and if successful,
// load it from storage so we and any other waiting goroutine can use it
var cert Certificate
err := cfg.ObtainCertAsync(ctx, name)
if err == nil {
// load from storage while others wait to make the op as atomic as possible
cert, err = cfg.loadCertFromStorage(ctx, log, hello)
if err != nil {
log.Error("loading newly-obtained certificate from storage", zap.String("server_name", name), zap.Error(err))
}
}
// success; certificate was just placed on disk, so
// we need only restart serving the certificate
return getCertWithoutReobtaining()
// immediately unblock anyone waiting for it
unblockWaiters()
return cert, err
}
// handshakeMaintenance performs a check on cert for expiration and OCSP validity.
@@ -485,35 +547,30 @@ func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.Cli
//
// This function is safe for use by multiple concurrent goroutines.
func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHelloInfo, cert Certificate) (Certificate, error) {
log := loggerNamed(cfg.Logger, "on_demand")
log := cfg.Logger.Named("on_demand")
// Check OCSP staple validity
if cert.ocsp != nil && !freshOCSP(cert.ocsp) {
if log != nil {
log.Debug("OCSP response needs refreshing",
zap.Strings("identifiers", cert.Names),
zap.Int("ocsp_status", cert.ocsp.Status),
zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("next_update", cert.ocsp.NextUpdate))
}
log.Debug("OCSP response needs refreshing",
zap.Strings("identifiers", cert.Names),
zap.Int("ocsp_status", cert.ocsp.Status),
zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("next_update", cert.ocsp.NextUpdate))
err := stapleOCSP(ctx, cfg.OCSP, cfg.Storage, &cert, nil)
if err != nil {
// An error with OCSP stapling is not the end of the world, and in fact, is
// quite common considering not all certs have issuer URLs that support it.
if log != nil {
log.Warn("stapling OCSP",
zap.String("server_name", hello.ServerName),
zap.Error(err))
}
} else if log != nil {
if log != nil {
log.Debug("successfully stapled new OCSP response",
zap.Strings("identifiers", cert.Names),
zap.Int("ocsp_status", cert.ocsp.Status),
zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("next_update", cert.ocsp.NextUpdate))
}
log.Warn("stapling OCSP",
zap.String("server_name", hello.ServerName),
zap.Strings("sans", cert.Names),
zap.Error(err))
} else {
log.Debug("successfully stapled new OCSP response",
zap.Strings("identifiers", cert.Names),
zap.Int("ocsp_status", cert.ocsp.Status),
zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("next_update", cert.ocsp.NextUpdate))
}
// our copy of cert has the new OCSP staple, so replace it in the cache
@@ -525,19 +582,27 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
// We attempt to replace any certificates that were revoked.
// Crucially, this happens OUTSIDE a lock on the certCache.
if certShouldBeForceRenewed(cert) {
if log != nil {
log.Warn("on-demand certificate's OCSP status is REVOKED; will try to forcefully renew",
zap.Strings("identifiers", cert.Names),
zap.Int("ocsp_status", cert.ocsp.Status),
zap.Time("revoked_at", cert.ocsp.RevokedAt),
zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("next_update", cert.ocsp.NextUpdate))
}
log.Warn("on-demand certificate's OCSP status is REVOKED; will try to forcefully renew",
zap.Strings("identifiers", cert.Names),
zap.Int("ocsp_status", cert.ocsp.Status),
zap.Time("revoked_at", cert.ocsp.RevokedAt),
zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("next_update", cert.ocsp.NextUpdate))
return cfg.renewDynamicCertificate(ctx, hello, cert)
}
// Check cert expiration
if currentlyInRenewalWindow(cert.Leaf.NotBefore, cert.Leaf.NotAfter, cfg.RenewalWindowRatio) {
if currentlyInRenewalWindow(cert.Leaf.NotBefore, expiresAt(cert.Leaf), cfg.RenewalWindowRatio) {
// Check if the certificate still exists on disk. If not, we need to obtain a new one.
// This can happen if the certificate was cleaned up by the storage cleaner, but still
// remains in the in-memory cache.
if !cfg.storageHasCertResourcesAnyIssuer(ctx, cert.Names[0]) {
log.Debug("certificate not found on disk; obtaining new certificate",
zap.Strings("identifiers", cert.Names))
return cfg.obtainOnDemandCertificate(ctx, hello)
}
// Otherwise, renew the certificate.
return cfg.renewDynamicCertificate(ctx, hello, cert)
}
@@ -556,17 +621,12 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
//
// This function is safe for use by multiple concurrent goroutines.
func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.ClientHelloInfo, currentCert Certificate) (Certificate, error) {
log := loggerNamed(cfg.Logger, "on_demand")
log := logWithRemote(cfg.Logger.Named("on_demand"), hello)
name := cfg.getNameFromClientHello(hello)
timeLeft := time.Until(currentCert.Leaf.NotAfter)
timeLeft := time.Until(expiresAt(currentCert.Leaf))
revoked := currentCert.ocsp != nil && currentCert.ocsp.Status == ocsp.Revoked
getCertWithoutReobtaining := func() (Certificate, error) {
// very important to set the obtainIfNecessary argument to false, so we don't repeat this infinitely
return cfg.getCertDuringHandshake(hello, true, false)
}
// see if another goroutine is already working on this certificate
obtainCertWaitChansMu.Lock()
wait, ok := obtainCertWaitChans[name]
@@ -578,23 +638,19 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
// renewing it, so we might as well serve what we have without blocking, UNLESS
// we're forcing renewal, in which case the current certificate is not usable
if timeLeft > 0 && !revoked {
if log != nil {
log.Debug("certificate expires soon but is already being renewed; serving current certificate",
zap.Strings("subjects", currentCert.Names),
zap.Duration("remaining", timeLeft))
}
log.Debug("certificate expires soon but is already being renewed; serving current certificate",
zap.Strings("subjects", currentCert.Names),
zap.Duration("remaining", timeLeft))
return currentCert, nil
}
// otherwise, we'll have to wait for the renewal to finish so we don't serve
// a revoked or expired certificate
if log != nil {
log.Debug("certificate has expired, but is already being renewed; waiting for renewal to complete",
zap.Strings("subjects", currentCert.Names),
zap.Time("expired", currentCert.Leaf.NotAfter),
zap.Bool("revoked", revoked))
}
log.Debug("certificate has expired, but is already being renewed; waiting for renewal to complete",
zap.Strings("subjects", currentCert.Names),
zap.Time("expired", expiresAt(currentCert.Leaf)),
zap.Bool("revoked", revoked))
// TODO: see if we can get a proper context in here, for true cancellation
timeout := time.NewTimer(2 * time.Minute)
@@ -605,7 +661,9 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
timeout.Stop()
}
return getCertWithoutReobtaining()
// it should now be loaded in the cache, ready to go; if not,
// the goroutine in charge of that probably had an error
return cfg.getCertDuringHandshake(ctx, hello, false)
}
// looks like it's up to us to do all the work and renew the cert
@@ -620,30 +678,36 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
obtainCertWaitChansMu.Unlock()
}
if log != nil {
log.Info("attempting certificate renewal",
zap.String("server_name", name),
zap.Strings("subjects", currentCert.Names),
zap.Time("expiration", currentCert.Leaf.NotAfter),
zap.Duration("remaining", timeLeft),
zap.Bool("revoked", revoked))
}
// Make sure a certificate for this name should be obtained on-demand
err := cfg.checkIfCertShouldBeObtained(name)
if err != nil {
// if not, remove from cache (it will be deleted from storage later)
cfg.certCache.mu.Lock()
cfg.certCache.removeCertificate(currentCert)
cfg.certCache.mu.Unlock()
unblockWaiters()
return Certificate{}, err
}
log = log.With(
zap.String("server_name", name),
zap.Strings("subjects", currentCert.Names),
zap.Time("expiration", expiresAt(currentCert.Leaf)),
zap.Duration("remaining", timeLeft),
zap.Bool("revoked", revoked),
)
// Renew and reload the certificate
renewAndReload := func(ctx context.Context, cancel context.CancelFunc) (Certificate, error) {
defer cancel()
// Make sure a certificate for this name should be renewed on-demand
err := cfg.checkIfCertShouldBeObtained(ctx, name, true)
if err != nil {
// if not, remove from cache (it will be deleted from storage later)
cfg.certCache.mu.Lock()
cfg.certCache.removeCertificate(currentCert)
cfg.certCache.mu.Unlock()
unblockWaiters()
if log != nil {
log.Error("certificate should not be obtained", zap.Error(err))
}
return Certificate{}, err
}
log.Info("attempting certificate renewal")
// otherwise, renew with issuer, etc.
var newCert Certificate
if revoked {
@@ -651,18 +715,8 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
} else {
err = cfg.RenewCertAsync(ctx, name, false)
if err == nil {
// even though the recursive nature of the dynamic cert loading
// would just call this function anyway, we do it here to
// make the replacement as atomic as possible.
newCert, err = cfg.CacheManagedCertificate(ctx, name)
if err != nil {
if log != nil {
log.Error("loading renewed certificate", zap.String("server_name", name), zap.Error(err))
}
} else {
// replace the old certificate with the new one
cfg.certCache.replaceCertificate(currentCert, newCert)
}
// load from storage while in lock to make the replacement as atomic as possible
newCert, err = cfg.reloadManagedCertificate(ctx, currentCert)
}
}
@@ -672,21 +726,15 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
unblockWaiters()
if err != nil {
if log != nil {
log.Error("renewing and reloading certificate",
zap.String("server_name", name),
zap.Error(err),
zap.Bool("forced", revoked))
}
return newCert, err
log.Error("renewing and reloading certificate", zap.String("server_name", name), zap.Error(err))
}
return getCertWithoutReobtaining()
return newCert, err
}
// if the certificate hasn't expired, we can serve what we have and renew in the background
if timeLeft > 0 {
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
go renewAndReload(ctx, cancel)
return currentCert, nil
}
@@ -699,20 +747,20 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
// getCertFromAnyCertManager gets a certificate from cfg's Managers. If there are no Managers defined, this is
// a no-op that returns empty values. Otherwise, it gets a certificate for hello from the first Manager that
// returns a certificate and no error.
func (cfg *Config) getCertFromAnyCertManager(ctx context.Context, hello *tls.ClientHelloInfo, log *zap.Logger) (Certificate, error) {
func (cfg *Config) getCertFromAnyCertManager(ctx context.Context, hello *tls.ClientHelloInfo, logger *zap.Logger) (Certificate, error) {
// fast path if nothing to do
if len(cfg.Managers) == 0 {
if cfg.OnDemand == nil || len(cfg.OnDemand.Managers) == 0 {
return Certificate{}, nil
}
var upstreamCert *tls.Certificate
// try all the GetCertificate methods on external managers; use first one that returns a certificate
for i, certManager := range cfg.Managers {
for i, certManager := range cfg.OnDemand.Managers {
var err error
upstreamCert, err = certManager.GetCertificate(ctx, hello)
if err != nil {
log.Error("getting certificate from external certificate manager",
logger.Error("getting certificate from external certificate manager",
zap.String("sni", hello.ServerName),
zap.Int("cert_manager", i),
zap.Error(err))
@@ -723,9 +771,7 @@ func (cfg *Config) getCertFromAnyCertManager(ctx context.Context, hello *tls.Cli
}
}
if upstreamCert == nil {
if log != nil {
log.Debug("all external certificate managers yielded no certificates and no errors", zap.String("sni", hello.ServerName))
}
logger.Debug("all external certificate managers yielded no certificates and no errors", zap.String("sni", hello.ServerName))
return Certificate{}, nil
}
@@ -735,12 +781,10 @@ func (cfg *Config) getCertFromAnyCertManager(ctx context.Context, hello *tls.Cli
return Certificate{}, fmt.Errorf("external certificate manager: %s: filling cert from leaf: %v", hello.ServerName, err)
}
if log != nil {
log.Debug("using externally-managed certificate",
zap.String("sni", hello.ServerName),
zap.Strings("names", cert.Names),
zap.Time("expiration", cert.Leaf.NotAfter))
}
logger.Debug("using externally-managed certificate",
zap.String("sni", hello.ServerName),
zap.Strings("names", cert.Names),
zap.Time("expiration", expiresAt(cert.Leaf)))
return cert, nil
}
@@ -785,6 +829,20 @@ func (*Config) getNameFromClientHello(hello *tls.ClientHelloInfo) string {
return localIPFromConn(hello.Conn)
}
// logWithRemote adds the remote host and port to the logger.
func logWithRemote(l *zap.Logger, hello *tls.ClientHelloInfo) *zap.Logger {
if hello.Conn == nil || l == nil {
return l
}
addr := hello.Conn.RemoteAddr().String()
ip, port, err := net.SplitHostPort(addr)
if err != nil {
ip = addr
port = ""
}
return l.With(zap.String("remote_ip", ip), zap.String("remote_port", port))
}
// localIPFromConn returns the host portion of c's local address
// and strips the scope ID if one exists (see RFC 4007).
func localIPFromConn(c net.Conn) string {
@@ -813,5 +871,62 @@ func normalizedName(serverName string) string {
}
// obtainCertWaitChans is used to coordinate obtaining certs for each hostname.
var obtainCertWaitChans = make(map[string]chan struct{})
var obtainCertWaitChansMu sync.Mutex
var (
obtainCertWaitChans = make(map[string]chan struct{})
obtainCertWaitChansMu sync.Mutex
)
// TODO: this lockset should probably be per-cache
var (
certLoadWaitChans = make(map[string]chan struct{})
certLoadWaitChansMu sync.Mutex
)
type serializableClientHello struct {
CipherSuites []uint16
ServerName string
SupportedCurves []tls.CurveID
SupportedPoints []uint8
SignatureSchemes []tls.SignatureScheme
SupportedProtos []string
SupportedVersions []uint16
RemoteAddr, LocalAddr net.Addr // values copied from the Conn as they are still useful/needed
conn net.Conn // unexported so it's not serialized
}
// clientHelloWithoutConn returns the data from the ClientHelloInfo without the
// pesky exported Conn field, which often causes an error when serializing because
// the underlying type may be unserializable.
func clientHelloWithoutConn(hello *tls.ClientHelloInfo) serializableClientHello {
if hello == nil {
return serializableClientHello{}
}
var remote, local net.Addr
if hello.Conn != nil {
remote = hello.Conn.RemoteAddr()
local = hello.Conn.LocalAddr()
}
return serializableClientHello{
CipherSuites: hello.CipherSuites,
ServerName: hello.ServerName,
SupportedCurves: hello.SupportedCurves,
SupportedPoints: hello.SupportedPoints,
SignatureSchemes: hello.SignatureSchemes,
SupportedProtos: hello.SupportedProtos,
SupportedVersions: hello.SupportedVersions,
RemoteAddr: remote,
LocalAddr: local,
conn: hello.Conn,
}
}
type helloInfoCtxKey string
// ClientHelloInfoCtxKey is the key by which the ClientHelloInfo can be extracted from
// a context.Context within a DecisionFunc. However, be advised that it is best practice
// that the decision whether to obtain a certificate is be based solely on the name,
// not other properties of the specific connection/client requesting the connection.
// For example, it is not adviseable to use a client's IP address to decide whether to
// allow a certificate. Instead, the ClientHello can be useful for logging, etc.
const ClientHelloInfoCtxKey helloInfoCtxKey = "certmagic:ClientHelloInfo"
+10 -12
View File
@@ -73,11 +73,11 @@ func (am *ACMEIssuer) distributedHTTPChallengeSolver(w http.ResponseWriter, r *h
host := hostOnly(r.Host)
chalInfo, distributed, err := am.config.getChallengeInfo(r.Context(), host)
if err != nil {
if am.Logger != nil {
am.Logger.Error("looking up info for HTTP challenge",
zap.String("host", host),
zap.Error(err))
}
am.Logger.Error("looking up info for HTTP challenge",
zap.String("host", host),
zap.String("remote_addr", r.RemoteAddr),
zap.String("user_agent", r.Header.Get("User-Agent")),
zap.Error(err))
return false
}
return solveHTTPChallenge(am.Logger, w, r, chalInfo.Challenge, distributed)
@@ -95,13 +95,11 @@ func solveHTTPChallenge(logger *zap.Logger, w http.ResponseWriter, r *http.Reque
w.Header().Add("Content-Type", "text/plain")
w.Write([]byte(challenge.KeyAuthorization))
r.Close = true
if logger != nil {
logger.Info("served key authentication",
zap.String("identifier", challenge.Identifier.Value),
zap.String("challenge", "http-01"),
zap.String("remote", r.RemoteAddr),
zap.Bool("distributed", distributed))
}
logger.Info("served key authentication",
zap.String("identifier", challenge.Identifier.Value),
zap.String("challenge", "http-01"),
zap.String("remote", r.RemoteAddr),
zap.Bool("distributed", distributed))
return true
}
return false
+184 -119
View File
@@ -17,9 +17,11 @@ package certmagic
import (
"context"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"log"
"io/fs"
"path"
"runtime"
"strings"
@@ -39,30 +41,26 @@ import (
// incrementing panicCount each time. Initial invocation should
// start panicCount at 0.
func (certCache *Cache) maintainAssets(panicCount int) {
log := loggerNamed(certCache.logger, "maintenance")
if log != nil {
log = log.With(zap.String("cache", fmt.Sprintf("%p", certCache)))
}
log := certCache.logger.Named("maintenance")
log = log.With(zap.String("cache", fmt.Sprintf("%p", certCache)))
defer func() {
if err := recover(); err != nil {
buf := make([]byte, stackTraceBufferSize)
buf = buf[:runtime.Stack(buf, false)]
if log != nil {
log.Error("panic", zap.Any("error", err), zap.ByteString("stack", buf))
}
log.Error("panic", zap.Any("error", err), zap.ByteString("stack", buf))
if panicCount < 10 {
certCache.maintainAssets(panicCount + 1)
}
}
}()
certCache.optionsMu.RLock()
renewalTicker := time.NewTicker(certCache.options.RenewCheckInterval)
ocspTicker := time.NewTicker(certCache.options.OCSPCheckInterval)
certCache.optionsMu.RUnlock()
if log != nil {
log.Info("started background certificate maintenance")
}
log.Info("started background certificate maintenance")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -71,7 +69,7 @@ func (certCache *Cache) maintainAssets(panicCount int) {
select {
case <-renewalTicker.C:
err := certCache.RenewManagedCertificates(ctx)
if err != nil && log != nil {
if err != nil {
log.Error("renewing managed certificates", zap.Error(err))
}
case <-ocspTicker.C:
@@ -79,9 +77,7 @@ func (certCache *Cache) maintainAssets(panicCount int) {
case <-certCache.stopChan:
renewalTicker.Stop()
ocspTicker.Stop()
if log != nil {
log.Info("stopped background certificate maintenance")
}
log.Info("stopped background certificate maintenance")
close(certCache.doneChan)
return
}
@@ -94,7 +90,7 @@ func (certCache *Cache) maintainAssets(panicCount int) {
// need to call this. This method assumes non-interactive
// mode (i.e. operating in the background).
func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
log := loggerNamed(certCache.logger, "maintenance")
log := certCache.logger.Named("maintenance")
// configs will hold a map of certificate name to the config
// to use when managing that certificate
@@ -116,9 +112,7 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
// the list of names on this cert should never be empty... programmer error?
if cert.Names == nil || len(cert.Names) == 0 {
if log != nil {
log.Warn("certificate has no names; removing from cache", zap.String("cert_key", certKey))
}
log.Warn("certificate has no names; removing from cache", zap.String("cert_key", certKey))
deleteQueue = append(deleteQueue, cert)
continue
}
@@ -126,19 +120,15 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
// get the config associated with this certificate
cfg, err := certCache.getConfig(cert)
if err != nil {
if log != nil {
log.Error("unable to get configuration to manage certificate; unable to renew",
zap.Strings("identifiers", cert.Names),
zap.Error(err))
}
log.Error("unable to get configuration to manage certificate; unable to renew",
zap.Strings("identifiers", cert.Names),
zap.Error(err))
continue
}
if cfg == nil {
// this is bad if this happens, probably a programmer error (oops)
if log != nil {
log.Error("no configuration associated with certificate; unable to manage",
zap.Strings("identifiers", cert.Names))
}
log.Error("no configuration associated with certificate; unable to manage",
zap.Strings("identifiers", cert.Names))
continue
}
if cfg.OnDemand != nil {
@@ -156,11 +146,9 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
storedCertExpiring, err := cfg.managedCertInStorageExpiresSoon(ctx, cert)
if err != nil {
// hmm, weird, but not a big deal, maybe it was deleted or something
if log != nil {
log.Warn("error while checking if stored certificate is also expiring soon",
zap.Strings("identifiers", cert.Names),
zap.Error(err))
}
log.Warn("error while checking if stored certificate is also expiring soon",
zap.Strings("identifiers", cert.Names),
zap.Error(err))
} else if !storedCertExpiring {
// if the certificate is NOT expiring soon and there was no error, then we
// are good to just reload the certificate from storage instead of repeating
@@ -180,23 +168,19 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
// Reload certificates that merely need to be updated in memory
for _, oldCert := range reloadQueue {
timeLeft := oldCert.Leaf.NotAfter.Sub(time.Now().UTC())
if log != nil {
log.Info("certificate expires soon, but is already renewed in storage; reloading stored certificate",
zap.Strings("identifiers", oldCert.Names),
zap.Duration("remaining", timeLeft))
}
timeLeft := expiresAt(oldCert.Leaf).Sub(time.Now().UTC())
log.Info("certificate expires soon, but is already renewed in storage; reloading stored certificate",
zap.Strings("identifiers", oldCert.Names),
zap.Duration("remaining", timeLeft))
cfg := configs[oldCert.Names[0]]
// crucially, this happens OUTSIDE a lock on the certCache
_, err := cfg.reloadManagedCertificate(ctx, oldCert)
if err != nil {
if log != nil {
log.Error("loading renewed certificate",
zap.Strings("identifiers", oldCert.Names),
zap.Error(err))
}
log.Error("loading renewed certificate",
zap.Strings("identifiers", oldCert.Names),
zap.Error(err))
continue
}
}
@@ -206,11 +190,9 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
cfg := configs[oldCert.Names[0]]
err := certCache.queueRenewalTask(ctx, oldCert, cfg)
if err != nil {
if log != nil {
log.Error("queueing renewal task",
zap.Strings("identifiers", oldCert.Names),
zap.Error(err))
}
log.Error("queueing renewal task",
zap.Strings("identifiers", oldCert.Names),
zap.Error(err))
continue
}
}
@@ -226,14 +208,12 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
}
func (certCache *Cache) queueRenewalTask(ctx context.Context, oldCert Certificate, cfg *Config) error {
log := loggerNamed(certCache.logger, "maintenance")
log := certCache.logger.Named("maintenance")
timeLeft := oldCert.Leaf.NotAfter.Sub(time.Now().UTC())
if log != nil {
log.Info("certificate expires soon; queuing for renewal",
zap.Strings("identifiers", oldCert.Names),
zap.Duration("remaining", timeLeft))
}
timeLeft := expiresAt(oldCert.Leaf).Sub(time.Now().UTC())
log.Info("certificate expires soon; queuing for renewal",
zap.Strings("identifiers", oldCert.Names),
zap.Duration("remaining", timeLeft))
// Get the name which we should use to renew this certificate;
// we only support managing certificates with one name per cert,
@@ -242,12 +222,10 @@ func (certCache *Cache) queueRenewalTask(ctx context.Context, oldCert Certificat
// queue up this renewal job (is a no-op if already active or queued)
jm.Submit(cfg.Logger, "renew_"+renewName, func() error {
timeLeft := oldCert.Leaf.NotAfter.Sub(time.Now().UTC())
if log != nil {
log.Info("attempting certificate renewal",
zap.Strings("identifiers", oldCert.Names),
zap.Duration("remaining", timeLeft))
}
timeLeft := expiresAt(oldCert.Leaf).Sub(time.Now().UTC())
log.Info("attempting certificate renewal",
zap.Strings("identifiers", oldCert.Names),
zap.Duration("remaining", timeLeft))
// perform renewal - crucially, this happens OUTSIDE a lock on certCache
err := cfg.RenewCertAsync(ctx, renewName, false)
@@ -280,7 +258,7 @@ func (certCache *Cache) queueRenewalTask(ctx context.Context, oldCert Certificat
// Ryan Sleevi's recommendations for good OCSP support:
// https://gist.github.com/sleevi/5efe9ef98961ecfb4da8
func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
logger := loggerNamed(certCache.logger, "maintenance")
logger := certCache.logger.Named("maintenance")
// temporary structures to store updates or tasks
// so that we can keep our locks short-lived
@@ -311,11 +289,9 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
}
cfg, err := certCache.getConfig(cert)
if err != nil {
if logger != nil {
logger.Error("unable to get automation config for certificate; maintenance for this certificate will likely fail",
zap.Strings("identifiers", cert.Names),
zap.Error(err))
}
logger.Error("unable to get automation config for certificate; maintenance for this certificate will likely fail",
zap.Strings("identifiers", cert.Names),
zap.Error(err))
continue
}
// always try to replace revoked certificates, even if OCSP response is still fresh
@@ -347,10 +323,8 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
if qe.cfg == nil {
// this is bad if this happens, probably a programmer error (oops)
if logger != nil {
logger.Error("no configuration associated with certificate; unable to manage OCSP staples",
zap.Strings("identifiers", cert.Names))
}
logger.Error("no configuration associated with certificate; unable to manage OCSP staples",
zap.Strings("identifiers", cert.Names))
continue
}
@@ -358,11 +332,9 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
if err != nil {
if cert.ocsp != nil {
// if there was no staple before, that's fine; otherwise we should log the error
if logger != nil {
logger.Error("stapling OCSP",
zap.Strings("identifiers", cert.Names),
zap.Error(err))
}
logger.Error("stapling OCSP",
zap.Strings("identifiers", cert.Names),
zap.Error(err))
}
continue
}
@@ -372,17 +344,22 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
// sure we apply the update to all names on the certificate if
// the status is still Good.
if cert.ocsp != nil && cert.ocsp.Status == ocsp.Good && (lastNextUpdate.IsZero() || lastNextUpdate != cert.ocsp.NextUpdate) {
if logger != nil {
logger.Info("advancing OCSP staple",
zap.Strings("identifiers", cert.Names),
zap.Time("from", lastNextUpdate),
zap.Time("to", cert.ocsp.NextUpdate))
}
logger.Info("advancing OCSP staple",
zap.Strings("identifiers", cert.Names),
zap.Time("from", lastNextUpdate),
zap.Time("to", cert.ocsp.NextUpdate))
updated[certHash] = ocspUpdate{rawBytes: cert.Certificate.OCSPStaple, parsed: cert.ocsp}
}
// If the updated staple shows that the certificate was revoked, we should immediately renew it
if certShouldBeForceRenewed(cert) {
qe.cfg.emit(ctx, "cert_ocsp_revoked", map[string]any{
"subjects": cert.Names,
"certificate": cert,
"reason": cert.ocsp.RevocationReason,
"revoked_at": cert.ocsp.RevokedAt,
})
renewQueue = append(renewQueue, renewQueueEntry{
oldCert: cert,
cfg: qe.cfg,
@@ -405,7 +382,7 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
// Crucially, this happens OUTSIDE a lock on the certCache.
for _, renew := range renewQueue {
_, err := renew.cfg.forceRenew(ctx, logger, renew.oldCert)
if err != nil && logger != nil {
if err != nil {
logger.Info("forcefully renewing certificate due to REVOKED status",
zap.Strings("identifiers", renew.oldCert.Names),
zap.Error(err))
@@ -415,30 +392,117 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
// CleanStorageOptions specifies how to clean up a storage unit.
type CleanStorageOptions struct {
OCSPStaples bool
// Optional custom logger.
Logger *zap.Logger
// Optional ID of the instance initiating the cleaning.
InstanceID string
// If set, cleaning will be skipped if it was performed
// more recently than this interval.
Interval time.Duration
// Whether to clean cached OCSP staples.
OCSPStaples bool
// Whether to cleanup expired certificates, and if so,
// how long to let them stay after they've expired.
ExpiredCerts bool
ExpiredCertGracePeriod time.Duration
}
// CleanStorage removes assets which are no longer useful,
// according to opts.
func CleanStorage(ctx context.Context, storage Storage, opts CleanStorageOptions) {
func CleanStorage(ctx context.Context, storage Storage, opts CleanStorageOptions) error {
const (
lockName = "storage_clean"
storageKey = "last_clean.json"
)
if opts.Logger == nil {
opts.Logger = defaultLogger.Named("clean_storage")
}
opts.Logger = opts.Logger.With(zap.Any("storage", storage))
// storage cleaning should be globally exclusive
if err := storage.Lock(ctx, lockName); err != nil {
return fmt.Errorf("unable to acquire %s lock: %v", lockName, err)
}
defer func() {
if err := storage.Unlock(ctx, lockName); err != nil {
opts.Logger.Error("unable to release lock", zap.Error(err))
return
}
}()
// cleaning should not happen more often than the interval
if opts.Interval > 0 {
lastCleanBytes, err := storage.Load(ctx, storageKey)
if !errors.Is(err, fs.ErrNotExist) {
if err != nil {
return fmt.Errorf("loading last clean timestamp: %v", err)
}
var lastClean lastCleanPayload
err = json.Unmarshal(lastCleanBytes, &lastClean)
if err != nil {
return fmt.Errorf("decoding last clean data: %v", err)
}
lastTLSClean := lastClean["tls"]
if time.Since(lastTLSClean.Timestamp) < opts.Interval {
nextTime := time.Now().Add(opts.Interval)
opts.Logger.Warn("storage cleaning happened too recently; skipping for now",
zap.String("instance", lastTLSClean.InstanceID),
zap.Time("try_again", nextTime),
zap.Duration("try_again_in", time.Until(nextTime)),
)
return nil
}
}
}
opts.Logger.Info("cleaning storage unit")
if opts.OCSPStaples {
err := deleteOldOCSPStaples(ctx, storage)
err := deleteOldOCSPStaples(ctx, storage, opts.Logger)
if err != nil {
log.Printf("[ERROR] Deleting old OCSP staples: %v", err)
opts.Logger.Error("deleting old OCSP staples", zap.Error(err))
}
}
if opts.ExpiredCerts {
err := deleteExpiredCerts(ctx, storage, opts.ExpiredCertGracePeriod)
err := deleteExpiredCerts(ctx, storage, opts.Logger, opts.ExpiredCertGracePeriod)
if err != nil {
log.Printf("[ERROR] Deleting expired certificates: %v", err)
opts.Logger.Error("deleting expired certificates staples", zap.Error(err))
}
}
// TODO: delete stale locks?
// update the last-clean time
lastCleanBytes, err := json.Marshal(lastCleanPayload{
"tls": lastCleaned{
Timestamp: time.Now(),
InstanceID: opts.InstanceID,
},
})
if err != nil {
return fmt.Errorf("encoding last cleaned info: %v", err)
}
if err := storage.Store(ctx, storageKey, lastCleanBytes); err != nil {
return fmt.Errorf("storing last clean info: %v", err)
}
return nil
}
func deleteOldOCSPStaples(ctx context.Context, storage Storage) error {
type lastCleanPayload map[string]lastCleaned
type lastCleaned struct {
Timestamp time.Time `json:"timestamp"`
InstanceID string `json:"instance_id,omitempty"`
}
func deleteOldOCSPStaples(ctx context.Context, storage Storage, logger *zap.Logger) error {
ocspKeys, err := storage.List(ctx, prefixOCSP, false)
if err != nil {
// maybe just hasn't been created yet; no big deal
@@ -453,7 +517,7 @@ func deleteOldOCSPStaples(ctx context.Context, storage Storage) error {
}
ocspBytes, err := storage.Load(ctx, key)
if err != nil {
log.Printf("[ERROR] While deleting old OCSP staples, unable to load staple file: %v", err)
logger.Error("while deleting old OCSP staples, unable to load staple file", zap.Error(err))
continue
}
resp, err := ocsp.ParseResponse(ocspBytes, nil)
@@ -461,7 +525,7 @@ func deleteOldOCSPStaples(ctx context.Context, storage Storage) error {
// contents are invalid; delete it
err = storage.Delete(ctx, key)
if err != nil {
log.Printf("[ERROR] Purging corrupt staple file %s: %v", key, err)
logger.Error("purging corrupt staple file", zap.String("storage_key", key), zap.Error(err))
}
continue
}
@@ -469,14 +533,14 @@ func deleteOldOCSPStaples(ctx context.Context, storage Storage) error {
// response has expired; delete it
err = storage.Delete(ctx, key)
if err != nil {
log.Printf("[ERROR] Purging expired staple file %s: %v", key, err)
logger.Error("purging expired staple file", zap.String("storage_key", key), zap.Error(err))
}
}
}
return nil
}
func deleteExpiredCerts(ctx context.Context, storage Storage, gracePeriod time.Duration) error {
func deleteExpiredCerts(ctx context.Context, storage Storage, logger *zap.Logger, gracePeriod time.Duration) error {
issuerKeys, err := storage.List(ctx, prefixCerts, false)
if err != nil {
// maybe just hasn't been created yet; no big deal
@@ -486,7 +550,7 @@ func deleteExpiredCerts(ctx context.Context, storage Storage, gracePeriod time.D
for _, issuerKey := range issuerKeys {
siteKeys, err := storage.List(ctx, issuerKey, false)
if err != nil {
log.Printf("[ERROR] Listing contents of %s: %v", issuerKey, err)
logger.Error("listing contents", zap.String("issuer_key", issuerKey), zap.Error(err))
continue
}
@@ -500,7 +564,7 @@ func deleteExpiredCerts(ctx context.Context, storage Storage, gracePeriod time.D
siteAssets, err := storage.List(ctx, siteKey, false)
if err != nil {
log.Printf("[ERROR] Listing contents of %s: %v", siteKey, err)
logger.Error("listing site contents", zap.String("site_key", siteKey), zap.Error(err))
continue
}
@@ -522,19 +586,24 @@ func deleteExpiredCerts(ctx context.Context, storage Storage, gracePeriod time.D
return fmt.Errorf("certificate file %s is malformed; error parsing PEM: %v", assetKey, err)
}
if expiredTime := time.Since(cert.NotAfter); expiredTime >= gracePeriod {
log.Printf("[INFO] Certificate %s expired %s ago; cleaning up", assetKey, expiredTime)
if expiredTime := time.Since(expiresAt(cert)); expiredTime >= gracePeriod {
logger.Info("certificate expired beyond grace period; cleaning up",
zap.String("asset_key", assetKey),
zap.Duration("expired_for", expiredTime),
zap.Duration("grace_period", gracePeriod))
baseName := strings.TrimSuffix(assetKey, ".crt")
for _, relatedAsset := range []string{
assetKey,
baseName + ".key",
baseName + ".json",
} {
log.Printf("[INFO] Deleting %s because resource expired", relatedAsset)
logger.Info("deleting asset because resource expired", zap.String("asset_key", relatedAsset))
err := storage.Delete(ctx, relatedAsset)
if err != nil {
log.Printf("[ERROR] Cleaning up asset related to expired certificate for %s: %s: %v",
baseName, relatedAsset, err)
logger.Error("could not clean up asset related to expired certificate",
zap.String("base_name", baseName),
zap.String("related_asset", relatedAsset),
zap.Error(err))
}
}
}
@@ -546,7 +615,7 @@ func deleteExpiredCerts(ctx context.Context, storage Storage, gracePeriod time.D
continue
}
if len(siteAssets) == 0 {
log.Printf("[INFO] Deleting %s because key is empty", siteKey)
logger.Info("deleting site folder because key is empty", zap.String("site_key", siteKey))
err := storage.Delete(ctx, siteKey)
if err != nil {
return fmt.Errorf("deleting empty site folder %s: %v", siteKey, err)
@@ -560,16 +629,14 @@ func deleteExpiredCerts(ctx context.Context, storage Storage, gracePeriod time.D
// forceRenew forcefully renews cert and replaces it in the cache, and returns the new certificate. It is intended
// for use primarily in the case of cert revocation. This MUST NOT be called within a lock on cfg.certCacheMu.
func (cfg *Config) forceRenew(ctx context.Context, logger *zap.Logger, cert Certificate) (Certificate, error) {
if logger != nil {
if cert.ocsp != nil && cert.ocsp.Status == ocsp.Revoked {
logger.Warn("OCSP status for managed certificate is REVOKED; attempting to replace with new certificate",
zap.Strings("identifiers", cert.Names),
zap.Time("expiration", cert.Leaf.NotAfter))
} else {
logger.Warn("forcefully renewing certificate",
zap.Strings("identifiers", cert.Names),
zap.Time("expiration", cert.Leaf.NotAfter))
}
if cert.ocsp != nil && cert.ocsp.Status == ocsp.Revoked {
logger.Warn("OCSP status for managed certificate is REVOKED; attempting to replace with new certificate",
zap.Strings("identifiers", cert.Names),
zap.Time("expiration", expiresAt(cert.Leaf)))
} else {
logger.Warn("forcefully renewing certificate",
zap.Strings("identifiers", cert.Names),
zap.Time("expiration", expiresAt(cert.Leaf)))
}
renewName := cert.Names[0]
@@ -584,7 +651,7 @@ func (cfg *Config) forceRenew(ctx context.Context, logger *zap.Logger, cert Cert
var obtainInsteadOfRenew bool
if cert.ocsp != nil && cert.ocsp.RevocationReason == acme.ReasonKeyCompromise {
err := cfg.moveCompromisedPrivateKey(ctx, cert, logger)
if err != nil && logger != nil {
if err != nil {
logger.Error("could not remove compromised private key from use",
zap.Strings("identifiers", cert.Names),
zap.String("issuer", cert.issuerKey),
@@ -605,11 +672,9 @@ func (cfg *Config) forceRenew(ctx context.Context, logger *zap.Logger, cert Cert
if err != nil {
if cert.ocsp != nil && cert.ocsp.Status == ocsp.Revoked {
// probably better to not serve a revoked certificate at all
if logger != nil {
logger.Error("unable to obtain new to certificate after OCSP status of REVOKED; removing from cache",
zap.Strings("identifiers", cert.Names),
zap.Error(err))
}
logger.Error("unable to obtain new to certificate after OCSP status of REVOKED; removing from cache",
zap.Strings("identifiers", cert.Names),
zap.Error(err))
cfg.certCache.mu.Lock()
cfg.certCache.removeCertificate(cert)
cfg.certCache.mu.Unlock()
+9 -4
View File
@@ -19,6 +19,7 @@ import (
"context"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"log"
@@ -28,6 +29,10 @@ import (
"golang.org/x/crypto/ocsp"
)
// ErrNoOCSPServerSpecified indicates that OCSP information could not be
// stapled because the certificate does not support OCSP.
var ErrNoOCSPServerSpecified = errors.New("no OCSP server specified in certificate")
// stapleOCSP staples OCSP information to cert for hostname name.
// If you have it handy, you should pass in the PEM-encoded certificate
// bundle; otherwise the DER-encoded cert will have to be PEM-encoded.
@@ -93,17 +98,17 @@ func stapleOCSP(ctx context.Context, ocspConfig OCSPConfig, storage Storage, cer
// not contain a link to an OCSP server. But we should log it anyway.
// There's nothing else we can do to get OCSP for this certificate,
// so we can return here with the error.
return fmt.Errorf("no OCSP stapling for %v: %v", cert.Names, ocspErr)
return fmt.Errorf("no OCSP stapling for %v: %w", cert.Names, ocspErr)
}
gotNewOCSP = true
}
if ocspResp.NextUpdate.After(cert.Leaf.NotAfter) {
if ocspResp.NextUpdate.After(expiresAt(cert.Leaf)) {
// uh oh, this OCSP response expires AFTER the certificate does, that's kinda bogus.
// it was the reason a lot of Symantec-validated sites (not Caddy) went down
// in October 2017. https://twitter.com/mattiasgeniar/status/919432824708648961
return fmt.Errorf("invalid: OCSP response for %v valid after certificate expiration (%s)",
cert.Names, cert.Leaf.NotAfter.Sub(ocspResp.NextUpdate))
cert.Names, expiresAt(cert.Leaf).Sub(ocspResp.NextUpdate))
}
// Attach the latest OCSP response to the certificate; this is NOT the same
@@ -149,7 +154,7 @@ func getOCSPForCert(ocspConfig OCSPConfig, bundle []byte) ([]byte, *ocsp.Respons
// we have only one certificate so far, we need to get the issuer cert.
issuedCert := certificates[0]
if len(issuedCert.OCSPServer) == 0 {
return nil, nil, fmt.Errorf("no OCSP server specified in certificate")
return nil, nil, ErrNoOCSPServerSpecified
}
// apply override for responder URL
+5 -4
View File
@@ -33,7 +33,7 @@ func NewRateLimiter(maxEvents int, window time.Duration) *RingBufferRateLimiter
panic("maxEvents cannot be less than zero")
}
if maxEvents == 0 && window != 0 {
panic("invalid configuration: maxEvents = 0 and window != 0 would not allow any events")
panic("NewRateLimiter: invalid configuration: maxEvents = 0 and window != 0 would not allow any events")
}
rbrl := &RingBufferRateLimiter{
window: window,
@@ -144,14 +144,15 @@ func (r *RingBufferRateLimiter) MaxEvents() int {
// the oldest events will be forgotten. If the new limit is
// higher, the window will suddenly have capacity for new
// reservations. It panics if maxEvents is 0 and window size
// is not zero.
// is not zero; if setting both the events limit and the
// window size to 0, call SetWindow() first.
func (r *RingBufferRateLimiter) SetMaxEvents(maxEvents int) {
newRing := make([]time.Time, maxEvents)
r.mu.Lock()
defer r.mu.Unlock()
if r.window != 0 && maxEvents == 0 {
panic("invalid configuration: maxEvents = 0 and window != 0 would not allow any events")
panic("SetMaxEvents: invalid configuration: maxEvents = 0 and window != 0 would not allow any events")
}
// only make the change if the new limit is different
@@ -203,7 +204,7 @@ func (r *RingBufferRateLimiter) SetWindow(window time.Duration) {
r.mu.Lock()
defer r.mu.Unlock()
if window != 0 && len(r.ring) == 0 {
panic("invalid configuration: maxEvents = 0 and window != 0 would not allow any events")
panic("SetWindow: invalid configuration: maxEvents = 0 and window != 0 would not allow any events")
}
r.window = window
}
+121 -97
View File
@@ -99,7 +99,7 @@ func (s *httpSolver) serve(ctx context.Context, si *solverInfo) {
}
// CleanUp cleans up the HTTP server if it is the last one to finish.
func (s *httpSolver) CleanUp(ctx context.Context, _ acme.Challenge) error {
func (s *httpSolver) CleanUp(_ context.Context, _ acme.Challenge) error {
solversMu.Lock()
defer solversMu.Unlock()
si := getSolverInfo(s.address)
@@ -220,7 +220,7 @@ func (*tlsALPNSolver) handleConn(conn net.Conn) {
// CleanUp removes the challenge certificate from the cache, and if
// it is the last one to finish, stops the TLS server.
func (s *tlsALPNSolver) CleanUp(ctx context.Context, chal acme.Challenge) error {
func (s *tlsALPNSolver) CleanUp(_ context.Context, chal acme.Challenge) error {
solversMu.Lock()
defer solversMu.Unlock()
si := getSolverInfo(s.address)
@@ -234,13 +234,21 @@ func (s *tlsALPNSolver) CleanUp(ctx context.Context, chal acme.Challenge) error
}
delete(solvers, s.address)
}
return nil
}
// DNS01Solver is a type that makes libdns providers usable
// as ACME dns-01 challenge solvers.
// See https://github.com/libdns/libdns
// DNS01Solver is a type that makes libdns providers usable as ACME dns-01
// challenge solvers. See https://github.com/libdns/libdns
//
// Note that challenges may be solved concurrently by some clients (such as
// acmez, which CertMagic uses), meaning that multiple TXT records may be
// created in a DNS zone simultaneously, and in some cases distinct TXT records
// may have the same name. For example, solving challenges for both example.com
// and *.example.com create a TXT record named _acme_challenge.example.com,
// but with different tokens as their values. This solver distinguishes
// between different records with the same name by looking at their values.
// DNS provider APIs and implementations of the libdns interfaces must also
// support multiple same-named TXT records.
type DNS01Solver struct {
// The implementation that interacts with the DNS
// provider to set or delete records. (REQUIRED)
@@ -266,7 +274,18 @@ type DNS01Solver struct {
// that the solver doesn't follow CNAME/NS record.
OverrideDomain string
txtRecords map[string]dnsPresentMemory // keyed by domain name
// Remember DNS records while challenges are active; i.e.
// records we have presented and not yet cleaned up.
// This lets us clean them up quickly and efficiently.
// Keyed by domain name (specifically the ACME DNS name).
// The map value is a slice because there can be multiple
// concurrent challenges for different domains that have
// the same ACME DNS name, for example: example.com and
// *.example.com. We distinguish individual memories by
// the value of their TXT records, which should contain
// unique challenge tokens.
// See https://github.com/caddyserver/caddy/issues/3474.
txtRecords map[string][]dnsPresentMemory
txtRecordsMu sync.Mutex
}
@@ -278,13 +297,6 @@ func (s *DNS01Solver) Present(ctx context.Context, challenge acme.Challenge) err
}
keyAuth := challenge.DNS01KeyAuthorization()
// multiple identifiers can have the same ACME challenge
// domain (e.g. example.com and *.example.com) so we need
// to ensure that we don't solve those concurrently and
// step on each challenges' metaphorical toes; see
// https://github.com/caddyserver/caddy/issues/3474
activeDNSChallenges.Lock(dnsName)
zone, err := findZoneByFQDN(dnsName, recursiveNameservers(s.Resolvers))
if err != nil {
return fmt.Errorf("could not determine zone for domain %q: %v", dnsName, err)
@@ -299,19 +311,18 @@ func (s *DNS01Solver) Present(ctx context.Context, challenge acme.Challenge) err
results, err := s.DNSProvider.AppendRecords(ctx, zone, []libdns.Record{rec})
if err != nil {
return fmt.Errorf("adding temporary record for zone %s: %w", zone, err)
return fmt.Errorf("adding temporary record for zone %q: %w", zone, err)
}
if len(results) != 1 {
return fmt.Errorf("expected one record, got %d: %v", len(results), results)
}
// remember the record and zone we got so we can clean up more efficiently
s.txtRecordsMu.Lock()
if s.txtRecords == nil {
s.txtRecords = make(map[string]dnsPresentMemory)
}
s.txtRecords[dnsName] = dnsPresentMemory{dnsZone: zone, rec: results[0]}
s.txtRecordsMu.Unlock()
s.saveDNSPresentMemory(dnsPresentMemory{
dnsZone: zone,
dnsName: dnsName,
rec: results[0],
})
return nil
}
@@ -345,7 +356,7 @@ func (s *DNS01Solver) Wait(ctx context.Context, challenge acme.Challenge) error
// timings
timeout := s.PropagationTimeout
if timeout == 0 {
timeout = 2 * time.Minute
timeout = defaultDNSPropagationTimeout
}
const interval = 2 * time.Second
@@ -363,7 +374,7 @@ func (s *DNS01Solver) Wait(ctx context.Context, challenge acme.Challenge) error
var ready bool
ready, err = checkDNSPropagation(dnsName, keyAuth, resolvers)
if err != nil {
return fmt.Errorf("checking DNS propagation of %s: %w", dnsName, err)
return fmt.Errorf("checking DNS propagation of %q: %w", dnsName, err)
}
if ready {
return nil
@@ -374,42 +385,94 @@ func (s *DNS01Solver) Wait(ctx context.Context, challenge acme.Challenge) error
}
// CleanUp deletes the DNS TXT record created in Present().
func (s *DNS01Solver) CleanUp(ctx context.Context, challenge acme.Challenge) error {
//
// We ignore the context because cleanup is often/likely performed after
// a context cancellation, and properly-implemented DNS providers should
// honor cancellation, which would result in cleanup being aborted.
// Cleanup must always occur.
func (s *DNS01Solver) CleanUp(_ context.Context, challenge acme.Challenge) error {
dnsName := challenge.DNS01TXTRecordName()
if s.OverrideDomain != "" {
dnsName = s.OverrideDomain
}
keyAuth := challenge.DNS01KeyAuthorization()
defer func() {
// always forget about it so we don't leak memory
s.txtRecordsMu.Lock()
delete(s.txtRecords, dnsName)
s.txtRecordsMu.Unlock()
// always do this last - but always do it!
activeDNSChallenges.Unlock(dnsName)
}()
// always forget about the record so we don't leak memory
defer s.deleteDNSPresentMemory(dnsName, keyAuth)
// recall the record we created and zone we looked up
s.txtRecordsMu.Lock()
memory, ok := s.txtRecords[dnsName]
if !ok {
s.txtRecordsMu.Unlock()
return fmt.Errorf("no memory of presenting a DNS record for %s (probably OK if presenting failed)", challenge.Identifier.Value)
}
s.txtRecordsMu.Unlock()
// clean up the record
_, err := s.DNSProvider.DeleteRecords(ctx, memory.dnsZone, []libdns.Record{memory.rec})
memory, err := s.getDNSPresentMemory(dnsName, keyAuth)
if err != nil {
return fmt.Errorf("deleting temporary record for zone %s: %w", memory.dnsZone, err)
return err
}
// clean up the record - use a different context though, since
// one common reason cleanup is performed is because a context
// was canceled, and if so, any HTTP requests by this provider
// should fail if the provider is properly implemented
// (see issue #200)
timeout := s.PropagationTimeout
if timeout <= 0 {
timeout = defaultDNSPropagationTimeout
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
_, err = s.DNSProvider.DeleteRecords(ctx, memory.dnsZone, []libdns.Record{memory.rec})
if err != nil {
return fmt.Errorf("deleting temporary record for name %q in zone %q: %w", memory.dnsName, memory.dnsZone, err)
}
return nil
}
const defaultDNSPropagationTimeout = 2 * time.Minute
type dnsPresentMemory struct {
dnsZone string
dnsName string
rec libdns.Record
}
func (s *DNS01Solver) saveDNSPresentMemory(mem dnsPresentMemory) {
s.txtRecordsMu.Lock()
if s.txtRecords == nil {
s.txtRecords = make(map[string][]dnsPresentMemory)
}
s.txtRecords[mem.dnsName] = append(s.txtRecords[mem.dnsName], mem)
s.txtRecordsMu.Unlock()
}
func (s *DNS01Solver) getDNSPresentMemory(dnsName, keyAuth string) (dnsPresentMemory, error) {
s.txtRecordsMu.Lock()
defer s.txtRecordsMu.Unlock()
var memory dnsPresentMemory
for _, mem := range s.txtRecords[dnsName] {
if mem.rec.Value == keyAuth {
memory = mem
break
}
}
if memory.rec.Name == "" {
return dnsPresentMemory{}, fmt.Errorf("no memory of presenting a DNS record for %q (usually OK if presenting also failed)", dnsName)
}
return memory, nil
}
func (s *DNS01Solver) deleteDNSPresentMemory(dnsName, keyAuth string) {
s.txtRecordsMu.Lock()
defer s.txtRecordsMu.Unlock()
for i, mem := range s.txtRecords[dnsName] {
if mem.rec.Value == keyAuth {
s.txtRecords[dnsName] = append(s.txtRecords[dnsName][:i], s.txtRecords[dnsName][i+1:]...)
return
}
}
}
// ACMEDNSProvider defines the set of operations required for
// ACME challenges. A DNS provider must be able to append and
// delete records in order to solve ACME challenges. Find one
@@ -420,47 +483,6 @@ type ACMEDNSProvider interface {
libdns.RecordDeleter
}
// activeDNSChallenges synchronizes DNS challenges for
// names to ensure that challenges for the same ACME
// DNS name do not overlap; for example, the TXT record
// to make for both example.com and *.example.com are
// the same; thus we cannot solve them concurrently.
var activeDNSChallenges = newMapMutex()
// mapMutex implements named mutexes.
type mapMutex struct {
cond *sync.Cond
set map[interface{}]struct{}
}
func newMapMutex() *mapMutex {
return &mapMutex{
cond: sync.NewCond(new(sync.Mutex)),
set: make(map[interface{}]struct{}),
}
}
func (mmu *mapMutex) Lock(key interface{}) {
mmu.cond.L.Lock()
defer mmu.cond.L.Unlock()
for mmu.locked(key) {
mmu.cond.Wait()
}
mmu.set[key] = struct{}{}
}
func (mmu *mapMutex) Unlock(key interface{}) {
mmu.cond.L.Lock()
defer mmu.cond.L.Unlock()
delete(mmu.set, key)
mmu.cond.Broadcast()
}
func (mmu *mapMutex) locked(key interface{}) (ok bool) {
_, ok = mmu.set[key]
return
}
// distributedSolver allows the ACME HTTP-01 and TLS-ALPN challenges
// to be solved by an instance other than the one which initiated it.
// This is useful behind load balancers or in other cluster/fleet
@@ -607,21 +629,23 @@ func robustTryListen(addr string) (net.Listener, error) {
return nil, nil
}
// hmm, we couldn't connect to the socket, so something else must
// be wrong, right? wrong!! we've had reports across multiple OSes
// now that sometimes connections fail even though the OS told us
// that the address was already in use; either the listener is
// fluctuating between open and closed very, very quickly, or the
// OS is inconsistent and contradicting itself; I have been unable
// to reproduce this, so I'm now resorting to hard-coding substring
// matching in error messages as a really hacky and unreliable
// safeguard against this, until we can idenify exactly what was
// happening; see the following threads for more info:
// Hmm, we couldn't connect to the socket, so something else must
// be wrong, right? wrong!! Apparently if a port is bound by another
// listener with a specific host, i.e. 'x:1234', we cannot bind to
// ':1234' -- it is considered a conflict, but 'y:1234' is not.
// I guess we need to assume the conflicting listener is properly
// configured and continue. But we should tell the user to specify
// the correct ListenHost to avoid conflict or at least so we can
// know that the user is intentional about that port and hopefully
// has an ACME solver on it.
//
// History:
// https://caddy.community/t/caddy-retry-error/7317
// https://caddy.community/t/v2-upgrade-to-caddy2-failing-with-errors/7423
// https://github.com/caddyserver/certmagic/issues/250
if strings.Contains(listenErr.Error(), "address already in use") ||
strings.Contains(listenErr.Error(), "one usage of each socket address") {
log.Printf("[WARNING] OS reports a contradiction: %v - but we cannot connect to it, with this error: %v; continuing anyway 🤞 (I don't know what causes this... if you do, please help?)", listenErr, connectErr)
log.Printf("[WARNING] %v - be sure to set the ACMEIssuer.ListenHost field; assuming conflicting listener is correctly configured and continuing", listenErr)
return nil, nil
}
}
@@ -674,7 +698,7 @@ var (
// data that can make it easier or more efficient to solve.
type Challenge struct {
acme.Challenge
data interface{}
data any
}
// challengeKey returns the map key for a given challenge; it is the identifier
+96 -51
View File
@@ -25,81 +25,128 @@ import (
"go.uber.org/zap"
)
// Storage is a type that implements a key-value store.
// Keys are prefix-based, with forward slash '/' as separators
// and without a leading slash.
// Storage is a type that implements a key-value store with
// basic file system (folder path) semantics. Keys use the
// forward slash '/' to separate path components and have no
// leading or trailing slashes.
//
// Processes running in a cluster will wish to use the
// same Storage value (its implementation and configuration)
// in order to share certificates and other TLS resources
// with the cluster.
// A "prefix" of a key is defined on a component basis,
// e.g. "a" is a prefix of "a/b" but not "ab/c".
//
// A "file" is a key with a value associated with it.
//
// A "directory" is a key with no value, but which may be
// the prefix of other keys.
//
// Keys passed into Load and Store always have "file" semantics,
// whereas "directories" are only implicit by leading up to the
// file.
//
// The Load, Delete, List, and Stat methods should return
// fs.ErrNotExist if the key does not exist.
//
// Implementations of Storage must be safe for concurrent use
// and honor context cancellations.
// Processes running in a cluster should use the same Storage
// value (with the same configuration) in order to share
// certificates and other TLS resources with the cluster.
//
// Implementations of Storage MUST be safe for concurrent use
// and honor context cancellations. Methods should block until
// their operation is complete; that is, Load() should always
// return the value from the last call to Store() for a given
// key, and concurrent calls to Store() should not corrupt a
// file.
//
// For simplicity, this is not a streaming API and is not
// suitable for very large files.
type Storage interface {
// Locker provides atomic synchronization
// operations, making Storage safe to share.
// Locker enables the storage backend to synchronize
// operational units of work.
//
// The use of Locker is NOT employed around every
// Storage method call (Store, Load, etc), as these
// should already be thread-safe. Locker is used for
// high-level jobs or transactions that need
// synchronization across a cluster; it's a simple
// distributed lock. For example, CertMagic uses the
// Locker interface to coordinate the obtaining of
// certificates.
Locker
// Store puts value at key.
// Store puts value at key. It creates the key if it does
// not exist and overwrites any existing value at this key.
Store(ctx context.Context, key string, value []byte) error
// Load retrieves the value at key.
Load(ctx context.Context, key string) ([]byte, error)
// Delete deletes key. An error should be
// returned only if the key still exists
// Delete deletes the named key. If the name is a
// directory (i.e. prefix of other keys), all keys
// prefixed by this key should be deleted. An error
// should be returned only if the key still exists
// when the method returns.
Delete(ctx context.Context, key string) error
// Exists returns true if the key exists
// Exists returns true if the key exists either as
// a directory (prefix to other keys) or a file,
// and there was no error checking.
Exists(ctx context.Context, key string) bool
// List returns all keys that match prefix.
// List returns all keys in the given path.
//
// If recursive is true, non-terminal keys
// will be enumerated (i.e. "directories"
// should be walked); otherwise, only keys
// prefixed exactly by prefix will be listed.
List(ctx context.Context, prefix string, recursive bool) ([]string, error)
List(ctx context.Context, path string, recursive bool) ([]string, error)
// Stat returns information about key.
Stat(ctx context.Context, key string) (KeyInfo, error)
}
// Locker facilitates synchronization of certificate tasks across
// machines and networks.
// Locker facilitates synchronization across machines and networks.
// It essentially provides a distributed named-mutex service so
// that multiple consumers can coordinate tasks and share resources.
//
// If possible, a Locker should implement a coordinated distributed
// locking mechanism by generating fencing tokens (see
// https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html).
// This typically requires a central server or consensus algorithm
// However, if that is not feasible, Lockers may implement an
// alternative mechanism that uses timeouts to detect node or network
// failures and avoid deadlocks. For example, the default FileStorage
// writes a timestamp to the lock file every few seconds, and if another
// node acquiring the lock sees that timestamp is too old, it may
// assume the lock is stale.
//
// As not all Locker implementations use fencing tokens, code relying
// upon Locker must be tolerant of some mis-synchronizations but can
// expect them to be rare.
//
// This interface should only be used for coordinating expensive
// operations across nodes in a cluster; not for internal, extremely
// short-lived, or high-contention locks.
type Locker interface {
// Lock acquires the lock for key, blocking until the lock
// can be obtained or an error is returned. Note that, even
// after acquiring a lock, an idempotent operation may have
// already been performed by another process that acquired
// the lock before - so always check to make sure idempotent
// operations still need to be performed after acquiring the
// lock.
// Lock acquires the lock for name, blocking until the lock
// can be obtained or an error is returned. Only one lock
// for the given name can exist at a time. A call to Lock for
// a name which already exists blocks until the named lock
// is released or becomes stale.
//
// The actual implementation of obtaining of a lock must be
// an atomic operation so that multiple Lock calls at the
// same time always results in only one caller receiving the
// lock at any given time.
// If the named lock represents an idempotent operation, callers
// should always check to make sure the work still needs to be
// completed after acquiring the lock. You never know if another
// process already completed the task while you were waiting to
// acquire it.
//
// To prevent deadlocks, all implementations (where this concern
// is relevant) should put a reasonable expiration on the lock in
// case Unlock is unable to be called due to some sort of network
// failure or system crash. Additionally, implementations should
// honor context cancellation as much as possible (in case the
// caller wishes to give up and free resources before the lock
// can be obtained).
Lock(ctx context.Context, key string) error
// Implementations should honor context cancellation.
Lock(ctx context.Context, name string) error
// Unlock releases the lock for key. This method must ONLY be
// called after a successful call to Lock, and only after the
// critical section is finished, even if it errored or timed
// out. Unlock cleans up any resources allocated during Lock.
Unlock(ctx context.Context, key string) error
// Unlock releases named lock. This method must ONLY be called
// after a successful call to Lock, and only after the critical
// section is finished, even if it errored or timed out. Unlock
// cleans up any resources allocated during Lock. Unlock should
// only return an error if the lock was unable to be released.
Unlock(ctx context.Context, name string) error
}
// KeyInfo holds information about a key in storage.
@@ -113,7 +160,7 @@ type KeyInfo struct {
Key string
Modified time.Time
Size int64
IsTerminal bool // false for keys that only contain other keys (like directories)
IsTerminal bool // false for directories (keys that act as prefix for other keys)
}
// storeTx stores all the values or none at all.
@@ -220,16 +267,14 @@ func CleanUpOwnLocks(ctx context.Context, logger *zap.Logger) {
locksMu.Lock()
defer locksMu.Unlock()
for lockKey, storage := range locks {
err := storage.Unlock(ctx, lockKey)
if err == nil {
delete(locks, lockKey)
} else if logger != nil {
if err := storage.Unlock(ctx, lockKey); err != nil {
logger.Error("unable to clean up lock in storage backend",
zap.Any("storage", storage),
zap.String("lock_key", lockKey),
zap.Error(err),
)
zap.Error(err))
continue
}
delete(locks, lockKey)
}
}
@@ -244,7 +289,7 @@ func acquireLock(ctx context.Context, storage Storage, lockKey string) error {
}
func releaseLock(ctx context.Context, storage Storage, lockKey string) error {
err := storage.Unlock(ctx, lockKey)
err := storage.Unlock(context.TODO(), lockKey) // TODO: in Go 1.21, use WithoutCancel (see #247)
if err == nil {
locksMu.Lock()
delete(locks, lockKey)
+246 -7
View File
@@ -9,17 +9,27 @@ You can access the CPU information by accessing the shared CPU variable of the c
Package home: https://github.com/klauspost/cpuid
[![PkgGoDev](https://pkg.go.dev/badge/github.com/klauspost/cpuid)](https://pkg.go.dev/github.com/klauspost/cpuid/v2)
[![Build Status][3]][4]
[3]: https://travis-ci.org/klauspost/cpuid.svg?branch=master
[4]: https://travis-ci.org/klauspost/cpuid
[![Go](https://github.com/klauspost/cpuid/actions/workflows/go.yml/badge.svg)](https://github.com/klauspost/cpuid/actions/workflows/go.yml)
## installing
`go get -u github.com/klauspost/cpuid/v2` using modules.
`go get -u github.com/klauspost/cpuid/v2` using modules.
Drop `v2` for others.
Installing binary:
`go install github.com/klauspost/cpuid/v2/cmd/cpuid@latest`
Or download binaries from release page: https://github.com/klauspost/cpuid/releases
### Homebrew
For macOS/Linux users, you can install via [brew](https://brew.sh/)
```sh
$ brew install cpuid
```
## example
```Go
@@ -77,10 +87,14 @@ We have Streaming SIMD 2 Extensions
The `cpuid.CPU` provides access to CPU features. Use `cpuid.CPU.Supports()` to check for CPU features.
A faster `cpuid.CPU.Has()` is provided which will usually be inlined by the gc compiler.
To test a larger number of features, they can be combined using `f := CombineFeatures(CMOV, CMPXCHG8, X87, FXSR, MMX, SYSCALL, SSE, SSE2)`, etc.
This can be using with `cpuid.CPU.HasAll(f)` to quickly test if all features are supported.
Note that for some cpu/os combinations some features will not be detected.
`amd64` has rather good support and should work reliably on all platforms.
Note that hypervisors may not pass through all CPU features.
Note that hypervisors may not pass through all CPU features through to the guest OS,
so even if your host supports a feature it may not be visible on guests.
## arm64 feature detection
@@ -253,6 +267,231 @@ Exit Code 0
Exit Code 1
```
## Available flags
### x86 & amd64
| Feature Flag | Description |
|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ADX | Intel ADX (Multi-Precision Add-Carry Instruction Extensions) |
| AESNI | Advanced Encryption Standard New Instructions |
| AMD3DNOW | AMD 3DNOW |
| AMD3DNOWEXT | AMD 3DNowExt |
| AMXBF16 | Tile computational operations on BFLOAT16 numbers |
| AMXINT8 | Tile computational operations on 8-bit integers |
| AMXFP16 | Tile computational operations on FP16 numbers |
| AMXTILE | Tile architecture |
| APX_F | Intel APX |
| AVX | AVX functions |
| AVX10 | If set the Intel AVX10 Converged Vector ISA is supported |
| AVX10_128 | If set indicates that AVX10 128-bit vector support is present |
| AVX10_256 | If set indicates that AVX10 256-bit vector support is present |
| AVX10_512 | If set indicates that AVX10 512-bit vector support is present |
| AVX2 | AVX2 functions |
| AVX512BF16 | AVX-512 BFLOAT16 Instructions |
| AVX512BITALG | AVX-512 Bit Algorithms |
| AVX512BW | AVX-512 Byte and Word Instructions |
| AVX512CD | AVX-512 Conflict Detection Instructions |
| AVX512DQ | AVX-512 Doubleword and Quadword Instructions |
| AVX512ER | AVX-512 Exponential and Reciprocal Instructions |
| AVX512F | AVX-512 Foundation |
| AVX512FP16 | AVX-512 FP16 Instructions |
| AVX512IFMA | AVX-512 Integer Fused Multiply-Add Instructions |
| AVX512PF | AVX-512 Prefetch Instructions |
| AVX512VBMI | AVX-512 Vector Bit Manipulation Instructions |
| AVX512VBMI2 | AVX-512 Vector Bit Manipulation Instructions, Version 2 |
| AVX512VL | AVX-512 Vector Length Extensions |
| AVX512VNNI | AVX-512 Vector Neural Network Instructions |
| AVX512VP2INTERSECT | AVX-512 Intersect for D/Q |
| AVX512VPOPCNTDQ | AVX-512 Vector Population Count Doubleword and Quadword |
| AVXIFMA | AVX-IFMA instructions |
| AVXNECONVERT | AVX-NE-CONVERT instructions |
| AVXSLOW | Indicates the CPU performs 2 128 bit operations instead of one |
| AVXVNNI | AVX (VEX encoded) VNNI neural network instructions |
| AVXVNNIINT8 | AVX-VNNI-INT8 instructions |
| BHI_CTRL | Branch History Injection and Intra-mode Branch Target Injection / CVE-2022-0001, CVE-2022-0002 / INTEL-SA-00598 |
| BMI1 | Bit Manipulation Instruction Set 1 |
| BMI2 | Bit Manipulation Instruction Set 2 |
| CETIBT | Intel CET Indirect Branch Tracking |
| CETSS | Intel CET Shadow Stack |
| CLDEMOTE | Cache Line Demote |
| CLMUL | Carry-less Multiplication |
| CLZERO | CLZERO instruction supported |
| CMOV | i686 CMOV |
| CMPCCXADD | CMPCCXADD instructions |
| CMPSB_SCADBS_SHORT | Fast short CMPSB and SCASB |
| CMPXCHG8 | CMPXCHG8 instruction |
| CPBOOST | Core Performance Boost |
| CPPC | AMD: Collaborative Processor Performance Control |
| CX16 | CMPXCHG16B Instruction |
| EFER_LMSLE_UNS | AMD: =Core::X86::Msr::EFER[LMSLE] is not supported, and MBZ |
| ENQCMD | Enqueue Command |
| ERMS | Enhanced REP MOVSB/STOSB |
| F16C | Half-precision floating-point conversion |
| FLUSH_L1D | Flush L1D cache |
| FMA3 | Intel FMA 3. Does not imply AVX. |
| FMA4 | Bulldozer FMA4 functions |
| FP128 | AMD: When set, the internal FP/SIMD execution datapath is 128-bits wide |
| FP256 | AMD: When set, the internal FP/SIMD execution datapath is 256-bits wide |
| FSRM | Fast Short Rep Mov |
| FXSR | FXSAVE, FXRESTOR instructions, CR4 bit 9 |
| FXSROPT | FXSAVE/FXRSTOR optimizations |
| GFNI | Galois Field New Instructions. May require other features (AVX, AVX512VL,AVX512F) based on usage. |
| HLE | Hardware Lock Elision |
| HRESET | If set CPU supports history reset and the IA32_HRESET_ENABLE MSR |
| HTT | Hyperthreading (enabled) |
| HWA | Hardware assert supported. Indicates support for MSRC001_10 |
| HYBRID_CPU | This part has CPUs of more than one type. |
| HYPERVISOR | This bit has been reserved by Intel & AMD for use by hypervisors |
| IA32_ARCH_CAP | IA32_ARCH_CAPABILITIES MSR (Intel) |
| IA32_CORE_CAP | IA32_CORE_CAPABILITIES MSR |
| IBPB | Indirect Branch Restricted Speculation (IBRS) and Indirect Branch Predictor Barrier (IBPB) |
| IBRS | AMD: Indirect Branch Restricted Speculation |
| IBRS_PREFERRED | AMD: IBRS is preferred over software solution |
| IBRS_PROVIDES_SMP | AMD: IBRS provides Same Mode Protection |
| IBS | Instruction Based Sampling (AMD) |
| IBSBRNTRGT | Instruction Based Sampling Feature (AMD) |
| IBSFETCHSAM | Instruction Based Sampling Feature (AMD) |
| IBSFFV | Instruction Based Sampling Feature (AMD) |
| IBSOPCNT | Instruction Based Sampling Feature (AMD) |
| IBSOPCNTEXT | Instruction Based Sampling Feature (AMD) |
| IBSOPSAM | Instruction Based Sampling Feature (AMD) |
| IBSRDWROPCNT | Instruction Based Sampling Feature (AMD) |
| IBSRIPINVALIDCHK | Instruction Based Sampling Feature (AMD) |
| IBS_FETCH_CTLX | AMD: IBS fetch control extended MSR supported |
| IBS_OPDATA4 | AMD: IBS op data 4 MSR supported |
| IBS_OPFUSE | AMD: Indicates support for IbsOpFuse |
| IBS_PREVENTHOST | Disallowing IBS use by the host supported |
| IBS_ZEN4 | Fetch and Op IBS support IBS extensions added with Zen4 |
| IDPRED_CTRL | IPRED_DIS |
| INT_WBINVD | WBINVD/WBNOINVD are interruptible. |
| INVLPGB | NVLPGB and TLBSYNC instruction supported |
| KEYLOCKER | Key locker |
| KEYLOCKERW | Key locker wide |
| LAHF | LAHF/SAHF in long mode |
| LAM | If set, CPU supports Linear Address Masking |
| LBRVIRT | LBR virtualization |
| LZCNT | LZCNT instruction |
| MCAOVERFLOW | MCA overflow recovery support. |
| MCDT_NO | Processor do not exhibit MXCSR Configuration Dependent Timing behavior and do not need to mitigate it. |
| MCOMMIT | MCOMMIT instruction supported |
| MD_CLEAR | VERW clears CPU buffers |
| MMX | standard MMX |
| MMXEXT | SSE integer functions or AMD MMX ext |
| MOVBE | MOVBE instruction (big-endian) |
| MOVDIR64B | Move 64 Bytes as Direct Store |
| MOVDIRI | Move Doubleword as Direct Store |
| MOVSB_ZL | Fast Zero-Length MOVSB |
| MPX | Intel MPX (Memory Protection Extensions) |
| MOVU | MOVU SSE instructions are more efficient and should be preferred to SSE MOVL/MOVH. MOVUPS is more efficient than MOVLPS/MOVHPS. MOVUPD is more efficient than MOVLPD/MOVHPD |
| MSRIRC | Instruction Retired Counter MSR available |
| MSRLIST | Read/Write List of Model Specific Registers |
| MSR_PAGEFLUSH | Page Flush MSR available |
| NRIPS | Indicates support for NRIP save on VMEXIT |
| NX | NX (No-Execute) bit |
| OSXSAVE | XSAVE enabled by OS |
| PCONFIG | PCONFIG for Intel Multi-Key Total Memory Encryption |
| POPCNT | POPCNT instruction |
| PPIN | AMD: Protected Processor Inventory Number support. Indicates that Protected Processor Inventory Number (PPIN) capability can be enabled |
| PREFETCHI | PREFETCHIT0/1 instructions |
| PSFD | Predictive Store Forward Disable |
| RDPRU | RDPRU instruction supported |
| RDRAND | RDRAND instruction is available |
| RDSEED | RDSEED instruction is available |
| RDTSCP | RDTSCP Instruction |
| RRSBA_CTRL | Restricted RSB Alternate |
| RTM | Restricted Transactional Memory |
| RTM_ALWAYS_ABORT | Indicates that the loaded microcode is forcing RTM abort. |
| SERIALIZE | Serialize Instruction Execution |
| SEV | AMD Secure Encrypted Virtualization supported |
| SEV_64BIT | AMD SEV guest execution only allowed from a 64-bit host |
| SEV_ALTERNATIVE | AMD SEV Alternate Injection supported |
| SEV_DEBUGSWAP | Full debug state swap supported for SEV-ES guests |
| SEV_ES | AMD SEV Encrypted State supported |
| SEV_RESTRICTED | AMD SEV Restricted Injection supported |
| SEV_SNP | AMD SEV Secure Nested Paging supported |
| SGX | Software Guard Extensions |
| SGXLC | Software Guard Extensions Launch Control |
| SHA | Intel SHA Extensions |
| SME | AMD Secure Memory Encryption supported |
| SME_COHERENT | AMD Hardware cache coherency across encryption domains enforced |
| SPEC_CTRL_SSBD | Speculative Store Bypass Disable |
| SRBDS_CTRL | SRBDS mitigation MSR available |
| SSE | SSE functions |
| SSE2 | P4 SSE functions |
| SSE3 | Prescott SSE3 functions |
| SSE4 | Penryn SSE4.1 functions |
| SSE42 | Nehalem SSE4.2 functions |
| SSE4A | AMD Barcelona microarchitecture SSE4a instructions |
| SSSE3 | Conroe SSSE3 functions |
| STIBP | Single Thread Indirect Branch Predictors |
| STIBP_ALWAYSON | AMD: Single Thread Indirect Branch Prediction Mode has Enhanced Performance and may be left Always On |
| STOSB_SHORT | Fast short STOSB |
| SUCCOR | Software uncorrectable error containment and recovery capability. |
| SVM | AMD Secure Virtual Machine |
| SVMDA | Indicates support for the SVM decode assists. |
| SVMFBASID | SVM, Indicates that TLB flush events, including CR3 writes and CR4.PGE toggles, flush only the current ASID's TLB entries. Also indicates support for the extended VMCBTLB_Control |
| SVML | AMD SVM lock. Indicates support for SVM-Lock. |
| SVMNP | AMD SVM nested paging |
| SVMPF | SVM pause intercept filter. Indicates support for the pause intercept filter |
| SVMPFT | SVM PAUSE filter threshold. Indicates support for the PAUSE filter cycle count threshold |
| SYSCALL | System-Call Extension (SCE): SYSCALL and SYSRET instructions. |
| SYSEE | SYSENTER and SYSEXIT instructions |
| TBM | AMD Trailing Bit Manipulation |
| TDX_GUEST | Intel Trust Domain Extensions Guest |
| TLB_FLUSH_NESTED | AMD: Flushing includes all the nested translations for guest translations |
| TME | Intel Total Memory Encryption. The following MSRs are supported: IA32_TME_CAPABILITY, IA32_TME_ACTIVATE, IA32_TME_EXCLUDE_MASK, and IA32_TME_EXCLUDE_BASE. |
| TOPEXT | TopologyExtensions: topology extensions support. Indicates support for CPUID Fn8000_001D_EAX_x[N:0]-CPUID Fn8000_001E_EDX. |
| TSCRATEMSR | MSR based TSC rate control. Indicates support for MSR TSC ratio MSRC000_0104 |
| TSXLDTRK | Intel TSX Suspend Load Address Tracking |
| VAES | Vector AES. AVX(512) versions requires additional checks. |
| VMCBCLEAN | VMCB clean bits. Indicates support for VMCB clean bits. |
| VMPL | AMD VM Permission Levels supported |
| VMSA_REGPROT | AMD VMSA Register Protection supported |
| VMX | Virtual Machine Extensions |
| VPCLMULQDQ | Carry-Less Multiplication Quadword. Requires AVX for 3 register versions. |
| VTE | AMD Virtual Transparent Encryption supported |
| WAITPKG | TPAUSE, UMONITOR, UMWAIT |
| WBNOINVD | Write Back and Do Not Invalidate Cache |
| WRMSRNS | Non-Serializing Write to Model Specific Register |
| X87 | FPU |
| XGETBV1 | Supports XGETBV with ECX = 1 |
| XOP | Bulldozer XOP functions |
| XSAVE | XSAVE, XRESTOR, XSETBV, XGETBV |
| XSAVEC | Supports XSAVEC and the compacted form of XRSTOR. |
| XSAVEOPT | XSAVEOPT available |
| XSAVES | Supports XSAVES/XRSTORS and IA32_XSS |
# ARM features:
| Feature Flag | Description |
|--------------|------------------------------------------------------------------|
| AESARM | AES instructions |
| ARMCPUID | Some CPU ID registers readable at user-level |
| ASIMD | Advanced SIMD |
| ASIMDDP | SIMD Dot Product |
| ASIMDHP | Advanced SIMD half-precision floating point |
| ASIMDRDM | Rounding Double Multiply Accumulate/Subtract (SQRDMLAH/SQRDMLSH) |
| ATOMICS | Large System Extensions (LSE) |
| CRC32 | CRC32/CRC32C instructions |
| DCPOP | Data cache clean to Point of Persistence (DC CVAP) |
| EVTSTRM | Generic timer |
| FCMA | Floatin point complex number addition and multiplication |
| FP | Single-precision and double-precision floating point |
| FPHP | Half-precision floating point |
| GPA | Generic Pointer Authentication |
| JSCVT | Javascript-style double->int convert (FJCVTZS) |
| LRCPC | Weaker release consistency (LDAPR, etc) |
| PMULL | Polynomial Multiply instructions (PMULL/PMULL2) |
| SHA1 | SHA-1 instructions (SHA1C, etc) |
| SHA2 | SHA-2 instructions (SHA256H, etc) |
| SHA3 | SHA-3 instructions (EOR3, RAXI, XAR, BCAX) |
| SHA512 | SHA512 instructions |
| SM3 | SM3 instructions |
| SM4 | SM4 instructions |
| SVE | Scalable Vector Extension |
# license
This code is published under an MIT license. See LICENSE file for more information.
+432 -180
View File
@@ -67,149 +67,200 @@ const (
// Keep index -1 as unknown
UNKNOWN = -1
// Add features
ADX FeatureID = iota // Intel ADX (Multi-Precision Add-Carry Instruction Extensions)
AESNI // Advanced Encryption Standard New Instructions
AMD3DNOW // AMD 3DNOW
AMD3DNOWEXT // AMD 3DNowExt
AMXBF16 // Tile computational operations on BFLOAT16 numbers
AMXINT8 // Tile computational operations on 8-bit integers
AMXTILE // Tile architecture
AVX // AVX functions
AVX2 // AVX2 functions
AVX512BF16 // AVX-512 BFLOAT16 Instructions
AVX512BITALG // AVX-512 Bit Algorithms
AVX512BW // AVX-512 Byte and Word Instructions
AVX512CD // AVX-512 Conflict Detection Instructions
AVX512DQ // AVX-512 Doubleword and Quadword Instructions
AVX512ER // AVX-512 Exponential and Reciprocal Instructions
AVX512F // AVX-512 Foundation
AVX512FP16 // AVX-512 FP16 Instructions
AVX512IFMA // AVX-512 Integer Fused Multiply-Add Instructions
AVX512PF // AVX-512 Prefetch Instructions
AVX512VBMI // AVX-512 Vector Bit Manipulation Instructions
AVX512VBMI2 // AVX-512 Vector Bit Manipulation Instructions, Version 2
AVX512VL // AVX-512 Vector Length Extensions
AVX512VNNI // AVX-512 Vector Neural Network Instructions
AVX512VP2INTERSECT // AVX-512 Intersect for D/Q
AVX512VPOPCNTDQ // AVX-512 Vector Population Count Doubleword and Quadword
AVXSLOW // Indicates the CPU performs 2 128 bit operations instead of one
AVXVNNI // AVX (VEX encoded) VNNI neural network instructions
BMI1 // Bit Manipulation Instruction Set 1
BMI2 // Bit Manipulation Instruction Set 2
CETIBT // Intel CET Indirect Branch Tracking
CETSS // Intel CET Shadow Stack
CLDEMOTE // Cache Line Demote
CLMUL // Carry-less Multiplication
CLZERO // CLZERO instruction supported
CMOV // i686 CMOV
CMPSB_SCADBS_SHORT // Fast short CMPSB and SCASB
CMPXCHG8 // CMPXCHG8 instruction
CPBOOST // Core Performance Boost
CX16 // CMPXCHG16B Instruction
ENQCMD // Enqueue Command
ERMS // Enhanced REP MOVSB/STOSB
F16C // Half-precision floating-point conversion
FMA3 // Intel FMA 3. Does not imply AVX.
FMA4 // Bulldozer FMA4 functions
FXSR // FXSAVE, FXRESTOR instructions, CR4 bit 9
FXSROPT // FXSAVE/FXRSTOR optimizations
GFNI // Galois Field New Instructions. May require other features (AVX, AVX512VL,AVX512F) based on usage.
HLE // Hardware Lock Elision
HRESET // If set CPU supports history reset and the IA32_HRESET_ENABLE MSR
HTT // Hyperthreading (enabled)
HWA // Hardware assert supported. Indicates support for MSRC001_10
HYPERVISOR // This bit has been reserved by Intel & AMD for use by hypervisors
IBPB // Indirect Branch Restricted Speculation (IBRS) and Indirect Branch Predictor Barrier (IBPB)
IBS // Instruction Based Sampling (AMD)
IBSBRNTRGT // Instruction Based Sampling Feature (AMD)
IBSFETCHSAM // Instruction Based Sampling Feature (AMD)
IBSFFV // Instruction Based Sampling Feature (AMD)
IBSOPCNT // Instruction Based Sampling Feature (AMD)
IBSOPCNTEXT // Instruction Based Sampling Feature (AMD)
IBSOPSAM // Instruction Based Sampling Feature (AMD)
IBSRDWROPCNT // Instruction Based Sampling Feature (AMD)
IBSRIPINVALIDCHK // Instruction Based Sampling Feature (AMD)
IBS_PREVENTHOST // Disallowing IBS use by the host supported
INT_WBINVD // WBINVD/WBNOINVD are interruptible.
INVLPGB // NVLPGB and TLBSYNC instruction supported
LAHF // LAHF/SAHF in long mode
LAM // If set, CPU supports Linear Address Masking
LBRVIRT // LBR virtualization
LZCNT // LZCNT instruction
MCAOVERFLOW // MCA overflow recovery support.
MCOMMIT // MCOMMIT instruction supported
MMX // standard MMX
MMXEXT // SSE integer functions or AMD MMX ext
MOVBE // MOVBE instruction (big-endian)
MOVDIR64B // Move 64 Bytes as Direct Store
MOVDIRI // Move Doubleword as Direct Store
MOVSB_ZL // Fast Zero-Length MOVSB
MPX // Intel MPX (Memory Protection Extensions)
MSRIRC // Instruction Retired Counter MSR available
MSR_PAGEFLUSH // Page Flush MSR available
NRIPS // Indicates support for NRIP save on VMEXIT
NX // NX (No-Execute) bit
OSXSAVE // XSAVE enabled by OS
PCONFIG // PCONFIG for Intel Multi-Key Total Memory Encryption
POPCNT // POPCNT instruction
RDPRU // RDPRU instruction supported
RDRAND // RDRAND instruction is available
RDSEED // RDSEED instruction is available
RDTSCP // RDTSCP Instruction
RTM // Restricted Transactional Memory
RTM_ALWAYS_ABORT // Indicates that the loaded microcode is forcing RTM abort.
SCE // SYSENTER and SYSEXIT instructions
SERIALIZE // Serialize Instruction Execution
SEV // AMD Secure Encrypted Virtualization supported
SEV_64BIT // AMD SEV guest execution only allowed from a 64-bit host
SEV_ALTERNATIVE // AMD SEV Alternate Injection supported
SEV_DEBUGSWAP // Full debug state swap supported for SEV-ES guests
SEV_ES // AMD SEV Encrypted State supported
SEV_RESTRICTED // AMD SEV Restricted Injection supported
SEV_SNP // AMD SEV Secure Nested Paging supported
SGX // Software Guard Extensions
SGXLC // Software Guard Extensions Launch Control
SHA // Intel SHA Extensions
SME // AMD Secure Memory Encryption supported
SME_COHERENT // AMD Hardware cache coherency across encryption domains enforced
SSE // SSE functions
SSE2 // P4 SSE functions
SSE3 // Prescott SSE3 functions
SSE4 // Penryn SSE4.1 functions
SSE42 // Nehalem SSE4.2 functions
SSE4A // AMD Barcelona microarchitecture SSE4a instructions
SSSE3 // Conroe SSSE3 functions
STIBP // Single Thread Indirect Branch Predictors
STOSB_SHORT // Fast short STOSB
SUCCOR // Software uncorrectable error containment and recovery capability.
SVM // AMD Secure Virtual Machine
SVMDA // Indicates support for the SVM decode assists.
SVMFBASID // SVM, Indicates that TLB flush events, including CR3 writes and CR4.PGE toggles, flush only the current ASID's TLB entries. Also indicates support for the extended VMCBTLB_Control
SVML // AMD SVM lock. Indicates support for SVM-Lock.
SVMNP // AMD SVM nested paging
SVMPF // SVM pause intercept filter. Indicates support for the pause intercept filter
SVMPFT // SVM PAUSE filter threshold. Indicates support for the PAUSE filter cycle count threshold
TBM // AMD Trailing Bit Manipulation
TME // Intel Total Memory Encryption. The following MSRs are supported: IA32_TME_CAPABILITY, IA32_TME_ACTIVATE, IA32_TME_EXCLUDE_MASK, and IA32_TME_EXCLUDE_BASE.
TSCRATEMSR // MSR based TSC rate control. Indicates support for MSR TSC ratio MSRC000_0104
TSXLDTRK // Intel TSX Suspend Load Address Tracking
VAES // Vector AES. AVX(512) versions requires additional checks.
VMCBCLEAN // VMCB clean bits. Indicates support for VMCB clean bits.
VMPL // AMD VM Permission Levels supported
VMSA_REGPROT // AMD VMSA Register Protection supported
VMX // Virtual Machine Extensions
VPCLMULQDQ // Carry-Less Multiplication Quadword. Requires AVX for 3 register versions.
VTE // AMD Virtual Transparent Encryption supported
WAITPKG // TPAUSE, UMONITOR, UMWAIT
WBNOINVD // Write Back and Do Not Invalidate Cache
X87 // FPU
XGETBV1 // Supports XGETBV with ECX = 1
XOP // Bulldozer XOP functions
XSAVE // XSAVE, XRESTOR, XSETBV, XGETBV
XSAVEC // Supports XSAVEC and the compacted form of XRSTOR.
XSAVEOPT // XSAVEOPT available
XSAVES // Supports XSAVES/XRSTORS and IA32_XSS
// x86 features
ADX FeatureID = iota // Intel ADX (Multi-Precision Add-Carry Instruction Extensions)
AESNI // Advanced Encryption Standard New Instructions
AMD3DNOW // AMD 3DNOW
AMD3DNOWEXT // AMD 3DNowExt
AMXBF16 // Tile computational operations on BFLOAT16 numbers
AMXFP16 // Tile computational operations on FP16 numbers
AMXINT8 // Tile computational operations on 8-bit integers
AMXTILE // Tile architecture
APX_F // Intel APX
AVX // AVX functions
AVX10 // If set the Intel AVX10 Converged Vector ISA is supported
AVX10_128 // If set indicates that AVX10 128-bit vector support is present
AVX10_256 // If set indicates that AVX10 256-bit vector support is present
AVX10_512 // If set indicates that AVX10 512-bit vector support is present
AVX2 // AVX2 functions
AVX512BF16 // AVX-512 BFLOAT16 Instructions
AVX512BITALG // AVX-512 Bit Algorithms
AVX512BW // AVX-512 Byte and Word Instructions
AVX512CD // AVX-512 Conflict Detection Instructions
AVX512DQ // AVX-512 Doubleword and Quadword Instructions
AVX512ER // AVX-512 Exponential and Reciprocal Instructions
AVX512F // AVX-512 Foundation
AVX512FP16 // AVX-512 FP16 Instructions
AVX512IFMA // AVX-512 Integer Fused Multiply-Add Instructions
AVX512PF // AVX-512 Prefetch Instructions
AVX512VBMI // AVX-512 Vector Bit Manipulation Instructions
AVX512VBMI2 // AVX-512 Vector Bit Manipulation Instructions, Version 2
AVX512VL // AVX-512 Vector Length Extensions
AVX512VNNI // AVX-512 Vector Neural Network Instructions
AVX512VP2INTERSECT // AVX-512 Intersect for D/Q
AVX512VPOPCNTDQ // AVX-512 Vector Population Count Doubleword and Quadword
AVXIFMA // AVX-IFMA instructions
AVXNECONVERT // AVX-NE-CONVERT instructions
AVXSLOW // Indicates the CPU performs 2 128 bit operations instead of one
AVXVNNI // AVX (VEX encoded) VNNI neural network instructions
AVXVNNIINT8 // AVX-VNNI-INT8 instructions
BHI_CTRL // Branch History Injection and Intra-mode Branch Target Injection / CVE-2022-0001, CVE-2022-0002 / INTEL-SA-00598
BMI1 // Bit Manipulation Instruction Set 1
BMI2 // Bit Manipulation Instruction Set 2
CETIBT // Intel CET Indirect Branch Tracking
CETSS // Intel CET Shadow Stack
CLDEMOTE // Cache Line Demote
CLMUL // Carry-less Multiplication
CLZERO // CLZERO instruction supported
CMOV // i686 CMOV
CMPCCXADD // CMPCCXADD instructions
CMPSB_SCADBS_SHORT // Fast short CMPSB and SCASB
CMPXCHG8 // CMPXCHG8 instruction
CPBOOST // Core Performance Boost
CPPC // AMD: Collaborative Processor Performance Control
CX16 // CMPXCHG16B Instruction
EFER_LMSLE_UNS // AMD: =Core::X86::Msr::EFER[LMSLE] is not supported, and MBZ
ENQCMD // Enqueue Command
ERMS // Enhanced REP MOVSB/STOSB
F16C // Half-precision floating-point conversion
FLUSH_L1D // Flush L1D cache
FMA3 // Intel FMA 3. Does not imply AVX.
FMA4 // Bulldozer FMA4 functions
FP128 // AMD: When set, the internal FP/SIMD execution datapath is no more than 128-bits wide
FP256 // AMD: When set, the internal FP/SIMD execution datapath is no more than 256-bits wide
FSRM // Fast Short Rep Mov
FXSR // FXSAVE, FXRESTOR instructions, CR4 bit 9
FXSROPT // FXSAVE/FXRSTOR optimizations
GFNI // Galois Field New Instructions. May require other features (AVX, AVX512VL,AVX512F) based on usage.
HLE // Hardware Lock Elision
HRESET // If set CPU supports history reset and the IA32_HRESET_ENABLE MSR
HTT // Hyperthreading (enabled)
HWA // Hardware assert supported. Indicates support for MSRC001_10
HYBRID_CPU // This part has CPUs of more than one type.
HYPERVISOR // This bit has been reserved by Intel & AMD for use by hypervisors
IA32_ARCH_CAP // IA32_ARCH_CAPABILITIES MSR (Intel)
IA32_CORE_CAP // IA32_CORE_CAPABILITIES MSR
IBPB // Indirect Branch Restricted Speculation (IBRS) and Indirect Branch Predictor Barrier (IBPB)
IBPB_BRTYPE // Indicates that MSR 49h (PRED_CMD) bit 0 (IBPB) flushes all branch type predictions from the CPU branch predictor
IBRS // AMD: Indirect Branch Restricted Speculation
IBRS_PREFERRED // AMD: IBRS is preferred over software solution
IBRS_PROVIDES_SMP // AMD: IBRS provides Same Mode Protection
IBS // Instruction Based Sampling (AMD)
IBSBRNTRGT // Instruction Based Sampling Feature (AMD)
IBSFETCHSAM // Instruction Based Sampling Feature (AMD)
IBSFFV // Instruction Based Sampling Feature (AMD)
IBSOPCNT // Instruction Based Sampling Feature (AMD)
IBSOPCNTEXT // Instruction Based Sampling Feature (AMD)
IBSOPSAM // Instruction Based Sampling Feature (AMD)
IBSRDWROPCNT // Instruction Based Sampling Feature (AMD)
IBSRIPINVALIDCHK // Instruction Based Sampling Feature (AMD)
IBS_FETCH_CTLX // AMD: IBS fetch control extended MSR supported
IBS_OPDATA4 // AMD: IBS op data 4 MSR supported
IBS_OPFUSE // AMD: Indicates support for IbsOpFuse
IBS_PREVENTHOST // Disallowing IBS use by the host supported
IBS_ZEN4 // AMD: Fetch and Op IBS support IBS extensions added with Zen4
IDPRED_CTRL // IPRED_DIS
INT_WBINVD // WBINVD/WBNOINVD are interruptible.
INVLPGB // NVLPGB and TLBSYNC instruction supported
KEYLOCKER // Key locker
KEYLOCKERW // Key locker wide
LAHF // LAHF/SAHF in long mode
LAM // If set, CPU supports Linear Address Masking
LBRVIRT // LBR virtualization
LZCNT // LZCNT instruction
MCAOVERFLOW // MCA overflow recovery support.
MCDT_NO // Processor do not exhibit MXCSR Configuration Dependent Timing behavior and do not need to mitigate it.
MCOMMIT // MCOMMIT instruction supported
MD_CLEAR // VERW clears CPU buffers
MMX // standard MMX
MMXEXT // SSE integer functions or AMD MMX ext
MOVBE // MOVBE instruction (big-endian)
MOVDIR64B // Move 64 Bytes as Direct Store
MOVDIRI // Move Doubleword as Direct Store
MOVSB_ZL // Fast Zero-Length MOVSB
MOVU // AMD: MOVU SSE instructions are more efficient and should be preferred to SSE MOVL/MOVH. MOVUPS is more efficient than MOVLPS/MOVHPS. MOVUPD is more efficient than MOVLPD/MOVHPD
MPX // Intel MPX (Memory Protection Extensions)
MSRIRC // Instruction Retired Counter MSR available
MSRLIST // Read/Write List of Model Specific Registers
MSR_PAGEFLUSH // Page Flush MSR available
NRIPS // Indicates support for NRIP save on VMEXIT
NX // NX (No-Execute) bit
OSXSAVE // XSAVE enabled by OS
PCONFIG // PCONFIG for Intel Multi-Key Total Memory Encryption
POPCNT // POPCNT instruction
PPIN // AMD: Protected Processor Inventory Number support. Indicates that Protected Processor Inventory Number (PPIN) capability can be enabled
PREFETCHI // PREFETCHIT0/1 instructions
PSFD // Predictive Store Forward Disable
RDPRU // RDPRU instruction supported
RDRAND // RDRAND instruction is available
RDSEED // RDSEED instruction is available
RDTSCP // RDTSCP Instruction
RRSBA_CTRL // Restricted RSB Alternate
RTM // Restricted Transactional Memory
RTM_ALWAYS_ABORT // Indicates that the loaded microcode is forcing RTM abort.
SBPB // Indicates support for the Selective Branch Predictor Barrier
SERIALIZE // Serialize Instruction Execution
SEV // AMD Secure Encrypted Virtualization supported
SEV_64BIT // AMD SEV guest execution only allowed from a 64-bit host
SEV_ALTERNATIVE // AMD SEV Alternate Injection supported
SEV_DEBUGSWAP // Full debug state swap supported for SEV-ES guests
SEV_ES // AMD SEV Encrypted State supported
SEV_RESTRICTED // AMD SEV Restricted Injection supported
SEV_SNP // AMD SEV Secure Nested Paging supported
SGX // Software Guard Extensions
SGXLC // Software Guard Extensions Launch Control
SHA // Intel SHA Extensions
SME // AMD Secure Memory Encryption supported
SME_COHERENT // AMD Hardware cache coherency across encryption domains enforced
SPEC_CTRL_SSBD // Speculative Store Bypass Disable
SRBDS_CTRL // SRBDS mitigation MSR available
SRSO_MSR_FIX // Indicates that software may use MSR BP_CFG[BpSpecReduce] to mitigate SRSO.
SRSO_NO // Indicates the CPU is not subject to the SRSO vulnerability
SRSO_USER_KERNEL_NO // Indicates the CPU is not subject to the SRSO vulnerability across user/kernel boundaries
SSE // SSE functions
SSE2 // P4 SSE functions
SSE3 // Prescott SSE3 functions
SSE4 // Penryn SSE4.1 functions
SSE42 // Nehalem SSE4.2 functions
SSE4A // AMD Barcelona microarchitecture SSE4a instructions
SSSE3 // Conroe SSSE3 functions
STIBP // Single Thread Indirect Branch Predictors
STIBP_ALWAYSON // AMD: Single Thread Indirect Branch Prediction Mode has Enhanced Performance and may be left Always On
STOSB_SHORT // Fast short STOSB
SUCCOR // Software uncorrectable error containment and recovery capability.
SVM // AMD Secure Virtual Machine
SVMDA // Indicates support for the SVM decode assists.
SVMFBASID // SVM, Indicates that TLB flush events, including CR3 writes and CR4.PGE toggles, flush only the current ASID's TLB entries. Also indicates support for the extended VMCBTLB_Control
SVML // AMD SVM lock. Indicates support for SVM-Lock.
SVMNP // AMD SVM nested paging
SVMPF // SVM pause intercept filter. Indicates support for the pause intercept filter
SVMPFT // SVM PAUSE filter threshold. Indicates support for the PAUSE filter cycle count threshold
SYSCALL // System-Call Extension (SCE): SYSCALL and SYSRET instructions.
SYSEE // SYSENTER and SYSEXIT instructions
TBM // AMD Trailing Bit Manipulation
TDX_GUEST // Intel Trust Domain Extensions Guest
TLB_FLUSH_NESTED // AMD: Flushing includes all the nested translations for guest translations
TME // Intel Total Memory Encryption. The following MSRs are supported: IA32_TME_CAPABILITY, IA32_TME_ACTIVATE, IA32_TME_EXCLUDE_MASK, and IA32_TME_EXCLUDE_BASE.
TOPEXT // TopologyExtensions: topology extensions support. Indicates support for CPUID Fn8000_001D_EAX_x[N:0]-CPUID Fn8000_001E_EDX.
TSCRATEMSR // MSR based TSC rate control. Indicates support for MSR TSC ratio MSRC000_0104
TSXLDTRK // Intel TSX Suspend Load Address Tracking
VAES // Vector AES. AVX(512) versions requires additional checks.
VMCBCLEAN // VMCB clean bits. Indicates support for VMCB clean bits.
VMPL // AMD VM Permission Levels supported
VMSA_REGPROT // AMD VMSA Register Protection supported
VMX // Virtual Machine Extensions
VPCLMULQDQ // Carry-Less Multiplication Quadword. Requires AVX for 3 register versions.
VTE // AMD Virtual Transparent Encryption supported
WAITPKG // TPAUSE, UMONITOR, UMWAIT
WBNOINVD // Write Back and Do Not Invalidate Cache
WRMSRNS // Non-Serializing Write to Model Specific Register
X87 // FPU
XGETBV1 // Supports XGETBV with ECX = 1
XOP // Bulldozer XOP functions
XSAVE // XSAVE, XRESTOR, XSETBV, XGETBV
XSAVEC // Supports XSAVEC and the compacted form of XRSTOR.
XSAVEOPT // XSAVEOPT available
XSAVES // Supports XSAVES/XRSTORS and IA32_XSS
// ARM features:
AESARM // AES instructions
@@ -253,6 +304,7 @@ type CPUInfo struct {
LogicalCores int // Number of physical cores times threads that can run on each core through the use of hyperthreading. Will be 0 if undetectable.
Family int // CPU family number
Model int // CPU model number
Stepping int // CPU stepping info
CacheLine int // Cache line size in bytes. Will be 0 if undetectable.
Hz int64 // Clock speed, if known, 0 otherwise. Will attempt to contain base clock speed.
BoostFreq int64 // Max clock speed, if known, 0 otherwise
@@ -262,9 +314,11 @@ type CPUInfo struct {
L2 int // L2 Cache (per core or shared). Will be -1 if undetected
L3 int // L3 Cache (per core, per ccx or shared). Will be -1 if undetected
}
SGX SGXSupport
maxFunc uint32
maxExFunc uint32
SGX SGXSupport
AMDMemEncryption AMDMemEncryptionSupport
AVX10Level uint8
maxFunc uint32
maxExFunc uint32
}
var cpuid func(op uint32) (eax, ebx, ecx, edx uint32)
@@ -355,30 +409,61 @@ func (c CPUInfo) Supports(ids ...FeatureID) bool {
// Has allows for checking a single feature.
// Should be inlined by the compiler.
func (c CPUInfo) Has(id FeatureID) bool {
func (c *CPUInfo) Has(id FeatureID) bool {
return c.featureSet.inSet(id)
}
// AnyOf returns whether the CPU supports one or more of the requested features.
func (c CPUInfo) AnyOf(ids ...FeatureID) bool {
for _, id := range ids {
if c.featureSet.inSet(id) {
return true
}
}
return false
}
// Features contains several features combined for a fast check using
// CpuInfo.HasAll
type Features *flagSet
// CombineFeatures allows to combine several features for a close to constant time lookup.
func CombineFeatures(ids ...FeatureID) Features {
var v flagSet
for _, id := range ids {
v.set(id)
}
return &v
}
func (c *CPUInfo) HasAll(f Features) bool {
return c.featureSet.hasSetP(f)
}
// https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels
var level1Features = flagSetWith(CMOV, CMPXCHG8, X87, FXSR, MMX, SCE, SSE, SSE2)
var level2Features = flagSetWith(CMOV, CMPXCHG8, X87, FXSR, MMX, SCE, SSE, SSE2, CX16, LAHF, POPCNT, SSE3, SSE4, SSE42, SSSE3)
var level3Features = flagSetWith(CMOV, CMPXCHG8, X87, FXSR, MMX, SCE, SSE, SSE2, CX16, LAHF, POPCNT, SSE3, SSE4, SSE42, SSSE3, AVX, AVX2, BMI1, BMI2, F16C, FMA3, LZCNT, MOVBE, OSXSAVE)
var level4Features = flagSetWith(CMOV, CMPXCHG8, X87, FXSR, MMX, SCE, SSE, SSE2, CX16, LAHF, POPCNT, SSE3, SSE4, SSE42, SSSE3, AVX, AVX2, BMI1, BMI2, F16C, FMA3, LZCNT, MOVBE, OSXSAVE, AVX512F, AVX512BW, AVX512CD, AVX512DQ, AVX512VL)
var oneOfLevel = CombineFeatures(SYSEE, SYSCALL)
var level1Features = CombineFeatures(CMOV, CMPXCHG8, X87, FXSR, MMX, SSE, SSE2)
var level2Features = CombineFeatures(CMOV, CMPXCHG8, X87, FXSR, MMX, SSE, SSE2, CX16, LAHF, POPCNT, SSE3, SSE4, SSE42, SSSE3)
var level3Features = CombineFeatures(CMOV, CMPXCHG8, X87, FXSR, MMX, SSE, SSE2, CX16, LAHF, POPCNT, SSE3, SSE4, SSE42, SSSE3, AVX, AVX2, BMI1, BMI2, F16C, FMA3, LZCNT, MOVBE, OSXSAVE)
var level4Features = CombineFeatures(CMOV, CMPXCHG8, X87, FXSR, MMX, SSE, SSE2, CX16, LAHF, POPCNT, SSE3, SSE4, SSE42, SSSE3, AVX, AVX2, BMI1, BMI2, F16C, FMA3, LZCNT, MOVBE, OSXSAVE, AVX512F, AVX512BW, AVX512CD, AVX512DQ, AVX512VL)
// X64Level returns the microarchitecture level detected on the CPU.
// If features are lacking or non x64 mode, 0 is returned.
// See https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels
func (c CPUInfo) X64Level() int {
if c.featureSet.hasSet(level4Features) {
if !c.featureSet.hasOneOf(oneOfLevel) {
return 0
}
if c.featureSet.hasSetP(level4Features) {
return 4
}
if c.featureSet.hasSet(level3Features) {
if c.featureSet.hasSetP(level3Features) {
return 3
}
if c.featureSet.hasSet(level2Features) {
if c.featureSet.hasSetP(level2Features) {
return 2
}
if c.featureSet.hasSet(level1Features) {
if c.featureSet.hasSetP(level1Features) {
return 1
}
return 0
@@ -542,7 +627,7 @@ const flagMask = flagBits - 1
// flagSet contains detected cpu features and characteristics in an array of flags
type flagSet [(lastID + flagMask) / flagBits]flags
func (s flagSet) inSet(feat FeatureID) bool {
func (s *flagSet) inSet(feat FeatureID) bool {
return s[feat>>flagBitsLog2]&(1<<(feat&flagMask)) != 0
}
@@ -572,7 +657,7 @@ func (s *flagSet) or(other flagSet) {
}
// hasSet returns whether all features are present.
func (s flagSet) hasSet(other flagSet) bool {
func (s *flagSet) hasSet(other flagSet) bool {
for i, v := range other[:] {
if s[i]&v != v {
return false
@@ -581,8 +666,28 @@ func (s flagSet) hasSet(other flagSet) bool {
return true
}
// hasSet returns whether all features are present.
func (s *flagSet) hasSetP(other *flagSet) bool {
for i, v := range other[:] {
if s[i]&v != v {
return false
}
}
return true
}
// hasOneOf returns whether one or more features are present.
func (s *flagSet) hasOneOf(other *flagSet) bool {
for i, v := range other[:] {
if s[i]&v != 0 {
return true
}
}
return false
}
// nEnabled will return the number of enabled flags.
func (s flagSet) nEnabled() (n int) {
func (s *flagSet) nEnabled() (n int) {
for _, v := range s[:] {
n += bits.OnesCount64(uint64(v))
}
@@ -677,7 +782,7 @@ func threadsPerCore() int {
if vend == AMD {
// Workaround for AMD returning 0, assume 2 if >= Zen 2
// It will be more correct than not.
fam, _ := familyModel()
fam, _, _ := familyModel()
_, _, _, d := cpuid(1)
if (d&(1<<28)) != 0 && fam >= 23 {
return 2
@@ -715,14 +820,27 @@ func logicalCores() int {
}
}
func familyModel() (int, int) {
func familyModel() (family, model, stepping int) {
if maxFunctionID() < 0x1 {
return 0, 0
return 0, 0, 0
}
eax, _, _, _ := cpuid(1)
family := ((eax >> 8) & 0xf) + ((eax >> 20) & 0xff)
model := ((eax >> 4) & 0xf) + ((eax >> 12) & 0xf0)
return int(family), int(model)
// If BaseFamily[3:0] is less than Fh then ExtendedFamily[7:0] is reserved and Family is equal to BaseFamily[3:0].
family = int((eax >> 8) & 0xf)
extFam := family == 0x6 // Intel is 0x6, needs extended model.
if family == 0xf {
// Add ExtFamily
family += int((eax >> 20) & 0xff)
extFam = true
}
// If BaseFamily[3:0] is less than 0Fh then ExtendedModel[3:0] is reserved and Model is equal to BaseModel[3:0].
model = int((eax >> 4) & 0xf)
if extFam {
// Add ExtModel
model += int((eax >> 12) & 0xf0)
}
stepping = int(eax & 0xf)
return family, model, stepping
}
func physicalCores() int {
@@ -857,7 +975,7 @@ func (c *CPUInfo) cacheSize() {
c.Cache.L2 = int(((ecx >> 16) & 0xFFFF) * 1024)
// CPUID Fn8000_001D_EAX_x[N:0] Cache Properties
if maxExtendedFunction() < 0x8000001D {
if maxExtendedFunction() < 0x8000001D || !c.Has(TOPEXT) {
return
}
@@ -967,6 +1085,32 @@ func hasSGX(available, lc bool) (rval SGXSupport) {
return
}
type AMDMemEncryptionSupport struct {
Available bool
CBitPossition uint32
NumVMPL uint32
PhysAddrReduction uint32
NumEntryptedGuests uint32
MinSevNoEsAsid uint32
}
func hasAMDMemEncryption(available bool) (rval AMDMemEncryptionSupport) {
rval.Available = available
if !available {
return
}
_, b, c, d := cpuidex(0x8000001f, 0)
rval.CBitPossition = b & 0x3f
rval.PhysAddrReduction = (b >> 6) & 0x3F
rval.NumVMPL = (b >> 12) & 0xf
rval.NumEntryptedGuests = c
rval.MinSevNoEsAsid = d
return
}
func support() flagSet {
var fs flagSet
mfi := maxFunctionID()
@@ -974,14 +1118,13 @@ func support() flagSet {
if mfi < 0x1 {
return fs
}
family, model := familyModel()
family, model, _ := familyModel()
_, _, c, d := cpuid(1)
fs.setIf((d&(1<<0)) != 0, X87)
fs.setIf((d&(1<<8)) != 0, CMPXCHG8)
fs.setIf((d&(1<<11)) != 0, SCE)
fs.setIf((d&(1<<11)) != 0, SYSEE)
fs.setIf((d&(1<<15)) != 0, CMOV)
fs.setIf((d&(1<<22)) != 0, MMXEXT)
fs.setIf((d&(1<<23)) != 0, MMX)
fs.setIf((d&(1<<24)) != 0, FXSR)
fs.setIf((d&(1<<25)) != 0, FXSROPT)
@@ -989,9 +1132,9 @@ func support() flagSet {
fs.setIf((d&(1<<26)) != 0, SSE2)
fs.setIf((c&1) != 0, SSE3)
fs.setIf((c&(1<<5)) != 0, VMX)
fs.setIf((c&0x00000200) != 0, SSSE3)
fs.setIf((c&0x00080000) != 0, SSE4)
fs.setIf((c&0x00100000) != 0, SSE42)
fs.setIf((c&(1<<9)) != 0, SSSE3)
fs.setIf((c&(1<<19)) != 0, SSE4)
fs.setIf((c&(1<<20)) != 0, SSE42)
fs.setIf((c&(1<<25)) != 0, AESNI)
fs.setIf((c&(1<<1)) != 0, CLMUL)
fs.setIf(c&(1<<22) != 0, MOVBE)
@@ -1062,29 +1205,47 @@ func support() flagSet {
fs.setIf(ecx&(1<<10) != 0, VPCLMULQDQ)
fs.setIf(ecx&(1<<13) != 0, TME)
fs.setIf(ecx&(1<<25) != 0, CLDEMOTE)
fs.setIf(ecx&(1<<23) != 0, KEYLOCKER)
fs.setIf(ecx&(1<<27) != 0, MOVDIRI)
fs.setIf(ecx&(1<<28) != 0, MOVDIR64B)
fs.setIf(ecx&(1<<29) != 0, ENQCMD)
fs.setIf(ecx&(1<<30) != 0, SGXLC)
// CPUID.(EAX=7, ECX=0).EDX
fs.setIf(edx&(1<<4) != 0, FSRM)
fs.setIf(edx&(1<<9) != 0, SRBDS_CTRL)
fs.setIf(edx&(1<<10) != 0, MD_CLEAR)
fs.setIf(edx&(1<<11) != 0, RTM_ALWAYS_ABORT)
fs.setIf(edx&(1<<14) != 0, SERIALIZE)
fs.setIf(edx&(1<<15) != 0, HYBRID_CPU)
fs.setIf(edx&(1<<16) != 0, TSXLDTRK)
fs.setIf(edx&(1<<18) != 0, PCONFIG)
fs.setIf(edx&(1<<20) != 0, CETIBT)
fs.setIf(edx&(1<<26) != 0, IBPB)
fs.setIf(edx&(1<<27) != 0, STIBP)
fs.setIf(edx&(1<<28) != 0, FLUSH_L1D)
fs.setIf(edx&(1<<29) != 0, IA32_ARCH_CAP)
fs.setIf(edx&(1<<30) != 0, IA32_CORE_CAP)
fs.setIf(edx&(1<<31) != 0, SPEC_CTRL_SSBD)
// CPUID.(EAX=7, ECX=1)
eax1, _, _, _ := cpuidex(7, 1)
// CPUID.(EAX=7, ECX=1).EAX
eax1, _, _, edx1 := cpuidex(7, 1)
fs.setIf(fs.inSet(AVX) && eax1&(1<<4) != 0, AVXVNNI)
fs.setIf(eax1&(1<<7) != 0, CMPCCXADD)
fs.setIf(eax1&(1<<10) != 0, MOVSB_ZL)
fs.setIf(eax1&(1<<11) != 0, STOSB_SHORT)
fs.setIf(eax1&(1<<12) != 0, CMPSB_SCADBS_SHORT)
fs.setIf(eax1&(1<<22) != 0, HRESET)
fs.setIf(eax1&(1<<23) != 0, AVXIFMA)
fs.setIf(eax1&(1<<26) != 0, LAM)
// CPUID.(EAX=7, ECX=1).EDX
fs.setIf(edx1&(1<<4) != 0, AVXVNNIINT8)
fs.setIf(edx1&(1<<5) != 0, AVXNECONVERT)
fs.setIf(edx1&(1<<14) != 0, PREFETCHI)
fs.setIf(edx1&(1<<19) != 0, AVX10)
fs.setIf(edx1&(1<<21) != 0, APX_F)
// Only detect AVX-512 features if XGETBV is supported
if c&((1<<26)|(1<<27)) == (1<<26)|(1<<27) {
// Check for OS support
@@ -1120,9 +1281,35 @@ func support() flagSet {
fs.setIf(edx&(1<<25) != 0, AMXINT8)
// eax1 = CPUID.(EAX=7, ECX=1).EAX
fs.setIf(eax1&(1<<5) != 0, AVX512BF16)
fs.setIf(eax1&(1<<19) != 0, WRMSRNS)
fs.setIf(eax1&(1<<21) != 0, AMXFP16)
fs.setIf(eax1&(1<<27) != 0, MSRLIST)
}
}
// CPUID.(EAX=7, ECX=2)
_, _, _, edx = cpuidex(7, 2)
fs.setIf(edx&(1<<0) != 0, PSFD)
fs.setIf(edx&(1<<1) != 0, IDPRED_CTRL)
fs.setIf(edx&(1<<2) != 0, RRSBA_CTRL)
fs.setIf(edx&(1<<4) != 0, BHI_CTRL)
fs.setIf(edx&(1<<5) != 0, MCDT_NO)
// Add keylocker features.
if fs.inSet(KEYLOCKER) && mfi >= 0x19 {
_, ebx, _, _ := cpuidex(0x19, 0)
fs.setIf(ebx&5 == 5, KEYLOCKERW) // Bit 0 and 2 (1+4)
}
// Add AVX10 features.
if fs.inSet(AVX10) && mfi >= 0x24 {
_, ebx, _, _ := cpuidex(0x24, 0)
fs.setIf(ebx&(1<<16) != 0, AVX10_128)
fs.setIf(ebx&(1<<17) != 0, AVX10_256)
fs.setIf(ebx&(1<<18) != 0, AVX10_512)
}
}
// Processor Extended State Enumeration Sub-leaf (EAX = 0DH, ECX = 1)
// EAX
// Bit 00: XSAVEOPT is available.
@@ -1156,20 +1343,24 @@ func support() flagSet {
fs.setIf((c&(1<<2)) != 0, SVM)
fs.setIf((c&(1<<6)) != 0, SSE4A)
fs.setIf((c&(1<<10)) != 0, IBS)
fs.setIf((c&(1<<22)) != 0, TOPEXT)
// EDX
fs.setIf((d&(1<<31)) != 0, AMD3DNOW)
fs.setIf((d&(1<<30)) != 0, AMD3DNOWEXT)
fs.setIf((d&(1<<23)) != 0, MMX)
fs.setIf((d&(1<<22)) != 0, MMXEXT)
fs.setIf(d&(1<<11) != 0, SYSCALL)
fs.setIf(d&(1<<20) != 0, NX)
fs.setIf(d&(1<<22) != 0, MMXEXT)
fs.setIf(d&(1<<23) != 0, MMX)
fs.setIf(d&(1<<24) != 0, FXSR)
fs.setIf(d&(1<<25) != 0, FXSROPT)
fs.setIf(d&(1<<27) != 0, RDTSCP)
fs.setIf(d&(1<<30) != 0, AMD3DNOWEXT)
fs.setIf(d&(1<<31) != 0, AMD3DNOW)
/* XOP and FMA4 use the AVX instruction coding scheme, so they can't be
* used unless the OS has AVX support. */
if fs.inSet(AVX) {
fs.setIf((c&0x00000800) != 0, XOP)
fs.setIf((c&0x00010000) != 0, FMA4)
fs.setIf((c&(1<<11)) != 0, XOP)
fs.setIf((c&(1<<16)) != 0, FMA4)
}
}
@@ -1183,9 +1374,21 @@ func support() flagSet {
if maxExtendedFunction() >= 0x80000008 {
_, b, _, _ := cpuid(0x80000008)
fs.setIf(b&(1<<28) != 0, PSFD)
fs.setIf(b&(1<<27) != 0, CPPC)
fs.setIf(b&(1<<24) != 0, SPEC_CTRL_SSBD)
fs.setIf(b&(1<<23) != 0, PPIN)
fs.setIf(b&(1<<21) != 0, TLB_FLUSH_NESTED)
fs.setIf(b&(1<<20) != 0, EFER_LMSLE_UNS)
fs.setIf(b&(1<<19) != 0, IBRS_PROVIDES_SMP)
fs.setIf(b&(1<<18) != 0, IBRS_PREFERRED)
fs.setIf(b&(1<<17) != 0, STIBP_ALWAYSON)
fs.setIf(b&(1<<15) != 0, STIBP)
fs.setIf(b&(1<<14) != 0, IBRS)
fs.setIf((b&(1<<13)) != 0, INT_WBINVD)
fs.setIf(b&(1<<12) != 0, IBPB)
fs.setIf((b&(1<<9)) != 0, WBNOINVD)
fs.setIf((b&(1<<8)) != 0, MCOMMIT)
fs.setIf((b&(1<<13)) != 0, INT_WBINVD)
fs.setIf((b&(1<<4)) != 0, RDPRU)
fs.setIf((b&(1<<3)) != 0, INVLPGB)
fs.setIf((b&(1<<1)) != 0, MSRIRC)
@@ -1206,6 +1409,13 @@ func support() flagSet {
fs.setIf((edx>>12)&1 == 1, SVMPFT)
}
if maxExtendedFunction() >= 0x8000001a {
eax, _, _, _ := cpuid(0x8000001a)
fs.setIf((eax>>0)&1 == 1, FP128)
fs.setIf((eax>>1)&1 == 1, MOVU)
fs.setIf((eax>>2)&1 == 1, FP256)
}
if maxExtendedFunction() >= 0x8000001b && fs.inSet(IBS) {
eax, _, _, _ := cpuid(0x8000001b)
fs.setIf((eax>>0)&1 == 1, IBSFFV)
@@ -1216,6 +1426,10 @@ func support() flagSet {
fs.setIf((eax>>5)&1 == 1, IBSBRNTRGT)
fs.setIf((eax>>6)&1 == 1, IBSOPCNTEXT)
fs.setIf((eax>>7)&1 == 1, IBSRIPINVALIDCHK)
fs.setIf((eax>>8)&1 == 1, IBS_OPFUSE)
fs.setIf((eax>>9)&1 == 1, IBS_FETCH_CTLX)
fs.setIf((eax>>10)&1 == 1, IBS_OPDATA4) // Doc says "Fixed,0. IBS op data 4 MSR supported", but assuming they mean 1.
fs.setIf((eax>>11)&1 == 1, IBS_ZEN4)
}
if maxExtendedFunction() >= 0x8000001f && vend == AMD {
@@ -1236,9 +1450,47 @@ func support() flagSet {
fs.setIf((a>>24)&1 == 1, VMSA_REGPROT)
}
if maxExtendedFunction() >= 0x80000021 && vend == AMD {
a, _, _, _ := cpuid(0x80000021)
fs.setIf((a>>31)&1 == 1, SRSO_MSR_FIX)
fs.setIf((a>>30)&1 == 1, SRSO_USER_KERNEL_NO)
fs.setIf((a>>29)&1 == 1, SRSO_NO)
fs.setIf((a>>28)&1 == 1, IBPB_BRTYPE)
fs.setIf((a>>27)&1 == 1, SBPB)
}
if mfi >= 0x20 {
// Microsoft has decided to purposefully hide the information
// of the guest TEE when VMs are being created using Hyper-V.
//
// This leads us to check for the Hyper-V cpuid features
// (0x4000000C), and then for the `ebx` value set.
//
// For Intel TDX, `ebx` is set as `0xbe3`, being 3 the part
// we're mostly interested about,according to:
// https://github.com/torvalds/linux/blob/d2f51b3516dade79269ff45eae2a7668ae711b25/arch/x86/include/asm/hyperv-tlfs.h#L169-L174
_, ebx, _, _ := cpuid(0x4000000C)
fs.setIf(ebx == 0xbe3, TDX_GUEST)
}
if mfi >= 0x21 {
// Intel Trusted Domain Extensions Guests have their own cpuid leaf (0x21).
_, ebx, ecx, edx := cpuid(0x21)
identity := string(valAsString(ebx, edx, ecx))
fs.setIf(identity == "IntelTDX ", TDX_GUEST)
}
return fs
}
func (c *CPUInfo) supportAVX10() uint8 {
if c.maxFunc >= 0x24 && c.featureSet.inSet(AVX10) {
_, ebx, _, _ := cpuidex(0x24, 0)
return uint8(ebx)
}
return 0
}
func valAsString(values ...uint32) []byte {
r := make([]byte, 4*len(values))
for i, v := range values {
+3 -1
View File
@@ -24,13 +24,15 @@ func addInfo(c *CPUInfo, safe bool) {
c.maxExFunc = maxExtendedFunction()
c.BrandName = brandName()
c.CacheLine = cacheLine()
c.Family, c.Model = familyModel()
c.Family, c.Model, c.Stepping = familyModel()
c.featureSet = support()
c.SGX = hasSGX(c.featureSet.inSet(SGX), c.featureSet.inSet(SGXLC))
c.AMDMemEncryption = hasAMDMemEncryption(c.featureSet.inSet(SME) || c.featureSet.inSet(SEV))
c.ThreadsPerCore = threadsPerCore()
c.LogicalCores = logicalCores()
c.PhysicalCores = physicalCores()
c.VendorID, c.VendorString = vendorID()
c.AVX10Level = c.supportAVX10()
c.cacheSize()
c.frequencies()
}
+215 -164
View File
@@ -13,174 +13,225 @@ func _() {
_ = x[AMD3DNOW-3]
_ = x[AMD3DNOWEXT-4]
_ = x[AMXBF16-5]
_ = x[AMXINT8-6]
_ = x[AMXTILE-7]
_ = x[AVX-8]
_ = x[AVX2-9]
_ = x[AVX512BF16-10]
_ = x[AVX512BITALG-11]
_ = x[AVX512BW-12]
_ = x[AVX512CD-13]
_ = x[AVX512DQ-14]
_ = x[AVX512ER-15]
_ = x[AVX512F-16]
_ = x[AVX512FP16-17]
_ = x[AVX512IFMA-18]
_ = x[AVX512PF-19]
_ = x[AVX512VBMI-20]
_ = x[AVX512VBMI2-21]
_ = x[AVX512VL-22]
_ = x[AVX512VNNI-23]
_ = x[AVX512VP2INTERSECT-24]
_ = x[AVX512VPOPCNTDQ-25]
_ = x[AVXSLOW-26]
_ = x[AVXVNNI-27]
_ = x[BMI1-28]
_ = x[BMI2-29]
_ = x[CETIBT-30]
_ = x[CETSS-31]
_ = x[CLDEMOTE-32]
_ = x[CLMUL-33]
_ = x[CLZERO-34]
_ = x[CMOV-35]
_ = x[CMPSB_SCADBS_SHORT-36]
_ = x[CMPXCHG8-37]
_ = x[CPBOOST-38]
_ = x[CX16-39]
_ = x[ENQCMD-40]
_ = x[ERMS-41]
_ = x[F16C-42]
_ = x[FMA3-43]
_ = x[FMA4-44]
_ = x[FXSR-45]
_ = x[FXSROPT-46]
_ = x[GFNI-47]
_ = x[HLE-48]
_ = x[HRESET-49]
_ = x[HTT-50]
_ = x[HWA-51]
_ = x[HYPERVISOR-52]
_ = x[IBPB-53]
_ = x[IBS-54]
_ = x[IBSBRNTRGT-55]
_ = x[IBSFETCHSAM-56]
_ = x[IBSFFV-57]
_ = x[IBSOPCNT-58]
_ = x[IBSOPCNTEXT-59]
_ = x[IBSOPSAM-60]
_ = x[IBSRDWROPCNT-61]
_ = x[IBSRIPINVALIDCHK-62]
_ = x[IBS_PREVENTHOST-63]
_ = x[INT_WBINVD-64]
_ = x[INVLPGB-65]
_ = x[LAHF-66]
_ = x[LAM-67]
_ = x[LBRVIRT-68]
_ = x[LZCNT-69]
_ = x[MCAOVERFLOW-70]
_ = x[MCOMMIT-71]
_ = x[MMX-72]
_ = x[MMXEXT-73]
_ = x[MOVBE-74]
_ = x[MOVDIR64B-75]
_ = x[MOVDIRI-76]
_ = x[MOVSB_ZL-77]
_ = x[MPX-78]
_ = x[MSRIRC-79]
_ = x[MSR_PAGEFLUSH-80]
_ = x[NRIPS-81]
_ = x[NX-82]
_ = x[OSXSAVE-83]
_ = x[PCONFIG-84]
_ = x[POPCNT-85]
_ = x[RDPRU-86]
_ = x[RDRAND-87]
_ = x[RDSEED-88]
_ = x[RDTSCP-89]
_ = x[RTM-90]
_ = x[RTM_ALWAYS_ABORT-91]
_ = x[SCE-92]
_ = x[SERIALIZE-93]
_ = x[SEV-94]
_ = x[SEV_64BIT-95]
_ = x[SEV_ALTERNATIVE-96]
_ = x[SEV_DEBUGSWAP-97]
_ = x[SEV_ES-98]
_ = x[SEV_RESTRICTED-99]
_ = x[SEV_SNP-100]
_ = x[SGX-101]
_ = x[SGXLC-102]
_ = x[SHA-103]
_ = x[SME-104]
_ = x[SME_COHERENT-105]
_ = x[SSE-106]
_ = x[SSE2-107]
_ = x[SSE3-108]
_ = x[SSE4-109]
_ = x[SSE42-110]
_ = x[SSE4A-111]
_ = x[SSSE3-112]
_ = x[STIBP-113]
_ = x[STOSB_SHORT-114]
_ = x[SUCCOR-115]
_ = x[SVM-116]
_ = x[SVMDA-117]
_ = x[SVMFBASID-118]
_ = x[SVML-119]
_ = x[SVMNP-120]
_ = x[SVMPF-121]
_ = x[SVMPFT-122]
_ = x[TBM-123]
_ = x[TME-124]
_ = x[TSCRATEMSR-125]
_ = x[TSXLDTRK-126]
_ = x[VAES-127]
_ = x[VMCBCLEAN-128]
_ = x[VMPL-129]
_ = x[VMSA_REGPROT-130]
_ = x[VMX-131]
_ = x[VPCLMULQDQ-132]
_ = x[VTE-133]
_ = x[WAITPKG-134]
_ = x[WBNOINVD-135]
_ = x[X87-136]
_ = x[XGETBV1-137]
_ = x[XOP-138]
_ = x[XSAVE-139]
_ = x[XSAVEC-140]
_ = x[XSAVEOPT-141]
_ = x[XSAVES-142]
_ = x[AESARM-143]
_ = x[ARMCPUID-144]
_ = x[ASIMD-145]
_ = x[ASIMDDP-146]
_ = x[ASIMDHP-147]
_ = x[ASIMDRDM-148]
_ = x[ATOMICS-149]
_ = x[CRC32-150]
_ = x[DCPOP-151]
_ = x[EVTSTRM-152]
_ = x[FCMA-153]
_ = x[FP-154]
_ = x[FPHP-155]
_ = x[GPA-156]
_ = x[JSCVT-157]
_ = x[LRCPC-158]
_ = x[PMULL-159]
_ = x[SHA1-160]
_ = x[SHA2-161]
_ = x[SHA3-162]
_ = x[SHA512-163]
_ = x[SM3-164]
_ = x[SM4-165]
_ = x[SVE-166]
_ = x[lastID-167]
_ = x[AMXFP16-6]
_ = x[AMXINT8-7]
_ = x[AMXTILE-8]
_ = x[APX_F-9]
_ = x[AVX-10]
_ = x[AVX10-11]
_ = x[AVX10_128-12]
_ = x[AVX10_256-13]
_ = x[AVX10_512-14]
_ = x[AVX2-15]
_ = x[AVX512BF16-16]
_ = x[AVX512BITALG-17]
_ = x[AVX512BW-18]
_ = x[AVX512CD-19]
_ = x[AVX512DQ-20]
_ = x[AVX512ER-21]
_ = x[AVX512F-22]
_ = x[AVX512FP16-23]
_ = x[AVX512IFMA-24]
_ = x[AVX512PF-25]
_ = x[AVX512VBMI-26]
_ = x[AVX512VBMI2-27]
_ = x[AVX512VL-28]
_ = x[AVX512VNNI-29]
_ = x[AVX512VP2INTERSECT-30]
_ = x[AVX512VPOPCNTDQ-31]
_ = x[AVXIFMA-32]
_ = x[AVXNECONVERT-33]
_ = x[AVXSLOW-34]
_ = x[AVXVNNI-35]
_ = x[AVXVNNIINT8-36]
_ = x[BHI_CTRL-37]
_ = x[BMI1-38]
_ = x[BMI2-39]
_ = x[CETIBT-40]
_ = x[CETSS-41]
_ = x[CLDEMOTE-42]
_ = x[CLMUL-43]
_ = x[CLZERO-44]
_ = x[CMOV-45]
_ = x[CMPCCXADD-46]
_ = x[CMPSB_SCADBS_SHORT-47]
_ = x[CMPXCHG8-48]
_ = x[CPBOOST-49]
_ = x[CPPC-50]
_ = x[CX16-51]
_ = x[EFER_LMSLE_UNS-52]
_ = x[ENQCMD-53]
_ = x[ERMS-54]
_ = x[F16C-55]
_ = x[FLUSH_L1D-56]
_ = x[FMA3-57]
_ = x[FMA4-58]
_ = x[FP128-59]
_ = x[FP256-60]
_ = x[FSRM-61]
_ = x[FXSR-62]
_ = x[FXSROPT-63]
_ = x[GFNI-64]
_ = x[HLE-65]
_ = x[HRESET-66]
_ = x[HTT-67]
_ = x[HWA-68]
_ = x[HYBRID_CPU-69]
_ = x[HYPERVISOR-70]
_ = x[IA32_ARCH_CAP-71]
_ = x[IA32_CORE_CAP-72]
_ = x[IBPB-73]
_ = x[IBPB_BRTYPE-74]
_ = x[IBRS-75]
_ = x[IBRS_PREFERRED-76]
_ = x[IBRS_PROVIDES_SMP-77]
_ = x[IBS-78]
_ = x[IBSBRNTRGT-79]
_ = x[IBSFETCHSAM-80]
_ = x[IBSFFV-81]
_ = x[IBSOPCNT-82]
_ = x[IBSOPCNTEXT-83]
_ = x[IBSOPSAM-84]
_ = x[IBSRDWROPCNT-85]
_ = x[IBSRIPINVALIDCHK-86]
_ = x[IBS_FETCH_CTLX-87]
_ = x[IBS_OPDATA4-88]
_ = x[IBS_OPFUSE-89]
_ = x[IBS_PREVENTHOST-90]
_ = x[IBS_ZEN4-91]
_ = x[IDPRED_CTRL-92]
_ = x[INT_WBINVD-93]
_ = x[INVLPGB-94]
_ = x[KEYLOCKER-95]
_ = x[KEYLOCKERW-96]
_ = x[LAHF-97]
_ = x[LAM-98]
_ = x[LBRVIRT-99]
_ = x[LZCNT-100]
_ = x[MCAOVERFLOW-101]
_ = x[MCDT_NO-102]
_ = x[MCOMMIT-103]
_ = x[MD_CLEAR-104]
_ = x[MMX-105]
_ = x[MMXEXT-106]
_ = x[MOVBE-107]
_ = x[MOVDIR64B-108]
_ = x[MOVDIRI-109]
_ = x[MOVSB_ZL-110]
_ = x[MOVU-111]
_ = x[MPX-112]
_ = x[MSRIRC-113]
_ = x[MSRLIST-114]
_ = x[MSR_PAGEFLUSH-115]
_ = x[NRIPS-116]
_ = x[NX-117]
_ = x[OSXSAVE-118]
_ = x[PCONFIG-119]
_ = x[POPCNT-120]
_ = x[PPIN-121]
_ = x[PREFETCHI-122]
_ = x[PSFD-123]
_ = x[RDPRU-124]
_ = x[RDRAND-125]
_ = x[RDSEED-126]
_ = x[RDTSCP-127]
_ = x[RRSBA_CTRL-128]
_ = x[RTM-129]
_ = x[RTM_ALWAYS_ABORT-130]
_ = x[SBPB-131]
_ = x[SERIALIZE-132]
_ = x[SEV-133]
_ = x[SEV_64BIT-134]
_ = x[SEV_ALTERNATIVE-135]
_ = x[SEV_DEBUGSWAP-136]
_ = x[SEV_ES-137]
_ = x[SEV_RESTRICTED-138]
_ = x[SEV_SNP-139]
_ = x[SGX-140]
_ = x[SGXLC-141]
_ = x[SHA-142]
_ = x[SME-143]
_ = x[SME_COHERENT-144]
_ = x[SPEC_CTRL_SSBD-145]
_ = x[SRBDS_CTRL-146]
_ = x[SRSO_MSR_FIX-147]
_ = x[SRSO_NO-148]
_ = x[SRSO_USER_KERNEL_NO-149]
_ = x[SSE-150]
_ = x[SSE2-151]
_ = x[SSE3-152]
_ = x[SSE4-153]
_ = x[SSE42-154]
_ = x[SSE4A-155]
_ = x[SSSE3-156]
_ = x[STIBP-157]
_ = x[STIBP_ALWAYSON-158]
_ = x[STOSB_SHORT-159]
_ = x[SUCCOR-160]
_ = x[SVM-161]
_ = x[SVMDA-162]
_ = x[SVMFBASID-163]
_ = x[SVML-164]
_ = x[SVMNP-165]
_ = x[SVMPF-166]
_ = x[SVMPFT-167]
_ = x[SYSCALL-168]
_ = x[SYSEE-169]
_ = x[TBM-170]
_ = x[TDX_GUEST-171]
_ = x[TLB_FLUSH_NESTED-172]
_ = x[TME-173]
_ = x[TOPEXT-174]
_ = x[TSCRATEMSR-175]
_ = x[TSXLDTRK-176]
_ = x[VAES-177]
_ = x[VMCBCLEAN-178]
_ = x[VMPL-179]
_ = x[VMSA_REGPROT-180]
_ = x[VMX-181]
_ = x[VPCLMULQDQ-182]
_ = x[VTE-183]
_ = x[WAITPKG-184]
_ = x[WBNOINVD-185]
_ = x[WRMSRNS-186]
_ = x[X87-187]
_ = x[XGETBV1-188]
_ = x[XOP-189]
_ = x[XSAVE-190]
_ = x[XSAVEC-191]
_ = x[XSAVEOPT-192]
_ = x[XSAVES-193]
_ = x[AESARM-194]
_ = x[ARMCPUID-195]
_ = x[ASIMD-196]
_ = x[ASIMDDP-197]
_ = x[ASIMDHP-198]
_ = x[ASIMDRDM-199]
_ = x[ATOMICS-200]
_ = x[CRC32-201]
_ = x[DCPOP-202]
_ = x[EVTSTRM-203]
_ = x[FCMA-204]
_ = x[FP-205]
_ = x[FPHP-206]
_ = x[GPA-207]
_ = x[JSCVT-208]
_ = x[LRCPC-209]
_ = x[PMULL-210]
_ = x[SHA1-211]
_ = x[SHA2-212]
_ = x[SHA3-213]
_ = x[SHA512-214]
_ = x[SM3-215]
_ = x[SM4-216]
_ = x[SVE-217]
_ = x[lastID-218]
_ = x[firstID-0]
}
const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXINT8AMXTILEAVXAVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512FP16AVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXSLOWAVXVNNIBMI1BMI2CETIBTCETSSCLDEMOTECLMULCLZEROCMOVCMPSB_SCADBS_SHORTCMPXCHG8CPBOOSTCX16ENQCMDERMSF16CFMA3FMA4FXSRFXSROPTGFNIHLEHRESETHTTHWAHYPERVISORIBPBIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKIBS_PREVENTHOSTINT_WBINVDINVLPGBLAHFLAMLBRVIRTLZCNTMCAOVERFLOWMCOMMITMMXMMXEXTMOVBEMOVDIR64BMOVDIRIMOVSB_ZLMPXMSRIRCMSR_PAGEFLUSHNRIPSNXOSXSAVEPCONFIGPOPCNTRDPRURDRANDRDSEEDRDTSCPRTMRTM_ALWAYS_ABORTSCESERIALIZESEVSEV_64BITSEV_ALTERNATIVESEV_DEBUGSWAPSEV_ESSEV_RESTRICTEDSEV_SNPSGXSGXLCSHASMESME_COHERENTSSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPSTOSB_SHORTSUCCORSVMSVMDASVMFBASIDSVMLSVMNPSVMPFSVMPFTTBMTMETSCRATEMSRTSXLDTRKVAESVMCBCLEANVMPLVMSA_REGPROTVMXVPCLMULQDQVTEWAITPKGWBNOINVDX87XGETBV1XOPXSAVEXSAVECXSAVEOPTXSAVESAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFPFPHPGPAJSCVTLRCPCPMULLSHA1SHA2SHA3SHA512SM3SM4SVElastID"
const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXFP16AMXINT8AMXTILEAPX_FAVXAVX10AVX10_128AVX10_256AVX10_512AVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512FP16AVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXIFMAAVXNECONVERTAVXSLOWAVXVNNIAVXVNNIINT8BHI_CTRLBMI1BMI2CETIBTCETSSCLDEMOTECLMULCLZEROCMOVCMPCCXADDCMPSB_SCADBS_SHORTCMPXCHG8CPBOOSTCPPCCX16EFER_LMSLE_UNSENQCMDERMSF16CFLUSH_L1DFMA3FMA4FP128FP256FSRMFXSRFXSROPTGFNIHLEHRESETHTTHWAHYBRID_CPUHYPERVISORIA32_ARCH_CAPIA32_CORE_CAPIBPBIBPB_BRTYPEIBRSIBRS_PREFERREDIBRS_PROVIDES_SMPIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKIBS_FETCH_CTLXIBS_OPDATA4IBS_OPFUSEIBS_PREVENTHOSTIBS_ZEN4IDPRED_CTRLINT_WBINVDINVLPGBKEYLOCKERKEYLOCKERWLAHFLAMLBRVIRTLZCNTMCAOVERFLOWMCDT_NOMCOMMITMD_CLEARMMXMMXEXTMOVBEMOVDIR64BMOVDIRIMOVSB_ZLMOVUMPXMSRIRCMSRLISTMSR_PAGEFLUSHNRIPSNXOSXSAVEPCONFIGPOPCNTPPINPREFETCHIPSFDRDPRURDRANDRDSEEDRDTSCPRRSBA_CTRLRTMRTM_ALWAYS_ABORTSBPBSERIALIZESEVSEV_64BITSEV_ALTERNATIVESEV_DEBUGSWAPSEV_ESSEV_RESTRICTEDSEV_SNPSGXSGXLCSHASMESME_COHERENTSPEC_CTRL_SSBDSRBDS_CTRLSRSO_MSR_FIXSRSO_NOSRSO_USER_KERNEL_NOSSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPSTIBP_ALWAYSONSTOSB_SHORTSUCCORSVMSVMDASVMFBASIDSVMLSVMNPSVMPFSVMPFTSYSCALLSYSEETBMTDX_GUESTTLB_FLUSH_NESTEDTMETOPEXTTSCRATEMSRTSXLDTRKVAESVMCBCLEANVMPLVMSA_REGPROTVMXVPCLMULQDQVTEWAITPKGWBNOINVDWRMSRNSX87XGETBV1XOPXSAVEXSAVECXSAVEOPTXSAVESAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFPFPHPGPAJSCVTLRCPCPMULLSHA1SHA2SHA3SHA512SM3SM4SVElastID"
var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 58, 62, 72, 84, 92, 100, 108, 116, 123, 133, 143, 151, 161, 172, 180, 190, 208, 223, 230, 237, 241, 245, 251, 256, 264, 269, 275, 279, 297, 305, 312, 316, 322, 326, 330, 334, 338, 342, 349, 353, 356, 362, 365, 368, 378, 382, 385, 395, 406, 412, 420, 431, 439, 451, 467, 482, 492, 499, 503, 506, 513, 518, 529, 536, 539, 545, 550, 559, 566, 574, 577, 583, 596, 601, 603, 610, 617, 623, 628, 634, 640, 646, 649, 665, 668, 677, 680, 689, 704, 717, 723, 737, 744, 747, 752, 755, 758, 770, 773, 777, 781, 785, 790, 795, 800, 805, 816, 822, 825, 830, 839, 843, 848, 853, 859, 862, 865, 875, 883, 887, 896, 900, 912, 915, 925, 928, 935, 943, 946, 953, 956, 961, 967, 975, 981, 987, 995, 1000, 1007, 1014, 1022, 1029, 1034, 1039, 1046, 1050, 1052, 1056, 1059, 1064, 1069, 1074, 1078, 1082, 1086, 1092, 1095, 1098, 1101, 1107}
var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 62, 67, 70, 75, 84, 93, 102, 106, 116, 128, 136, 144, 152, 160, 167, 177, 187, 195, 205, 216, 224, 234, 252, 267, 274, 286, 293, 300, 311, 319, 323, 327, 333, 338, 346, 351, 357, 361, 370, 388, 396, 403, 407, 411, 425, 431, 435, 439, 448, 452, 456, 461, 466, 470, 474, 481, 485, 488, 494, 497, 500, 510, 520, 533, 546, 550, 561, 565, 579, 596, 599, 609, 620, 626, 634, 645, 653, 665, 681, 695, 706, 716, 731, 739, 750, 760, 767, 776, 786, 790, 793, 800, 805, 816, 823, 830, 838, 841, 847, 852, 861, 868, 876, 880, 883, 889, 896, 909, 914, 916, 923, 930, 936, 940, 949, 953, 958, 964, 970, 976, 986, 989, 1005, 1009, 1018, 1021, 1030, 1045, 1058, 1064, 1078, 1085, 1088, 1093, 1096, 1099, 1111, 1125, 1135, 1147, 1154, 1173, 1176, 1180, 1184, 1188, 1193, 1198, 1203, 1208, 1222, 1233, 1239, 1242, 1247, 1256, 1260, 1265, 1270, 1276, 1283, 1288, 1291, 1300, 1316, 1319, 1325, 1335, 1343, 1347, 1356, 1360, 1372, 1375, 1385, 1388, 1395, 1403, 1410, 1413, 1420, 1423, 1428, 1434, 1442, 1448, 1454, 1462, 1467, 1474, 1481, 1489, 1496, 1501, 1506, 1513, 1517, 1519, 1523, 1526, 1531, 1536, 1541, 1545, 1549, 1553, 1559, 1562, 1565, 1568, 1574}
func (i FeatureID) String() string {
if i < 0 || i >= FeatureID(len(_FeatureID_index)-1) {
+1 -1
View File
@@ -83,7 +83,7 @@ func tryToFillCPUInfoFomSysctl(c *CPUInfo) {
c.Model = sysctlGetInt(0, "machdep.cpu.model")
c.CacheLine = sysctlGetInt64(0, "hw.cachelinesize")
c.Cache.L1I = sysctlGetInt64(-1, "hw.l1icachesize")
c.Cache.L1D = sysctlGetInt64(-1, "hw.l1icachesize")
c.Cache.L1D = sysctlGetInt64(-1, "hw.l1dcachesize")
c.Cache.L2 = sysctlGetInt64(-1, "hw.l2cachesize")
c.Cache.L3 = sysctlGetInt64(-1, "hw.l3cachesize")
+27 -7
View File
@@ -30,16 +30,36 @@ In other words, the `acmez` package is **porcelain** while the `acme` package is
- Supports niche aspects of RFC 8555 (such as alt cert chains and account key rollover)
- Efficient solving of large SAN lists (e.g. for slow DNS record propagation)
- Utility functions for solving challenges
- Helpers for RFC 8737 (tls-alpn-01 challenge)
The `acmez` package is "bring-your-own-solver." It provides helper utilities for http-01, dns-01, and tls-alpn-01 challenges, but does not actually solve them for you. You must write an implementation of `acmez.Solver` in order to get certificates. How this is done depends on the environment in which you're using this code.
This is not a command line utility either. The goal is to not add more external tooling to already-complex infrastructure: ACME and TLS should be built-in to servers rather than tacked on as an afterthought.
- [Device attestation challenges](https://datatracker.ietf.org/doc/draft-acme-device-attest/)
- RFC 8737 (tls-alpn-01 challenge)
## Examples
See the `examples` folder for tutorials on how to use either package.
See the [`examples` folder](https://github.com/mholt/acmez/tree/master/examples) for tutorials on how to use either package. **Most users should follow the [porcelain guide](https://github.com/mholt/acmez/blob/master/examples/porcelain/main.go) to get started.**
## Challenge solvers
The `acmez` package is "bring-your-own-solver." It provides helper utilities for http-01, dns-01, and tls-alpn-01 challenges, but does not actually solve them for you. You must write or use an implementation of [`acmez.Solver`](https://pkg.go.dev/github.com/mholt/acmez#Solver) in order to get certificates. How this is done depends on your environment/situation.
However, you can find [a general-purpose dns-01 solver in CertMagic](https://pkg.go.dev/github.com/caddyserver/certmagic#DNS01Solver), which uses [libdns](https://github.com/libdns) packages to integrate with numerous DNS providers. You can use it like this:
```go
// minimal example using Cloudflare
solver := &certmagic.DNS01Solver{
DNSProvider: &cloudflare.Provider{APIToken: "topsecret"},
}
client := acmez.Client{
ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeDNS01: solver,
},
// ...
}
```
If you're implementing a tls-alpn-01 solver, the `acmez` package can help. It has the constant [`ACMETLS1Protocol`](https://pkg.go.dev/github.com/mholt/acmez#pkg-constants) which you can use to identify challenge handshakes by inspecting the ClientHello's ALPN extension. Simply complete the handshake using a certificate from the [`acmez.TLSALPN01ChallengeCert()`](https://pkg.go.dev/github.com/mholt/acmez#TLSALPN01ChallengeCert) function to solve the challenge.
## History
@@ -52,7 +72,7 @@ Since then, Caddy has seen use in production longer than any other ACME client i
A few years later, Caddy's novel auto-HTTPS logic was extracted into a library called [CertMagic](https://github.com/caddyserver/certmagic) to be usable by any Go program. Caddy would continue to use CertMagic, which implemented the certificate _automation and management_ logic on top of the low-level certificate _obtain_ logic that lego provided.
Soon thereafter, the lego project shifted maintainership and the goals and vision of the project diverged from those of Caddy's use case of managing tens of thousands of certificates per instance. Eventually, [the original Caddy author announced work on a new ACME client library in Go](https://github.com/caddyserver/certmagic/issues/71) that exceeded Caddy's harsh requirements for large-scale enterprise deployments, lean builds, and simple API. This work finally came to fruition in 2020 as ACMEz.
Soon thereafter, the lego project shifted maintainership and the goals and vision of the project diverged from those of Caddy's use case of managing tens of thousands of certificates per instance. Eventually, [the original Caddy author announced work on a new ACME client library in Go](https://github.com/caddyserver/certmagic/issues/71) that satisfied Caddy's harsh requirements for large-scale enterprise deployments, lean builds, and simple API. This work exceeded expectations and finally came to fruition in 2020 as ACMEz. It is much more lightweight with zero core dependencies, has a simple and elegant code base, and is thoroughly documented and easy to build upon.
---
+205
View File
@@ -0,0 +1,205 @@
// Copyright 2020 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 acme
import (
"context"
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"fmt"
"io"
"math/big"
"net/http"
"time"
"go.uber.org/zap"
)
// RenewalInfo "is a new resource type introduced to ACME protocol.
// This new resource both allows clients to query the server for
// suggestions on when they should renew certificates, and allows
// clients to inform the server when they have completed renewal
// (or otherwise replaced the certificate to their satisfaction)."
//
// ACME Renewal Information (ARI):
// https://datatracker.ietf.org/doc/draft-ietf-acme-ari/
//
// This is a DRAFT specification and the API is subject to change.
type RenewalInfo struct {
SuggestedWindow struct {
Start time.Time `json:"start"`
End time.Time `json:"end"`
} `json:"suggestedWindow"`
ExplanationURL string `json:"explanationURL"`
// This field is not part of the specified structure, but is
// important for proper conformance to the specification,
// so the Retry-After response header will be read and this
// field will be populated for ACME client consideration.
// Polling again for renewal info should not occur before
// this time.
RetryAfter time.Time `json:"-"`
}
// GetRenewalInfo returns the ACME Renewal Information (ARI) for the certificate represented by the
// "base64url-encoded [RFC4648] bytes of a DER-encoded CertID ASN.1 sequence [RFC6960]" without padding
// (call `CertIDSequence()` to get this value). It tacks on the Retry-After value if present.
func (c *Client) GetRenewalInfo(ctx context.Context, b64CertIDSeq string) (RenewalInfo, error) {
if err := c.provision(ctx); err != nil {
return RenewalInfo{}, err
}
endpoint := c.dir.RenewalInfo + b64CertIDSeq
var ari RenewalInfo
resp, err := c.httpReq(ctx, http.MethodGet, endpoint, nil, &ari)
if err != nil {
return RenewalInfo{}, err
}
ra, err := retryAfterTime(resp)
if err != nil && c.Logger != nil {
c.Logger.Error("setting Retry-After value", zap.Error(err))
}
ari.RetryAfter = ra
return ari, nil
}
// UpdateRenewalInfo notifies the ACME server that the certificate represented by b64CertIDSeq
// has been replaced. The b64CertIDSeq string can be obtained by calling `CertIDSequence()`.
func (c *Client) UpdateRenewalInfo(ctx context.Context, account Account, b64CertIDSeq string) error {
if err := c.provision(ctx); err != nil {
return err
}
payload := struct {
CertID string `json:"certID"`
Replaced bool `json:"replaced"`
}{
CertID: b64CertIDSeq,
Replaced: true,
}
resp, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, c.dir.RenewalInfo, payload, nil)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("updating renewal status: HTTP %d", resp.StatusCode)
}
return nil
}
// CertIDSequence returns the "base64url-encoded [RFC4648] bytes of a DER-encoded CertID ASN.1 sequence [RFC6960]"
// without padding for the given certificate chain. It is used primarily for requests to OCSP and ARI.
//
// The certificate chain must contain at least two elements: an end-entity certificate first, followed by an issuer
// certificate second. Of the end-entity certificate, only the SerialNumber field is required; and of the issuer
// certificate, only the RawSubjectPublicKeyInfo and RawSubject fields are required. If the issuer certificate is
// not provided, then it will be downloaded if the end-entity certificate contains the IssuingCertificateURL.
//
// As the return value may be used often during a certificate's lifetime, and in bulk with potentially tens of
// thousands of other certificates, it may be preferable to store or cache this value so that ASN.1 documents do
// not need to be repeatedly decoded and re-encoded.
func CertIDSequence(_ context.Context, certChain []*x509.Certificate, hash crypto.Hash, client *http.Client) (string, error) {
endEntityCert := certChain[0]
// if no chain was provided, we'll need to download the issuer cert
if len(certChain) == 1 {
if len(endEntityCert.IssuingCertificateURL) == 0 {
return "", fmt.Errorf("no URL to issuing certificate")
}
if client == nil {
client = http.DefaultClient
}
resp, err := client.Get(endEntityCert.IssuingCertificateURL[0])
if err != nil {
return "", fmt.Errorf("getting issuer certificate: %v", err)
}
defer resp.Body.Close()
issuerBytes, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024))
if err != nil {
return "", fmt.Errorf("reading issuer certificate: %v", err)
}
issuerCert, err := x509.ParseCertificate(issuerBytes)
if err != nil {
return "", fmt.Errorf("parsing issuer certificate: %v", err)
}
certChain = append(certChain, issuerCert)
}
issuerCert := certChain[1]
hashAlg, ok := hashOIDs[hash]
if !ok {
return "", x509.ErrUnsupportedAlgorithm
}
if !hash.Available() {
return "", x509.ErrUnsupportedAlgorithm
}
h := hash.New()
var publicKeyInfo struct {
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}
if _, err := asn1.Unmarshal(issuerCert.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil {
return "", err
}
h.Write(publicKeyInfo.PublicKey.RightAlign())
issuerKeyHash := h.Sum(nil)
h.Reset()
h.Write(issuerCert.RawSubject)
issuerNameHash := h.Sum(nil)
val, err := asn1.Marshal(certID{
HashAlgorithm: pkix.AlgorithmIdentifier{
Algorithm: hashAlg,
},
NameHash: issuerNameHash,
IssuerKeyHash: issuerKeyHash,
SerialNumber: endEntityCert.SerialNumber,
})
if err != nil {
return "", err
}
return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(val), nil
}
type certID struct {
HashAlgorithm pkix.AlgorithmIdentifier
NameHash []byte
IssuerKeyHash []byte
SerialNumber *big.Int
}
var hashOIDs = map[crypto.Hash]asn1.ObjectIdentifier{
crypto.SHA1: asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}),
crypto.SHA256: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1}),
crypto.SHA384: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2}),
crypto.SHA512: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3}),
}
+12 -1
View File
@@ -26,7 +26,9 @@ import (
// Certificate represents a certificate chain, which we usually refer
// to as "a certificate" because in practice an end-entity certificate
// is seldom useful/practical without a chain.
// is seldom useful/practical without a chain. This structure can be
// JSON-encoded and stored alongside the certificate chain to preserve
// potentially-useful metadata.
type Certificate struct {
// The certificate resource URL as provisioned by
// the ACME server. Some ACME servers may split
@@ -36,7 +38,15 @@ type Certificate struct {
URL string `json:"url"`
// The PEM-encoded certificate chain, end-entity first.
// It is excluded from JSON marshalling since the
// chain is usually stored in its own file.
ChainPEM []byte `json:"-"`
// For convenience, the directory URL of the ACME CA that
// issued this certificate. This field is not part of the
// ACME spec, but it can be useful to save this along with
// the certificate for restoring a lost ACME client config.
CA string `json:"ca,omitempty"`
}
// GetCertificateChain downloads all available certificate chains originating from
@@ -74,6 +84,7 @@ func (c *Client) GetCertificateChain(ctx context.Context, account Account, certU
chains = append(chains, Certificate{
URL: certURL,
ChainPEM: buf.Bytes(),
CA: c.Directory,
})
default:
return resp, fmt.Errorf("unrecognized Content-Type from server: %s", contentType)
+14 -4
View File
@@ -78,6 +78,12 @@ type Challenge struct {
// structure as defined by the spec but is added by us to provide enough
// information to solve the DNS-01 challenge.
Identifier Identifier `json:"identifier,omitempty"`
// Payload contains a JSON-marshallable value that will be sent to the CA
// when responding to challenges. If not set, an empty JSON body "{}" will
// be included in the POST request. This field is applicable when responding
// to "device-attest-01" challenges.
Payload any `json:"-"`
}
// HTTP01ResourcePath returns the URI path for solving the http-01 challenge.
@@ -121,13 +127,17 @@ func (c *Client) InitiateChallenge(ctx context.Context, account Account, challen
if err := c.provision(ctx); err != nil {
return Challenge{}, err
}
_, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, challenge.URL, struct{}{}, &challenge)
if challenge.Payload == nil {
challenge.Payload = struct{}{}
}
_, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, challenge.URL, challenge.Payload, &challenge)
return challenge, err
}
// The standard or well-known ACME challenge types.
const (
ChallengeTypeHTTP01 = "http-01" // RFC 8555 §8.3
ChallengeTypeDNS01 = "dns-01" // RFC 8555 §8.4
ChallengeTypeTLSALPN01 = "tls-alpn-01" // RFC 8737 §3
ChallengeTypeHTTP01 = "http-01" // RFC 8555 §8.3
ChallengeTypeDNS01 = "dns-01" // RFC 8555 §8.4
ChallengeTypeTLSALPN01 = "tls-alpn-01" // RFC 8737 §3
ChallengeTypeDeviceAttest01 = "device-attest-01" // draft-acme-device-attest-00 §5
)
+16 -11
View File
@@ -46,10 +46,10 @@ import (
// response. This package wraps errors that may be of type Problem,
// so you can access the details using the conventional Go pattern:
//
// var problem Problem
// if errors.As(err, &problem) {
// log.Printf("Houston, we have a problem: %+v", problem)
// }
// var problem Problem
// if errors.As(err, &problem) {
// log.Printf("Houston, we have a problem: %+v", problem)
// }
//
// All Problem errors originate from the ACME server.
type Client struct {
@@ -126,6 +126,10 @@ func (c *Client) provisionDirectory(ctx context.Context) error {
if err != nil {
return err
}
if c.dir.NewOrder == "" {
// catch faulty ACME servers that may not return proper HTTP status on errors
return fmt.Errorf("server did not return error headers, but required directory fields are missing: %+v", c.dir)
}
directories[c.Directory] = cachedDirectory{c.dir, time.Now()}
return nil
}
@@ -168,13 +172,14 @@ func (c *Client) pollTimeout() time.Duration {
// ACME operation, ACME servers provide a directory
// object." §7.1.1
type Directory struct {
NewNonce string `json:"newNonce"`
NewAccount string `json:"newAccount"`
NewOrder string `json:"newOrder"`
NewAuthz string `json:"newAuthz,omitempty"`
RevokeCert string `json:"revokeCert"`
KeyChange string `json:"keyChange"`
Meta *DirectoryMeta `json:"meta,omitempty"`
NewNonce string `json:"newNonce"`
NewAccount string `json:"newAccount"`
NewOrder string `json:"newOrder"`
NewAuthz string `json:"newAuthz,omitempty"`
RevokeCert string `json:"revokeCert"`
KeyChange string `json:"keyChange"`
RenewalInfo string `json:"renewalInfo,omitempty"` // draft-ietf-acme-ari
Meta *DirectoryMeta `json:"meta,omitempty"`
}
// DirectoryMeta is optional extra data that may be
+25 -9
View File
@@ -41,7 +41,7 @@ import (
// body will have been drained and closed, so there is no need to close it again.
// It automatically retries in the case of network, I/O, or badNonce errors.
func (c *Client) httpPostJWS(ctx context.Context, privateKey crypto.Signer,
kid, endpoint string, input, output interface{}) (*http.Response, error) {
kid, endpoint string, input, output any) (*http.Response, error) {
if err := c.provision(ctx); err != nil {
return nil, err
@@ -130,7 +130,7 @@ func (c *Client) httpPostJWS(ctx context.Context, privateKey crypto.Signer,
//
// If there are any network or I/O errors, the request will be retried as safely and resiliently as
// possible.
func (c *Client) httpReq(ctx context.Context, method, endpoint string, joseJSONPayload []byte, output interface{}) (*http.Response, error) {
func (c *Client) httpReq(ctx context.Context, method, endpoint string, joseJSONPayload []byte, output any) (*http.Response, error) {
// even if the caller doesn't specify an output, we still use a
// buffer to store possible error response (we reset it later)
buf := bufPool.Get().(*bytes.Buffer)
@@ -373,19 +373,35 @@ func retryAfter(resp *http.Response, fallback time.Duration) (time.Duration, err
if resp == nil {
return fallback, nil
}
raSeconds := resp.Header.Get("Retry-After")
if raSeconds == "" {
raTime, err := retryAfterTime(resp)
if err != nil {
return fallback, fmt.Errorf("response had invalid Retry-After header: %v", err)
}
if raTime.IsZero() {
return fallback, nil
}
ra, err := strconv.Atoi(raSeconds)
if err != nil || ra < 0 {
return 0, fmt.Errorf("response had invalid Retry-After header: %s", raSeconds)
return time.Until(raTime), nil
}
// retryAfterTime returns the timestamp represented by the Retry-After header of the response.
// It returns a zero-value if there is no Retry-After header.
func retryAfterTime(resp *http.Response) (time.Time, error) {
if resp == nil {
return time.Time{}, nil
}
return time.Duration(ra) * time.Second, nil
raHeader := resp.Header.Get("Retry-After")
if raHeader == "" {
return time.Time{}, nil
}
raSeconds, err := strconv.Atoi(raHeader)
if err == nil && raSeconds >= 0 {
return time.Now().Add(time.Duration(raSeconds) * time.Second), nil
}
return time.Parse(http.TimeFormat, raHeader)
}
var bufPool = sync.Pool{
New: func() interface{} {
New: func() any {
return new(bytes.Buffer)
},
}
+1 -1
View File
@@ -89,7 +89,7 @@ func jwsEncodeEAB(accountKey crypto.PublicKey, hmacKey []byte, kid keyID, url st
// See https://tools.ietf.org/html/rfc7515#section-7.
//
// If nonce is empty, it will not be encoded into the header.
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, url string) ([]byte, error) {
func jwsEncodeJSON(claimset any, key crypto.Signer, kid keyID, nonce, url string) ([]byte, error) {
alg, sha := jwsHasher(key.Public())
if alg == "" || !sha.Available() {
return nil, errUnsupportedKey
+1 -1
View File
@@ -70,7 +70,7 @@ type Problem struct {
// spec, but, if a challenge fails for example, we can associate the
// error with the problematic authz object by setting this field.
// Challenge failures will have this set to an Authorization type.
Resource interface{} `json:"-"`
Resource any `json:"-"`
}
func (p Problem) Error() string {
+172 -42
View File
@@ -21,10 +21,13 @@
// implementing solvers and using the certificates. It DOES NOT manage
// certificates, it only gets them from the ACME server.
//
// NOTE: This package's main function is to get a certificate, not manage it.
// Most users will want to *manage* certificates over the lifetime of a
// long-running program such as a HTTPS or TLS server, and should use CertMagic
// NOTE: This package's primary purpose is to get a certificate, not manage it.
// Most users actually want to *manage* certificates over the lifetime of
// long-running programs such as HTTPS or TLS servers, and should use CertMagic
// instead: https://github.com/caddyserver/certmagic.
//
// COMPATIBILITY: Exported identifiers that are related to draft specifications
// are subject to change or removal without a major version bump.
package acmez
import (
@@ -47,10 +50,6 @@ import (
"golang.org/x/net/idna"
)
func init() {
weakrand.Seed(time.Now().UnixNano())
}
// Client is a high-level API for ACME operations. It wraps
// a lower-level ACME client with useful functions to make
// common flows easier, especially for the issuance of
@@ -60,48 +59,41 @@ type Client struct {
// Map of solvers keyed by name of the challenge type.
ChallengeSolvers map[string]Solver
// An optional logger. Default: no logs
Logger *zap.Logger
}
// ObtainCertificateUsingCSR obtains all resulting certificate chains using the given CSR, which
// must be completely and properly filled out (particularly its DNSNames and Raw fields - this
// usually involves creating a template CSR, then calling x509.CreateCertificateRequest, then
// x509.ParseCertificateRequest on the output). The Subject CommonName is NOT considered.
// CSRSource is an interface that provides users of this
// package the ability to provide a CSR as part of the
// ACME flow. This allows the final CSR to be provided
// just before the Order is finalized.
type CSRSource interface {
CSR(context.Context) (*x509.CertificateRequest, error)
}
// ObtainCertificateUsingCSRSource obtains all resulting certificate chains using the given
// ACME Identifiers and the CSRSource. The CSRSource can be used to create and sign a final
// CSR to be submitted to the ACME server just before finalization. The CSR must be completely
// and properly filled out, because the provided ACME Identifiers will be validated against
// the Identifiers that can be extracted from the CSR. This package currently supports the
// DNS, IP address, Permanent Identifier and Hardware Module Name identifiers. The Subject
// CommonName is NOT considered.
//
// It implements every single part of the ACME flow described in RFC 8555 §7.1 with the exception
// of "Create account" because this method signature does not have a way to return the udpated
// account object. The account's status MUST be "valid" in order to succeed.
// The CSR's Raw field containing the DER encoded signed certificate request must also be
// set. This usually involves creating a template CSR, then calling x509.CreateCertificateRequest,
// then x509.ParseCertificateRequest on the output.
//
// As far as SANs go, this method currently only supports DNSNames and IPAddresses on the csr.
func (c *Client) ObtainCertificateUsingCSR(ctx context.Context, account acme.Account, csr *x509.CertificateRequest) ([]acme.Certificate, error) {
// The method implements every single part of the ACME flow described in RFC 8555 §7.1 with the
// exception of "Create account" because this method signature does not have a way to return
// the updated account object. The account's status MUST be "valid" in order to succeed.
func (c *Client) ObtainCertificateUsingCSRSource(ctx context.Context, account acme.Account, identifiers []acme.Identifier, source CSRSource) ([]acme.Certificate, error) {
if account.Status != acme.StatusValid {
return nil, fmt.Errorf("account status is not valid: %s", account.Status)
}
if csr == nil {
return nil, fmt.Errorf("missing CSR")
if source == nil {
return nil, errors.New("missing CSR source")
}
var ids []acme.Identifier
for _, name := range csr.DNSNames {
ids = append(ids, acme.Identifier{
Type: "dns", // RFC 8555 §9.7.7
Value: name,
})
}
for _, ip := range csr.IPAddresses {
ids = append(ids, acme.Identifier{
Type: "ip", // RFC 8738
Value: ip.String(),
})
}
if len(ids) == 0 {
return nil, fmt.Errorf("no identifiers found")
}
order := acme.Order{Identifiers: ids}
var err error
order := acme.Order{Identifiers: identifiers}
// remember which challenge types failed for which identifiers
// so we can retry with other challenge types
@@ -164,6 +156,20 @@ func (c *Client) ObtainCertificateUsingCSR(ctx context.Context, account acme.Acc
c.Logger.Info("validations succeeded; finalizing order", zap.String("order", order.Location))
}
// get the CSR from its source
csr, err := source.CSR(ctx)
if err != nil {
return nil, fmt.Errorf("getting CSR from source: %w", err)
}
if csr == nil {
return nil, errors.New("source did not provide CSR")
}
// validate the order identifiers
if err := validateOrderIdentifiers(&order, csr); err != nil {
return nil, fmt.Errorf("validating order identifiers: %w", err)
}
// finalize the order, which requests the CA to issue us a certificate
order, err = c.Client.FinalizeOrder(ctx, account, order, csr.Raw)
if err != nil {
@@ -190,6 +196,80 @@ func (c *Client) ObtainCertificateUsingCSR(ctx context.Context, account acme.Acc
return certChains, nil
}
// validateOrderIdentifiers checks if the ACME identifiers provided for the
// Order match the identifiers that are in the CSR. A mismatch between the two
// should result the certificate not being issued by the ACME server, but
// checking this on the client side is faster. Currently there's no way to
// skip this validation.
func validateOrderIdentifiers(order *acme.Order, csr *x509.CertificateRequest) error {
csrIdentifiers, err := createIdentifiersUsingCSR(csr)
if err != nil {
return fmt.Errorf("extracting identifiers from CSR: %w", err)
}
if len(csrIdentifiers) != len(order.Identifiers) {
return fmt.Errorf("number of identifiers in Order %v (%d) does not match the number of identifiers extracted from CSR %v (%d)", order.Identifiers, len(order.Identifiers), csrIdentifiers, len(csrIdentifiers))
}
identifiers := make([]acme.Identifier, 0, len(order.Identifiers))
for _, identifier := range order.Identifiers {
for _, csrIdentifier := range csrIdentifiers {
if csrIdentifier.Value == identifier.Value && csrIdentifier.Type == identifier.Type {
identifiers = append(identifiers, identifier)
}
}
}
if len(identifiers) != len(csrIdentifiers) {
return fmt.Errorf("identifiers in Order %v do not match the identifiers extracted from CSR %v", order.Identifiers, csrIdentifiers)
}
return nil
}
// csrSource implements the CSRSource interface and is used internally
// to pass a CSR to ObtainCertificateUsingCSRSource from the existing
// ObtainCertificateUsingCSR method.
type csrSource struct {
csr *x509.CertificateRequest
}
func (i *csrSource) CSR(_ context.Context) (*x509.CertificateRequest, error) {
return i.csr, nil
}
var _ CSRSource = (*csrSource)(nil)
// ObtainCertificateUsingCSR obtains all resulting certificate chains using the given CSR, which
// must be completely and properly filled out (particularly its DNSNames and Raw fields - this
// usually involves creating a template CSR, then calling x509.CreateCertificateRequest, then
// x509.ParseCertificateRequest on the output). The Subject CommonName is NOT considered.
//
// It implements every single part of the ACME flow described in RFC 8555 §7.1 with the exception
// of "Create account" because this method signature does not have a way to return the updated
// account object. The account's status MUST be "valid" in order to succeed.
//
// As far as SANs go, this method currently only supports DNSNames, IPAddresses, Permanent
// Identifiers and Hardware Module Names on the CSR.
func (c *Client) ObtainCertificateUsingCSR(ctx context.Context, account acme.Account, csr *x509.CertificateRequest) ([]acme.Certificate, error) {
if csr == nil {
return nil, errors.New("missing CSR")
}
ids, err := createIdentifiersUsingCSR(csr)
if err != nil {
return nil, err
}
if len(ids) == 0 {
return nil, errors.New("no identifiers found")
}
csrSource := &csrSource{
csr: csr,
}
return c.ObtainCertificateUsingCSRSource(ctx, account, ids, csrSource)
}
// ObtainCertificate is the same as ObtainCertificateUsingCSR, except it is a slight wrapper
// that generates the CSR for you. Doing so requires the private key you will be using for
// the certificate (different from the account private key). It obtains a certificate for
@@ -262,10 +342,12 @@ func (c *Client) getAuthzObjects(ctx context.Context, account acme.Account, orde
preferredChallenges.addUnique(chal.Type)
}
if preferredWasEmpty {
weakrand.Shuffle(len(preferredChallenges), func(i, j int) {
randomSourceMu.Lock()
randomSource.Shuffle(len(preferredChallenges), func(i, j int) {
preferredChallenges[i], preferredChallenges[j] =
preferredChallenges[j], preferredChallenges[i]
})
randomSourceMu.Unlock()
}
preferredChallengesMu.Unlock()
@@ -407,6 +489,11 @@ func (c *Client) presentForNextChallenge(ctx context.Context, authz *authzState)
func (c *Client) initiateCurrentChallenge(ctx context.Context, authz *authzState) error {
if authz.Status != acme.StatusPending {
if c.Logger != nil {
c.Logger.Debug("skipping challenge initiation because authorization is not pending",
zap.String("identifier", authz.IdentifierValue()),
zap.String("authz_status", authz.Status))
}
return nil
}
@@ -416,12 +503,42 @@ func (c *Client) initiateCurrentChallenge(ctx context.Context, authz *authzState
// that's probably OK, since we can't finalize the order until the slow
// challenges are done too)
if waiter, ok := authz.currentSolver.(Waiter); ok {
if c.Logger != nil {
c.Logger.Debug("waiting for solver before continuing",
zap.String("identifier", authz.IdentifierValue()),
zap.String("challenge_type", authz.currentChallenge.Type))
}
err := waiter.Wait(ctx, authz.currentChallenge)
if c.Logger != nil {
c.Logger.Debug("done waiting for solver",
zap.String("identifier", authz.IdentifierValue()),
zap.String("challenge_type", authz.currentChallenge.Type))
}
if err != nil {
return fmt.Errorf("waiting for solver %T to be ready: %w", authz.currentSolver, err)
}
}
// for device-attest-01 challenges the client needs to present a payload
// that will be validated by the CA.
if payloader, ok := authz.currentSolver.(Payloader); ok {
if c.Logger != nil {
c.Logger.Debug("getting payload from solver before continuing",
zap.String("identifier", authz.IdentifierValue()),
zap.String("challenge_type", authz.currentChallenge.Type))
}
p, err := payloader.Payload(ctx, authz.currentChallenge)
if c.Logger != nil {
c.Logger.Debug("done getting payload from solver",
zap.String("identifier", authz.IdentifierValue()),
zap.String("challenge_type", authz.currentChallenge.Type))
}
if err != nil {
return fmt.Errorf("getting payload from solver %T failed: %w", authz.currentSolver, err)
}
authz.currentChallenge.Payload = p
}
// tell the server to initiate the challenge
var err error
authz.currentChallenge, err = c.Client.InitiateChallenge(ctx, authz.account, authz.currentChallenge)
@@ -531,6 +648,13 @@ func (c *Client) pollAuthorization(ctx context.Context, account acme.Account, au
}
return fmt.Errorf("[%s] %w", authz.Authorization.IdentifierValue(), err)
}
if c.Logger != nil {
c.Logger.Info("authorization finalized",
zap.String("identifier", authz.IdentifierValue()),
zap.String("authz_status", authz.Status))
}
return nil
}
@@ -670,9 +794,15 @@ type retryableErr struct{ error }
func (re retryableErr) Unwrap() error { return re.error }
// Keep a list of challenges we've seen offered by servers,
// and prefer keep an ordered list of
// Keep a list of challenges we've seen offered by servers, ordered by success rate.
var (
preferredChallenges challengeTypes
preferredChallengesMu sync.Mutex
)
// Best practice is to avoid the default RNG source and seed our own;
// custom sources are not safe for concurrent use, hence the mutex.
var (
randomSource = weakrand.New(weakrand.NewSource(time.Now().UnixNano()))
randomSourceMu sync.Mutex
)
+149
View File
@@ -0,0 +1,149 @@
// Copyright 2020 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 acmez
import (
"crypto/x509"
"encoding/asn1"
"errors"
"github.com/mholt/acmez/acme"
"golang.org/x/crypto/cryptobyte"
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
)
var (
oidExtensionSubjectAltName = []int{2, 5, 29, 17}
oidPermanentIdentifier = []int{1, 3, 6, 1, 5, 5, 7, 8, 3}
oidHardwareModuleName = []int{1, 3, 6, 1, 5, 5, 7, 8, 4}
)
// RFC 5280 - https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
//
// OtherName ::= SEQUENCE {
// type-id OBJECT IDENTIFIER,
// value [0] EXPLICIT ANY DEFINED BY type-id }
type otherName struct {
TypeID asn1.ObjectIdentifier
Value asn1.RawValue
}
// permanentIdentifier is defined in RFC 4043 as an optional feature that can be
// used by a CA to indicate that two or more certificates relate to the same
// entity.
//
// The OID defined for this SAN is "1.3.6.1.5.5.7.8.3".
//
// See https://www.rfc-editor.org/rfc/rfc4043
//
// PermanentIdentifier ::= SEQUENCE {
// identifierValue UTF8String OPTIONAL,
// assigner OBJECT IDENTIFIER OPTIONAL
// }
type permanentIdentifier struct {
IdentifierValue string `asn1:"utf8,optional"`
Assigner asn1.ObjectIdentifier `asn1:"optional"`
}
// hardwareModuleName is defined in RFC 4108 as an optional feature that can be
// used to identify a hardware module.
//
// The OID defined for this SAN is "1.3.6.1.5.5.7.8.4".
//
// See https://www.rfc-editor.org/rfc/rfc4108#section-5
//
// HardwareModuleName ::= SEQUENCE {
// hwType OBJECT IDENTIFIER,
// hwSerialNum OCTET STRING
// }
type hardwareModuleName struct {
Type asn1.ObjectIdentifier
SerialNumber []byte `asn1:"tag:4"`
}
func forEachSAN(der cryptobyte.String, callback func(tag int, data []byte) error) error {
if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) {
return errors.New("invalid subject alternative name extension")
}
for !der.Empty() {
var san cryptobyte.String
var tag cryptobyte_asn1.Tag
if !der.ReadAnyASN1Element(&san, &tag) {
return errors.New("invalid subject alternative name extension")
}
if err := callback(int(tag^0x80), san); err != nil {
return err
}
}
return nil
}
// createIdentifiersUsingCSR extracts the list of ACME identifiers from the
// given Certificate Signing Request.
func createIdentifiersUsingCSR(csr *x509.CertificateRequest) ([]acme.Identifier, error) {
var ids []acme.Identifier
for _, name := range csr.DNSNames {
ids = append(ids, acme.Identifier{
Type: "dns", // RFC 8555 §9.7.7
Value: name,
})
}
for _, ip := range csr.IPAddresses {
ids = append(ids, acme.Identifier{
Type: "ip", // RFC 8738
Value: ip.String(),
})
}
// Extract permanent identifiers and hardware module values.
// This block will ignore errors.
for _, ext := range csr.Extensions {
if ext.Id.Equal(oidExtensionSubjectAltName) {
err := forEachSAN(ext.Value, func(tag int, data []byte) error {
var on otherName
if rest, err := asn1.UnmarshalWithParams(data, &on, "tag:0"); err != nil || len(rest) > 0 {
return nil
}
switch {
case on.TypeID.Equal(oidPermanentIdentifier):
var pi permanentIdentifier
if _, err := asn1.Unmarshal(on.Value.Bytes, &pi); err == nil {
ids = append(ids, acme.Identifier{
Type: "permanent-identifier", // draft-acme-device-attest-00 §3
Value: pi.IdentifierValue,
})
}
case on.TypeID.Equal(oidHardwareModuleName):
var hmn hardwareModuleName
if _, err := asn1.Unmarshal(on.Value.Bytes, &hmn); err == nil {
ids = append(ids, acme.Identifier{
Type: "hardware-module", // draft-acme-device-attest-00 §4
Value: string(hmn.SerialNumber),
})
}
}
return nil
})
if err != nil {
return nil, err
}
break
}
}
return ids, nil
}
+13
View File
@@ -70,3 +70,16 @@ type Solver interface {
type Waiter interface {
Wait(context.Context, acme.Challenge) error
}
// Payloader is an optional interface for Solvers to implement. Its purpose is
// to return the payload sent to the CA when responding to a challenge. This
// interface is applicable when responding to "device-attest-01" challenges
//
// If implemented, it will be called after Present() and if a Waiter is
// implemented it will be called after Wait(), just before the challenge is
// initiated with the server.
//
// Implementations MUST honor context cancellation.
type Payloader interface {
Payload(context.Context, acme.Challenge) (any, error)
}
+24 -25
View File
@@ -1,30 +1,29 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
BSD 3-Clause License
Copyright (c) 2009, The Go Authors. Extensions copyright (c) 2011, Miek Gieben.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
As this is fork of the official Go code the same license applies.
Extensions of the original work are copyright (c) 2011 Miek Gieben
+9
View File
@@ -77,6 +77,12 @@ A not-so-up-to-date-list-that-may-be-actually-current:
* https://ping.sx/dig
* https://fleetdeck.io/
* https://github.com/markdingo/autoreverse
* https://github.com/slackhq/nebula
* https://addr.tools/
* https://dnscheck.tools/
* https://github.com/egbakou/domainverifier
* https://github.com/semihalev/sdns
* https://github.com/wintbiit/NineDNS
Send pull request if you want to be listed here.
@@ -120,6 +126,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
*all of them*
* 103{4,5} - DNS standard
* 1183 - ISDN, X25 and other deprecated records
* 1348 - NSAP record (removed the record)
* 1982 - Serial Arithmetic
* 1876 - LOC record
@@ -140,6 +147,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
* 340{1,2,3} - NAPTR record
* 3445 - Limiting the scope of (DNS)KEY
* 3597 - Unknown RRs
* 4025 - A Method for Storing IPsec Keying Material in DNS
* 403{3,4,5} - DNSSEC + validation functions
* 4255 - SSHFP record
* 4343 - Case insensitivity
@@ -175,6 +183,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
* 8080 - EdDSA for DNSSEC
* 8499 - DNS Terminology
* 8659 - DNS Certification Authority Authorization (CAA) Resource Record
* 8777 - DNS Reverse IP Automatic Multicast Tunneling (AMT) Discovery
* 8914 - Extended DNS Errors
* 8976 - Message Digest for DNS Zones (ZONEMD RR)
-3
View File
@@ -10,8 +10,6 @@ type MsgAcceptFunc func(dh Header) MsgAcceptAction
//
// * opcode isn't OpcodeQuery or OpcodeNotify
//
// * Zero bit isn't zero
//
// * does not have exactly 1 question in the question section
//
// * has more than 1 RR in the Answer section
@@ -19,7 +17,6 @@ type MsgAcceptFunc func(dh Header) MsgAcceptAction
// * has more than 0 RRs in the Authority section
//
// * has more than 2 RRs in the Additional section
//
var DefaultMsgAcceptFunc MsgAcceptFunc = defaultMsgAcceptFunc
// MsgAcceptAction represents the action to be taken.
+24 -45
View File
@@ -6,7 +6,6 @@ import (
"context"
"crypto/tls"
"encoding/binary"
"fmt"
"io"
"net"
"strings"
@@ -56,14 +55,20 @@ type Client struct {
// Timeout is a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout,
// WriteTimeout when non-zero. Can be overridden with net.Dialer.Timeout (see Client.ExchangeWithDialer and
// Client.Dialer) or context.Context.Deadline (see ExchangeContext)
Timeout time.Duration
DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds, or net.Dialer.Timeout if expiring earlier - overridden by Timeout when that value is non-zero
ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass
group singleflight
Timeout time.Duration
DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds, or net.Dialer.Timeout if expiring earlier - overridden by Timeout when that value is non-zero
ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations.
// SingleInflight previously serialised multiple concurrent queries for the
// same Qname, Qtype and Qclass to ensure only one would be in flight at a
// time.
//
// Deprecated: This is a no-op. Callers should implement their own in flight
// query caching if needed. See github.com/miekg/dns/issues/1449.
SingleInflight bool
}
// Exchange performs a synchronous UDP query. It sends the message m to the address
@@ -106,7 +111,6 @@ func (c *Client) Dial(address string) (conn *Conn, err error) {
}
// DialContext connects to the address on the named network, with a context.Context.
// For TLS over TCP (DoT) the context isn't used yet. This will be enabled when Go 1.18 is released.
func (c *Client) DialContext(ctx context.Context, address string) (conn *Conn, err error) {
// create a new dialer with the appropriate timeout
var d net.Dialer
@@ -127,15 +131,11 @@ func (c *Client) DialContext(ctx context.Context, address string) (conn *Conn, e
if useTLS {
network = strings.TrimSuffix(network, "-tls")
// TODO(miekg): Enable after Go 1.18 is released, to be able to support two prev. releases.
/*
tlsDialer := tls.Dialer{
NetDialer: &d,
Config: c.TLSConfig,
}
conn.Conn, err = tlsDialer.DialContext(ctx, network, address)
*/
conn.Conn, err = tls.DialWithDialer(&d, network, address, c.TLSConfig)
tlsDialer := tls.Dialer{
NetDialer: &d,
Config: c.TLSConfig,
}
conn.Conn, err = tlsDialer.DialContext(ctx, network, address)
} else {
conn.Conn, err = d.DialContext(ctx, network, address)
}
@@ -183,33 +183,13 @@ func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, er
// This allows users of the library to implement their own connection management,
// as opposed to Exchange, which will always use new connections and incur the added overhead
// that entails when using "tcp" and especially "tcp-tls" clients.
//
// When the singleflight is set for this client the context is _not_ forwarded to the (shared) exchange, to
// prevent one cancelation from canceling all outstanding requests.
func (c *Client) ExchangeWithConn(m *Msg, conn *Conn) (r *Msg, rtt time.Duration, err error) {
return c.exchangeWithConnContext(context.Background(), m, conn)
return c.ExchangeWithConnContext(context.Background(), m, conn)
}
func (c *Client) exchangeWithConnContext(ctx context.Context, m *Msg, conn *Conn) (r *Msg, rtt time.Duration, err error) {
if !c.SingleInflight {
return c.exchangeContext(ctx, m, conn)
}
q := m.Question[0]
key := fmt.Sprintf("%s:%d:%d", q.Name, q.Qtype, q.Qclass)
r, rtt, err, shared := c.group.Do(key, func() (*Msg, time.Duration, error) {
// When we're doing singleflight we don't want one context cancelation, cancel _all_ outstanding queries.
// Hence we ignore the context and use Background().
return c.exchangeContext(context.Background(), m, conn)
})
if r != nil && shared {
r = r.Copy()
}
return r, rtt, err
}
func (c *Client) exchangeContext(ctx context.Context, m *Msg, co *Conn) (r *Msg, rtt time.Duration, err error) {
// ExchangeWithConnContext has the same behaviour as ExchangeWithConn and
// additionally obeys deadlines from the passed Context.
func (c *Client) ExchangeWithConnContext(ctx context.Context, m *Msg, co *Conn) (r *Msg, rtt time.Duration, err error) {
opt := m.IsEdns0()
// If EDNS0 is used use that for size.
if opt != nil && opt.UDPSize() >= MinMsgSize {
@@ -431,7 +411,6 @@ func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error)
// co.WriteMsg(m)
// in, _ := co.ReadMsg()
// co.Close()
//
func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) {
println("dns: ExchangeConn: this function is deprecated")
co := new(Conn)
@@ -480,5 +459,5 @@ func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg,
}
defer conn.Close()
return c.exchangeWithConnContext(ctx, m, conn)
return c.ExchangeWithConnContext(ctx, m, conn)
}
+1 -1
View File
@@ -68,7 +68,7 @@ func ClientConfigFromReader(resolvconf io.Reader) (*ClientConfig, error) {
}
case "search": // set search path to given servers
c.Search = append([]string(nil), f[1:]...)
c.Search = cloneSlice(f[1:])
case "options": // magic options
for _, s := range f[1:] {
+29 -25
View File
@@ -22,8 +22,7 @@ func (dns *Msg) SetReply(request *Msg) *Msg {
}
dns.Rcode = RcodeSuccess
if len(request.Question) > 0 {
dns.Question = make([]Question, 1)
dns.Question[0] = request.Question[0]
dns.Question = []Question{request.Question[0]}
}
return dns
}
@@ -208,7 +207,7 @@ func IsDomainName(s string) (labels int, ok bool) {
}
// check for \DDD
if i+3 < len(s) && isDigit(s[i+1]) && isDigit(s[i+2]) && isDigit(s[i+3]) {
if isDDD(s[i+1:]) {
i += 3
begin += 3
} else {
@@ -272,40 +271,39 @@ func IsMsg(buf []byte) error {
// IsFqdn checks if a domain name is fully qualified.
func IsFqdn(s string) bool {
s2 := strings.TrimSuffix(s, ".")
if s == s2 {
// Check for (and remove) a trailing dot, returning if there isn't one.
if s == "" || s[len(s)-1] != '.' {
return false
}
s = s[:len(s)-1]
i := strings.LastIndexFunc(s2, func(r rune) bool {
// If we don't have an escape sequence before the final dot, we know it's
// fully qualified and can return here.
if s == "" || s[len(s)-1] != '\\' {
return true
}
// Otherwise we have to check if the dot is escaped or not by checking if
// there are an odd or even number of escape sequences before the dot.
i := strings.LastIndexFunc(s, func(r rune) bool {
return r != '\\'
})
// Test whether we have an even number of escape sequences before
// the dot or none.
return (len(s2)-i)%2 != 0
return (len(s)-i)%2 != 0
}
// IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181.
// This means the RRs need to have the same type, name, and class. Returns true
// if the RR set is valid, otherwise false.
// IsRRset reports whether a set of RRs is a valid RRset as defined by RFC 2181.
// This means the RRs need to have the same type, name, and class.
func IsRRset(rrset []RR) bool {
if len(rrset) == 0 {
return false
}
if len(rrset) == 1 {
return true
}
rrHeader := rrset[0].Header()
rrType := rrHeader.Rrtype
rrClass := rrHeader.Class
rrName := rrHeader.Name
baseH := rrset[0].Header()
for _, rr := range rrset[1:] {
curRRHeader := rr.Header()
if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName {
curH := rr.Header()
if curH.Rrtype != baseH.Rrtype || curH.Class != baseH.Class || curH.Name != baseH.Name {
// Mismatch between the records, so this is not a valid rrset for
//signing/verifying
// signing/verifying
return false
}
}
@@ -323,9 +321,15 @@ func Fqdn(s string) string {
}
// CanonicalName returns the domain name in canonical form. A name in canonical
// form is lowercase and fully qualified. See Section 6.2 in RFC 4034.
// form is lowercase and fully qualified. Only US-ASCII letters are affected. See
// Section 6.2 in RFC 4034.
func CanonicalName(s string) string {
return strings.ToLower(Fqdn(s))
return strings.Map(func(r rune) rune {
if r >= 'A' && r <= 'Z' {
r += 'a' - 'A'
}
return r
}, Fqdn(s))
}
// Copied from the official Go code.
+5 -9
View File
@@ -128,10 +128,6 @@ type dnskeyWireFmt struct {
/* Nothing is left out */
}
func divRoundUp(a, b int) int {
return (a + b - 1) / b
}
// KeyTag calculates the keytag (or key-id) of the DNSKEY.
func (k *DNSKEY) KeyTag() uint16 {
if k == nil {
@@ -417,11 +413,11 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
return err
}
sigbuf := rr.sigBuf() // Get the binary signature data
if rr.Algorithm == PRIVATEDNS { // PRIVATEOID
// TODO(miek)
// remove the domain name and assume its ours?
}
sigbuf := rr.sigBuf() // Get the binary signature data
// TODO(miek)
// remove the domain name and assume its ours?
// if rr.Algorithm == PRIVATEDNS { // PRIVATEOID
// }
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
if err != nil {
+3 -2
View File
@@ -37,7 +37,8 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
return nil, ErrPrivKey
}
// TODO(mg): check if the pubkey matches the private key
algo, err := strconv.ParseUint(strings.SplitN(m["algorithm"], " ", 2)[0], 10, 8)
algoStr, _, _ := strings.Cut(m["algorithm"], " ")
algo, err := strconv.ParseUint(algoStr, 10, 8)
if err != nil {
return nil, ErrPrivKey
}
@@ -159,7 +160,7 @@ func parseKey(r io.Reader, file string) (map[string]string, error) {
k = l.token
case zValue:
if k == "" {
return nil, &ParseError{file, "no private key seen", l}
return nil, &ParseError{file: file, err: "no private key seen", lex: l}
}
m[strings.ToLower(k)] = l.token
+43 -43
View File
@@ -13,28 +13,28 @@ names in a message will result in a packing failure.
Resource records are native types. They are not stored in wire format. Basic
usage pattern for creating a new resource record:
r := new(dns.MX)
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 3600}
r.Preference = 10
r.Mx = "mx.miek.nl."
r := new(dns.MX)
r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 3600}
r.Preference = 10
r.Mx = "mx.miek.nl."
Or directly from a string:
mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.")
mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.")
Or when the default origin (.) and TTL (3600) and class (IN) suit you:
mx, err := dns.NewRR("miek.nl MX 10 mx.miek.nl")
mx, err := dns.NewRR("miek.nl MX 10 mx.miek.nl")
Or even:
mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek")
mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek")
In the DNS messages are exchanged, these messages contain resource records
(sets). Use pattern for creating a message:
m := new(dns.Msg)
m.SetQuestion("miek.nl.", dns.TypeMX)
m := new(dns.Msg)
m.SetQuestion("miek.nl.", dns.TypeMX)
Or when not certain if the domain name is fully qualified:
@@ -45,17 +45,17 @@ records for the miek.nl. zone.
The following is slightly more verbose, but more flexible:
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.RecursionDesired = true
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.RecursionDesired = true
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
After creating a message it can be sent. Basic use pattern for synchronous
querying the DNS at a server configured on 127.0.0.1 and port 53:
c := new(dns.Client)
in, rtt, err := c.Exchange(m1, "127.0.0.1:53")
c := new(dns.Client)
in, rtt, err := c.Exchange(m1, "127.0.0.1:53")
Suppressing multiple outstanding queries (with the same question, type and
class) is as easy as setting:
@@ -72,7 +72,7 @@ and port to use for the connection:
Port: 12345,
Zone: "",
}
c.Dialer := &net.Dialer{
c.Dialer = &net.Dialer{
Timeout: 200 * time.Millisecond,
LocalAddr: &laddr,
}
@@ -96,7 +96,7 @@ the Answer section:
// do something with t.Txt
}
Domain Name and TXT Character String Representations
# Domain Name and TXT Character String Representations
Both domain names and TXT character strings are converted to presentation form
both when unpacked and when converted to strings.
@@ -108,7 +108,7 @@ be escaped. Bytes below 32 and above 127 will be converted to \DDD form.
For domain names, in addition to the above rules brackets, periods, spaces,
semicolons and the at symbol are escaped.
DNSSEC
# DNSSEC
DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It uses
public key cryptography to sign resource records. The public keys are stored in
@@ -117,12 +117,12 @@ DNSKEY records and the signatures in RRSIG records.
Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK)
bit to a request.
m := new(dns.Msg)
m.SetEdns0(4096, true)
m := new(dns.Msg)
m.SetEdns0(4096, true)
Signature generation, signature verification and key generation are all supported.
DYNAMIC UPDATES
# DYNAMIC UPDATES
Dynamic updates reuses the DNS message format, but renames three of the
sections. Question is Zone, Answer is Prerequisite, Authority is Update, only
@@ -133,30 +133,30 @@ certain resource records or names in a zone to specify if resource records
should be added or removed. The table from RFC 2136 supplemented with the Go
DNS function shows which functions exist to specify the prerequisites.
3.2.4 - Table Of Metavalues Used In Prerequisite Section
3.2.4 - Table Of Metavalues Used In Prerequisite Section
CLASS TYPE RDATA Meaning Function
--------------------------------------------------------------
ANY ANY empty Name is in use dns.NameUsed
ANY rrset empty RRset exists (value indep) dns.RRsetUsed
NONE ANY empty Name is not in use dns.NameNotUsed
NONE rrset empty RRset does not exist dns.RRsetNotUsed
zone rrset rr RRset exists (value dep) dns.Used
CLASS TYPE RDATA Meaning Function
--------------------------------------------------------------
ANY ANY empty Name is in use dns.NameUsed
ANY rrset empty RRset exists (value indep) dns.RRsetUsed
NONE ANY empty Name is not in use dns.NameNotUsed
NONE rrset empty RRset does not exist dns.RRsetNotUsed
zone rrset rr RRset exists (value dep) dns.Used
The prerequisite section can also be left empty. If you have decided on the
prerequisites you can tell what RRs should be added or deleted. The next table
shows the options you have and what functions to call.
3.4.2.6 - Table Of Metavalues Used In Update Section
3.4.2.6 - Table Of Metavalues Used In Update Section
CLASS TYPE RDATA Meaning Function
---------------------------------------------------------------
ANY ANY empty Delete all RRsets from name dns.RemoveName
ANY rrset empty Delete an RRset dns.RemoveRRset
NONE rrset rr Delete an RR from RRset dns.Remove
zone rrset rr Add to an RRset dns.Insert
CLASS TYPE RDATA Meaning Function
---------------------------------------------------------------
ANY ANY empty Delete all RRsets from name dns.RemoveName
ANY rrset empty Delete an RRset dns.RemoveRRset
NONE rrset rr Delete an RR from RRset dns.Remove
zone rrset rr Add to an RRset dns.Insert
TRANSACTION SIGNATURE
# TRANSACTION SIGNATURE
An TSIG or transaction signature adds a HMAC TSIG record to each message sent.
The supported algorithms include: HmacSHA1, HmacSHA256 and HmacSHA512.
@@ -239,7 +239,7 @@ Basic use pattern validating and replying to a message that has TSIG set.
w.WriteMsg(m)
}
PRIVATE RRS
# PRIVATE RRS
RFC 6895 sets aside a range of type codes for private use. This range is 65,280
- 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these
@@ -248,7 +248,7 @@ can be used, before requesting an official type code from IANA.
See https://miek.nl/2014/september/21/idn-and-private-rr-in-go-dns/ for more
information.
EDNS0
# EDNS0
EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated by
RFC 6891. It defines a new RR type, the OPT RR, which is then completely
@@ -279,9 +279,9 @@ SIG(0)
From RFC 2931:
SIG(0) provides protection for DNS transactions and requests ....
... protection for glue records, DNS requests, protection for message headers
on requests and responses, and protection of the overall integrity of a response.
SIG(0) provides protection for DNS transactions and requests ....
... protection for glue records, DNS requests, protection for message headers
on requests and responses, and protection of the overall integrity of a response.
It works like TSIG, except that SIG(0) uses public key cryptography, instead of
the shared secret approach in TSIG. Supported algorithms: ECDSAP256SHA256,
+24 -31
View File
@@ -78,7 +78,10 @@ func (rr *OPT) String() string {
if rr.Do() {
s += "flags: do; "
} else {
s += "flags: ; "
s += "flags:; "
}
if rr.Hdr.Ttl&0x7FFF != 0 {
s += fmt.Sprintf("MBZ: 0x%04x, ", rr.Hdr.Ttl&0x7FFF)
}
s += "udp: " + strconv.Itoa(int(rr.UDPSize()))
@@ -98,6 +101,8 @@ func (rr *OPT) String() string {
s += "\n; SUBNET: " + o.String()
case *EDNS0_COOKIE:
s += "\n; COOKIE: " + o.String()
case *EDNS0_EXPIRE:
s += "\n; EXPIRE: " + o.String()
case *EDNS0_TCP_KEEPALIVE:
s += "\n; KEEPALIVE: " + o.String()
case *EDNS0_UL:
@@ -180,7 +185,7 @@ func (rr *OPT) Do() bool {
// SetDo sets the DO (DNSSEC OK) bit.
// If we pass an argument, set the DO bit to that value.
// It is possible to pass 2 or more arguments. Any arguments after the 1st is silently ignored.
// It is possible to pass 2 or more arguments, but they will be ignored.
func (rr *OPT) SetDo(do ...bool) {
if len(do) == 1 {
if do[0] {
@@ -258,7 +263,7 @@ func (e *EDNS0_NSID) copy() EDNS0 { return &EDNS0_NSID{e.Code, e.Nsid}
// o.Hdr.Name = "."
// o.Hdr.Rrtype = dns.TypeOPT
// e := new(dns.EDNS0_SUBNET)
// e.Code = dns.EDNS0SUBNET
// e.Code = dns.EDNS0SUBNET // by default this is filled in through unpacking OPT packets (unpackDataOpt)
// e.Family = 1 // 1 for IPv4 source address, 2 for IPv6
// e.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6
// e.SourceScope = 0
@@ -503,6 +508,7 @@ func (e *EDNS0_LLQ) String() string {
" " + strconv.FormatUint(uint64(e.LeaseLife), 10)
return s
}
func (e *EDNS0_LLQ) copy() EDNS0 {
return &EDNS0_LLQ{e.Code, e.Version, e.Opcode, e.Error, e.Id, e.LeaseLife}
}
@@ -515,8 +521,8 @@ type EDNS0_DAU struct {
// Option implements the EDNS0 interface.
func (e *EDNS0_DAU) Option() uint16 { return EDNS0DAU }
func (e *EDNS0_DAU) pack() ([]byte, error) { return e.AlgCode, nil }
func (e *EDNS0_DAU) unpack(b []byte) error { e.AlgCode = b; return nil }
func (e *EDNS0_DAU) pack() ([]byte, error) { return cloneSlice(e.AlgCode), nil }
func (e *EDNS0_DAU) unpack(b []byte) error { e.AlgCode = cloneSlice(b); return nil }
func (e *EDNS0_DAU) String() string {
s := ""
@@ -539,8 +545,8 @@ type EDNS0_DHU struct {
// Option implements the EDNS0 interface.
func (e *EDNS0_DHU) Option() uint16 { return EDNS0DHU }
func (e *EDNS0_DHU) pack() ([]byte, error) { return e.AlgCode, nil }
func (e *EDNS0_DHU) unpack(b []byte) error { e.AlgCode = b; return nil }
func (e *EDNS0_DHU) pack() ([]byte, error) { return cloneSlice(e.AlgCode), nil }
func (e *EDNS0_DHU) unpack(b []byte) error { e.AlgCode = cloneSlice(b); return nil }
func (e *EDNS0_DHU) String() string {
s := ""
@@ -563,8 +569,8 @@ type EDNS0_N3U struct {
// Option implements the EDNS0 interface.
func (e *EDNS0_N3U) Option() uint16 { return EDNS0N3U }
func (e *EDNS0_N3U) pack() ([]byte, error) { return e.AlgCode, nil }
func (e *EDNS0_N3U) unpack(b []byte) error { e.AlgCode = b; return nil }
func (e *EDNS0_N3U) pack() ([]byte, error) { return cloneSlice(e.AlgCode), nil }
func (e *EDNS0_N3U) unpack(b []byte) error { e.AlgCode = cloneSlice(b); return nil }
func (e *EDNS0_N3U) String() string {
// Re-use the hash map
@@ -641,30 +647,21 @@ type EDNS0_LOCAL struct {
// Option implements the EDNS0 interface.
func (e *EDNS0_LOCAL) Option() uint16 { return e.Code }
func (e *EDNS0_LOCAL) String() string {
return strconv.FormatInt(int64(e.Code), 10) + ":0x" + hex.EncodeToString(e.Data)
}
func (e *EDNS0_LOCAL) copy() EDNS0 {
b := make([]byte, len(e.Data))
copy(b, e.Data)
return &EDNS0_LOCAL{e.Code, b}
return &EDNS0_LOCAL{e.Code, cloneSlice(e.Data)}
}
func (e *EDNS0_LOCAL) pack() ([]byte, error) {
b := make([]byte, len(e.Data))
copied := copy(b, e.Data)
if copied != len(e.Data) {
return nil, ErrBuf
}
return b, nil
return cloneSlice(e.Data), nil
}
func (e *EDNS0_LOCAL) unpack(b []byte) error {
e.Data = make([]byte, len(b))
copied := copy(e.Data, b)
if copied != len(b) {
return ErrBuf
}
e.Data = cloneSlice(b)
return nil
}
@@ -727,14 +724,10 @@ type EDNS0_PADDING struct {
// Option implements the EDNS0 interface.
func (e *EDNS0_PADDING) Option() uint16 { return EDNS0PADDING }
func (e *EDNS0_PADDING) pack() ([]byte, error) { return e.Padding, nil }
func (e *EDNS0_PADDING) unpack(b []byte) error { e.Padding = b; return nil }
func (e *EDNS0_PADDING) pack() ([]byte, error) { return cloneSlice(e.Padding), nil }
func (e *EDNS0_PADDING) unpack(b []byte) error { e.Padding = cloneSlice(b); return nil }
func (e *EDNS0_PADDING) String() string { return fmt.Sprintf("%0X", e.Padding) }
func (e *EDNS0_PADDING) copy() EDNS0 {
b := make([]byte, len(e.Padding))
copy(b, e.Padding)
return &EDNS0_PADDING{b}
}
func (e *EDNS0_PADDING) copy() EDNS0 { return &EDNS0_PADDING{cloneSlice(e.Padding)} }
// Extended DNS Error Codes (RFC 8914).
const (
@@ -821,7 +814,7 @@ func (e *EDNS0_EDE) String() string {
func (e *EDNS0_EDE) pack() ([]byte, error) {
b := make([]byte, 2+len(e.ExtraText))
binary.BigEndian.PutUint16(b[0:], e.InfoCode)
copy(b[2:], []byte(e.ExtraText))
copy(b[2:], e.ExtraText)
return b, nil
}
+1
View File
@@ -1,3 +1,4 @@
//go:build fuzz
// +build fuzz
package dns
+18 -17
View File
@@ -35,17 +35,17 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) {
token = token[:i]
}
sx := strings.SplitN(token, "-", 2)
if len(sx) != 2 {
startStr, endStr, ok := strings.Cut(token, "-")
if !ok {
return zp.setParseError("bad start-stop in $GENERATE range", l)
}
start, err := strconv.ParseInt(sx[0], 10, 64)
start, err := strconv.ParseInt(startStr, 10, 64)
if err != nil {
return zp.setParseError("bad start in $GENERATE range", l)
}
end, err := strconv.ParseInt(sx[1], 10, 64)
end, err := strconv.ParseInt(endStr, 10, 64)
if err != nil {
return zp.setParseError("bad stop in $GENERATE range", l)
}
@@ -54,7 +54,7 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) {
}
// _BLANK
l, ok := zp.c.Next()
l, ok = zp.c.Next()
if !ok || l.value != zBlank {
return zp.setParseError("garbage after $GENERATE range", l)
}
@@ -116,7 +116,7 @@ func (r *generateReader) parseError(msg string, end int) *ParseError {
l.token = r.s[r.si-1 : end]
l.column += r.si // l.column starts one zBLANK before r.s
return &ParseError{r.file, msg, l}
return &ParseError{file: r.file, err: msg, lex: l}
}
func (r *generateReader) Read(p []byte) (int, error) {
@@ -211,15 +211,16 @@ func (r *generateReader) ReadByte() (byte, error) {
func modToPrintf(s string) (string, int64, string) {
// Modifier is { offset [ ,width [ ,base ] ] } - provide default
// values for optional width and type, if necessary.
var offStr, widthStr, base string
switch xs := strings.Split(s, ","); len(xs) {
case 1:
offStr, widthStr, base = xs[0], "0", "d"
case 2:
offStr, widthStr, base = xs[0], xs[1], "d"
case 3:
offStr, widthStr, base = xs[0], xs[1], xs[2]
default:
offStr, s, ok0 := strings.Cut(s, ",")
widthStr, s, ok1 := strings.Cut(s, ",")
base, _, ok2 := strings.Cut(s, ",")
if !ok0 {
widthStr = "0"
}
if !ok1 {
base = "d"
}
if ok2 {
return "", 0, "bad modifier in $GENERATE"
}
@@ -234,8 +235,8 @@ func modToPrintf(s string) (string, int64, string) {
return "", 0, "bad offset in $GENERATE"
}
width, err := strconv.ParseInt(widthStr, 10, 64)
if err != nil || width < 0 || width > 255 {
width, err := strconv.ParseUint(widthStr, 10, 8)
if err != nil {
return "", 0, "bad width in $GENERATE"
}
+1 -1
View File
@@ -122,7 +122,7 @@ func Split(s string) []int {
}
// NextLabel returns the index of the start of the next label in the
// string s starting at offset.
// string s starting at offset. A negative offset will cause a panic.
// The bool end is true when the end of the string has been reached.
// Also see PrevLabel.
func NextLabel(s string, offset int) (i int, end bool) {
+8 -5
View File
@@ -1,4 +1,5 @@
// +build !go1.11 !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd
package dns
@@ -6,16 +7,18 @@ import "net"
const supportsReusePort = false
func listenTCP(network, addr string, reuseport bool) (net.Listener, error) {
if reuseport {
func listenTCP(network, addr string, reuseport, reuseaddr bool) (net.Listener, error) {
if reuseport || reuseaddr {
// TODO(tmthrgd): return an error?
}
return net.Listen(network, addr)
}
func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) {
if reuseport {
const supportsReuseAddr = false
func listenUDP(network, addr string, reuseport, reuseaddr bool) (net.PacketConn, error) {
if reuseport || reuseaddr {
// TODO(tmthrgd): return an error?
}
+27 -5
View File
@@ -1,4 +1,4 @@
// +build go1.11
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
// +build aix darwin dragonfly freebsd linux netbsd openbsd
package dns
@@ -25,19 +25,41 @@ func reuseportControl(network, address string, c syscall.RawConn) error {
return opErr
}
func listenTCP(network, addr string, reuseport bool) (net.Listener, error) {
const supportsReuseAddr = true
func reuseaddrControl(network, address string, c syscall.RawConn) error {
var opErr error
err := c.Control(func(fd uintptr) {
opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
})
if err != nil {
return err
}
return opErr
}
func listenTCP(network, addr string, reuseport, reuseaddr bool) (net.Listener, error) {
var lc net.ListenConfig
if reuseport {
switch {
case reuseaddr && reuseport:
case reuseport:
lc.Control = reuseportControl
case reuseaddr:
lc.Control = reuseaddrControl
}
return lc.Listen(context.Background(), network, addr)
}
func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) {
func listenUDP(network, addr string, reuseport, reuseaddr bool) (net.PacketConn, error) {
var lc net.ListenConfig
if reuseport {
switch {
case reuseaddr && reuseport:
case reuseport:
lc.Control = reuseportControl
case reuseaddr:
lc.Control = reuseaddrControl
}
return lc.ListenPacket(context.Background(), network, addr)
+61 -50
View File
@@ -252,7 +252,7 @@ loop:
}
// check for \DDD
if i+3 < ls && isDigit(bs[i+1]) && isDigit(bs[i+2]) && isDigit(bs[i+3]) {
if isDDD(bs[i+1:]) {
bs[i] = dddToByte(bs[i+1:])
copy(bs[i+1:ls-3], bs[i+4:])
ls -= 3
@@ -448,7 +448,7 @@ Loop:
return string(s), off1, nil
}
func packTxt(txt []string, msg []byte, offset int, tmp []byte) (int, error) {
func packTxt(txt []string, msg []byte, offset int) (int, error) {
if len(txt) == 0 {
if offset >= len(msg) {
return offset, ErrBuf
@@ -458,10 +458,7 @@ func packTxt(txt []string, msg []byte, offset int, tmp []byte) (int, error) {
}
var err error
for _, s := range txt {
if len(s) > len(tmp) {
return offset, ErrBuf
}
offset, err = packTxtString(s, msg, offset, tmp)
offset, err = packTxtString(s, msg, offset)
if err != nil {
return offset, err
}
@@ -469,32 +466,30 @@ func packTxt(txt []string, msg []byte, offset int, tmp []byte) (int, error) {
return offset, nil
}
func packTxtString(s string, msg []byte, offset int, tmp []byte) (int, error) {
func packTxtString(s string, msg []byte, offset int) (int, error) {
lenByteOffset := offset
if offset >= len(msg) || len(s) > len(tmp) {
if offset >= len(msg) || len(s) > 256*4+1 /* If all \DDD */ {
return offset, ErrBuf
}
offset++
bs := tmp[:len(s)]
copy(bs, s)
for i := 0; i < len(bs); i++ {
for i := 0; i < len(s); i++ {
if len(msg) <= offset {
return offset, ErrBuf
}
if bs[i] == '\\' {
if s[i] == '\\' {
i++
if i == len(bs) {
if i == len(s) {
break
}
// check for \DDD
if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) {
msg[offset] = dddToByte(bs[i:])
if isDDD(s[i:]) {
msg[offset] = dddToByte(s[i:])
i += 2
} else {
msg[offset] = bs[i]
msg[offset] = s[i]
}
} else {
msg[offset] = bs[i]
msg[offset] = s[i]
}
offset++
}
@@ -506,30 +501,28 @@ func packTxtString(s string, msg []byte, offset int, tmp []byte) (int, error) {
return offset, nil
}
func packOctetString(s string, msg []byte, offset int, tmp []byte) (int, error) {
if offset >= len(msg) || len(s) > len(tmp) {
func packOctetString(s string, msg []byte, offset int) (int, error) {
if offset >= len(msg) || len(s) > 256*4+1 {
return offset, ErrBuf
}
bs := tmp[:len(s)]
copy(bs, s)
for i := 0; i < len(bs); i++ {
for i := 0; i < len(s); i++ {
if len(msg) <= offset {
return offset, ErrBuf
}
if bs[i] == '\\' {
if s[i] == '\\' {
i++
if i == len(bs) {
if i == len(s) {
break
}
// check for \DDD
if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) {
msg[offset] = dddToByte(bs[i:])
if isDDD(s[i:]) {
msg[offset] = dddToByte(s[i:])
i += 2
} else {
msg[offset] = bs[i]
msg[offset] = s[i]
}
} else {
msg[offset] = bs[i]
msg[offset] = s[i]
}
offset++
}
@@ -551,12 +544,11 @@ func unpackTxt(msg []byte, off0 int) (ss []string, off int, err error) {
// Helpers for dealing with escaped bytes
func isDigit(b byte) bool { return b >= '0' && b <= '9' }
func dddToByte(s []byte) byte {
_ = s[2] // bounds check hint to compiler; see golang.org/issue/14808
return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0'))
func isDDD[T ~[]byte | ~string](s T) bool {
return len(s) >= 3 && isDigit(s[0]) && isDigit(s[1]) && isDigit(s[2])
}
func dddStringToByte(s string) byte {
func dddToByte[T ~[]byte | ~string](s T) byte {
_ = s[2] // bounds check hint to compiler; see golang.org/issue/14808
return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0'))
}
@@ -680,9 +672,9 @@ func unpackRRslice(l int, msg []byte, off int) (dst1 []RR, off1 int, err error)
// Convert a MsgHdr to a string, with dig-like headers:
//
//;; opcode: QUERY, status: NOERROR, id: 48404
// ;; opcode: QUERY, status: NOERROR, id: 48404
//
//;; flags: qr aa rd ra;
// ;; flags: qr aa rd ra;
func (h *MsgHdr) String() string {
if h == nil {
return "<nil> MsgHdr"
@@ -866,7 +858,7 @@ func (dns *Msg) unpack(dh Header, msg []byte, off int) (err error) {
// The header counts might have been wrong so we need to update it
dh.Nscount = uint16(len(dns.Ns))
if err == nil {
dns.Extra, off, err = unpackRRslice(int(dh.Arcount), msg, off)
dns.Extra, _, err = unpackRRslice(int(dh.Arcount), msg, off)
}
// The header counts might have been wrong so we need to update it
dh.Arcount = uint16(len(dns.Extra))
@@ -876,11 +868,11 @@ func (dns *Msg) unpack(dh Header, msg []byte, off int) (err error) {
dns.Rcode |= opt.ExtendedRcode()
}
if off != len(msg) {
// TODO(miek) make this an error?
// use PackOpt to let people tell how detailed the error reporting should be?
// println("dns: extra bytes in dns packet", off, "<", len(msg))
}
// TODO(miek) make this an error?
// use PackOpt to let people tell how detailed the error reporting should be?
// if off != len(msg) {
// // println("dns: extra bytes in dns packet", off, "<", len(msg))
// }
return err
}
@@ -902,23 +894,38 @@ func (dns *Msg) String() string {
return "<nil> MsgHdr"
}
s := dns.MsgHdr.String() + " "
s += "QUERY: " + strconv.Itoa(len(dns.Question)) + ", "
s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", "
s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", "
s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
if dns.MsgHdr.Opcode == OpcodeUpdate {
s += "ZONE: " + strconv.Itoa(len(dns.Question)) + ", "
s += "PREREQ: " + strconv.Itoa(len(dns.Answer)) + ", "
s += "UPDATE: " + strconv.Itoa(len(dns.Ns)) + ", "
s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
} else {
s += "QUERY: " + strconv.Itoa(len(dns.Question)) + ", "
s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", "
s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", "
s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
}
opt := dns.IsEdns0()
if opt != nil {
// OPT PSEUDOSECTION
s += opt.String() + "\n"
}
if len(dns.Question) > 0 {
s += "\n;; QUESTION SECTION:\n"
if dns.MsgHdr.Opcode == OpcodeUpdate {
s += "\n;; ZONE SECTION:\n"
} else {
s += "\n;; QUESTION SECTION:\n"
}
for _, r := range dns.Question {
s += r.String() + "\n"
}
}
if len(dns.Answer) > 0 {
s += "\n;; ANSWER SECTION:\n"
if dns.MsgHdr.Opcode == OpcodeUpdate {
s += "\n;; PREREQUISITE SECTION:\n"
} else {
s += "\n;; ANSWER SECTION:\n"
}
for _, r := range dns.Answer {
if r != nil {
s += r.String() + "\n"
@@ -926,7 +933,11 @@ func (dns *Msg) String() string {
}
}
if len(dns.Ns) > 0 {
s += "\n;; AUTHORITY SECTION:\n"
if dns.MsgHdr.Opcode == OpcodeUpdate {
s += "\n;; UPDATE SECTION:\n"
} else {
s += "\n;; AUTHORITY SECTION:\n"
}
for _, r := range dns.Ns {
if r != nil {
s += r.String() + "\n"
@@ -1024,7 +1035,7 @@ func escapedNameLen(s string) int {
continue
}
if i+3 < len(s) && isDigit(s[i+1]) && isDigit(s[i+2]) && isDigit(s[i+3]) {
if isDDD(s[i+1:]) {
nameLen -= 3
i += 3
} else {
@@ -1065,8 +1076,8 @@ func (dns *Msg) CopyTo(r1 *Msg) *Msg {
r1.Compress = dns.Compress
if len(dns.Question) > 0 {
r1.Question = make([]Question, len(dns.Question))
copy(r1.Question, dns.Question) // TODO(miek): Question is an immutable value, ok to do a shallow-copy
// TODO(miek): Question is an immutable value, ok to do a shallow-copy
r1.Question = cloneSlice(dns.Question)
}
rrArr := make([]RR, len(dns.Answer)+len(dns.Ns)+len(dns.Extra))
+57 -35
View File
@@ -20,9 +20,7 @@ func unpackDataA(msg []byte, off int) (net.IP, int, error) {
if off+net.IPv4len > len(msg) {
return nil, len(msg), &Error{err: "overflow unpacking a"}
}
a := append(make(net.IP, 0, net.IPv4len), msg[off:off+net.IPv4len]...)
off += net.IPv4len
return a, off, nil
return cloneSlice(msg[off : off+net.IPv4len]), off + net.IPv4len, nil
}
func packDataA(a net.IP, msg []byte, off int) (int, error) {
@@ -47,9 +45,7 @@ func unpackDataAAAA(msg []byte, off int) (net.IP, int, error) {
if off+net.IPv6len > len(msg) {
return nil, len(msg), &Error{err: "overflow unpacking aaaa"}
}
aaaa := append(make(net.IP, 0, net.IPv6len), msg[off:off+net.IPv6len]...)
off += net.IPv6len
return aaaa, off, nil
return cloneSlice(msg[off : off+net.IPv6len]), off + net.IPv6len, nil
}
func packDataAAAA(aaaa net.IP, msg []byte, off int) (int, error) {
@@ -299,8 +295,7 @@ func unpackString(msg []byte, off int) (string, int, error) {
}
func packString(s string, msg []byte, off int) (int, error) {
txtTmp := make([]byte, 256*4+1)
off, err := packTxtString(s, msg, off, txtTmp)
off, err := packTxtString(s, msg, off)
if err != nil {
return len(msg), err
}
@@ -402,8 +397,7 @@ func unpackStringTxt(msg []byte, off int) ([]string, int, error) {
}
func packStringTxt(s []string, msg []byte, off int) (int, error) {
txtTmp := make([]byte, 256*4+1) // If the whole string consists out of \DDD we need this many.
off, err := packTxt(s, msg, off, txtTmp)
off, err := packTxt(s, msg, off)
if err != nil {
return len(msg), err
}
@@ -412,29 +406,24 @@ func packStringTxt(s []string, msg []byte, off int) (int, error) {
func unpackDataOpt(msg []byte, off int) ([]EDNS0, int, error) {
var edns []EDNS0
Option:
var code uint16
if off+4 > len(msg) {
return nil, len(msg), &Error{err: "overflow unpacking opt"}
for off < len(msg) {
if off+4 > len(msg) {
return nil, len(msg), &Error{err: "overflow unpacking opt"}
}
code := binary.BigEndian.Uint16(msg[off:])
off += 2
optlen := binary.BigEndian.Uint16(msg[off:])
off += 2
if off+int(optlen) > len(msg) {
return nil, len(msg), &Error{err: "overflow unpacking opt"}
}
opt := makeDataOpt(code)
if err := opt.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, opt)
off += int(optlen)
}
code = binary.BigEndian.Uint16(msg[off:])
off += 2
optlen := binary.BigEndian.Uint16(msg[off:])
off += 2
if off+int(optlen) > len(msg) {
return nil, len(msg), &Error{err: "overflow unpacking opt"}
}
e := makeDataOpt(code)
if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
return nil, len(msg), err
}
edns = append(edns, e)
off += int(optlen)
if off < len(msg) {
goto Option
}
return edns, off, nil
}
@@ -463,8 +452,7 @@ func unpackStringOctet(msg []byte, off int) (string, int, error) {
}
func packStringOctet(s string, msg []byte, off int) (int, error) {
txtTmp := make([]byte, 256*4+1)
off, err := packOctetString(s, msg, off, txtTmp)
off, err := packOctetString(s, msg, off)
if err != nil {
return len(msg), err
}
@@ -625,7 +613,7 @@ func unpackDataSVCB(msg []byte, off int) ([]SVCBKeyValue, int, error) {
}
func packDataSVCB(pairs []SVCBKeyValue, msg []byte, off int) (int, error) {
pairs = append([]SVCBKeyValue(nil), pairs...)
pairs = cloneSlice(pairs)
sort.Slice(pairs, func(i, j int) bool {
return pairs[i].Key() < pairs[j].Key()
})
@@ -810,3 +798,37 @@ func unpackDataAplPrefix(msg []byte, off int) (APLPrefix, int, error) {
Network: ipnet,
}, off, nil
}
func unpackIPSECGateway(msg []byte, off int, gatewayType uint8) (net.IP, string, int, error) {
var retAddr net.IP
var retString string
var err error
switch gatewayType {
case IPSECGatewayNone: // do nothing
case IPSECGatewayIPv4:
retAddr, off, err = unpackDataA(msg, off)
case IPSECGatewayIPv6:
retAddr, off, err = unpackDataAAAA(msg, off)
case IPSECGatewayHost:
retString, off, err = UnpackDomainName(msg, off)
}
return retAddr, retString, off, err
}
func packIPSECGateway(gatewayAddr net.IP, gatewayString string, msg []byte, off int, gatewayType uint8, compression compressionMap, compress bool) (int, error) {
var err error
switch gatewayType {
case IPSECGatewayNone: // do nothing
case IPSECGatewayIPv4:
off, err = packDataA(gatewayAddr, msg, off)
case IPSECGatewayIPv6:
off, err = packDataAAAA(gatewayAddr, msg, off)
case IPSECGatewayHost:
off, err = packDomainName(gatewayString, msg, off, compression, compress)
}
return off, err
}
+1 -1
View File
@@ -84,7 +84,7 @@ Fetch:
err := r.Data.Parse(text)
if err != nil {
return &ParseError{"", err.Error(), l}
return &ParseError{wrappedErr: err, lex: l}
}
return nil
+105 -69
View File
@@ -4,19 +4,21 @@ import (
"bufio"
"fmt"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"strconv"
"strings"
)
const maxTok = 2048 // Largest token we can return.
const maxTok = 512 // Token buffer start size, and growth size amount.
// The maximum depth of $INCLUDE directives supported by the
// ZoneParser API.
const maxIncludeDepth = 7
// Tokinize a RFC 1035 zone file. The tokenizer will normalize it:
// Tokenize a RFC 1035 zone file. The tokenizer will normalize it:
// * Add ownernames if they are left blank;
// * Suppress sequences of spaces;
// * Make each RR fit on one line (_NEWLINE is send as last)
@@ -64,20 +66,26 @@ const (
// ParseError is a parsing error. It contains the parse error and the location in the io.Reader
// where the error occurred.
type ParseError struct {
file string
err string
lex lex
file string
err string
wrappedErr error
lex lex
}
func (e *ParseError) Error() (s string) {
if e.file != "" {
s = e.file + ": "
}
if e.err == "" && e.wrappedErr != nil {
e.err = e.wrappedErr.Error()
}
s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " +
strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column)
return
}
func (e *ParseError) Unwrap() error { return e.wrappedErr }
type lex struct {
token string // text of the token
err bool // when true, token text has lexer error
@@ -168,8 +176,9 @@ type ZoneParser struct {
// sub is used to parse $INCLUDE files and $GENERATE directives.
// Next, by calling subNext, forwards the resulting RRs from this
// sub parser to the calling code.
sub *ZoneParser
osFile *os.File
sub *ZoneParser
r io.Reader
fsys fs.FS
includeDepth uint8
@@ -188,7 +197,7 @@ func NewZoneParser(r io.Reader, origin, file string) *ZoneParser {
if origin != "" {
origin = Fqdn(origin)
if _, ok := IsDomainName(origin); !ok {
pe = &ParseError{file, "bad initial origin name", lex{}}
pe = &ParseError{file: file, err: "bad initial origin name"}
}
}
@@ -220,6 +229,24 @@ func (zp *ZoneParser) SetIncludeAllowed(v bool) {
zp.includeAllowed = v
}
// SetIncludeFS provides an [fs.FS] to use when looking for the target of
// $INCLUDE directives. ($INCLUDE must still be enabled separately by calling
// [ZoneParser.SetIncludeAllowed].) If fsys is nil, [os.Open] will be used.
//
// When fsys is an on-disk FS, the ability of $INCLUDE to reach files from
// outside its root directory depends upon the FS implementation. For
// instance, [os.DirFS] will refuse to open paths like "../../etc/passwd",
// however it will still follow links which may point anywhere on the system.
//
// FS paths are slash-separated on all systems, even Windows. $INCLUDE paths
// containing other characters such as backslash and colon may be accepted as
// valid, but those characters will never be interpreted by an FS
// implementation as path element separators. See [fs.ValidPath] for more
// details.
func (zp *ZoneParser) SetIncludeFS(fsys fs.FS) {
zp.fsys = fsys
}
// Err returns the first non-EOF error that was encountered by the
// ZoneParser.
func (zp *ZoneParser) Err() error {
@@ -237,7 +264,7 @@ func (zp *ZoneParser) Err() error {
}
func (zp *ZoneParser) setParseError(err string, l lex) (RR, bool) {
zp.parseErr = &ParseError{zp.file, err, l}
zp.parseErr = &ParseError{file: zp.file, err: err, lex: l}
return nil, false
}
@@ -260,9 +287,11 @@ func (zp *ZoneParser) subNext() (RR, bool) {
return rr, true
}
if zp.sub.osFile != nil {
zp.sub.osFile.Close()
zp.sub.osFile = nil
if zp.sub.r != nil {
if c, ok := zp.sub.r.(io.Closer); ok {
c.Close()
}
zp.sub.r = nil
}
if zp.sub.Err() != nil {
@@ -402,24 +431,44 @@ func (zp *ZoneParser) Next() (RR, bool) {
// Start with the new file
includePath := l.token
if !filepath.IsAbs(includePath) {
includePath = filepath.Join(filepath.Dir(zp.file), includePath)
}
r1, e1 := os.Open(includePath)
if e1 != nil {
var as string
if !filepath.IsAbs(l.token) {
as = fmt.Sprintf(" as `%s'", includePath)
var r1 io.Reader
var e1 error
if zp.fsys != nil {
// fs.FS always uses / as separator, even on Windows, so use
// path instead of filepath here:
if !path.IsAbs(includePath) {
includePath = path.Join(path.Dir(zp.file), includePath)
}
msg := fmt.Sprintf("failed to open `%s'%s: %v", l.token, as, e1)
return zp.setParseError(msg, l)
// os.DirFS, and probably others, expect all paths to be
// relative, so clean the path and remove leading / if
// present:
includePath = strings.TrimLeft(path.Clean(includePath), "/")
r1, e1 = zp.fsys.Open(includePath)
} else {
if !filepath.IsAbs(includePath) {
includePath = filepath.Join(filepath.Dir(zp.file), includePath)
}
r1, e1 = os.Open(includePath)
}
if e1 != nil {
var as string
if includePath != l.token {
as = fmt.Sprintf(" as `%s'", includePath)
}
zp.parseErr = &ParseError{
file: zp.file,
wrappedErr: fmt.Errorf("failed to open `%s'%s: %w", l.token, as, e1),
lex: l,
}
return nil, false
}
zp.sub = NewZoneParser(r1, neworigin, includePath)
zp.sub.defttl, zp.sub.includeDepth, zp.sub.osFile = zp.defttl, zp.includeDepth+1, r1
zp.sub.defttl, zp.sub.includeDepth, zp.sub.r = zp.defttl, zp.includeDepth+1, r1
zp.sub.SetIncludeAllowed(true)
zp.sub.SetIncludeFS(zp.fsys)
return zp.subNext()
case zExpectDirTTLBl:
if l.value != zBlank {
@@ -605,8 +654,6 @@ func (zp *ZoneParser) Next() (RR, bool) {
if !isPrivate && zp.c.Peek().token == "" {
// This is a dynamic update rr.
// TODO(tmthrgd): Previously slurpRemainder was only called
// for certain RR types, which may have been important.
if err := slurpRemainder(zp.c); err != nil {
return zp.setParseError(err.err, err.lex)
}
@@ -765,8 +812,8 @@ func (zl *zlexer) Next() (lex, bool) {
}
var (
str [maxTok]byte // Hold string text
com [maxTok]byte // Hold comment text
str = make([]byte, maxTok) // Hold string text
com = make([]byte, maxTok) // Hold comment text
stri int // Offset in str (0 means empty)
comi int // Offset in com (0 means empty)
@@ -785,14 +832,12 @@ func (zl *zlexer) Next() (lex, bool) {
l.line, l.column = zl.line, zl.column
if stri >= len(str) {
l.token = "token length insufficient for parsing"
l.err = true
return *l, true
// if buffer length is insufficient, increase it.
str = append(str[:], make([]byte, maxTok)...)
}
if comi >= len(com) {
l.token = "comment length insufficient for parsing"
l.err = true
return *l, true
// if buffer length is insufficient, increase it.
com = append(com[:], make([]byte, maxTok)...)
}
switch x {
@@ -816,7 +861,7 @@ func (zl *zlexer) Next() (lex, bool) {
if stri == 0 {
// Space directly in the beginning, handled in the grammar
} else if zl.owner {
// If we have a string and its the first, make it an owner
// If we have a string and it's the first, make it an owner
l.value = zOwner
l.token = string(str[:stri])
@@ -1218,42 +1263,34 @@ func stringToCm(token string) (e, m uint8, ok bool) {
if token[len(token)-1] == 'M' || token[len(token)-1] == 'm' {
token = token[0 : len(token)-1]
}
s := strings.SplitN(token, ".", 2)
var meters, cmeters, val int
var err error
switch len(s) {
case 2:
if cmeters, err = strconv.Atoi(s[1]); err != nil {
return
}
var (
meters, cmeters, val int
err error
)
mStr, cmStr, hasCM := strings.Cut(token, ".")
if hasCM {
// There's no point in having more than 2 digits in this part, and would rather make the implementation complicated ('123' should be treated as '12').
// So we simply reject it.
// We also make sure the first character is a digit to reject '+-' signs.
if len(s[1]) > 2 || s[1][0] < '0' || s[1][0] > '9' {
cmeters, err = strconv.Atoi(cmStr)
if err != nil || len(cmStr) > 2 || cmStr[0] < '0' || cmStr[0] > '9' {
return
}
if len(s[1]) == 1 {
if len(cmStr) == 1 {
// 'nn.1' must be treated as 'nn-meters and 10cm, not 1cm.
cmeters *= 10
}
if s[0] == "" {
// This will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm).
break
}
fallthrough
case 1:
if meters, err = strconv.Atoi(s[0]); err != nil {
return
}
// RFC1876 states the max value is 90000000.00. The latter two conditions enforce it.
if s[0][0] < '0' || s[0][0] > '9' || meters > 90000000 || (meters == 90000000 && cmeters != 0) {
return
}
case 0:
// huh?
return 0, 0, false
}
ok = true
// This slighly ugly condition will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm).
if !hasCM || mStr != "" {
meters, err = strconv.Atoi(mStr)
// RFC1876 states the max value is 90000000.00. The latter two conditions enforce it.
if err != nil || mStr[0] < '0' || mStr[0] > '9' || meters > 90000000 || (meters == 90000000 && cmeters != 0) {
return
}
}
if meters > 0 {
e = 2
val = meters
@@ -1265,8 +1302,7 @@ func stringToCm(token string) (e, m uint8, ok bool) {
e++
val /= 10
}
m = uint8(val)
return
return e, uint8(val), true
}
func toAbsoluteName(name, origin string) (absolute string, ok bool) {
@@ -1339,12 +1375,12 @@ func slurpRemainder(c *zlexer) *ParseError {
case zBlank:
l, _ = c.Next()
if l.value != zNewline && l.value != zEOF {
return &ParseError{"", "garbage after rdata", l}
return &ParseError{err: "garbage after rdata", lex: l}
}
case zNewline:
case zEOF:
default:
return &ParseError{"", "garbage after rdata", l}
return &ParseError{err: "garbage after rdata", lex: l}
}
return nil
}
@@ -1353,16 +1389,16 @@ func slurpRemainder(c *zlexer) *ParseError {
// Used for NID and L64 record.
func stringToNodeID(l lex) (uint64, *ParseError) {
if len(l.token) < 19 {
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
}
// There must be three colons at fixes positions, if not its a parse error
if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' {
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
}
s := l.token[0:4] + l.token[5:9] + l.token[10:14] + l.token[15:19]
u, err := strconv.ParseUint(s, 16, 64)
if err != nil {
return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
}
return u, nil
}
+319 -175
View File
File diff suppressed because it is too large Load Diff
+9 -5
View File
@@ -18,7 +18,7 @@ import (
const maxTCPQueries = 128
// aLongTimeAgo is a non-zero time, far in the past, used for
// immediate cancelation of network operations.
// immediate cancellation of network operations.
var aLongTimeAgo = time.Unix(1, 0)
// Handler is implemented by any value that implements ServeDNS.
@@ -224,8 +224,12 @@ type Server struct {
// Maximum number of TCP queries before we close the socket. Default is maxTCPQueries (unlimited if -1).
MaxTCPQueries int
// Whether to set the SO_REUSEPORT socket option, allowing multiple listeners to be bound to a single address.
// It is only supported on go1.11+ and when using ListenAndServe.
// It is only supported on certain GOOSes and when using ListenAndServe.
ReusePort bool
// Whether to set the SO_REUSEADDR socket option, allowing multiple listeners to be bound to a single address.
// Crucially this allows binding when an existing server is listening on `0.0.0.0` or `::`.
// It is only supported on certain GOOSes and when using ListenAndServe.
ReuseAddr bool
// AcceptMsgFunc will check the incoming message and will reject it early in the process.
// By default DefaultMsgAcceptFunc will be used.
MsgAcceptFunc MsgAcceptFunc
@@ -304,7 +308,7 @@ func (srv *Server) ListenAndServe() error {
switch srv.Net {
case "tcp", "tcp4", "tcp6":
l, err := listenTCP(srv.Net, addr, srv.ReusePort)
l, err := listenTCP(srv.Net, addr, srv.ReusePort, srv.ReuseAddr)
if err != nil {
return err
}
@@ -317,7 +321,7 @@ func (srv *Server) ListenAndServe() error {
return errors.New("dns: neither Certificates nor GetCertificate set in Config")
}
network := strings.TrimSuffix(srv.Net, "-tls")
l, err := listenTCP(network, addr, srv.ReusePort)
l, err := listenTCP(network, addr, srv.ReusePort, srv.ReuseAddr)
if err != nil {
return err
}
@@ -327,7 +331,7 @@ func (srv *Server) ListenAndServe() error {
unlock()
return srv.serveTCP(l)
case "udp", "udp4", "udp6":
l, err := listenUDP(srv.Net, addr, srv.ReusePort)
l, err := listenUDP(srv.Net, addr, srv.ReusePort, srv.ReuseAddr)
if err != nil {
return err
}
-61
View File
@@ -1,61 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Adapted for dns package usage by Miek Gieben.
package dns
import "sync"
import "time"
// call is an in-flight or completed singleflight.Do call
type call struct {
wg sync.WaitGroup
val *Msg
rtt time.Duration
err error
dups int
}
// singleflight represents a class of work and forms a namespace in
// which units of work can be executed with duplicate suppression.
type singleflight struct {
sync.Mutex // protects m
m map[string]*call // lazily initialized
dontDeleteForTesting bool // this is only to be used by TestConcurrentExchanges
}
// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
// The return value shared indicates whether v was given to multiple callers.
func (g *singleflight) Do(key string, fn func() (*Msg, time.Duration, error)) (v *Msg, rtt time.Duration, err error, shared bool) {
g.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
if c, ok := g.m[key]; ok {
c.dups++
g.Unlock()
c.wg.Wait()
return c.val, c.rtt, c.err, true
}
c := new(call)
c.wg.Add(1)
g.m[key] = c
g.Unlock()
c.val, c.rtt, c.err = fn()
c.wg.Done()
if !g.dontDeleteForTesting {
g.Lock()
delete(g.m, key)
g.Unlock()
}
return c.val, c.rtt, c.err, c.dups > 0
}
+61 -61
View File
@@ -85,7 +85,7 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
l, _ := c.Next()
i, e := strconv.ParseUint(l.token, 10, 16)
if e != nil || l.err {
return &ParseError{l.token, "bad SVCB priority", l}
return &ParseError{file: l.token, err: "bad SVCB priority", lex: l}
}
rr.Priority = uint16(i)
@@ -95,7 +95,7 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
name, nameOk := toAbsoluteName(l.token, o)
if l.err || !nameOk {
return &ParseError{l.token, "bad SVCB Target", l}
return &ParseError{file: l.token, err: "bad SVCB Target", lex: l}
}
rr.Target = name
@@ -111,7 +111,7 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
if !canHaveNextKey {
// The key we can now read was probably meant to be
// a part of the last value.
return &ParseError{l.token, "bad SVCB value quotation", l}
return &ParseError{file: l.token, err: "bad SVCB value quotation", lex: l}
}
// In key=value pairs, value does not have to be quoted unless value
@@ -124,7 +124,7 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
// Key with no value and no equality sign
key = l.token
} else if idx == 0 {
return &ParseError{l.token, "bad SVCB key", l}
return &ParseError{file: l.token, err: "bad SVCB key", lex: l}
} else {
key, value = l.token[:idx], l.token[idx+1:]
@@ -144,30 +144,30 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
value = l.token
l, _ = c.Next()
if l.value != zQuote {
return &ParseError{l.token, "SVCB unterminated value", l}
return &ParseError{file: l.token, err: "SVCB unterminated value", lex: l}
}
case zQuote:
// There's nothing in double quotes.
default:
return &ParseError{l.token, "bad SVCB value", l}
return &ParseError{file: l.token, err: "bad SVCB value", lex: l}
}
}
}
}
kv := makeSVCBKeyValue(svcbStringToKey(key))
if kv == nil {
return &ParseError{l.token, "bad SVCB key", l}
return &ParseError{file: l.token, err: "bad SVCB key", lex: l}
}
if err := kv.parse(value); err != nil {
return &ParseError{l.token, err.Error(), l}
return &ParseError{file: l.token, wrappedErr: err, lex: l}
}
xs = append(xs, kv)
case zQuote:
return &ParseError{l.token, "SVCB key can't contain double quotes", l}
return &ParseError{file: l.token, err: "SVCB key can't contain double quotes", lex: l}
case zBlank:
canHaveNextKey = true
default:
return &ParseError{l.token, "bad SVCB values", l}
return &ParseError{file: l.token, err: "bad SVCB values", lex: l}
}
l, _ = c.Next()
}
@@ -289,7 +289,7 @@ func (s *SVCBMandatory) String() string {
}
func (s *SVCBMandatory) pack() ([]byte, error) {
codes := append([]SVCBKey(nil), s.Code...)
codes := cloneSlice(s.Code)
sort.Slice(codes, func(i, j int) bool {
return codes[i] < codes[j]
})
@@ -314,10 +314,11 @@ func (s *SVCBMandatory) unpack(b []byte) error {
}
func (s *SVCBMandatory) parse(b string) error {
str := strings.Split(b, ",")
codes := make([]SVCBKey, 0, len(str))
for _, e := range str {
codes = append(codes, svcbStringToKey(e))
codes := make([]SVCBKey, 0, strings.Count(b, ",")+1)
for len(b) > 0 {
var key string
key, b, _ = strings.Cut(b, ",")
codes = append(codes, svcbStringToKey(key))
}
s.Code = codes
return nil
@@ -328,9 +329,7 @@ func (s *SVCBMandatory) len() int {
}
func (s *SVCBMandatory) copy() SVCBKeyValue {
return &SVCBMandatory{
append([]SVCBKey(nil), s.Code...),
}
return &SVCBMandatory{cloneSlice(s.Code)}
}
// SVCBAlpn pair is used to list supported connection protocols.
@@ -353,7 +352,7 @@ func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN }
func (s *SVCBAlpn) String() string {
// An ALPN value is a comma-separated list of values, each of which can be
// an arbitrary binary value. In order to allow parsing, the comma and
// backslash characters are themselves excaped.
// backslash characters are themselves escaped.
//
// However, this escaping is done in addition to the normal escaping which
// happens in zone files, meaning that these values must be
@@ -481,9 +480,7 @@ func (s *SVCBAlpn) len() int {
}
func (s *SVCBAlpn) copy() SVCBKeyValue {
return &SVCBAlpn{
append([]string(nil), s.Alpn...),
}
return &SVCBAlpn{cloneSlice(s.Alpn)}
}
// SVCBNoDefaultAlpn pair signifies no support for default connection protocols.
@@ -563,15 +560,15 @@ func (s *SVCBPort) parse(b string) error {
// to the hinted IP address may be terminated and a new connection may be opened.
// Basic use pattern for creating an ipv4hint option:
//
// h := new(dns.HTTPS)
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
// e := new(dns.SVCBIPv4Hint)
// e.Hint = []net.IP{net.IPv4(1,1,1,1).To4()}
// h := new(dns.HTTPS)
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
// e := new(dns.SVCBIPv4Hint)
// e.Hint = []net.IP{net.IPv4(1,1,1,1).To4()}
//
// Or
// Or
//
// e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()}
// h.Value = append(h.Value, e)
// e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()}
// h.Value = append(h.Value, e)
type SVCBIPv4Hint struct {
Hint []net.IP
}
@@ -595,6 +592,7 @@ func (s *SVCBIPv4Hint) unpack(b []byte) error {
if len(b) == 0 || len(b)%4 != 0 {
return errors.New("dns: svcbipv4hint: ipv4 address byte array length is not a multiple of 4")
}
b = cloneSlice(b)
x := make([]net.IP, 0, len(b)/4)
for i := 0; i < len(b); i += 4 {
x = append(x, net.IP(b[i:i+4]))
@@ -616,31 +614,33 @@ func (s *SVCBIPv4Hint) String() string {
}
func (s *SVCBIPv4Hint) parse(b string) error {
if b == "" {
return errors.New("dns: svcbipv4hint: empty hint")
}
if strings.Contains(b, ":") {
return errors.New("dns: svcbipv4hint: expected ipv4, got ipv6")
}
str := strings.Split(b, ",")
dst := make([]net.IP, len(str))
for i, e := range str {
hint := make([]net.IP, 0, strings.Count(b, ",")+1)
for len(b) > 0 {
var e string
e, b, _ = strings.Cut(b, ",")
ip := net.ParseIP(e).To4()
if ip == nil {
return errors.New("dns: svcbipv4hint: bad ip")
}
dst[i] = ip
hint = append(hint, ip)
}
s.Hint = dst
s.Hint = hint
return nil
}
func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
hint := make([]net.IP, len(s.Hint))
for i, ip := range s.Hint {
hint[i] = copyIP(ip)
}
return &SVCBIPv4Hint{
Hint: hint,
hint[i] = cloneSlice(ip)
}
return &SVCBIPv4Hint{Hint: hint}
}
// SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx].
@@ -660,19 +660,18 @@ func (s *SVCBECHConfig) String() string { return toBase64(s.ECH) }
func (s *SVCBECHConfig) len() int { return len(s.ECH) }
func (s *SVCBECHConfig) pack() ([]byte, error) {
return append([]byte(nil), s.ECH...), nil
return cloneSlice(s.ECH), nil
}
func (s *SVCBECHConfig) copy() SVCBKeyValue {
return &SVCBECHConfig{
append([]byte(nil), s.ECH...),
}
return &SVCBECHConfig{cloneSlice(s.ECH)}
}
func (s *SVCBECHConfig) unpack(b []byte) error {
s.ECH = append([]byte(nil), b...)
s.ECH = cloneSlice(b)
return nil
}
func (s *SVCBECHConfig) parse(b string) error {
x, err := fromBase64([]byte(b))
if err != nil {
@@ -715,6 +714,7 @@ func (s *SVCBIPv6Hint) unpack(b []byte) error {
if len(b) == 0 || len(b)%16 != 0 {
return errors.New("dns: svcbipv6hint: ipv6 address byte array length not a multiple of 16")
}
b = cloneSlice(b)
x := make([]net.IP, 0, len(b)/16)
for i := 0; i < len(b); i += 16 {
ip := net.IP(b[i : i+16])
@@ -739,9 +739,14 @@ func (s *SVCBIPv6Hint) String() string {
}
func (s *SVCBIPv6Hint) parse(b string) error {
str := strings.Split(b, ",")
dst := make([]net.IP, len(str))
for i, e := range str {
if b == "" {
return errors.New("dns: svcbipv6hint: empty hint")
}
hint := make([]net.IP, 0, strings.Count(b, ",")+1)
for len(b) > 0 {
var e string
e, b, _ = strings.Cut(b, ",")
ip := net.ParseIP(e)
if ip == nil {
return errors.New("dns: svcbipv6hint: bad ip")
@@ -749,21 +754,18 @@ func (s *SVCBIPv6Hint) parse(b string) error {
if ip.To4() != nil {
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4-mapped-ipv6")
}
dst[i] = ip
hint = append(hint, ip)
}
s.Hint = dst
s.Hint = hint
return nil
}
func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
hint := make([]net.IP, len(s.Hint))
for i, ip := range s.Hint {
hint[i] = copyIP(ip)
}
return &SVCBIPv6Hint{
Hint: hint,
hint[i] = cloneSlice(ip)
}
return &SVCBIPv6Hint{Hint: hint}
}
// SVCBDoHPath pair is used to indicate the URI template that the
@@ -831,11 +833,11 @@ type SVCBLocal struct {
func (s *SVCBLocal) Key() SVCBKey { return s.KeyCode }
func (s *SVCBLocal) String() string { return svcbParamToStr(s.Data) }
func (s *SVCBLocal) pack() ([]byte, error) { return append([]byte(nil), s.Data...), nil }
func (s *SVCBLocal) pack() ([]byte, error) { return cloneSlice(s.Data), nil }
func (s *SVCBLocal) len() int { return len(s.Data) }
func (s *SVCBLocal) unpack(b []byte) error {
s.Data = append([]byte(nil), b...)
s.Data = cloneSlice(b)
return nil
}
@@ -849,9 +851,7 @@ func (s *SVCBLocal) parse(b string) error {
}
func (s *SVCBLocal) copy() SVCBKeyValue {
return &SVCBLocal{s.KeyCode,
append([]byte(nil), s.Data...),
}
return &SVCBLocal{s.KeyCode, cloneSlice(s.Data)}
}
func (rr *SVCB) String() string {
@@ -867,8 +867,8 @@ func (rr *SVCB) String() string {
// areSVCBPairArraysEqual checks if SVCBKeyValue arrays are equal after sorting their
// copies. arrA and arrB have equal lengths, otherwise zduplicate.go wouldn't call this function.
func areSVCBPairArraysEqual(a []SVCBKeyValue, b []SVCBKeyValue) bool {
a = append([]SVCBKeyValue(nil), a...)
b = append([]SVCBKeyValue(nil), b...)
a = cloneSlice(a)
b = cloneSlice(b)
sort.Slice(a, func(i, j int) bool { return a[i].Key() < a[j].Key() })
sort.Slice(b, func(i, j int) bool { return b[i].Key() < b[j].Key() })
for i, e := range a {
+1
View File
@@ -1,3 +1,4 @@
//go:build tools
// +build tools
// We include our tool dependencies for `go generate` here to ensure they're
+131 -26
View File
@@ -65,6 +65,7 @@ const (
TypeAPL uint16 = 42
TypeDS uint16 = 43
TypeSSHFP uint16 = 44
TypeIPSECKEY uint16 = 45
TypeRRSIG uint16 = 46
TypeNSEC uint16 = 47
TypeDNSKEY uint16 = 48
@@ -98,6 +99,7 @@ const (
TypeURI uint16 = 256
TypeCAA uint16 = 257
TypeAVC uint16 = 258
TypeAMTRELAY uint16 = 260
TypeTKEY uint16 = 249
TypeTSIG uint16 = 250
@@ -133,8 +135,8 @@ const (
RcodeNXRrset = 8 // NXRRSet - RR Set that should exist does not [DNS Update]
RcodeNotAuth = 9 // NotAuth - Server Not Authoritative for zone [DNS Update]
RcodeNotZone = 10 // NotZone - Name not contained in zone [DNS Update/TSIG]
RcodeBadSig = 16 // BADSIG - TSIG Signature Failure [TSIG]
RcodeBadVers = 16 // BADVERS - Bad OPT Version [EDNS0]
RcodeBadSig = 16 // BADSIG - TSIG Signature Failure [TSIG] https://www.rfc-editor.org/rfc/rfc6895.html#section-2.3
RcodeBadVers = 16 // BADVERS - Bad OPT Version [EDNS0] https://www.rfc-editor.org/rfc/rfc6895.html#section-2.3
RcodeBadKey = 17 // BADKEY - Key not recognized [TSIG]
RcodeBadTime = 18 // BADTIME - Signature out of time window [TSIG]
RcodeBadMode = 19 // BADMODE - Bad TKEY Mode [TKEY]
@@ -159,6 +161,22 @@ const (
ZoneMDHashAlgSHA512 = 2
)
// Used in IPSEC https://datatracker.ietf.org/doc/html/rfc4025#section-2.3
const (
IPSECGatewayNone uint8 = iota
IPSECGatewayIPv4
IPSECGatewayIPv6
IPSECGatewayHost
)
// Used in AMTRELAY https://datatracker.ietf.org/doc/html/rfc8777#section-4.2.3
const (
AMTRELAYNone = IPSECGatewayNone
AMTRELAYIPv4 = IPSECGatewayIPv4
AMTRELAYIPv6 = IPSECGatewayIPv6
AMTRELAYHost = IPSECGatewayHost
)
// Header is the wire format for the DNS packet header.
type Header struct {
Id uint16
@@ -180,7 +198,7 @@ const (
_CD = 1 << 4 // checking disabled
)
// Various constants used in the LOC RR. See RFC 1887.
// Various constants used in the LOC RR. See RFC 1876.
const (
LOC_EQUATOR = 1 << 31 // RFC 1876, Section 2.
LOC_PRIMEMERIDIAN = 1 << 31 // RFC 1876, Section 2.
@@ -218,6 +236,9 @@ var CertTypeToString = map[uint16]string{
CertOID: "OID",
}
// Prefix for IPv4 encoded as IPv6 address
const ipv4InIPv6Prefix = "::ffff:"
//go:generate go run types_generate.go
// Question holds a DNS question. Usually there is just one. While the
@@ -381,6 +402,17 @@ func (rr *X25) String() string {
return rr.Hdr.String() + rr.PSDNAddress
}
// ISDN RR. See RFC 1183, Section 3.2.
type ISDN struct {
Hdr RR_Header
Address string
SubAddress string
}
func (rr *ISDN) String() string {
return rr.Hdr.String() + sprintTxt([]string{rr.Address, rr.SubAddress})
}
// RT RR. See RFC 1183, Section 3.3.
type RT struct {
Hdr RR_Header
@@ -613,8 +645,8 @@ func nextByte(s string, offset int) (byte, int) {
return 0, 0
case 2, 3: // too short to be \ddd
default: // maybe \ddd
if isDigit(s[offset+1]) && isDigit(s[offset+2]) && isDigit(s[offset+3]) {
return dddStringToByte(s[offset+1:]), 4
if isDDD(s[offset+1:]) {
return dddToByte(s[offset+1:]), 4
}
}
// not \ddd, just an RFC 1035 "quoted" character
@@ -733,6 +765,11 @@ func (rr *AAAA) String() string {
if rr.AAAA == nil {
return rr.Hdr.String()
}
if rr.AAAA.To4() != nil {
return rr.Hdr.String() + ipv4InIPv6Prefix + rr.AAAA.String()
}
return rr.Hdr.String() + rr.AAAA.String()
}
@@ -760,7 +797,7 @@ func (rr *GPOS) String() string {
return rr.Hdr.String() + rr.Longitude + " " + rr.Latitude + " " + rr.Altitude
}
// LOC RR. See RFC RFC 1876.
// LOC RR. See RFC 1876.
type LOC struct {
Hdr RR_Header
Version uint8
@@ -774,7 +811,10 @@ type LOC struct {
// cmToM takes a cm value expressed in RFC 1876 SIZE mantissa/exponent
// format and returns a string in m (two decimals for the cm).
func cmToM(m, e uint8) string {
func cmToM(x uint8) string {
m := x & 0xf0 >> 4
e := x & 0x0f
if e < 2 {
if e == 1 {
m *= 10
@@ -830,10 +870,9 @@ func (rr *LOC) String() string {
s += fmt.Sprintf("%.0fm ", alt)
}
s += cmToM(rr.Size&0xf0>>4, rr.Size&0x0f) + "m "
s += cmToM(rr.HorizPre&0xf0>>4, rr.HorizPre&0x0f) + "m "
s += cmToM(rr.VertPre&0xf0>>4, rr.VertPre&0x0f) + "m"
s += cmToM(rr.Size) + "m "
s += cmToM(rr.HorizPre) + "m "
s += cmToM(rr.VertPre) + "m"
return s
}
@@ -870,6 +909,11 @@ func (rr *RRSIG) String() string {
return s
}
// NXT RR. See RFC 2535.
type NXT struct {
NSEC
}
// NSEC RR. See RFC 4034 and RFC 3755.
type NSEC struct {
Hdr RR_Header
@@ -954,7 +998,7 @@ func (rr *TALINK) String() string {
sprintName(rr.PreviousName) + " " + sprintName(rr.NextName)
}
// SSHFP RR. See RFC RFC 4255.
// SSHFP RR. See RFC 4255.
type SSHFP struct {
Hdr RR_Header
Algorithm uint8
@@ -968,7 +1012,7 @@ func (rr *SSHFP) String() string {
" " + strings.ToUpper(rr.FingerPrint)
}
// KEY RR. See RFC RFC 2535.
// KEY RR. See RFC 2535.
type KEY struct {
DNSKEY
}
@@ -994,6 +1038,69 @@ func (rr *DNSKEY) String() string {
" " + rr.PublicKey
}
// IPSECKEY RR. See RFC 4025.
type IPSECKEY struct {
Hdr RR_Header
Precedence uint8
GatewayType uint8
Algorithm uint8
GatewayAddr net.IP `dns:"-"` // packing/unpacking/parsing/etc handled together with GatewayHost
GatewayHost string `dns:"ipsechost"`
PublicKey string `dns:"base64"`
}
func (rr *IPSECKEY) String() string {
var gateway string
switch rr.GatewayType {
case IPSECGatewayIPv4, IPSECGatewayIPv6:
gateway = rr.GatewayAddr.String()
case IPSECGatewayHost:
gateway = rr.GatewayHost
case IPSECGatewayNone:
fallthrough
default:
gateway = "."
}
return rr.Hdr.String() + strconv.Itoa(int(rr.Precedence)) +
" " + strconv.Itoa(int(rr.GatewayType)) +
" " + strconv.Itoa(int(rr.Algorithm)) +
" " + gateway +
" " + rr.PublicKey
}
// AMTRELAY RR. See RFC 8777.
type AMTRELAY struct {
Hdr RR_Header
Precedence uint8
GatewayType uint8 // discovery is packed in here at bit 0x80
GatewayAddr net.IP `dns:"-"` // packing/unpacking/parsing/etc handled together with GatewayHost
GatewayHost string `dns:"amtrelayhost"`
}
func (rr *AMTRELAY) String() string {
var gateway string
switch rr.GatewayType & 0x7f {
case AMTRELAYIPv4, AMTRELAYIPv6:
gateway = rr.GatewayAddr.String()
case AMTRELAYHost:
gateway = rr.GatewayHost
case AMTRELAYNone:
fallthrough
default:
gateway = "."
}
boolS := "0"
if rr.GatewayType&0x80 == 0x80 {
boolS = "1"
}
return rr.Hdr.String() + strconv.Itoa(int(rr.Precedence)) +
" " + boolS +
" " + strconv.Itoa(int(rr.GatewayType&0x7f)) +
" " + gateway
}
// RKEY RR. See https://www.iana.org/assignments/dns-parameters/RKEY/rkey-completed-template.
type RKEY struct {
Hdr RR_Header
@@ -1215,7 +1322,7 @@ type NINFO struct {
func (rr *NINFO) String() string { return rr.Hdr.String() + sprintTxt(rr.ZSData) }
// NID RR. See RFC RFC 6742.
// NID RR. See RFC 6742.
type NID struct {
Hdr RR_Header
Preference uint16
@@ -1434,7 +1541,7 @@ func (a *APLPrefix) str() string {
case net.IPv6len:
// add prefix for IPv4-mapped IPv6
if v4 := a.Network.IP.To4(); v4 != nil {
sb.WriteString("::ffff:")
sb.WriteString(ipv4InIPv6Prefix)
}
sb.WriteString(a.Network.IP.String())
}
@@ -1450,7 +1557,7 @@ func (a *APLPrefix) str() string {
// equals reports whether two APL prefixes are identical.
func (a *APLPrefix) equals(b *APLPrefix) bool {
return a.Negation == b.Negation &&
bytes.Equal(a.Network.IP, b.Network.IP) &&
a.Network.IP.Equal(b.Network.IP) &&
bytes.Equal(a.Network.Mask, b.Network.Mask)
}
@@ -1518,21 +1625,19 @@ func euiToString(eui uint64, bits int) (hex string) {
return
}
// copyIP returns a copy of ip.
func copyIP(ip net.IP) net.IP {
p := make(net.IP, len(ip))
copy(p, ip)
return p
// cloneSlice returns a shallow copy of s.
func cloneSlice[E any, S ~[]E](s S) S {
if s == nil {
return nil
}
return append(S(nil), s...)
}
// copyNet returns a copy of a subnet.
func copyNet(n net.IPNet) net.IPNet {
m := make(net.IPMask, len(n.Mask))
copy(m, n.Mask)
return net.IPNet{
IP: copyIP(n.IP),
Mask: m,
IP: cloneSlice(n.IP),
Mask: cloneSlice(n.Mask),
}
}
+1
View File
@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package dns
+4 -4
View File
@@ -1,5 +1,9 @@
//go:build windows
// +build windows
// TODO(tmthrgd): Remove this Windows-specific code if go.dev/issue/7175 and
// go.dev/issue/7174 are ever fixed.
package dns
import "net"
@@ -14,7 +18,6 @@ func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr }
// ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a
// net.UDPAddr.
// TODO(fastest963): Once go1.10 is released, use ReadMsgUDP.
func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) {
n, raddr, err := conn.ReadFrom(b)
if err != nil {
@@ -24,12 +27,9 @@ func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) {
}
// WriteToSessionUDP acts just like net.UDPConn.WriteTo(), but uses a *SessionUDP instead of a net.Addr.
// TODO(fastest963): Once go1.10 is released, use WriteMsgUDP.
func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) {
return conn.WriteTo(b, session.raddr)
}
// TODO(fastest963): Once go1.10 is released and we can use *MsgUDP methods
// use the standard method in udp.go for these.
func setUDPSocketOptions(*net.UDPConn) error { return nil }
func parseDstFromOOB([]byte, net.IP) net.IP { return nil }
+1 -1
View File
@@ -3,7 +3,7 @@ package dns
import "fmt"
// Version is current version of this library.
var Version = v{1, 1, 50}
var Version = v{1, 1, 58}
// v holds the version of this library.
type v struct {
+14 -5
View File
@@ -44,7 +44,6 @@ func (t *Transfer) tsigProvider() TsigProvider {
// dnscon := &dns.Conn{Conn:con}
// transfer = &dns.Transfer{Conn: dnscon}
// channel, err := transfer.In(message, master)
//
func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
switch q.Question[0].Qtype {
case TypeAXFR, TypeIXFR:
@@ -81,8 +80,13 @@ func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
func (t *Transfer) inAxfr(q *Msg, c chan *Envelope) {
first := true
defer t.Close()
defer close(c)
defer func() {
// First close the connection, then the channel. This allows functions blocked on
// the channel to assume that the connection is closed and no further operations are
// pending when they resume.
t.Close()
close(c)
}()
timeout := dnsTimeout
if t.ReadTimeout != 0 {
timeout = t.ReadTimeout
@@ -132,8 +136,13 @@ func (t *Transfer) inIxfr(q *Msg, c chan *Envelope) {
axfr := true
n := 0
qser := q.Ns[0].(*SOA).Serial
defer t.Close()
defer close(c)
defer func() {
// First close the connection, then the channel. This allows functions blocked on
// the channel to assume that the connection is closed and no further operations are
// pending when they resume.
t.Close()
close(c)
}()
timeout := dnsTimeout
if t.ReadTimeout != 0 {
timeout = t.ReadTimeout
+93
View File
@@ -43,6 +43,32 @@ func (r1 *AFSDB) isDuplicate(_r2 RR) bool {
return true
}
func (r1 *AMTRELAY) isDuplicate(_r2 RR) bool {
r2, ok := _r2.(*AMTRELAY)
if !ok {
return false
}
_ = r2
if r1.Precedence != r2.Precedence {
return false
}
if r1.GatewayType != r2.GatewayType {
return false
}
switch r1.GatewayType {
case IPSECGatewayIPv4, IPSECGatewayIPv6:
if !r1.GatewayAddr.Equal(r2.GatewayAddr) {
return false
}
case IPSECGatewayHost:
if !isDuplicateName(r1.GatewayHost, r2.GatewayHost) {
return false
}
}
return true
}
func (r1 *ANY) isDuplicate(_r2 RR) bool {
r2, ok := _r2.(*ANY)
if !ok {
@@ -423,6 +449,53 @@ func (r1 *HTTPS) isDuplicate(_r2 RR) bool {
return true
}
func (r1 *IPSECKEY) isDuplicate(_r2 RR) bool {
r2, ok := _r2.(*IPSECKEY)
if !ok {
return false
}
_ = r2
if r1.Precedence != r2.Precedence {
return false
}
if r1.GatewayType != r2.GatewayType {
return false
}
if r1.Algorithm != r2.Algorithm {
return false
}
switch r1.GatewayType {
case IPSECGatewayIPv4, IPSECGatewayIPv6:
if !r1.GatewayAddr.Equal(r2.GatewayAddr) {
return false
}
case IPSECGatewayHost:
if !isDuplicateName(r1.GatewayHost, r2.GatewayHost) {
return false
}
}
if r1.PublicKey != r2.PublicKey {
return false
}
return true
}
func (r1 *ISDN) isDuplicate(_r2 RR) bool {
r2, ok := _r2.(*ISDN)
if !ok {
return false
}
_ = r2
if r1.Address != r2.Address {
return false
}
if r1.SubAddress != r2.SubAddress {
return false
}
return true
}
func (r1 *KEY) isDuplicate(_r2 RR) bool {
r2, ok := _r2.(*KEY)
if !ok {
@@ -813,6 +886,26 @@ func (r1 *NULL) isDuplicate(_r2 RR) bool {
return true
}
func (r1 *NXT) isDuplicate(_r2 RR) bool {
r2, ok := _r2.(*NXT)
if !ok {
return false
}
_ = r2
if !isDuplicateName(r1.NextDomain, r2.NextDomain) {
return false
}
if len(r1.TypeBitMap) != len(r2.TypeBitMap) {
return false
}
for i := 0; i < len(r1.TypeBitMap); i++ {
if r1.TypeBitMap[i] != r2.TypeBitMap[i] {
return false
}
}
return true
}
func (r1 *OPENPGPKEY) isDuplicate(_r2 RR) bool {
r2, ok := _r2.(*OPENPGPKEY)
if !ok {
+170
View File
@@ -32,6 +32,22 @@ func (rr *AFSDB) pack(msg []byte, off int, compression compressionMap, compress
return off, nil
}
func (rr *AMTRELAY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
off, err = packUint8(rr.Precedence, msg, off)
if err != nil {
return off, err
}
off, err = packUint8(rr.GatewayType, msg, off)
if err != nil {
return off, err
}
off, err = packIPSECGateway(rr.GatewayAddr, rr.GatewayHost, msg, off, rr.GatewayType, compression, false)
if err != nil {
return off, err
}
return off, nil
}
func (rr *ANY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
return off, nil
}
@@ -332,6 +348,42 @@ func (rr *HTTPS) pack(msg []byte, off int, compression compressionMap, compress
return off, nil
}
func (rr *IPSECKEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
off, err = packUint8(rr.Precedence, msg, off)
if err != nil {
return off, err
}
off, err = packUint8(rr.GatewayType, msg, off)
if err != nil {
return off, err
}
off, err = packUint8(rr.Algorithm, msg, off)
if err != nil {
return off, err
}
off, err = packIPSECGateway(rr.GatewayAddr, rr.GatewayHost, msg, off, rr.GatewayType, compression, false)
if err != nil {
return off, err
}
off, err = packStringBase64(rr.PublicKey, msg, off)
if err != nil {
return off, err
}
return off, nil
}
func (rr *ISDN) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
off, err = packString(rr.Address, msg, off)
if err != nil {
return off, err
}
off, err = packString(rr.SubAddress, msg, off)
if err != nil {
return off, err
}
return off, nil
}
func (rr *KEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
off, err = packUint16(rr.Flags, msg, off)
if err != nil {
@@ -654,6 +706,18 @@ func (rr *NULL) pack(msg []byte, off int, compression compressionMap, compress b
return off, nil
}
func (rr *NXT) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
off, err = packDomainName(rr.NextDomain, msg, off, compression, false)
if err != nil {
return off, err
}
off, err = packDataNsec(rr.TypeBitMap, msg, off)
if err != nil {
return off, err
}
return off, nil
}
func (rr *OPENPGPKEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
off, err = packStringBase64(rr.PublicKey, msg, off)
if err != nil {
@@ -1180,6 +1244,34 @@ func (rr *AFSDB) unpack(msg []byte, off int) (off1 int, err error) {
return off, nil
}
func (rr *AMTRELAY) unpack(msg []byte, off int) (off1 int, err error) {
rdStart := off
_ = rdStart
rr.Precedence, off, err = unpackUint8(msg, off)
if err != nil {
return off, err
}
if off == len(msg) {
return off, nil
}
rr.GatewayType, off, err = unpackUint8(msg, off)
if err != nil {
return off, err
}
if off == len(msg) {
return off, nil
}
if off == len(msg) {
return off, nil
}
rr.GatewayAddr, rr.GatewayHost, off, err = unpackIPSECGateway(msg, off, rr.GatewayType)
if err != nil {
return off, err
}
return off, nil
}
func (rr *ANY) unpack(msg []byte, off int) (off1 int, err error) {
rdStart := off
_ = rdStart
@@ -1636,6 +1728,66 @@ func (rr *HTTPS) unpack(msg []byte, off int) (off1 int, err error) {
return off, nil
}
func (rr *IPSECKEY) unpack(msg []byte, off int) (off1 int, err error) {
rdStart := off
_ = rdStart
rr.Precedence, off, err = unpackUint8(msg, off)
if err != nil {
return off, err
}
if off == len(msg) {
return off, nil
}
rr.GatewayType, off, err = unpackUint8(msg, off)
if err != nil {
return off, err
}
if off == len(msg) {
return off, nil
}
rr.Algorithm, off, err = unpackUint8(msg, off)
if err != nil {
return off, err
}
if off == len(msg) {
return off, nil
}
if off == len(msg) {
return off, nil
}
rr.GatewayAddr, rr.GatewayHost, off, err = unpackIPSECGateway(msg, off, rr.GatewayType)
if err != nil {
return off, err
}
if off == len(msg) {
return off, nil
}
rr.PublicKey, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength))
if err != nil {
return off, err
}
return off, nil
}
func (rr *ISDN) unpack(msg []byte, off int) (off1 int, err error) {
rdStart := off
_ = rdStart
rr.Address, off, err = unpackString(msg, off)
if err != nil {
return off, err
}
if off == len(msg) {
return off, nil
}
rr.SubAddress, off, err = unpackString(msg, off)
if err != nil {
return off, err
}
return off, nil
}
func (rr *KEY) unpack(msg []byte, off int) (off1 int, err error) {
rdStart := off
_ = rdStart
@@ -2114,6 +2266,24 @@ func (rr *NULL) unpack(msg []byte, off int) (off1 int, err error) {
return off, nil
}
func (rr *NXT) unpack(msg []byte, off int) (off1 int, err error) {
rdStart := off
_ = rdStart
rr.NextDomain, off, err = UnpackDomainName(msg, off)
if err != nil {
return off, err
}
if off == len(msg) {
return off, nil
}
rr.TypeBitMap, off, err = unpackDataNsec(msg, off)
if err != nil {
return off, err
}
return off, nil
}
func (rr *OPENPGPKEY) unpack(msg []byte, off int) (off1 int, err error) {
rdStart := off
_ = rdStart
+423 -49
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
*.pprof
*.test
*.txt
*.out
/upstream
+125
View File
@@ -0,0 +1,125 @@
This work is released into the public domain with CC0 1.0.
-------------------------------------------------------------------------------
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
+34
View File
@@ -0,0 +1,34 @@
asm: internal/alg/hash/hash_avx2/impl_amd64.s internal/alg/compress/compress_sse41/impl_amd64.s
internal/alg/hash/hash_avx2/impl_amd64.s: avo/avx2/*.go
( cd avo; go run ./avx2 ) > internal/alg/hash/hash_avx2/impl_amd64.s
internal/alg/compress/compress_sse41/impl_amd64.s: avo/sse41/*.go
( cd avo; go run ./sse41 ) > internal/alg/compress/compress_sse41/impl_amd64.s
.PHONY: fmt
fmt:
go fmt ./...
.PHONY: clean
clean:
rm -f internal/alg/hash/hash_avx2/impl_amd64.s
rm -f internal/alg/compress/compress_sse41/impl_amd64.s
.PHONY: test
test:
go test -race -bench=. -benchtime=1x
.PHONY: vet
vet:
GOOS=linux GOARCH=386 GO386=softfloat go vet ./...
GOOS=windows GOARCH=386 GO386=softfloat go vet ./...
GOOS=linux GOARCH=amd64 go vet ./...
GOOS=windows GOARCH=amd64 go vet ./...
GOOS=darwin GOARCH=amd64 go vet ./...
GOOS=linux GOARCH=arm go vet ./...
GOOS=linux GOARCH=arm64 go vet ./...
GOOS=windows GOARCH=arm64 go vet ./...
GOOS=darwin GOARCH=arm64 go vet ./...
GOOS=js GOARCH=wasm go vet ./...
GOOS=linux GOARCH=mips go vet ./...
+77
View File
@@ -0,0 +1,77 @@
# BLAKE3
<p>
<a href="https://pkg.go.dev/github.com/zeebo/blake3"><img src="https://img.shields.io/badge/doc-reference-007d9b?logo=go&style=flat-square" alt="go.dev" /></a>
<a href="https://goreportcard.com/report/github.com/zeebo/blake3"><img src="https://goreportcard.com/badge/github.com/zeebo/blake3?style=flat-square" alt="Go Report Card" /></a>
<a href="https://sourcegraph.com/github.com/zeebo/blake3?badge"><img src="https://sourcegraph.com/github.com/zeebo/blake3/-/badge.svg?style=flat-square" alt="SourceGraph" /></a>
</p>
Pure Go implementation of [BLAKE3](https://blake3.io) with AVX2 and SSE4.1 acceleration.
Special thanks to the excellent [avo](https://github.com/mmcloughlin/avo) making writing vectorized version much easier.
# Benchmarks
## Caveats
This library makes some different design decisions than the upstream Rust crate around internal buffering. Specifically, because it does not target the embedded system space, nor does it support multithreading, it elects to do its own internal buffering. This means that a user does not have to worry about providing large enough buffers to get the best possible performance, but it does worse on smaller input sizes. So some notes:
- The Rust benchmarks below are all single-threaded to match this Go implementation.
- I make no attempt to get precise measurements (cpu throttling, noisy environment, etc.) so please benchmark on your own systems.
- These benchmarks are run on an i7-6700K which does not support AVX-512, so Rust is limited to use AVX2 at sizes above 8 kib.
- I tried my best to make them benchmark the same thing, but who knows? :smile:
## Charts
In this case, both libraries are able to avoid a lot of data copying and will use vectorized instructions to hash as fast as possible, and perform similarly.
![Large Full Buffer](/assets/large-full-buffer.svg)
For incremental writes, you must provide the Rust version large enough buffers so that it can use vectorized instructions. This Go library performs consistently regardless of the size being sent into the update function.
![Incremental](/assets/incremental.svg)
The downside of internal buffering is most apparent with small sizes as most time is spent initializing the hasher state. In terms of hashing rate, the difference is 3-4x, but in an absolute sense it's ~100ns (see tables below). If you wish to hash a large number of very small strings and you care about those nanoseconds, be sure to use the Reset method to avoid re-initializing the state.
![Small Full Buffer](/assets/small-full-buffer.svg)
## Timing Tables
### Small
| Size | Full Buffer | Reset | | Full Buffer Rate | Reset Rate |
|--------|-------------|------------|-|------------------|--------------|
| 64 b | `205ns` | `86.5ns` | | `312MB/s` | `740MB/s` |
| 256 b | `364ns` | `250ns` | | `703MB/s` | `1.03GB/s` |
| 512 b | `575ns` | `468ns` | | `892MB/s` | `1.10GB/s` |
| 768 b | `795ns` | `682ns` | | `967MB/s` | `1.13GB/s` |
### Large
| Size | Incremental | Full Buffer | Reset | | Incremental Rate | Full Buffer Rate | Reset Rate |
|----------|-------------|-------------|------------|-|------------------|------------------|--------------|
| 1 kib | `1.02µs` | `1.01µs` | `891ns` | | `1.00GB/s` | `1.01GB/s` | `1.15GB/s` |
| 2 kib | `2.11µs` | `2.07µs` | `1.95µs` | | `968MB/s` | `990MB/s` | `1.05GB/s` |
| 4 kib | `2.28µs` | `2.15µs` | `2.05µs` | | `1.80GB/s` | `1.90GB/s` | `2.00GB/s` |
| 8 kib | `2.64µs` | `2.52µs` | `2.44µs` | | `3.11GB/s` | `3.25GB/s` | `3.36GB/s` |
| 16 kib | `4.93µs` | `4.54µs` | `4.48µs` | | `3.33GB/s` | `3.61GB/s` | `3.66GB/s` |
| 32 kib | `9.41µs` | `8.62µs` | `8.54µs` | | `3.48GB/s` | `3.80GB/s` | `3.84GB/s` |
| 64 kib | `18.2µs` | `16.7µs` | `16.6µs` | | `3.59GB/s` | `3.91GB/s` | `3.94GB/s` |
| 128 kib | `36.3µs` | `32.9µs` | `33.1µs` | | `3.61GB/s` | `3.99GB/s` | `3.96GB/s` |
| 256 kib | `72.5µs` | `65.7µs` | `66.0µs` | | `3.62GB/s` | `3.99GB/s` | `3.97GB/s` |
| 512 kib | `145µs` | `131µs` | `132µs` | | `3.60GB/s` | `4.00GB/s` | `3.97GB/s` |
| 1024 kib | `290µs` | `262µs` | `262µs` | | `3.62GB/s` | `4.00GB/s` | `4.00GB/s` |
### No ASM
| Size | Incremental | Full Buffer | Reset | | Incremental Rate | Full Buffer Rate | Reset Rate |
|----------|-------------|-------------|------------|-|------------------|------------------|-------------|
| 64 b | `253ns` | `254ns` | `134ns` | | `253MB/s` | `252MB/s` | `478MB/s` |
| 256 b | `553ns` | `557ns` | `441ns` | | `463MB/s` | `459MB/s` | `580MB/s` |
| 512 b | `948ns` | `953ns` | `841ns` | | `540MB/s` | `538MB/s` | `609MB/s` |
| 768 b | `1.38µs` | `1.40µs` | `1.35µs` | | `558MB/s` | `547MB/s` | `570MB/s` |
| 1 kib | `1.77µs` | `1.77µs` | `1.70µs` | | `577MB/s` | `580MB/s` | `602MB/s` |
| | | | | | | | |
| 1024 kib | `880µs` | `883µs` | `878µs` | | `596MB/s` | `595MB/s` | `598MB/s` |
The speed caps out at around 1 kib, so most rows have been elided from the presentation.
+165
View File
@@ -0,0 +1,165 @@
// Package blake3 provides an SSE4.1/AVX2 accelerated BLAKE3 implementation.
package blake3
import (
"errors"
"github.com/zeebo/blake3/internal/consts"
"github.com/zeebo/blake3/internal/utils"
)
// Hasher is a hash.Hash for BLAKE3.
type Hasher struct {
size int
h hasher
}
// New returns a new Hasher that has a digest size of 32 bytes.
//
// If you need more or less output bytes than that, use Digest method.
func New() *Hasher {
return &Hasher{
size: 32,
h: hasher{
key: consts.IV,
},
}
}
// NewKeyed returns a new Hasher that uses the 32 byte input key and has
// a digest size of 32 bytes.
//
// If you need more or less output bytes than that, use the Digest method.
func NewKeyed(key []byte) (*Hasher, error) {
if len(key) != 32 {
return nil, errors.New("invalid key size")
}
h := &Hasher{
size: 32,
h: hasher{
flags: consts.Flag_Keyed,
},
}
utils.KeyFromBytes(key, &h.h.key)
return h, nil
}
// DeriveKey derives a key based on reusable key material of any
// length, in the given context. The key will be stored in out, using
// all of its current length.
//
// Context strings must be hardcoded constants, and the recommended
// format is "[application] [commit timestamp] [purpose]", e.g.,
// "example.com 2019-12-25 16:18:03 session tokens v1".
func DeriveKey(context string, material []byte, out []byte) {
h := NewDeriveKey(context)
_, _ = h.Write(material)
_, _ = h.Digest().Read(out)
}
// NewDeriveKey returns a Hasher that is initialized with the context
// string. See DeriveKey for details. It has a digest size of 32 bytes.
//
// If you need more or less output bytes than that, use the Digest method.
func NewDeriveKey(context string) *Hasher {
// hash the context string and use that instead of IV
h := &Hasher{
size: 32,
h: hasher{
key: consts.IV,
flags: consts.Flag_DeriveKeyContext,
},
}
var buf [32]byte
_, _ = h.WriteString(context)
_, _ = h.Digest().Read(buf[:])
h.Reset()
utils.KeyFromBytes(buf[:], &h.h.key)
h.h.flags = consts.Flag_DeriveKeyMaterial
return h
}
// Write implements part of the hash.Hash interface. It never returns an error.
func (h *Hasher) Write(p []byte) (int, error) {
h.h.update(p)
return len(p), nil
}
// WriteString is like Write but specialized to strings to avoid allocations.
func (h *Hasher) WriteString(p string) (int, error) {
h.h.updateString(p)
return len(p), nil
}
// Reset implements part of the hash.Hash interface. It causes the Hasher to
// act as if it was newly created.
func (h *Hasher) Reset() {
h.h.reset()
}
// Clone returns a new Hasher with the same internal state.
//
// Modifying the resulting Hasher will not modify the original Hasher, and vice versa.
func (h *Hasher) Clone() *Hasher {
return &Hasher{size: h.size, h: h.h}
}
// Size implements part of the hash.Hash interface. It returns the number of
// bytes the hash will output in Sum.
func (h *Hasher) Size() int {
return h.size
}
// BlockSize implements part of the hash.Hash interface. It returns the most
// natural size to write to the Hasher.
func (h *Hasher) BlockSize() int {
return 64
}
// Sum implements part of the hash.Hash interface. It appends the digest of
// the Hasher to the provided buffer and returns it.
func (h *Hasher) Sum(b []byte) []byte {
if top := len(b) + h.size; top <= cap(b) && top >= len(b) {
h.h.finalize(b[len(b):top])
return b[:top]
}
tmp := make([]byte, h.size)
h.h.finalize(tmp)
return append(b, tmp...)
}
// Digest takes a snapshot of the hash state and returns an object that can
// be used to read and seek through 2^64 bytes of digest output.
func (h *Hasher) Digest() *Digest {
var d Digest
h.h.finalizeDigest(&d)
return &d
}
// Sum256 returns the first 256 bits of the unkeyed digest of the data.
func Sum256(data []byte) (sum [32]byte) {
out := Sum512(data)
copy(sum[:], out[:32])
return sum
}
// Sum512 returns the first 512 bits of the unkeyed digest of the data.
func Sum512(data []byte) (sum [64]byte) {
if len(data) <= consts.ChunkLen {
var d Digest
compressAll(&d, data, 0, consts.IV)
_, _ = d.Read(sum[:])
return sum
} else {
h := hasher{key: consts.IV}
h.update(data)
h.finalize(sum[:])
return sum
}
}
+285
View File
@@ -0,0 +1,285 @@
package blake3
import (
"math/bits"
"unsafe"
"github.com/zeebo/blake3/internal/alg"
"github.com/zeebo/blake3/internal/consts"
"github.com/zeebo/blake3/internal/utils"
)
//
// hasher contains state for a blake3 hash
//
type hasher struct {
len uint64
chunks uint64
flags uint32
key [8]uint32
stack cvstack
buf [8192]byte
}
func (a *hasher) reset() {
a.len = 0
a.chunks = 0
a.stack.occ = 0
a.stack.lvls = [8]uint8{}
a.stack.bufn = 0
}
func (a *hasher) update(buf []byte) {
// relies on the first two words of a string being the same as a slice
a.updateString(*(*string)(unsafe.Pointer(&buf)))
}
func (a *hasher) updateString(buf string) {
var input *[8192]byte
for len(buf) > 0 {
if a.len == 0 && len(buf) > 8192 {
// relies on the data pointer being the first word in the string header
input = (*[8192]byte)(*(*unsafe.Pointer)(unsafe.Pointer(&buf)))
buf = buf[8192:]
} else if a.len < 8192 {
n := copy(a.buf[a.len:], buf)
a.len += uint64(n)
buf = buf[n:]
continue
} else {
input = &a.buf
}
a.consume(input)
a.len = 0
a.chunks += 8
}
}
func (a *hasher) consume(input *[8192]byte) {
var out chainVector
var chain [8]uint32
alg.HashF(input, 8192, a.chunks, a.flags, &a.key, &out, &chain)
a.stack.pushN(0, &out, 8, a.flags, &a.key)
}
func (a *hasher) finalize(p []byte) {
var d Digest
a.finalizeDigest(&d)
_, _ = d.Read(p)
}
func (a *hasher) finalizeDigest(d *Digest) {
if a.chunks == 0 && a.len <= consts.ChunkLen {
compressAll(d, a.buf[:a.len], a.flags, a.key)
return
}
d.chain = a.key
d.flags = a.flags | consts.Flag_ChunkEnd
if a.len > 64 {
var buf chainVector
alg.HashF(&a.buf, a.len, a.chunks, a.flags, &a.key, &buf, &d.chain)
if a.len > consts.ChunkLen {
complete := (a.len - 1) / consts.ChunkLen
a.stack.pushN(0, &buf, int(complete), a.flags, &a.key)
a.chunks += complete
a.len = uint64(copy(a.buf[:], a.buf[complete*consts.ChunkLen:a.len]))
}
}
if a.len <= 64 {
d.flags |= consts.Flag_ChunkStart
}
d.counter = a.chunks
d.blen = uint32(a.len) % 64
base := a.len / 64 * 64
if a.len > 0 && d.blen == 0 {
d.blen = 64
base -= 64
}
if consts.OptimizeLittleEndian {
copy((*[64]byte)(unsafe.Pointer(&d.block[0]))[:], a.buf[base:a.len])
} else {
var tmp [64]byte
copy(tmp[:], a.buf[base:a.len])
utils.BytesToWords(&tmp, &d.block)
}
for a.stack.bufn > 0 {
a.stack.flush(a.flags, &a.key)
}
var tmp [16]uint32
for occ := a.stack.occ; occ != 0; occ &= occ - 1 {
col := uint(bits.TrailingZeros64(occ)) % 64
alg.Compress(&d.chain, &d.block, d.counter, d.blen, d.flags, &tmp)
*(*[8]uint32)(unsafe.Pointer(&d.block[0])) = a.stack.stack[col]
*(*[8]uint32)(unsafe.Pointer(&d.block[8])) = *(*[8]uint32)(unsafe.Pointer(&tmp[0]))
if occ == a.stack.occ {
d.chain = a.key
d.counter = 0
d.blen = consts.BlockLen
d.flags = a.flags | consts.Flag_Parent
}
}
d.flags |= consts.Flag_Root
}
//
// chain value stack
//
type chainVector = [64]uint32
type cvstack struct {
occ uint64 // which levels in stack are occupied
lvls [8]uint8 // what level the buf input was in
bufn int // how many pairs are loaded into buf
buf [2]chainVector
stack [64][8]uint32
}
func (a *cvstack) pushN(l uint8, cv *chainVector, n int, flags uint32, key *[8]uint32) {
for i := 0; i < n; i++ {
a.pushL(l, cv, i)
for a.bufn == 8 {
a.flush(flags, key)
}
}
}
func (a *cvstack) pushL(l uint8, cv *chainVector, n int) {
bit := uint64(1) << (l & 63)
if a.occ&bit == 0 {
readChain(cv, n, &a.stack[l&63])
a.occ ^= bit
return
}
a.lvls[a.bufn&7] = l
writeChain(&a.stack[l&63], &a.buf[0], a.bufn)
copyChain(cv, n, &a.buf[1], a.bufn)
a.bufn++
a.occ ^= bit
}
func (a *cvstack) flush(flags uint32, key *[8]uint32) {
var out chainVector
alg.HashP(&a.buf[0], &a.buf[1], flags|consts.Flag_Parent, key, &out, a.bufn)
bufn, lvls := a.bufn, a.lvls
a.bufn, a.lvls = 0, [8]uint8{}
for i := 0; i < bufn; i++ {
a.pushL(lvls[i]+1, &out, i)
}
}
//
// helpers to deal with reading/writing transposed values
//
func copyChain(in *chainVector, icol int, out *chainVector, ocol int) {
type u = uintptr
type p = unsafe.Pointer
type a = *uint32
i := p(u(p(in)) + u(icol*4))
o := p(u(p(out)) + u(ocol*4))
*a(p(u(o) + 0*32)) = *a(p(u(i) + 0*32))
*a(p(u(o) + 1*32)) = *a(p(u(i) + 1*32))
*a(p(u(o) + 2*32)) = *a(p(u(i) + 2*32))
*a(p(u(o) + 3*32)) = *a(p(u(i) + 3*32))
*a(p(u(o) + 4*32)) = *a(p(u(i) + 4*32))
*a(p(u(o) + 5*32)) = *a(p(u(i) + 5*32))
*a(p(u(o) + 6*32)) = *a(p(u(i) + 6*32))
*a(p(u(o) + 7*32)) = *a(p(u(i) + 7*32))
}
func readChain(in *chainVector, col int, out *[8]uint32) {
type u = uintptr
type p = unsafe.Pointer
type a = *uint32
i := p(u(p(in)) + u(col*4))
out[0] = *a(p(u(i) + 0*32))
out[1] = *a(p(u(i) + 1*32))
out[2] = *a(p(u(i) + 2*32))
out[3] = *a(p(u(i) + 3*32))
out[4] = *a(p(u(i) + 4*32))
out[5] = *a(p(u(i) + 5*32))
out[6] = *a(p(u(i) + 6*32))
out[7] = *a(p(u(i) + 7*32))
}
func writeChain(in *[8]uint32, out *chainVector, col int) {
type u = uintptr
type p = unsafe.Pointer
type a = *uint32
o := p(u(p(out)) + u(col*4))
*a(p(u(o) + 0*32)) = in[0]
*a(p(u(o) + 1*32)) = in[1]
*a(p(u(o) + 2*32)) = in[2]
*a(p(u(o) + 3*32)) = in[3]
*a(p(u(o) + 4*32)) = in[4]
*a(p(u(o) + 5*32)) = in[5]
*a(p(u(o) + 6*32)) = in[6]
*a(p(u(o) + 7*32)) = in[7]
}
//
// compress <= chunkLen bytes in one shot
//
func compressAll(d *Digest, in []byte, flags uint32, key [8]uint32) {
var compressed [16]uint32
d.chain = key
d.flags = flags | consts.Flag_ChunkStart
for len(in) > 64 {
buf := (*[64]byte)(unsafe.Pointer(&in[0]))
var block *[16]uint32
if consts.OptimizeLittleEndian {
block = (*[16]uint32)(unsafe.Pointer(buf))
} else {
block = &d.block
utils.BytesToWords(buf, block)
}
alg.Compress(&d.chain, block, 0, consts.BlockLen, d.flags, &compressed)
d.chain = *(*[8]uint32)(unsafe.Pointer(&compressed[0]))
d.flags &^= consts.Flag_ChunkStart
in = in[64:]
}
if consts.OptimizeLittleEndian {
copy((*[64]byte)(unsafe.Pointer(&d.block[0]))[:], in)
} else {
var tmp [64]byte
copy(tmp[:], in)
utils.BytesToWords(&tmp, &d.block)
}
d.blen = uint32(len(in))
d.flags |= consts.Flag_ChunkEnd | consts.Flag_Root
}
+100
View File
@@ -0,0 +1,100 @@
package blake3
import (
"fmt"
"io"
"unsafe"
"github.com/zeebo/blake3/internal/alg"
"github.com/zeebo/blake3/internal/consts"
"github.com/zeebo/blake3/internal/utils"
)
// Digest captures the state of a Hasher allowing reading and seeking through
// the output stream.
type Digest struct {
counter uint64
chain [8]uint32
block [16]uint32
blen uint32
flags uint32
buf [16]uint32
bufn int
}
// Read reads data frm the hasher into out. It always fills the entire buffer and
// never errors. The stream will wrap around when reading past 2^64 bytes.
func (d *Digest) Read(p []byte) (n int, err error) {
n = len(p)
if d.bufn > 0 {
n := d.slowCopy(p)
p = p[n:]
d.bufn -= n
}
for len(p) >= 64 {
d.fillBuf()
if consts.OptimizeLittleEndian {
*(*[64]byte)(unsafe.Pointer(&p[0])) = *(*[64]byte)(unsafe.Pointer(&d.buf[0]))
} else {
utils.WordsToBytes(&d.buf, p)
}
p = p[64:]
d.bufn = 0
}
if len(p) == 0 {
return n, nil
}
d.fillBuf()
d.bufn -= d.slowCopy(p)
return n, nil
}
// Seek sets the position to the provided location. Only SeekStart and
// SeekCurrent are allowed.
func (d *Digest) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekStart:
case io.SeekEnd:
return 0, fmt.Errorf("seek from end not supported")
case io.SeekCurrent:
offset += int64(consts.BlockLen*d.counter) - int64(d.bufn)
default:
return 0, fmt.Errorf("invalid whence: %d", whence)
}
if offset < 0 {
return 0, fmt.Errorf("seek before start")
}
d.setPosition(uint64(offset))
return offset, nil
}
func (d *Digest) setPosition(pos uint64) {
d.counter = pos / consts.BlockLen
d.fillBuf()
d.bufn -= int(pos % consts.BlockLen)
}
func (d *Digest) slowCopy(p []byte) (n int) {
off := uint(consts.BlockLen-d.bufn) % consts.BlockLen
if consts.OptimizeLittleEndian {
n = copy(p, (*[consts.BlockLen]byte)(unsafe.Pointer(&d.buf[0]))[off:])
} else {
var tmp [consts.BlockLen]byte
utils.WordsToBytes(&d.buf, tmp[:])
n = copy(p, tmp[off:])
}
return n
}
func (d *Digest) fillBuf() {
alg.Compress(&d.chain, &d.block, d.counter, d.blen, d.flags, &d.buf)
d.counter++
d.bufn = consts.BlockLen
}
+18
View File
@@ -0,0 +1,18 @@
package alg
import (
"github.com/zeebo/blake3/internal/alg/compress"
"github.com/zeebo/blake3/internal/alg/hash"
)
func HashF(input *[8192]byte, length, counter uint64, flags uint32, key *[8]uint32, out *[64]uint32, chain *[8]uint32) {
hash.HashF(input, length, counter, flags, key, out, chain)
}
func HashP(left, right *[64]uint32, flags uint32, key *[8]uint32, out *[64]uint32, n int) {
hash.HashP(left, right, flags, key, out, n)
}
func Compress(chain *[8]uint32, block *[16]uint32, counter uint64, blen uint32, flags uint32, out *[16]uint32) {
compress.Compress(chain, block, counter, blen, flags, out)
}
+15
View File
@@ -0,0 +1,15 @@
package compress
import (
"github.com/zeebo/blake3/internal/alg/compress/compress_pure"
"github.com/zeebo/blake3/internal/alg/compress/compress_sse41"
"github.com/zeebo/blake3/internal/consts"
)
func Compress(chain *[8]uint32, block *[16]uint32, counter uint64, blen uint32, flags uint32, out *[16]uint32) {
if consts.HasSSE41 {
compress_sse41.Compress(chain, block, counter, blen, flags, out)
} else {
compress_pure.Compress(chain, block, counter, blen, flags, out)
}
}
@@ -0,0 +1,135 @@
package compress_pure
import (
"math/bits"
"github.com/zeebo/blake3/internal/consts"
)
func Compress(
chain *[8]uint32,
block *[16]uint32,
counter uint64,
blen uint32,
flags uint32,
out *[16]uint32,
) {
*out = [16]uint32{
chain[0], chain[1], chain[2], chain[3],
chain[4], chain[5], chain[6], chain[7],
consts.IV0, consts.IV1, consts.IV2, consts.IV3,
uint32(counter), uint32(counter >> 32), blen, flags,
}
rcompress(out, block)
}
func g(a, b, c, d, mx, my uint32) (uint32, uint32, uint32, uint32) {
a += b + mx
d = bits.RotateLeft32(d^a, -16)
c += d
b = bits.RotateLeft32(b^c, -12)
a += b + my
d = bits.RotateLeft32(d^a, -8)
c += d
b = bits.RotateLeft32(b^c, -7)
return a, b, c, d
}
func rcompress(s *[16]uint32, m *[16]uint32) {
const (
a = 10
b = 11
c = 12
d = 13
e = 14
f = 15
)
s0, s1, s2, s3 := s[0+0], s[0+1], s[0+2], s[0+3]
s4, s5, s6, s7 := s[0+4], s[0+5], s[0+6], s[0+7]
s8, s9, sa, sb := s[8+0], s[8+1], s[8+2], s[8+3]
sc, sd, se, sf := s[8+4], s[8+5], s[8+6], s[8+7]
s0, s4, s8, sc = g(s0, s4, s8, sc, m[0], m[1])
s1, s5, s9, sd = g(s1, s5, s9, sd, m[2], m[3])
s2, s6, sa, se = g(s2, s6, sa, se, m[4], m[5])
s3, s7, sb, sf = g(s3, s7, sb, sf, m[6], m[7])
s0, s5, sa, sf = g(s0, s5, sa, sf, m[8], m[9])
s1, s6, sb, sc = g(s1, s6, sb, sc, m[a], m[b])
s2, s7, s8, sd = g(s2, s7, s8, sd, m[c], m[d])
s3, s4, s9, se = g(s3, s4, s9, se, m[e], m[f])
s0, s4, s8, sc = g(s0, s4, s8, sc, m[2], m[6])
s1, s5, s9, sd = g(s1, s5, s9, sd, m[3], m[a])
s2, s6, sa, se = g(s2, s6, sa, se, m[7], m[0])
s3, s7, sb, sf = g(s3, s7, sb, sf, m[4], m[d])
s0, s5, sa, sf = g(s0, s5, sa, sf, m[1], m[b])
s1, s6, sb, sc = g(s1, s6, sb, sc, m[c], m[5])
s2, s7, s8, sd = g(s2, s7, s8, sd, m[9], m[e])
s3, s4, s9, se = g(s3, s4, s9, se, m[f], m[8])
s0, s4, s8, sc = g(s0, s4, s8, sc, m[3], m[4])
s1, s5, s9, sd = g(s1, s5, s9, sd, m[a], m[c])
s2, s6, sa, se = g(s2, s6, sa, se, m[d], m[2])
s3, s7, sb, sf = g(s3, s7, sb, sf, m[7], m[e])
s0, s5, sa, sf = g(s0, s5, sa, sf, m[6], m[5])
s1, s6, sb, sc = g(s1, s6, sb, sc, m[9], m[0])
s2, s7, s8, sd = g(s2, s7, s8, sd, m[b], m[f])
s3, s4, s9, se = g(s3, s4, s9, se, m[8], m[1])
s0, s4, s8, sc = g(s0, s4, s8, sc, m[a], m[7])
s1, s5, s9, sd = g(s1, s5, s9, sd, m[c], m[9])
s2, s6, sa, se = g(s2, s6, sa, se, m[e], m[3])
s3, s7, sb, sf = g(s3, s7, sb, sf, m[d], m[f])
s0, s5, sa, sf = g(s0, s5, sa, sf, m[4], m[0])
s1, s6, sb, sc = g(s1, s6, sb, sc, m[b], m[2])
s2, s7, s8, sd = g(s2, s7, s8, sd, m[5], m[8])
s3, s4, s9, se = g(s3, s4, s9, se, m[1], m[6])
s0, s4, s8, sc = g(s0, s4, s8, sc, m[c], m[d])
s1, s5, s9, sd = g(s1, s5, s9, sd, m[9], m[b])
s2, s6, sa, se = g(s2, s6, sa, se, m[f], m[a])
s3, s7, sb, sf = g(s3, s7, sb, sf, m[e], m[8])
s0, s5, sa, sf = g(s0, s5, sa, sf, m[7], m[2])
s1, s6, sb, sc = g(s1, s6, sb, sc, m[5], m[3])
s2, s7, s8, sd = g(s2, s7, s8, sd, m[0], m[1])
s3, s4, s9, se = g(s3, s4, s9, se, m[6], m[4])
s0, s4, s8, sc = g(s0, s4, s8, sc, m[9], m[e])
s1, s5, s9, sd = g(s1, s5, s9, sd, m[b], m[5])
s2, s6, sa, se = g(s2, s6, sa, se, m[8], m[c])
s3, s7, sb, sf = g(s3, s7, sb, sf, m[f], m[1])
s0, s5, sa, sf = g(s0, s5, sa, sf, m[d], m[3])
s1, s6, sb, sc = g(s1, s6, sb, sc, m[0], m[a])
s2, s7, s8, sd = g(s2, s7, s8, sd, m[2], m[6])
s3, s4, s9, se = g(s3, s4, s9, se, m[4], m[7])
s0, s4, s8, sc = g(s0, s4, s8, sc, m[b], m[f])
s1, s5, s9, sd = g(s1, s5, s9, sd, m[5], m[0])
s2, s6, sa, se = g(s2, s6, sa, se, m[1], m[9])
s3, s7, sb, sf = g(s3, s7, sb, sf, m[8], m[6])
s0, s5, sa, sf = g(s0, s5, sa, sf, m[e], m[a])
s1, s6, sb, sc = g(s1, s6, sb, sc, m[2], m[c])
s2, s7, s8, sd = g(s2, s7, s8, sd, m[3], m[4])
s3, s4, s9, se = g(s3, s4, s9, se, m[7], m[d])
s[8+0] = s8 ^ s[0]
s[8+1] = s9 ^ s[1]
s[8+2] = sa ^ s[2]
s[8+3] = sb ^ s[3]
s[8+4] = sc ^ s[4]
s[8+5] = sd ^ s[5]
s[8+6] = se ^ s[6]
s[8+7] = sf ^ s[7]
s[0] = s0 ^ s8
s[1] = s1 ^ s9
s[2] = s2 ^ sa
s[3] = s3 ^ sb
s[4] = s4 ^ sc
s[5] = s5 ^ sd
s[6] = s6 ^ se
s[7] = s7 ^ sf
}
@@ -0,0 +1,560 @@
// Code generated by command: go run compress.go. DO NOT EDIT.
#include "textflag.h"
DATA iv<>+0(SB)/4, $0x6a09e667
DATA iv<>+4(SB)/4, $0xbb67ae85
DATA iv<>+8(SB)/4, $0x3c6ef372
DATA iv<>+12(SB)/4, $0xa54ff53a
DATA iv<>+16(SB)/4, $0x510e527f
DATA iv<>+20(SB)/4, $0x9b05688c
DATA iv<>+24(SB)/4, $0x1f83d9ab
DATA iv<>+28(SB)/4, $0x5be0cd19
GLOBL iv<>(SB), RODATA|NOPTR, $32
DATA rot16_shuf<>+0(SB)/1, $0x02
DATA rot16_shuf<>+1(SB)/1, $0x03
DATA rot16_shuf<>+2(SB)/1, $0x00
DATA rot16_shuf<>+3(SB)/1, $0x01
DATA rot16_shuf<>+4(SB)/1, $0x06
DATA rot16_shuf<>+5(SB)/1, $0x07
DATA rot16_shuf<>+6(SB)/1, $0x04
DATA rot16_shuf<>+7(SB)/1, $0x05
DATA rot16_shuf<>+8(SB)/1, $0x0a
DATA rot16_shuf<>+9(SB)/1, $0x0b
DATA rot16_shuf<>+10(SB)/1, $0x08
DATA rot16_shuf<>+11(SB)/1, $0x09
DATA rot16_shuf<>+12(SB)/1, $0x0e
DATA rot16_shuf<>+13(SB)/1, $0x0f
DATA rot16_shuf<>+14(SB)/1, $0x0c
DATA rot16_shuf<>+15(SB)/1, $0x0d
DATA rot16_shuf<>+16(SB)/1, $0x12
DATA rot16_shuf<>+17(SB)/1, $0x13
DATA rot16_shuf<>+18(SB)/1, $0x10
DATA rot16_shuf<>+19(SB)/1, $0x11
DATA rot16_shuf<>+20(SB)/1, $0x16
DATA rot16_shuf<>+21(SB)/1, $0x17
DATA rot16_shuf<>+22(SB)/1, $0x14
DATA rot16_shuf<>+23(SB)/1, $0x15
DATA rot16_shuf<>+24(SB)/1, $0x1a
DATA rot16_shuf<>+25(SB)/1, $0x1b
DATA rot16_shuf<>+26(SB)/1, $0x18
DATA rot16_shuf<>+27(SB)/1, $0x19
DATA rot16_shuf<>+28(SB)/1, $0x1e
DATA rot16_shuf<>+29(SB)/1, $0x1f
DATA rot16_shuf<>+30(SB)/1, $0x1c
DATA rot16_shuf<>+31(SB)/1, $0x1d
GLOBL rot16_shuf<>(SB), RODATA|NOPTR, $32
DATA rot8_shuf<>+0(SB)/1, $0x01
DATA rot8_shuf<>+1(SB)/1, $0x02
DATA rot8_shuf<>+2(SB)/1, $0x03
DATA rot8_shuf<>+3(SB)/1, $0x00
DATA rot8_shuf<>+4(SB)/1, $0x05
DATA rot8_shuf<>+5(SB)/1, $0x06
DATA rot8_shuf<>+6(SB)/1, $0x07
DATA rot8_shuf<>+7(SB)/1, $0x04
DATA rot8_shuf<>+8(SB)/1, $0x09
DATA rot8_shuf<>+9(SB)/1, $0x0a
DATA rot8_shuf<>+10(SB)/1, $0x0b
DATA rot8_shuf<>+11(SB)/1, $0x08
DATA rot8_shuf<>+12(SB)/1, $0x0d
DATA rot8_shuf<>+13(SB)/1, $0x0e
DATA rot8_shuf<>+14(SB)/1, $0x0f
DATA rot8_shuf<>+15(SB)/1, $0x0c
DATA rot8_shuf<>+16(SB)/1, $0x11
DATA rot8_shuf<>+17(SB)/1, $0x12
DATA rot8_shuf<>+18(SB)/1, $0x13
DATA rot8_shuf<>+19(SB)/1, $0x10
DATA rot8_shuf<>+20(SB)/1, $0x15
DATA rot8_shuf<>+21(SB)/1, $0x16
DATA rot8_shuf<>+22(SB)/1, $0x17
DATA rot8_shuf<>+23(SB)/1, $0x14
DATA rot8_shuf<>+24(SB)/1, $0x19
DATA rot8_shuf<>+25(SB)/1, $0x1a
DATA rot8_shuf<>+26(SB)/1, $0x1b
DATA rot8_shuf<>+27(SB)/1, $0x18
DATA rot8_shuf<>+28(SB)/1, $0x1d
DATA rot8_shuf<>+29(SB)/1, $0x1e
DATA rot8_shuf<>+30(SB)/1, $0x1f
DATA rot8_shuf<>+31(SB)/1, $0x1c
GLOBL rot8_shuf<>(SB), RODATA|NOPTR, $32
// func Compress(chain *[8]uint32, block *[16]uint32, counter uint64, blen uint32, flags uint32, out *[16]uint32)
// Requires: SSE, SSE2, SSE4.1, SSSE3
TEXT ·Compress(SB), NOSPLIT, $0-40
MOVQ chain+0(FP), AX
MOVQ block+8(FP), CX
MOVQ counter+16(FP), DX
MOVL blen+24(FP), BX
MOVL flags+28(FP), SI
MOVQ out+32(FP), DI
MOVUPS (AX), X0
MOVUPS 16(AX), X1
MOVUPS iv<>+0(SB), X2
PINSRD $0x00, DX, X3
SHRQ $0x20, DX
PINSRD $0x01, DX, X3
PINSRD $0x02, BX, X3
PINSRD $0x03, SI, X3
MOVUPS (CX), X4
MOVUPS 16(CX), X5
MOVUPS 32(CX), X6
MOVUPS 48(CX), X7
MOVUPS rot16_shuf<>+0(SB), X8
MOVUPS rot8_shuf<>+0(SB), X9
// round 1
MOVAPS X4, X10
SHUFPS $0x88, X5, X10
PADDD X10, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X11
PSRLL $0x0c, X1
PSLLL $0x14, X11
POR X11, X1
MOVAPS X4, X4
SHUFPS $0xdd, X5, X4
PADDD X4, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X5
PSRLL $0x07, X1
PSLLL $0x19, X5
POR X5, X1
PSHUFD $0x93, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x39, X2, X2
MOVAPS X6, X5
SHUFPS $0x88, X7, X5
SHUFPS $0x93, X5, X5
PADDD X5, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X11
PSRLL $0x0c, X1
PSLLL $0x14, X11
POR X11, X1
MOVAPS X6, X6
SHUFPS $0xdd, X7, X6
SHUFPS $0x93, X6, X6
PADDD X6, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X7
PSRLL $0x07, X1
PSLLL $0x19, X7
POR X7, X1
PSHUFD $0x39, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x93, X2, X2
// round 2
MOVAPS X10, X7
SHUFPS $0xd6, X4, X7
SHUFPS $0x39, X7, X7
PADDD X7, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X11
PSRLL $0x0c, X1
PSLLL $0x14, X11
POR X11, X1
MOVAPS X5, X11
SHUFPS $0xfa, X6, X11
PSHUFD $0x0f, X10, X10
PBLENDW $0x33, X10, X11
PADDD X11, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X10
PSRLL $0x07, X1
PSLLL $0x19, X10
POR X10, X1
PSHUFD $0x93, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x39, X2, X2
MOVAPS X6, X12
PUNPCKLLQ X4, X12
PBLENDW $0xc0, X5, X12
SHUFPS $0xb4, X12, X12
PADDD X12, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X10
PSRLL $0x0c, X1
PSLLL $0x14, X10
POR X10, X1
MOVAPS X4, X10
PUNPCKHLQ X6, X10
MOVAPS X5, X4
PUNPCKLLQ X10, X4
SHUFPS $0x1e, X4, X4
PADDD X4, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X5
PSRLL $0x07, X1
PSLLL $0x19, X5
POR X5, X1
PSHUFD $0x39, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x93, X2, X2
// round 3
MOVAPS X7, X5
SHUFPS $0xd6, X11, X5
SHUFPS $0x39, X5, X5
PADDD X5, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X6
PSRLL $0x0c, X1
PSLLL $0x14, X6
POR X6, X1
MOVAPS X12, X6
SHUFPS $0xfa, X4, X6
PSHUFD $0x0f, X7, X7
PBLENDW $0x33, X7, X6
PADDD X6, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X7
PSRLL $0x07, X1
PSLLL $0x19, X7
POR X7, X1
PSHUFD $0x93, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x39, X2, X2
MOVAPS X4, X10
PUNPCKLLQ X11, X10
PBLENDW $0xc0, X12, X10
SHUFPS $0xb4, X10, X10
PADDD X10, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X7
PSRLL $0x0c, X1
PSLLL $0x14, X7
POR X7, X1
MOVAPS X11, X7
PUNPCKHLQ X4, X7
MOVAPS X12, X4
PUNPCKLLQ X7, X4
SHUFPS $0x1e, X4, X4
PADDD X4, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X7
PSRLL $0x07, X1
PSLLL $0x19, X7
POR X7, X1
PSHUFD $0x39, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x93, X2, X2
// round 4
MOVAPS X5, X7
SHUFPS $0xd6, X6, X7
SHUFPS $0x39, X7, X7
PADDD X7, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X11
PSRLL $0x0c, X1
PSLLL $0x14, X11
POR X11, X1
MOVAPS X10, X11
SHUFPS $0xfa, X4, X11
PSHUFD $0x0f, X5, X5
PBLENDW $0x33, X5, X11
PADDD X11, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X5
PSRLL $0x07, X1
PSLLL $0x19, X5
POR X5, X1
PSHUFD $0x93, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x39, X2, X2
MOVAPS X4, X12
PUNPCKLLQ X6, X12
PBLENDW $0xc0, X10, X12
SHUFPS $0xb4, X12, X12
PADDD X12, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X5
PSRLL $0x0c, X1
PSLLL $0x14, X5
POR X5, X1
MOVAPS X6, X5
PUNPCKHLQ X4, X5
MOVAPS X10, X4
PUNPCKLLQ X5, X4
SHUFPS $0x1e, X4, X4
PADDD X4, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X5
PSRLL $0x07, X1
PSLLL $0x19, X5
POR X5, X1
PSHUFD $0x39, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x93, X2, X2
// round 5
MOVAPS X7, X5
SHUFPS $0xd6, X11, X5
SHUFPS $0x39, X5, X5
PADDD X5, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X6
PSRLL $0x0c, X1
PSLLL $0x14, X6
POR X6, X1
MOVAPS X12, X6
SHUFPS $0xfa, X4, X6
PSHUFD $0x0f, X7, X7
PBLENDW $0x33, X7, X6
PADDD X6, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X7
PSRLL $0x07, X1
PSLLL $0x19, X7
POR X7, X1
PSHUFD $0x93, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x39, X2, X2
MOVAPS X4, X10
PUNPCKLLQ X11, X10
PBLENDW $0xc0, X12, X10
SHUFPS $0xb4, X10, X10
PADDD X10, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X7
PSRLL $0x0c, X1
PSLLL $0x14, X7
POR X7, X1
MOVAPS X11, X7
PUNPCKHLQ X4, X7
MOVAPS X12, X4
PUNPCKLLQ X7, X4
SHUFPS $0x1e, X4, X4
PADDD X4, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X7
PSRLL $0x07, X1
PSLLL $0x19, X7
POR X7, X1
PSHUFD $0x39, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x93, X2, X2
// round 6
MOVAPS X5, X7
SHUFPS $0xd6, X6, X7
SHUFPS $0x39, X7, X7
PADDD X7, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X11
PSRLL $0x0c, X1
PSLLL $0x14, X11
POR X11, X1
MOVAPS X10, X11
SHUFPS $0xfa, X4, X11
PSHUFD $0x0f, X5, X5
PBLENDW $0x33, X5, X11
PADDD X11, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X5
PSRLL $0x07, X1
PSLLL $0x19, X5
POR X5, X1
PSHUFD $0x93, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x39, X2, X2
MOVAPS X4, X12
PUNPCKLLQ X6, X12
PBLENDW $0xc0, X10, X12
SHUFPS $0xb4, X12, X12
PADDD X12, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X5
PSRLL $0x0c, X1
PSLLL $0x14, X5
POR X5, X1
MOVAPS X6, X5
PUNPCKHLQ X4, X5
MOVAPS X10, X4
PUNPCKLLQ X5, X4
SHUFPS $0x1e, X4, X4
PADDD X4, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X5
PSRLL $0x07, X1
PSLLL $0x19, X5
POR X5, X1
PSHUFD $0x39, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x93, X2, X2
// round 7
MOVAPS X7, X5
SHUFPS $0xd6, X11, X5
SHUFPS $0x39, X5, X5
PADDD X5, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X5
PSRLL $0x0c, X1
PSLLL $0x14, X5
POR X5, X1
MOVAPS X12, X5
SHUFPS $0xfa, X4, X5
PSHUFD $0x0f, X7, X6
PBLENDW $0x33, X6, X5
PADDD X5, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X5
PSRLL $0x07, X1
PSLLL $0x19, X5
POR X5, X1
PSHUFD $0x93, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x39, X2, X2
MOVAPS X4, X5
PUNPCKLLQ X11, X5
PBLENDW $0xc0, X12, X5
SHUFPS $0xb4, X5, X5
PADDD X5, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X8, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X5
PSRLL $0x0c, X1
PSLLL $0x14, X5
POR X5, X1
MOVAPS X11, X6
PUNPCKHLQ X4, X6
MOVAPS X12, X4
PUNPCKLLQ X6, X4
SHUFPS $0x1e, X4, X4
PADDD X4, X0
PADDD X1, X0
PXOR X0, X3
PSHUFB X9, X3
PADDD X3, X2
PXOR X2, X1
MOVAPS X1, X4
PSRLL $0x07, X1
PSLLL $0x19, X4
POR X4, X1
PSHUFD $0x39, X0, X0
PSHUFD $0x4e, X3, X3
PSHUFD $0x93, X2, X2
// finalize
PXOR X2, X0
PXOR X3, X1
MOVUPS (AX), X4
PXOR X4, X2
MOVUPS 16(AX), X4
PXOR X4, X3
MOVUPS X0, (DI)
MOVUPS X1, 16(DI)
MOVUPS X2, 32(DI)
MOVUPS X3, 48(DI)
RET
@@ -0,0 +1,10 @@
//go:build !amd64
// +build !amd64
package compress_sse41
import "github.com/zeebo/blake3/internal/alg/compress/compress_pure"
func Compress(chain *[8]uint32, block *[16]uint32, counter uint64, blen uint32, flags uint32, out *[16]uint32) {
compress_pure.Compress(chain, block, counter, blen, flags, out)
}
@@ -0,0 +1,7 @@
//go:build amd64
// +build amd64
package compress_sse41
//go:noescape
func Compress(chain *[8]uint32, block *[16]uint32, counter uint64, blen uint32, flags uint32, out *[16]uint32)
+23
View File
@@ -0,0 +1,23 @@
package hash
import (
"github.com/zeebo/blake3/internal/alg/hash/hash_avx2"
"github.com/zeebo/blake3/internal/alg/hash/hash_pure"
"github.com/zeebo/blake3/internal/consts"
)
func HashF(input *[8192]byte, length, counter uint64, flags uint32, key *[8]uint32, out *[64]uint32, chain *[8]uint32) {
if consts.HasAVX2 && length > 2*consts.ChunkLen {
hash_avx2.HashF(input, length, counter, flags, key, out, chain)
} else {
hash_pure.HashF(input, length, counter, flags, key, out, chain)
}
}
func HashP(left, right *[64]uint32, flags uint32, key *[8]uint32, out *[64]uint32, n int) {
if consts.HasAVX2 && n >= 2 {
hash_avx2.HashP(left, right, flags, key, out, n)
} else {
hash_pure.HashP(left, right, flags, key, out, n)
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
//go:build !amd64
// +build !amd64
package hash_avx2
import "github.com/zeebo/blake3/internal/alg/hash/hash_pure"
func HashF(input *[8192]byte, length, counter uint64, flags uint32, key *[8]uint32, out *[64]uint32, chain *[8]uint32) {
hash_pure.HashF(input, length, counter, flags, key, out, chain)
}
func HashP(left, right *[64]uint32, flags uint32, key *[8]uint32, out *[64]uint32, n int) {
hash_pure.HashP(left, right, flags, key, out, n)
}
+10
View File
@@ -0,0 +1,10 @@
//go:build amd64
// +build amd64
package hash_avx2
//go:noescape
func HashF(input *[8192]byte, length, counter uint64, flags uint32, key *[8]uint32, out *[64]uint32, chain *[8]uint32)
//go:noescape
func HashP(left, right *[64]uint32, flags uint32, key *[8]uint32, out *[64]uint32, n int)
+56
View File
@@ -0,0 +1,56 @@
package hash_pure
import (
"unsafe"
"github.com/zeebo/blake3/internal/alg/compress"
"github.com/zeebo/blake3/internal/consts"
"github.com/zeebo/blake3/internal/utils"
)
func HashF(input *[8192]byte, length, counter uint64, flags uint32, key *[8]uint32, out *[64]uint32, chain *[8]uint32) {
var tmp [16]uint32
for i := uint64(0); consts.ChunkLen*i < length && i < 8; i++ {
bchain := *key
bflags := flags | consts.Flag_ChunkStart
start := consts.ChunkLen * i
for n := uint64(0); n < 16; n++ {
if n == 15 {
bflags |= consts.Flag_ChunkEnd
}
if start+64*n >= length {
break
}
if start+64+64*n >= length {
*chain = bchain
}
var blockPtr *[16]uint32
if consts.OptimizeLittleEndian {
blockPtr = (*[16]uint32)(unsafe.Pointer(&input[consts.ChunkLen*i+consts.BlockLen*n]))
} else {
var block [16]uint32
utils.BytesToWords((*[64]uint8)(unsafe.Pointer(&input[consts.ChunkLen*i+consts.BlockLen*n])), &block)
blockPtr = &block
}
compress.Compress(&bchain, blockPtr, counter, consts.BlockLen, bflags, &tmp)
bchain = *(*[8]uint32)(unsafe.Pointer(&tmp[0]))
bflags = flags
}
out[i+0] = bchain[0]
out[i+8] = bchain[1]
out[i+16] = bchain[2]
out[i+24] = bchain[3]
out[i+32] = bchain[4]
out[i+40] = bchain[5]
out[i+48] = bchain[6]
out[i+56] = bchain[7]
counter++
}
}
+38
View File
@@ -0,0 +1,38 @@
package hash_pure
import "github.com/zeebo/blake3/internal/alg/compress"
func HashP(left, right *[64]uint32, flags uint32, key *[8]uint32, out *[64]uint32, n int) {
var tmp [16]uint32
var block [16]uint32
for i := 0; i < n && i < 8; i++ {
block[0] = left[i+0]
block[1] = left[i+8]
block[2] = left[i+16]
block[3] = left[i+24]
block[4] = left[i+32]
block[5] = left[i+40]
block[6] = left[i+48]
block[7] = left[i+56]
block[8] = right[i+0]
block[9] = right[i+8]
block[10] = right[i+16]
block[11] = right[i+24]
block[12] = right[i+32]
block[13] = right[i+40]
block[14] = right[i+48]
block[15] = right[i+56]
compress.Compress(key, &block, 0, 64, flags, &tmp)
out[i+0] = tmp[0]
out[i+8] = tmp[1]
out[i+16] = tmp[2]
out[i+24] = tmp[3]
out[i+32] = tmp[4]
out[i+40] = tmp[5]
out[i+48] = tmp[6]
out[i+56] = tmp[7]
}
}
+29
View File
@@ -0,0 +1,29 @@
package consts
var IV = [...]uint32{IV0, IV1, IV2, IV3, IV4, IV5, IV6, IV7}
const (
IV0 = 0x6A09E667
IV1 = 0xBB67AE85
IV2 = 0x3C6EF372
IV3 = 0xA54FF53A
IV4 = 0x510E527F
IV5 = 0x9B05688C
IV6 = 0x1F83D9AB
IV7 = 0x5BE0CD19
)
const (
Flag_ChunkStart uint32 = 1 << 0
Flag_ChunkEnd uint32 = 1 << 1
Flag_Parent uint32 = 1 << 2
Flag_Root uint32 = 1 << 3
Flag_Keyed uint32 = 1 << 4
Flag_DeriveKeyContext uint32 = 1 << 5
Flag_DeriveKeyMaterial uint32 = 1 << 6
)
const (
BlockLen = 64
ChunkLen = 1024
)
+17
View File
@@ -0,0 +1,17 @@
package consts
import (
"os"
"github.com/klauspost/cpuid/v2"
)
var (
HasAVX2 = cpuid.CPU.Has(cpuid.AVX2) &&
os.Getenv("BLAKE3_DISABLE_AVX2") == "" &&
os.Getenv("BLAKE3_PUREGO") == ""
HasSSE41 = cpuid.CPU.Has(cpuid.SSE4) &&
os.Getenv("BLAKE3_DISABLE_SSE41") == "" &&
os.Getenv("BLAKE3_PUREGO") == ""
)
+6
View File
@@ -0,0 +1,6 @@
//go:build amd64 || 386 || arm || arm64 || mipsle || mips64le || ppc64le || riscv64 || wasm
// +build amd64 386 arm arm64 mipsle mips64le ppc64le riscv64 wasm
package consts
const OptimizeLittleEndian = true
+6
View File
@@ -0,0 +1,6 @@
//go:build !amd64 && !386 && !arm && !arm64 && !mipsle && !mips64le && !ppc64le && !riscv64 && !wasm
// +build !amd64,!386,!arm,!arm64,!mipsle,!mips64le,!ppc64le,!riscv64,!wasm
package consts
const OptimizeLittleEndian = false
+60
View File
@@ -0,0 +1,60 @@
package utils
import (
"encoding/binary"
"unsafe"
)
func SliceToArray32(bytes []byte) *[32]uint8 { return (*[32]uint8)(unsafe.Pointer(&bytes[0])) }
func SliceToArray64(bytes []byte) *[64]uint8 { return (*[64]uint8)(unsafe.Pointer(&bytes[0])) }
func BytesToWords(bytes *[64]uint8, words *[16]uint32) {
words[0] = binary.LittleEndian.Uint32(bytes[0*4:])
words[1] = binary.LittleEndian.Uint32(bytes[1*4:])
words[2] = binary.LittleEndian.Uint32(bytes[2*4:])
words[3] = binary.LittleEndian.Uint32(bytes[3*4:])
words[4] = binary.LittleEndian.Uint32(bytes[4*4:])
words[5] = binary.LittleEndian.Uint32(bytes[5*4:])
words[6] = binary.LittleEndian.Uint32(bytes[6*4:])
words[7] = binary.LittleEndian.Uint32(bytes[7*4:])
words[8] = binary.LittleEndian.Uint32(bytes[8*4:])
words[9] = binary.LittleEndian.Uint32(bytes[9*4:])
words[10] = binary.LittleEndian.Uint32(bytes[10*4:])
words[11] = binary.LittleEndian.Uint32(bytes[11*4:])
words[12] = binary.LittleEndian.Uint32(bytes[12*4:])
words[13] = binary.LittleEndian.Uint32(bytes[13*4:])
words[14] = binary.LittleEndian.Uint32(bytes[14*4:])
words[15] = binary.LittleEndian.Uint32(bytes[15*4:])
}
func WordsToBytes(words *[16]uint32, bytes []byte) {
bytes = bytes[:64]
binary.LittleEndian.PutUint32(bytes[0*4:1*4], words[0])
binary.LittleEndian.PutUint32(bytes[1*4:2*4], words[1])
binary.LittleEndian.PutUint32(bytes[2*4:3*4], words[2])
binary.LittleEndian.PutUint32(bytes[3*4:4*4], words[3])
binary.LittleEndian.PutUint32(bytes[4*4:5*4], words[4])
binary.LittleEndian.PutUint32(bytes[5*4:6*4], words[5])
binary.LittleEndian.PutUint32(bytes[6*4:7*4], words[6])
binary.LittleEndian.PutUint32(bytes[7*4:8*4], words[7])
binary.LittleEndian.PutUint32(bytes[8*4:9*4], words[8])
binary.LittleEndian.PutUint32(bytes[9*4:10*4], words[9])
binary.LittleEndian.PutUint32(bytes[10*4:11*4], words[10])
binary.LittleEndian.PutUint32(bytes[11*4:12*4], words[11])
binary.LittleEndian.PutUint32(bytes[12*4:13*4], words[12])
binary.LittleEndian.PutUint32(bytes[13*4:14*4], words[13])
binary.LittleEndian.PutUint32(bytes[14*4:15*4], words[14])
binary.LittleEndian.PutUint32(bytes[15*4:16*4], words[15])
}
func KeyFromBytes(key []byte, out *[8]uint32) {
key = key[:32]
out[0] = binary.LittleEndian.Uint32(key[0:])
out[1] = binary.LittleEndian.Uint32(key[4:])
out[2] = binary.LittleEndian.Uint32(key[8:])
out[3] = binary.LittleEndian.Uint32(key[12:])
out[4] = binary.LittleEndian.Uint32(key[16:])
out[5] = binary.LittleEndian.Uint32(key[20:])
out[6] = binary.LittleEndian.Uint32(key[24:])
out[7] = binary.LittleEndian.Uint32(key[28:])
}

Some files were not shown because too many files have changed in this diff Show More