mirror of
https://github.com/caddyserver/caddy.git
synced 2025-11-09 08:13:12 -05:00
vendor: Update dependencies; add certmagic, update lego
This commit is contained in:
parent
e0f1a02c37
commit
a68b01080c
21
vendor/github.com/codahale/aesnicheck/LICENSE
generated
vendored
Normal file
21
vendor/github.com/codahale/aesnicheck/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Coda Hale
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
9
vendor/github.com/codahale/aesnicheck/asm_amd64.s
generated
vendored
Normal file
9
vendor/github.com/codahale/aesnicheck/asm_amd64.s
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// func HasAESNI() bool
|
||||||
|
TEXT ·HasAESNI(SB),$0
|
||||||
|
XORQ AX, AX
|
||||||
|
INCL AX
|
||||||
|
CPUID
|
||||||
|
SHRQ $25, CX
|
||||||
|
ANDQ $1, CX
|
||||||
|
MOVB CX, ret+0(FP)
|
||||||
|
RET
|
||||||
6
vendor/github.com/codahale/aesnicheck/check_asm.go
generated
vendored
Normal file
6
vendor/github.com/codahale/aesnicheck/check_asm.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// +build amd64
|
||||||
|
|
||||||
|
package aesnicheck
|
||||||
|
|
||||||
|
// HasAESNI returns whether AES-NI is supported by the CPU.
|
||||||
|
func HasAESNI() bool
|
||||||
8
vendor/github.com/codahale/aesnicheck/check_generic.go
generated
vendored
Normal file
8
vendor/github.com/codahale/aesnicheck/check_generic.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// +build !amd64
|
||||||
|
|
||||||
|
package aesnicheck
|
||||||
|
|
||||||
|
// HasAESNI returns whether AES-NI is supported by the CPU.
|
||||||
|
func HasAESNI() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
22
vendor/github.com/codahale/aesnicheck/cmd/aesnicheck/aesnicheck.go
generated
vendored
Normal file
22
vendor/github.com/codahale/aesnicheck/cmd/aesnicheck/aesnicheck.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Command aesnicheck queries the CPU for AES-NI support. If AES-NI is supported,
|
||||||
|
// aesnicheck will print "supported" and exit with a status of 0. If AES-NI is
|
||||||
|
// not supported, aesnicheck will print "unsupported" and exit with a status of
|
||||||
|
// -1.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/codahale/aesnicheck"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if aesnicheck.HasAESNI() {
|
||||||
|
fmt.Println("supported")
|
||||||
|
os.Exit(0)
|
||||||
|
} else {
|
||||||
|
fmt.Println("unsupported")
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
9
vendor/github.com/codahale/aesnicheck/docs.go
generated
vendored
Normal file
9
vendor/github.com/codahale/aesnicheck/docs.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Package aesnicheck provides a simple check to see if crypto/aes is using
|
||||||
|
// AES-NI instructions or if the AES transform is being done in software. AES-NI
|
||||||
|
// is constant-time, which makes it impervious to cache-level timing attacks. For
|
||||||
|
// security-conscious deployments on public cloud infrastructure (Amazon EC2,
|
||||||
|
// Google Compute Engine, Microsoft Azure, etc.) this may be critical.
|
||||||
|
//
|
||||||
|
// See http://eprint.iacr.org/2014/248 for details on cross-VM timing attacks on
|
||||||
|
// AES keys.
|
||||||
|
package aesnicheck
|
||||||
201
vendor/github.com/mholt/certmagic/LICENSE.txt
generated
vendored
Normal file
201
vendor/github.com/mholt/certmagic/LICENSE.txt
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
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.
|
||||||
170
vendor/github.com/mholt/certmagic/cache.go
generated
vendored
Normal file
170
vendor/github.com/mholt/certmagic/cache.go
generated
vendored
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache is a structure that stores certificates in memory.
|
||||||
|
// Generally, there should only be one per process. However,
|
||||||
|
// complex applications that virtualize the concept of a
|
||||||
|
// "process" (such as Caddy, which virtualizes processes as
|
||||||
|
// "instances" so it can do graceful, in-memory reloads of
|
||||||
|
// its configuration) may use more of these per OS process.
|
||||||
|
//
|
||||||
|
// Using just one cache per process avoids duplication of
|
||||||
|
// certificates across multiple configurations and makes
|
||||||
|
// maintenance easier.
|
||||||
|
//
|
||||||
|
// An empty cache is INVALID and must not be used.
|
||||||
|
// Be sure to call NewCertificateCache to get one.
|
||||||
|
//
|
||||||
|
// These should be very long-lived values, and must not be
|
||||||
|
// copied. Before all references leave scope to be garbage
|
||||||
|
// collected, ensure you call Stop() to stop maintenance
|
||||||
|
// maintenance on the certificates stored in this cache.
|
||||||
|
type Cache struct {
|
||||||
|
// How often to check certificates for renewal
|
||||||
|
RenewInterval time.Duration
|
||||||
|
|
||||||
|
// How often to check if OCSP stapling needs updating
|
||||||
|
OCSPInterval time.Duration
|
||||||
|
|
||||||
|
// The storage implementation
|
||||||
|
storage Storage
|
||||||
|
|
||||||
|
// The cache is keyed by certificate hash
|
||||||
|
cache map[string]Certificate
|
||||||
|
|
||||||
|
// Protects the cache map
|
||||||
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
// Close this channel to cancel asset maintenance
|
||||||
|
stopChan chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCache returns a new, valid Cache backed by the
|
||||||
|
// given storage implementation. It also begins a
|
||||||
|
// maintenance goroutine for any managed certificates
|
||||||
|
// stored in this cache.
|
||||||
|
//
|
||||||
|
// See the godoc for Cache to use it properly.
|
||||||
|
//
|
||||||
|
// Note that all processes running in a cluster
|
||||||
|
// configuration must use the same storage value
|
||||||
|
// in order to share certificates. (A single storage
|
||||||
|
// value may be shared by multiple clusters as well.)
|
||||||
|
func NewCache(storage Storage) *Cache {
|
||||||
|
c := &Cache{
|
||||||
|
RenewInterval: DefaultRenewInterval,
|
||||||
|
OCSPInterval: DefaultOCSPInterval,
|
||||||
|
storage: storage,
|
||||||
|
cache: make(map[string]Certificate),
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
go c.maintainAssets()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the maintenance goroutine for
|
||||||
|
// certificates in certCache.
|
||||||
|
func (certCache *Cache) Stop() {
|
||||||
|
close(certCache.stopChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaceCertificate replaces oldCert with newCert in the cache, and
|
||||||
|
// updates all configs that are pointing to the old certificate to
|
||||||
|
// point to the new one instead. newCert must already be loaded into
|
||||||
|
// the cache (this method does NOT load it into the cache).
|
||||||
|
//
|
||||||
|
// Note that all the names on the old certificate will be deleted
|
||||||
|
// from the name lookup maps of each config, then all the names on
|
||||||
|
// the new certificate will be added to the lookup maps as long as
|
||||||
|
// they do not overwrite any entries.
|
||||||
|
//
|
||||||
|
// The newCert may be modified and its cache entry updated.
|
||||||
|
//
|
||||||
|
// This method is safe for concurrent use.
|
||||||
|
func (certCache *Cache) replaceCertificate(oldCert, newCert Certificate) error {
|
||||||
|
certCache.mu.Lock()
|
||||||
|
defer certCache.mu.Unlock()
|
||||||
|
|
||||||
|
// have all the configs that are pointing to the old
|
||||||
|
// certificate point to the new certificate instead
|
||||||
|
for _, cfg := range oldCert.configs {
|
||||||
|
// first delete all the name lookup entries that
|
||||||
|
// pointed to the old certificate
|
||||||
|
for name, certKey := range cfg.certificates {
|
||||||
|
if certKey == oldCert.Hash {
|
||||||
|
delete(cfg.certificates, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// then add name lookup entries for the names
|
||||||
|
// on the new certificate, but don't overwrite
|
||||||
|
// entries that may already exist, not only as
|
||||||
|
// a courtesy, but importantly: because if we
|
||||||
|
// overwrote a value here, and this config no
|
||||||
|
// longer pointed to a certain certificate in
|
||||||
|
// the cache, that certificate's list of configs
|
||||||
|
// referring to it would be incorrect; so just
|
||||||
|
// insert entries, don't overwrite any
|
||||||
|
for _, name := range newCert.Names {
|
||||||
|
if _, ok := cfg.certificates[name]; !ok {
|
||||||
|
cfg.certificates[name] = newCert.Hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// since caching a new certificate attaches only the config
|
||||||
|
// that loaded it, the new certificate needs to be given the
|
||||||
|
// list of all the configs that use it, so copy the list
|
||||||
|
// over from the old certificate to the new certificate
|
||||||
|
// in the cache
|
||||||
|
newCert.configs = oldCert.configs
|
||||||
|
certCache.cache[newCert.Hash] = newCert
|
||||||
|
|
||||||
|
// finally, delete the old certificate from the cache
|
||||||
|
delete(certCache.cache, oldCert.Hash)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reloadManagedCertificate reloads the certificate corresponding to the name(s)
|
||||||
|
// on oldCert into the cache, from storage. This also replaces the old certificate
|
||||||
|
// with the new one, so that all configurations that used the old cert now point
|
||||||
|
// to the new cert.
|
||||||
|
func (certCache *Cache) reloadManagedCertificate(oldCert Certificate) error {
|
||||||
|
// get the certificate from storage and cache it
|
||||||
|
newCert, err := oldCert.configs[0].CacheManagedCertificate(oldCert.Names[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to reload certificate for %v into cache: %v", oldCert.Names, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// and replace the old certificate with the new one
|
||||||
|
err = certCache.replaceCertificate(oldCert, newCert)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("replacing certificate %v: %v", oldCert.Names, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultCache is a convenient, default certificate cache for
|
||||||
|
// use by this process when no other certificate cache is provided.
|
||||||
|
var defaultCache = NewCache(DefaultStorage)
|
||||||
353
vendor/github.com/mholt/certmagic/certificates.go
generated
vendored
Normal file
353
vendor/github.com/mholt/certmagic/certificates.go
generated
vendored
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ocsp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Certificate is a tls.Certificate with associated metadata tacked on.
|
||||||
|
// Even if the metadata can be obtained by parsing the certificate,
|
||||||
|
// we are more efficient by extracting the metadata onto this struct.
|
||||||
|
type Certificate struct {
|
||||||
|
tls.Certificate
|
||||||
|
|
||||||
|
// Names is the list of names this certificate is written for.
|
||||||
|
// The first is the CommonName (if any), the rest are SAN.
|
||||||
|
Names []string
|
||||||
|
|
||||||
|
// NotAfter is when the certificate expires.
|
||||||
|
NotAfter time.Time
|
||||||
|
|
||||||
|
// OCSP contains the certificate's parsed OCSP response.
|
||||||
|
OCSP *ocsp.Response
|
||||||
|
|
||||||
|
// The hex-encoded hash of this cert's chain's bytes.
|
||||||
|
Hash string
|
||||||
|
|
||||||
|
// configs is the list of configs that use or refer to
|
||||||
|
// The first one is assumed to be the config that is
|
||||||
|
// "in charge" of this certificate (i.e. determines
|
||||||
|
// whether it is managed, how it is managed, etc).
|
||||||
|
// This field will be populated by cacheCertificate.
|
||||||
|
// Only meddle with it if you know what you're doing!
|
||||||
|
configs []*Config
|
||||||
|
|
||||||
|
// whether this certificate is under our management
|
||||||
|
managed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeedsRenewal returns true if the certificate is
|
||||||
|
// expiring soon or has expired.
|
||||||
|
func (c Certificate) NeedsRenewal() bool {
|
||||||
|
if c.NotAfter.IsZero() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
timeLeft := c.NotAfter.UTC().Sub(time.Now().UTC())
|
||||||
|
renewDurationBefore := DefaultRenewDurationBefore
|
||||||
|
if len(c.configs) > 0 && c.configs[0].RenewDurationBefore > 0 {
|
||||||
|
renewDurationBefore = c.configs[0].RenewDurationBefore
|
||||||
|
}
|
||||||
|
return timeLeft < renewDurationBefore
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// This is a lower-level method; normally you'll call Manage() instead.
|
||||||
|
//
|
||||||
|
// This method is safe for concurrent use.
|
||||||
|
func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) {
|
||||||
|
certRes, err := cfg.loadCertResource(domain)
|
||||||
|
if err != nil {
|
||||||
|
return Certificate{}, err
|
||||||
|
}
|
||||||
|
cert, err := cfg.makeCertificateWithOCSP(certRes.Certificate, certRes.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return cert, err
|
||||||
|
}
|
||||||
|
cert.managed = true
|
||||||
|
if cfg.OnEvent != nil {
|
||||||
|
cfg.OnEvent("cached_managed_cert", cert.Names)
|
||||||
|
}
|
||||||
|
return cfg.cacheCertificate(cert), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// This method is safe for concurrent use.
|
||||||
|
func (cfg *Config) CacheUnmanagedCertificatePEMFile(certFile, keyFile string) error {
|
||||||
|
cert, err := cfg.makeCertificateFromDiskWithOCSP(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg.cacheCertificate(cert)
|
||||||
|
if cfg.OnEvent != nil {
|
||||||
|
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheUnmanagedTLSCertificate adds tlsCert to the certificate cache.
|
||||||
|
// It staples OCSP if possible.
|
||||||
|
//
|
||||||
|
// This method is safe for concurrent use.
|
||||||
|
func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate) error {
|
||||||
|
var cert Certificate
|
||||||
|
err := fillCertFromLeaf(&cert, tlsCert)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = cfg.certCache.stapleOCSP(&cert, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[WARNING] Stapling OCSP: %v", err)
|
||||||
|
}
|
||||||
|
if cfg.OnEvent != nil {
|
||||||
|
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
|
||||||
|
}
|
||||||
|
cfg.cacheCertificate(cert)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheUnmanagedCertificatePEMBytes makes a certificate out of the PEM bytes
|
||||||
|
// of the certificate and key, then caches it in memory.
|
||||||
|
//
|
||||||
|
// This method is safe for concurrent use.
|
||||||
|
func (cfg *Config) CacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte) error {
|
||||||
|
cert, err := cfg.makeCertificateWithOCSP(certBytes, keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg.cacheCertificate(cert)
|
||||||
|
if cfg.OnEvent != nil {
|
||||||
|
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeCertificateFromDiskWithOCSP makes a Certificate by loading the
|
||||||
|
// certificate and key files. It fills out all the fields in
|
||||||
|
// the certificate except for the Managed and OnDemand flags.
|
||||||
|
// (It is up to the caller to set those.) It staples OCSP.
|
||||||
|
func (cfg *Config) makeCertificateFromDiskWithOCSP(certFile, keyFile string) (Certificate, error) {
|
||||||
|
certPEMBlock, err := ioutil.ReadFile(certFile)
|
||||||
|
if err != nil {
|
||||||
|
return Certificate{}, err
|
||||||
|
}
|
||||||
|
keyPEMBlock, err := ioutil.ReadFile(keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return Certificate{}, err
|
||||||
|
}
|
||||||
|
return cfg.makeCertificateWithOCSP(certPEMBlock, keyPEMBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeCertificate turns a certificate PEM bundle and a key PEM block into
|
||||||
|
// a Certificate with necessary metadata from parsing its bytes filled into
|
||||||
|
// its struct fields for convenience (except for the OnDemand and Managed
|
||||||
|
// flags; it is up to the caller to set those properties!). This function
|
||||||
|
// does NOT staple OCSP.
|
||||||
|
func (*Config) makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
|
||||||
|
var cert Certificate
|
||||||
|
|
||||||
|
// Convert to a tls.Certificate
|
||||||
|
tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
|
||||||
|
if err != nil {
|
||||||
|
return cert, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract necessary metadata
|
||||||
|
err = fillCertFromLeaf(&cert, tlsCert)
|
||||||
|
if err != nil {
|
||||||
|
return cert, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeCertificateWithOCSP is the same as makeCertificate except that it also
|
||||||
|
// staples OCSP to the certificate.
|
||||||
|
func (cfg *Config) makeCertificateWithOCSP(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
|
||||||
|
cert, err := cfg.makeCertificate(certPEMBlock, keyPEMBlock)
|
||||||
|
if err != nil {
|
||||||
|
return cert, err
|
||||||
|
}
|
||||||
|
err = cfg.certCache.stapleOCSP(&cert, certPEMBlock)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[WARNING] Stapling OCSP: %v", err)
|
||||||
|
}
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fillCertFromLeaf populates metadata fields on cert from tlsCert.
|
||||||
|
func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
|
||||||
|
if len(tlsCert.Certificate) == 0 {
|
||||||
|
return fmt.Errorf("certificate is empty")
|
||||||
|
}
|
||||||
|
cert.Certificate = tlsCert
|
||||||
|
|
||||||
|
// the leaf cert should be the one for the site; it has what we need
|
||||||
|
leaf, err := x509.ParseCertificate(tlsCert.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if leaf.Subject.CommonName != "" { // TODO: CommonName is deprecated
|
||||||
|
cert.Names = []string{strings.ToLower(leaf.Subject.CommonName)}
|
||||||
|
}
|
||||||
|
for _, name := range leaf.DNSNames {
|
||||||
|
if name != leaf.Subject.CommonName { // TODO: CommonName is deprecated
|
||||||
|
cert.Names = append(cert.Names, strings.ToLower(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ip := range leaf.IPAddresses {
|
||||||
|
if ipStr := ip.String(); ipStr != leaf.Subject.CommonName { // TODO: CommonName is deprecated
|
||||||
|
cert.Names = append(cert.Names, strings.ToLower(ipStr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, email := range leaf.EmailAddresses {
|
||||||
|
if email != leaf.Subject.CommonName { // TODO: CommonName is deprecated
|
||||||
|
cert.Names = append(cert.Names, strings.ToLower(email))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(cert.Names) == 0 {
|
||||||
|
return fmt.Errorf("certificate has no names")
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the hash of this certificate (chain) and
|
||||||
|
// expiration date, for necessity and efficiency
|
||||||
|
cert.Hash = hashCertificateChain(cert.Certificate.Certificate)
|
||||||
|
cert.NotAfter = leaf.NotAfter
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// managedCertInStorageExpiresSoon returns true if cert (being a
|
||||||
|
// managed certificate) is expiring within RenewDurationBefore.
|
||||||
|
// It returns false if there was an error checking the expiration
|
||||||
|
// of the certificate as found in storage, or if the certificate
|
||||||
|
// in storage is NOT expiring soon. A certificate that is expiring
|
||||||
|
// soon in our cache but is not expiring soon in storage probably
|
||||||
|
// means that another instance renewed the certificate in the
|
||||||
|
// meantime, and it would be a good idea to simply load the cert
|
||||||
|
// into our cache rather than repeating the renewal process again.
|
||||||
|
func managedCertInStorageExpiresSoon(cert Certificate) (bool, error) {
|
||||||
|
if len(cert.configs) == 0 {
|
||||||
|
return false, fmt.Errorf("no configs for certificate")
|
||||||
|
}
|
||||||
|
cfg := cert.configs[0]
|
||||||
|
certRes, err := cfg.loadCertResource(cert.Names[0])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
tlsCert, err := tls.X509KeyPair(certRes.Certificate, certRes.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
leaf, err := x509.ParseCertificate(tlsCert.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
timeLeft := leaf.NotAfter.Sub(time.Now().UTC())
|
||||||
|
return timeLeft < cfg.RenewDurationBefore, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheCertificate adds cert to the in-memory cache. If a certificate
|
||||||
|
// with the same hash is already cached, it is NOT overwritten; instead,
|
||||||
|
// cfg is added to the existing certificate's list of configs if not
|
||||||
|
// already in the list. Then all the names on cert are used to add
|
||||||
|
// entries to cfg.certificates (the config's name lookup map).
|
||||||
|
// Then the certificate is stored/updated in the cache. It returns
|
||||||
|
// a copy of the certificate that ends up being stored in the cache.
|
||||||
|
//
|
||||||
|
// It is VERY important, even for some test cases, that the Hash field
|
||||||
|
// of the cert be set properly.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent use.
|
||||||
|
func (cfg *Config) cacheCertificate(cert Certificate) Certificate {
|
||||||
|
cfg.certCache.mu.Lock()
|
||||||
|
defer cfg.certCache.mu.Unlock()
|
||||||
|
|
||||||
|
// if this certificate already exists in the cache,
|
||||||
|
// use it instead of overwriting it -- very important!
|
||||||
|
if existingCert, ok := cfg.certCache.cache[cert.Hash]; ok {
|
||||||
|
cert = existingCert
|
||||||
|
}
|
||||||
|
|
||||||
|
// attach this config to the certificate so we know which
|
||||||
|
// configs are referencing/using the certificate, but don't
|
||||||
|
// duplicate entries
|
||||||
|
var found bool
|
||||||
|
for _, c := range cert.configs {
|
||||||
|
if c == cfg {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
cert.configs = append(cert.configs, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// key the certificate by all its names for this config only,
|
||||||
|
// this is how we find the certificate during handshakes
|
||||||
|
// (yes, if certs overlap in the names they serve, one will
|
||||||
|
// overwrite another here, but that's just how it goes)
|
||||||
|
for _, name := range cert.Names {
|
||||||
|
cfg.certificates[name] = cert.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the certificate
|
||||||
|
cfg.certCache.cache[cert.Hash] = cert
|
||||||
|
|
||||||
|
return cert
|
||||||
|
}
|
||||||
|
|
||||||
|
// HostQualifies returns true if the hostname alone
|
||||||
|
// appears eligible for automagic TLS. For example:
|
||||||
|
// localhost, empty hostname, and IP addresses are
|
||||||
|
// not eligible because we cannot obtain certificates
|
||||||
|
// for those names. Wildcard names are allowed, as long
|
||||||
|
// as they conform to CABF requirements (only one wildcard
|
||||||
|
// label, and it must be the left-most label).
|
||||||
|
func HostQualifies(hostname string) bool {
|
||||||
|
return hostname != "localhost" && // localhost is ineligible
|
||||||
|
|
||||||
|
// hostname must not be empty
|
||||||
|
strings.TrimSpace(hostname) != "" &&
|
||||||
|
|
||||||
|
// only one wildcard label allowed, and it must be left-most
|
||||||
|
(!strings.Contains(hostname, "*") ||
|
||||||
|
(strings.Count(hostname, "*") == 1 &&
|
||||||
|
strings.HasPrefix(hostname, "*."))) &&
|
||||||
|
|
||||||
|
// must not start or end with a dot
|
||||||
|
!strings.HasPrefix(hostname, ".") &&
|
||||||
|
!strings.HasSuffix(hostname, ".") &&
|
||||||
|
|
||||||
|
// cannot be an IP address, see
|
||||||
|
// https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
|
||||||
|
net.ParseIP(hostname) == nil
|
||||||
|
}
|
||||||
511
vendor/github.com/mholt/certmagic/certmagic.go
generated
vendored
Normal file
511
vendor/github.com/mholt/certmagic/certmagic.go
generated
vendored
Normal file
@ -0,0 +1,511 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/certcrypto"
|
||||||
|
"github.com/xenolf/lego/challenge"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPS serves mux for all domainNames using the HTTP
|
||||||
|
// and HTTPS ports, redirecting all HTTP requests to HTTPS.
|
||||||
|
//
|
||||||
|
// Calling this function signifies your acceptance to
|
||||||
|
// the CA's Subscriber Agreement and/or Terms of Service.
|
||||||
|
func HTTPS(domainNames []string, mux http.Handler) error {
|
||||||
|
if mux == nil {
|
||||||
|
mux = http.DefaultServeMux
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := manageWithDefaultConfig(domainNames, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpWg.Add(1)
|
||||||
|
defer httpWg.Done()
|
||||||
|
|
||||||
|
// if we haven't made listeners yet, do so now,
|
||||||
|
// and clean them up when all servers are done
|
||||||
|
lnMu.Lock()
|
||||||
|
if httpLn == nil && httpsLn == nil {
|
||||||
|
httpLn, err = net.Listen("tcp", fmt.Sprintf(":%d", HTTPPort))
|
||||||
|
if err != nil {
|
||||||
|
lnMu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpsLn, err = tls.Listen("tcp", fmt.Sprintf(":%d", HTTPSPort), cfg.TLSConfig())
|
||||||
|
if err != nil {
|
||||||
|
httpLn.Close()
|
||||||
|
httpLn = nil
|
||||||
|
lnMu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
httpWg.Wait()
|
||||||
|
lnMu.Lock()
|
||||||
|
httpLn.Close()
|
||||||
|
httpsLn.Close()
|
||||||
|
lnMu.Unlock()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
hln, hsln := httpLn, httpsLn
|
||||||
|
lnMu.Unlock()
|
||||||
|
|
||||||
|
httpHandler := cfg.HTTPChallengeHandler(http.HandlerFunc(httpRedirectHandler))
|
||||||
|
|
||||||
|
log.Printf("%v Serving HTTP->HTTPS on %s and %s",
|
||||||
|
domainNames, hln.Addr(), hsln.Addr())
|
||||||
|
|
||||||
|
go http.Serve(hln, httpHandler)
|
||||||
|
return http.Serve(hsln, mux)
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpRedirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
toURL := "https://"
|
||||||
|
|
||||||
|
// since we redirect to the standard HTTPS port, we
|
||||||
|
// do not need to include it in the redirect URL
|
||||||
|
requestHost, _, err := net.SplitHostPort(r.Host)
|
||||||
|
if err != nil {
|
||||||
|
requestHost = r.Host // host probably did not contain a port
|
||||||
|
}
|
||||||
|
|
||||||
|
toURL += requestHost
|
||||||
|
toURL += r.URL.RequestURI()
|
||||||
|
|
||||||
|
// get rid of this disgusting unencrypted HTTP connection 🤢
|
||||||
|
w.Header().Set("Connection", "close")
|
||||||
|
|
||||||
|
http.Redirect(w, r, toURL, http.StatusMovedPermanently)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS enables management of certificates for domainNames
|
||||||
|
// and returns a valid tls.Config.
|
||||||
|
//
|
||||||
|
// Because this is a convenience function that returns
|
||||||
|
// only a tls.Config, it does not assume HTTP is being
|
||||||
|
// served on the HTTP port, so the HTTP challenge is
|
||||||
|
// disabled (no HTTPChallengeHandler is necessary).
|
||||||
|
//
|
||||||
|
// Calling this function signifies your acceptance to
|
||||||
|
// the CA's Subscriber Agreement and/or Terms of Service.
|
||||||
|
func TLS(domainNames []string) (*tls.Config, error) {
|
||||||
|
cfg, err := manageWithDefaultConfig(domainNames, true)
|
||||||
|
return cfg.TLSConfig(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen manages certificates for domainName and returns a
|
||||||
|
// TLS listener.
|
||||||
|
//
|
||||||
|
// Because this convenience function returns only a TLS-enabled
|
||||||
|
// listener and does not presume HTTP is also being served,
|
||||||
|
// the HTTP challenge will be disabled.
|
||||||
|
//
|
||||||
|
// Calling this function signifies your acceptance to
|
||||||
|
// the CA's Subscriber Agreement and/or Terms of Service.
|
||||||
|
func Listen(domainNames []string) (net.Listener, error) {
|
||||||
|
cfg, err := manageWithDefaultConfig(domainNames, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tls.Listen("tcp", fmt.Sprintf(":%d", HTTPSPort), cfg.TLSConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manage obtains certificates for domainNames and keeps them
|
||||||
|
// renewed using the returned Config.
|
||||||
|
//
|
||||||
|
// You will need to ensure that you use a TLS config that gets
|
||||||
|
// certificates from this Config and that the HTTP and TLS-ALPN
|
||||||
|
// challenges can be solved. The easiest way to do this is to
|
||||||
|
// use cfg.TLSConfig() as your TLS config and to wrap your
|
||||||
|
// HTTP handler with cfg.HTTPChallengeHandler(). If you don't
|
||||||
|
// have an HTTP server, you will need to disable the HTTP
|
||||||
|
// challenge.
|
||||||
|
//
|
||||||
|
// If you already have a TLS config you want to use, you can
|
||||||
|
// simply set its GetCertificate field to cfg.GetCertificate.
|
||||||
|
//
|
||||||
|
// Calling this function signifies your acceptance to
|
||||||
|
// the CA's Subscriber Agreement and/or Terms of Service.
|
||||||
|
func Manage(domainNames []string) (cfg *Config, err error) {
|
||||||
|
return manageWithDefaultConfig(domainNames, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// manageWithDefaultConfig returns a TLS configuration that
|
||||||
|
// is fully managed for the given names, optionally
|
||||||
|
// with the HTTP challenge disabled.
|
||||||
|
func manageWithDefaultConfig(domainNames []string, disableHTTPChallenge bool) (*Config, error) {
|
||||||
|
cfg := NewDefault()
|
||||||
|
cfg.DisableHTTPChallenge = disableHTTPChallenge
|
||||||
|
return cfg, cfg.Manage(domainNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locker facilitates synchronization of certificate tasks across
|
||||||
|
// machines and networks.
|
||||||
|
type Locker interface {
|
||||||
|
// TryLock will attempt to acquire the lock for key. If a
|
||||||
|
// lock could be obtained, nil values are returned as no
|
||||||
|
// waiting is required. If not (meaning another process is
|
||||||
|
// already working on key), a Waiter value will be returned,
|
||||||
|
// upon which you should Wait() until it is finished.
|
||||||
|
//
|
||||||
|
// The key should be a carefully-chosen value that uniquely
|
||||||
|
// and precisely identifies the operation being locked. For
|
||||||
|
// example, if it is for a certificate obtain or renew with
|
||||||
|
// the ACME protocol to the same CA endpoint (remembering
|
||||||
|
// that an obtain and renew are the same according to ACME,
|
||||||
|
// thus both obtain and renew should share a lock key), a
|
||||||
|
// good key would identify that operation by some name,
|
||||||
|
// concatenated with the domain name and the CA endpoint.
|
||||||
|
//
|
||||||
|
// TryLock never blocks; it always returns without waiting.
|
||||||
|
//
|
||||||
|
// 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 storage
|
||||||
|
// system failure or crash.
|
||||||
|
TryLock(key string) (Waiter, error)
|
||||||
|
|
||||||
|
// Unlock releases the lock for key. This method must ONLY be
|
||||||
|
// called after a successful call to TryLock where no Waiter was
|
||||||
|
// returned, and only after the operation requiring the lock is
|
||||||
|
// finished, even if it returned an error or timed out. Unlock
|
||||||
|
// should also clean up any unused resources allocated during
|
||||||
|
// TryLock.
|
||||||
|
Unlock(key string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Waiter is a type that can block until a lock is released.
|
||||||
|
type Waiter interface {
|
||||||
|
Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnDemandConfig contains some state relevant for providing
|
||||||
|
// on-demand TLS.
|
||||||
|
type OnDemandConfig struct {
|
||||||
|
// If set, this function will be the absolute
|
||||||
|
// authority on whether the hostname (according
|
||||||
|
// to SNI) is allowed to try to get a cert.
|
||||||
|
DecisionFunc func(name string) error
|
||||||
|
|
||||||
|
// If no DecisionFunc is set, this whitelist
|
||||||
|
// is the absolute authority as to whether
|
||||||
|
// a certificate should be allowed to be tried.
|
||||||
|
// Names are compared against SNI value.
|
||||||
|
HostWhitelist []string
|
||||||
|
|
||||||
|
// If no DecisionFunc or HostWhitelist are set,
|
||||||
|
// then an HTTP request will be made to AskURL
|
||||||
|
// to determine if a certificate should be
|
||||||
|
// obtained. If the request fails or the response
|
||||||
|
// is anything other than 2xx status code, the
|
||||||
|
// issuance will be denied.
|
||||||
|
AskURL *url.URL
|
||||||
|
|
||||||
|
// If no DecisionFunc, HostWhitelist, or AskURL
|
||||||
|
// are set, then only this many certificates may
|
||||||
|
// be obtained on-demand; this field is required
|
||||||
|
// if all others are empty, otherwise, all cert
|
||||||
|
// issuances will fail.
|
||||||
|
MaxObtain int32
|
||||||
|
|
||||||
|
// The number of certificates that have been issued on-demand
|
||||||
|
// by this config. It is only safe to modify this count atomically.
|
||||||
|
// If it reaches MaxObtain, on-demand issuances must fail.
|
||||||
|
obtainedCount int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowed returns whether the issuance for name is allowed according to o.
|
||||||
|
func (o *OnDemandConfig) Allowed(name string) error {
|
||||||
|
// The decision function has absolute authority, if set
|
||||||
|
if o.DecisionFunc != nil {
|
||||||
|
return o.DecisionFunc(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, the host whitelist has decision authority
|
||||||
|
if len(o.HostWhitelist) > 0 {
|
||||||
|
return o.checkWhitelistForObtainingNewCerts(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, a URL is checked for permission to issue this cert
|
||||||
|
if o.AskURL != nil {
|
||||||
|
return o.checkURLForObtainingNewCerts(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise use the limit defined by the "max_certs" setting
|
||||||
|
return o.checkLimitsForObtainingNewCerts(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnDemandConfig) whitelistContains(name string) bool {
|
||||||
|
for _, n := range o.HostWhitelist {
|
||||||
|
if strings.ToLower(n) == strings.ToLower(name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnDemandConfig) checkWhitelistForObtainingNewCerts(name string) error {
|
||||||
|
if !o.whitelistContains(name) {
|
||||||
|
return fmt.Errorf("%s: name is not whitelisted", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnDemandConfig) checkURLForObtainingNewCerts(name string) error {
|
||||||
|
client := http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return fmt.Errorf("following http redirects is not allowed")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the URL from the config in order to modify it for this request
|
||||||
|
askURL := new(url.URL)
|
||||||
|
*askURL = *o.AskURL
|
||||||
|
|
||||||
|
query := askURL.Query()
|
||||||
|
query.Set("domain", name)
|
||||||
|
askURL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
resp, err := client.Get(askURL.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error checking %v to deterine if certificate for hostname '%s' should be allowed: %v", o.AskURL, name, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||||
|
return fmt.Errorf("certificate for hostname '%s' not allowed, non-2xx status code %d returned from %v", name, resp.StatusCode, o.AskURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkLimitsForObtainingNewCerts checks to see if name can be issued right
|
||||||
|
// now according the maximum count defined in the configuration. If a non-nil
|
||||||
|
// error is returned, do not issue a new certificate for name.
|
||||||
|
func (o *OnDemandConfig) checkLimitsForObtainingNewCerts(name string) error {
|
||||||
|
if o.MaxObtain == 0 {
|
||||||
|
return fmt.Errorf("%s: no certificates allowed to be issued on-demand", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// User can set hard limit for number of certs for the process to issue
|
||||||
|
if o.MaxObtain > 0 &&
|
||||||
|
atomic.LoadInt32(&o.obtainedCount) >= o.MaxObtain {
|
||||||
|
return fmt.Errorf("%s: maximum certificates issued (%d)", name, o.MaxObtain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure name hasn't failed a challenge recently
|
||||||
|
failedIssuanceMu.RLock()
|
||||||
|
when, ok := failedIssuance[name]
|
||||||
|
failedIssuanceMu.RUnlock()
|
||||||
|
if ok {
|
||||||
|
return fmt.Errorf("%s: throttled; refusing to issue cert since last attempt on %s failed", name, when.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure, if we've issued a few certificates already, that we haven't
|
||||||
|
// issued any recently
|
||||||
|
lastIssueTimeMu.Lock()
|
||||||
|
since := time.Since(lastIssueTime)
|
||||||
|
lastIssueTimeMu.Unlock()
|
||||||
|
if atomic.LoadInt32(&o.obtainedCount) >= 10 && since < 10*time.Minute {
|
||||||
|
return fmt.Errorf("%s: throttled; last certificate was obtained %v ago", name, since)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good to go 👍
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// failedIssuance is a set of names that we recently failed to get a
|
||||||
|
// certificate for from the ACME CA. They are removed after some time.
|
||||||
|
// When a name is in this map, do not issue a certificate for it on-demand.
|
||||||
|
var failedIssuance = make(map[string]time.Time)
|
||||||
|
var failedIssuanceMu sync.RWMutex
|
||||||
|
|
||||||
|
// lastIssueTime records when we last obtained a certificate successfully.
|
||||||
|
// If this value is recent, do not make any on-demand certificate requests.
|
||||||
|
var lastIssueTime time.Time
|
||||||
|
var lastIssueTimeMu sync.Mutex
|
||||||
|
|
||||||
|
// isLoopback returns true if the hostname of addr looks
|
||||||
|
// explicitly like a common local hostname. addr must only
|
||||||
|
// be a host or a host:port combination.
|
||||||
|
func isLoopback(addr string) bool {
|
||||||
|
host, _, err := net.SplitHostPort(strings.ToLower(addr))
|
||||||
|
if err != nil {
|
||||||
|
host = addr // happens if the addr is only a hostname
|
||||||
|
}
|
||||||
|
return host == "localhost" ||
|
||||||
|
strings.Trim(host, "[]") == "::1" ||
|
||||||
|
strings.HasPrefix(host, "127.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInternal returns true if the IP of addr
|
||||||
|
// belongs to a private network IP range. addr
|
||||||
|
// must only be an IP or an IP:port combination.
|
||||||
|
// Loopback addresses are considered false.
|
||||||
|
func isInternal(addr string) bool {
|
||||||
|
privateNetworks := []string{
|
||||||
|
"10.0.0.0/8",
|
||||||
|
"172.16.0.0/12",
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"fc00::/7",
|
||||||
|
}
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
host = addr // happens if the addr is just a hostname, missing port
|
||||||
|
// if we encounter an error, the brackets need to be stripped
|
||||||
|
// because SplitHostPort didn't do it for us
|
||||||
|
host = strings.Trim(host, "[]")
|
||||||
|
}
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
if ip == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, privateNetwork := range privateNetworks {
|
||||||
|
_, ipnet, _ := net.ParseCIDR(privateNetwork)
|
||||||
|
if ipnet.Contains(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package defaults
|
||||||
|
var (
|
||||||
|
// The endpoint of the directory for the ACME
|
||||||
|
// CA we are to use
|
||||||
|
CA = LetsEncryptProductionCA
|
||||||
|
|
||||||
|
// The email address to use when creating or
|
||||||
|
// selecting an existing ACME server account
|
||||||
|
Email string
|
||||||
|
|
||||||
|
// The synchronization implementation - all
|
||||||
|
// instances of certmagic in a cluster must
|
||||||
|
// use the same value here, otherwise some
|
||||||
|
// cert operations will not be properly
|
||||||
|
// coordinated
|
||||||
|
Sync Locker
|
||||||
|
|
||||||
|
// Set to true if agreed to the CA's
|
||||||
|
// subscriber agreement
|
||||||
|
Agreed bool
|
||||||
|
|
||||||
|
// Disable all HTTP challenges
|
||||||
|
DisableHTTPChallenge bool
|
||||||
|
|
||||||
|
// Disable all TLS-ALPN challenges
|
||||||
|
DisableTLSALPNChallenge bool
|
||||||
|
|
||||||
|
// How long before expiration to renew certificates
|
||||||
|
RenewDurationBefore = DefaultRenewDurationBefore
|
||||||
|
|
||||||
|
// How long before expiration to require a renewed
|
||||||
|
// certificate when in interactive mode, like when
|
||||||
|
// the program is first starting up (see
|
||||||
|
// mholt/caddy#1680). A wider window between
|
||||||
|
// RenewDurationBefore and this value will suppress
|
||||||
|
// errors under duress (bad) but hopefully this duration
|
||||||
|
// will give it enough time for the blockage to be
|
||||||
|
// relieved.
|
||||||
|
RenewDurationBeforeAtStartup = DefaultRenewDurationBeforeAtStartup
|
||||||
|
|
||||||
|
// An optional event callback clients can set
|
||||||
|
// to subscribe to certain things happening
|
||||||
|
// internally by this config; invocations are
|
||||||
|
// synchronous, so make them return quickly!
|
||||||
|
OnEvent func(event string, data interface{})
|
||||||
|
|
||||||
|
// The host (ONLY the host, not port) to listen
|
||||||
|
// on if necessary to start a listener to solve
|
||||||
|
// an ACME challenge
|
||||||
|
ListenHost string
|
||||||
|
|
||||||
|
// The alternate port to use for the ACME HTTP
|
||||||
|
// challenge; if non-empty, this port will be
|
||||||
|
// used instead of HTTPChallengePort to spin up
|
||||||
|
// a listener for the HTTP challenge
|
||||||
|
AltHTTPPort int
|
||||||
|
|
||||||
|
// The alternate port to use for the ACME
|
||||||
|
// TLS-ALPN challenge; the system must forward
|
||||||
|
// TLSALPNChallengePort to this port for
|
||||||
|
// challenge to succeed
|
||||||
|
AltTLSALPNPort int
|
||||||
|
|
||||||
|
// The DNS provider to use when solving the
|
||||||
|
// ACME DNS challenge
|
||||||
|
DNSProvider challenge.Provider
|
||||||
|
|
||||||
|
// The type of key to use when generating
|
||||||
|
// certificates
|
||||||
|
KeyType = certcrypto.RSA2048
|
||||||
|
|
||||||
|
// The state needed to operate on-demand TLS
|
||||||
|
OnDemand *OnDemandConfig
|
||||||
|
|
||||||
|
// Add the must staple TLS extension to the
|
||||||
|
// CSR generated by lego/acme
|
||||||
|
MustStaple bool
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HTTPChallengePort is the officially-designated port for
|
||||||
|
// the HTTP challenge according to the ACME spec.
|
||||||
|
HTTPChallengePort = 80
|
||||||
|
|
||||||
|
// TLSALPNChallengePort is the officially-designated port for
|
||||||
|
// the TLS-ALPN challenge according to the ACME spec.
|
||||||
|
TLSALPNChallengePort = 443
|
||||||
|
)
|
||||||
|
|
||||||
|
// Some well-known CA endpoints available to use.
|
||||||
|
const (
|
||||||
|
LetsEncryptStagingCA = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
LetsEncryptProductionCA = "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Port variables must remain their defaults unless you
|
||||||
|
// forward packets from the defaults to whatever these
|
||||||
|
// are set to; otherwise ACME challenges will fail.
|
||||||
|
var (
|
||||||
|
// HTTPPort is the port on which to serve HTTP.
|
||||||
|
HTTPPort = 80
|
||||||
|
|
||||||
|
// HTTPSPort is the port on which to serve HTTPS.
|
||||||
|
HTTPSPort = 443
|
||||||
|
)
|
||||||
|
|
||||||
|
// Variables for conveniently serving HTTPS
|
||||||
|
var (
|
||||||
|
httpLn, httpsLn net.Listener
|
||||||
|
lnMu sync.Mutex
|
||||||
|
httpWg sync.WaitGroup
|
||||||
|
)
|
||||||
378
vendor/github.com/mholt/certmagic/client.go
generated
vendored
Normal file
378
vendor/github.com/mholt/certmagic/client.go
generated
vendored
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/certificate"
|
||||||
|
"github.com/xenolf/lego/challenge"
|
||||||
|
"github.com/xenolf/lego/challenge/http01"
|
||||||
|
"github.com/xenolf/lego/challenge/tlsalpn01"
|
||||||
|
"github.com/xenolf/lego/lego"
|
||||||
|
"github.com/xenolf/lego/registration"
|
||||||
|
)
|
||||||
|
|
||||||
|
// acmeMu ensures that only one ACME challenge occurs at a time.
|
||||||
|
var acmeMu sync.Mutex
|
||||||
|
|
||||||
|
// acmeClient is a wrapper over acme.Client with
|
||||||
|
// some custom state attached. It is used to obtain,
|
||||||
|
// renew, and revoke certificates with ACME.
|
||||||
|
type acmeClient struct {
|
||||||
|
config *Config
|
||||||
|
acmeClient *lego.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// listenerAddressInUse returns true if a TCP connection
|
||||||
|
// can be made to addr within a short time interval.
|
||||||
|
func listenerAddressInUse(addr string) bool {
|
||||||
|
conn, err := net.DialTimeout("tcp", addr, 250*time.Millisecond)
|
||||||
|
if err == nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) {
|
||||||
|
// look up or create the user account
|
||||||
|
leUser, err := cfg.getUser(cfg.Email)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure key type is set
|
||||||
|
keyType := KeyType
|
||||||
|
if cfg.KeyType != "" {
|
||||||
|
keyType = cfg.KeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure CA URL (directory endpoint) is set
|
||||||
|
caURL := CA
|
||||||
|
if cfg.CA != "" {
|
||||||
|
caURL = cfg.CA
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure endpoint is secure (assume HTTPS if scheme is missing)
|
||||||
|
if !strings.Contains(caURL, "://") {
|
||||||
|
caURL = "https://" + caURL
|
||||||
|
}
|
||||||
|
u, err := url.Parse(caURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "https" && !isLoopback(u.Host) && !isInternal(u.Host) {
|
||||||
|
return nil, fmt.Errorf("%s: insecure CA URL (HTTPS required)", caURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientKey := caURL + leUser.Email + string(keyType)
|
||||||
|
|
||||||
|
// if an underlying client with this configuration already exists, reuse it
|
||||||
|
cfg.acmeClientsMu.Lock()
|
||||||
|
client, ok := cfg.acmeClients[clientKey]
|
||||||
|
if !ok {
|
||||||
|
// the client facilitates our communication with the CA server
|
||||||
|
legoCfg := lego.NewConfig(&leUser)
|
||||||
|
legoCfg.CADirURL = caURL
|
||||||
|
legoCfg.KeyType = keyType
|
||||||
|
legoCfg.UserAgent = UserAgent
|
||||||
|
legoCfg.HTTPClient.Timeout = HTTPTimeout
|
||||||
|
client, err = lego.NewClient(legoCfg)
|
||||||
|
if err != nil {
|
||||||
|
cfg.acmeClientsMu.Unlock()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.acmeClients[clientKey] = client
|
||||||
|
}
|
||||||
|
cfg.acmeClientsMu.Unlock()
|
||||||
|
|
||||||
|
// if not registered, the user must register an account
|
||||||
|
// with the CA and agree to terms
|
||||||
|
if leUser.Registration == nil {
|
||||||
|
if interactive { // can't prompt a user who isn't there
|
||||||
|
termsURL := client.GetToSURL()
|
||||||
|
if !cfg.Agreed && termsURL != "" {
|
||||||
|
cfg.Agreed = cfg.askUserAgreement(client.GetToSURL())
|
||||||
|
}
|
||||||
|
if !cfg.Agreed && termsURL != "" {
|
||||||
|
return nil, fmt.Errorf("user must agree to CA terms")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: cfg.Agreed})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("registration error: %v", err)
|
||||||
|
}
|
||||||
|
leUser.Registration = reg
|
||||||
|
|
||||||
|
// persist the user to storage
|
||||||
|
err = cfg.saveUser(leUser)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not save user: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &acmeClient{
|
||||||
|
config: cfg,
|
||||||
|
acmeClient: client,
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.DNSProvider == nil {
|
||||||
|
// Use HTTP and TLS-ALPN challenges by default
|
||||||
|
|
||||||
|
// figure out which ports we'll be serving the challenges on
|
||||||
|
useHTTPPort := HTTPChallengePort
|
||||||
|
useTLSALPNPort := TLSALPNChallengePort
|
||||||
|
if cfg.AltHTTPPort > 0 {
|
||||||
|
useHTTPPort = cfg.AltHTTPPort
|
||||||
|
}
|
||||||
|
if cfg.AltTLSALPNPort > 0 {
|
||||||
|
useTLSALPNPort = cfg.AltTLSALPNPort
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this machine is already listening on the HTTP or TLS-ALPN port
|
||||||
|
// designated for the challenges, then we need to handle the challenges
|
||||||
|
// a little differently: for HTTP, we will answer the challenge request
|
||||||
|
// using our own HTTP handler (the HandleHTTPChallenge function - this
|
||||||
|
// works only because challenge info is written to storage associated
|
||||||
|
// with cfg when the challenge is initiated); for TLS-ALPN, we will add
|
||||||
|
// the challenge cert to our cert cache and serve it up during the
|
||||||
|
// handshake. As for the default solvers... we are careful to honor the
|
||||||
|
// listener bind preferences by using cfg.ListenHost.
|
||||||
|
var httpSolver, alpnSolver challenge.Provider
|
||||||
|
httpSolver = http01.NewProviderServer(cfg.ListenHost, fmt.Sprintf("%d", useHTTPPort))
|
||||||
|
alpnSolver = tlsalpn01.NewProviderServer(cfg.ListenHost, fmt.Sprintf("%d", useTLSALPNPort))
|
||||||
|
if listenerAddressInUse(net.JoinHostPort(cfg.ListenHost, fmt.Sprintf("%d", useHTTPPort))) {
|
||||||
|
httpSolver = nil
|
||||||
|
}
|
||||||
|
if listenerAddressInUse(net.JoinHostPort(cfg.ListenHost, fmt.Sprintf("%d", useTLSALPNPort))) {
|
||||||
|
alpnSolver = tlsALPNSolver{certCache: cfg.certCache}
|
||||||
|
}
|
||||||
|
|
||||||
|
// because of our nifty Storage interface, we can distribute the HTTP and
|
||||||
|
// TLS-ALPN challenges across all instances that share the same storage -
|
||||||
|
// in fact, this is required now for successful solving of the HTTP challenge
|
||||||
|
// if the port is already in use, since we must write the challenge info
|
||||||
|
// to storage for the HTTPChallengeHandler to solve it successfully
|
||||||
|
c.acmeClient.Challenge.SetHTTP01Provider(distributedSolver{
|
||||||
|
config: cfg,
|
||||||
|
providerServer: httpSolver,
|
||||||
|
})
|
||||||
|
c.acmeClient.Challenge.SetTLSALPN01Provider(distributedSolver{
|
||||||
|
config: cfg,
|
||||||
|
providerServer: alpnSolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
// disable any challenges that should not be used
|
||||||
|
var disabledChallenges []challenge.Type
|
||||||
|
if cfg.DisableHTTPChallenge {
|
||||||
|
disabledChallenges = append(disabledChallenges, challenge.HTTP01)
|
||||||
|
}
|
||||||
|
if cfg.DisableTLSALPNChallenge {
|
||||||
|
disabledChallenges = append(disabledChallenges, challenge.TLSALPN01)
|
||||||
|
}
|
||||||
|
if len(disabledChallenges) > 0 {
|
||||||
|
c.acmeClient.Challenge.Exclude(disabledChallenges)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, use DNS challenge exclusively
|
||||||
|
c.acmeClient.Challenge.Exclude([]challenge.Type{challenge.HTTP01, challenge.TLSALPN01})
|
||||||
|
c.acmeClient.Challenge.SetDNS01Provider(cfg.DNSProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) lockKey(op, domainName string) string {
|
||||||
|
return fmt.Sprintf("%s:%s:%s", op, domainName, cfg.CA)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain obtains a single certificate for name. It stores the certificate
|
||||||
|
// on the disk if successful. This function is safe for concurrent use.
|
||||||
|
//
|
||||||
|
// Right now our storage mechanism only supports one name per certificate,
|
||||||
|
// so this function (along with Renew and Revoke) only accepts one domain
|
||||||
|
// as input. It can be easily modified to support SAN certificates if our
|
||||||
|
// storage mechanism is upgraded later.
|
||||||
|
//
|
||||||
|
// Callers who have access to a Config value should use the ObtainCert
|
||||||
|
// method on that instead of this lower-level method.
|
||||||
|
func (c *acmeClient) Obtain(name string) error {
|
||||||
|
if c.config.Sync != nil {
|
||||||
|
lockKey := c.config.lockKey("cert_acme", name)
|
||||||
|
waiter, err := c.config.Sync.TryLock(lockKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if waiter != nil {
|
||||||
|
log.Printf("[INFO] Certificate for %s is already being obtained elsewhere and stored; waiting", name)
|
||||||
|
waiter.Wait()
|
||||||
|
return nil // we assume the process with the lock succeeded, rather than hammering this execution path again
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := c.config.Sync.Unlock(lockKey); err != nil {
|
||||||
|
log.Printf("[ERROR] Unable to unlock obtain call for %s: %v", name, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
for attempts := 0; attempts < 2; attempts++ {
|
||||||
|
request := certificate.ObtainRequest{
|
||||||
|
Domains: []string{name},
|
||||||
|
Bundle: true,
|
||||||
|
MustStaple: c.config.MustStaple,
|
||||||
|
}
|
||||||
|
acmeMu.Lock()
|
||||||
|
certificate, err := c.acmeClient.Certificate.Obtain(request)
|
||||||
|
acmeMu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[%s] failed to obtain certificate: %s", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// double-check that we actually got a certificate, in case there's a bug upstream (see issue mholt/caddy#2121)
|
||||||
|
if certificate.Domain == "" || certificate.Certificate == nil {
|
||||||
|
return fmt.Errorf("returned certificate was empty; probably an unchecked error obtaining it")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success - immediately save the certificate resource
|
||||||
|
err = c.config.saveCertResource(certificate)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error saving assets for %v: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.config.OnEvent != nil {
|
||||||
|
c.config.OnEvent("acme_cert_obtained", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renew renews the managed certificate for name. It puts the renewed
|
||||||
|
// certificate into storage (not the cache). This function is safe for
|
||||||
|
// concurrent use.
|
||||||
|
//
|
||||||
|
// Callers who have access to a Config value should use the RenewCert
|
||||||
|
// method on that instead of this lower-level method.
|
||||||
|
func (c *acmeClient) Renew(name string) error {
|
||||||
|
if c.config.Sync != nil {
|
||||||
|
lockKey := c.config.lockKey("cert_acme", name)
|
||||||
|
waiter, err := c.config.Sync.TryLock(lockKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if waiter != nil {
|
||||||
|
log.Printf("[INFO] Certificate for %s is already being renewed elsewhere and stored; waiting", name)
|
||||||
|
waiter.Wait()
|
||||||
|
return nil // assume that the worker that renewed the cert succeeded; avoid hammering this path over and over
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := c.config.Sync.Unlock(lockKey); err != nil {
|
||||||
|
log.Printf("[ERROR] Unable to unlock renew call for %s: %v", name, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare for renewal (load PEM cert, key, and meta)
|
||||||
|
certRes, err := c.config.loadCertResource(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform renewal and retry if necessary, but not too many times.
|
||||||
|
var newCertMeta *certificate.Resource
|
||||||
|
var success bool
|
||||||
|
for attempts := 0; attempts < 2; attempts++ {
|
||||||
|
acmeMu.Lock()
|
||||||
|
newCertMeta, err = c.acmeClient.Certificate.Renew(certRes, true, c.config.MustStaple)
|
||||||
|
acmeMu.Unlock()
|
||||||
|
if err == nil {
|
||||||
|
// double-check that we actually got a certificate; check a couple fields, just in case
|
||||||
|
if newCertMeta == nil || newCertMeta.Domain == "" || newCertMeta.Certificate == nil {
|
||||||
|
err = fmt.Errorf("returned certificate was empty; probably an unchecked error renewing it")
|
||||||
|
} else {
|
||||||
|
success = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait a little bit and try again
|
||||||
|
wait := 10 * time.Second
|
||||||
|
log.Printf("[ERROR] Renewing [%v]: %v; trying again in %s", name, err, wait)
|
||||||
|
time.Sleep(wait)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
return fmt.Errorf("too many renewal attempts; last error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.config.OnEvent != nil {
|
||||||
|
c.config.OnEvent("acme_cert_renewed", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.config.saveCertResource(newCertMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revoke revokes the certificate for name and deletes
|
||||||
|
// it from storage.
|
||||||
|
func (c *acmeClient) Revoke(name string) error {
|
||||||
|
if !c.config.certCache.storage.Exists(prefixSiteKey(c.config.CA, name)) {
|
||||||
|
return fmt.Errorf("private key not found for %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
certRes, err := c.config.loadCertResource(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.acmeClient.Certificate.Revoke(certRes.Certificate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.config.OnEvent != nil {
|
||||||
|
c.config.OnEvent("acme_cert_revoked", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.config.certCache.storage.Delete(prefixSiteCert(c.config.CA, name))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("certificate revoked, but unable to delete certificate file: %v", err)
|
||||||
|
}
|
||||||
|
err = c.config.certCache.storage.Delete(prefixSiteKey(c.config.CA, name))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("certificate revoked, but unable to delete private key: %v", err)
|
||||||
|
}
|
||||||
|
err = c.config.certCache.storage.Delete(prefixSiteMeta(c.config.CA, name))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("certificate revoked, but unable to delete certificate metadata: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some default values passed down to the underlying lego client.
|
||||||
|
var (
|
||||||
|
UserAgent string
|
||||||
|
HTTPTimeout = 30 * time.Second
|
||||||
|
)
|
||||||
363
vendor/github.com/mholt/certmagic/config.go
generated
vendored
Normal file
363
vendor/github.com/mholt/certmagic/config.go
generated
vendored
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/certcrypto"
|
||||||
|
"github.com/xenolf/lego/challenge"
|
||||||
|
"github.com/xenolf/lego/challenge/tlsalpn01"
|
||||||
|
"github.com/xenolf/lego/lego"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config configures a certificate manager instance.
|
||||||
|
// An empty Config is not valid: use New() to obtain
|
||||||
|
// a valid Config.
|
||||||
|
type Config struct {
|
||||||
|
// The endpoint of the directory for the ACME
|
||||||
|
// CA we are to use
|
||||||
|
CA string
|
||||||
|
|
||||||
|
// The email address to use when creating or
|
||||||
|
// selecting an existing ACME server account
|
||||||
|
Email string
|
||||||
|
|
||||||
|
// The synchronization implementation - although
|
||||||
|
// it is not strictly required to have a Sync
|
||||||
|
// value in general, all instances running in
|
||||||
|
// in a cluster for the same domain names must
|
||||||
|
// specify a Sync and use the same one, otherwise
|
||||||
|
// some cert operations will not be properly
|
||||||
|
// coordinated
|
||||||
|
Sync Locker
|
||||||
|
|
||||||
|
// Set to true if agreed to the CA's
|
||||||
|
// subscriber agreement
|
||||||
|
Agreed bool
|
||||||
|
|
||||||
|
// Disable all HTTP challenges
|
||||||
|
DisableHTTPChallenge bool
|
||||||
|
|
||||||
|
// Disable all TLS-ALPN challenges
|
||||||
|
DisableTLSALPNChallenge bool
|
||||||
|
|
||||||
|
// How long before expiration to renew certificates
|
||||||
|
RenewDurationBefore time.Duration
|
||||||
|
|
||||||
|
// How long before expiration to require a renewed
|
||||||
|
// certificate when in interactive mode, like when
|
||||||
|
// the program is first starting up (see
|
||||||
|
// mholt/caddy#1680). A wider window between
|
||||||
|
// RenewDurationBefore and this value will suppress
|
||||||
|
// errors under duress (bad) but hopefully this duration
|
||||||
|
// will give it enough time for the blockage to be
|
||||||
|
// relieved.
|
||||||
|
RenewDurationBeforeAtStartup time.Duration
|
||||||
|
|
||||||
|
// An optional event callback clients can set
|
||||||
|
// to subscribe to certain things happening
|
||||||
|
// internally by this config; invocations are
|
||||||
|
// synchronous, so make them return quickly!
|
||||||
|
OnEvent func(event string, data interface{})
|
||||||
|
|
||||||
|
// The host (ONLY the host, not port) to listen
|
||||||
|
// on if necessary to start a listener to solve
|
||||||
|
// an ACME challenge
|
||||||
|
ListenHost string
|
||||||
|
|
||||||
|
// The alternate port to use for the ACME HTTP
|
||||||
|
// challenge; if non-empty, this port will be
|
||||||
|
// used instead of HTTPChallengePort to spin up
|
||||||
|
// a listener for the HTTP challenge
|
||||||
|
AltHTTPPort int
|
||||||
|
|
||||||
|
// The alternate port to use for the ACME
|
||||||
|
// TLS-ALPN challenge; the system must forward
|
||||||
|
// TLSALPNChallengePort to this port for
|
||||||
|
// challenge to succeed
|
||||||
|
AltTLSALPNPort int
|
||||||
|
|
||||||
|
// The DNS provider to use when solving the
|
||||||
|
// ACME DNS challenge
|
||||||
|
DNSProvider challenge.Provider
|
||||||
|
|
||||||
|
// The type of key to use when generating
|
||||||
|
// certificates
|
||||||
|
KeyType certcrypto.KeyType
|
||||||
|
|
||||||
|
// The state needed to operate on-demand TLS
|
||||||
|
OnDemand *OnDemandConfig
|
||||||
|
|
||||||
|
// Add the must staple TLS extension to the
|
||||||
|
// CSR generated by lego/acme
|
||||||
|
MustStaple bool
|
||||||
|
|
||||||
|
// Map of hostname to certificate hash; used
|
||||||
|
// to complete handshakes and serve the right
|
||||||
|
// certificate given SNI
|
||||||
|
certificates map[string]string
|
||||||
|
|
||||||
|
// Pointer to the certificate store to use
|
||||||
|
certCache *Cache
|
||||||
|
|
||||||
|
// Map of client config key to ACME clients
|
||||||
|
// so they can be reused
|
||||||
|
acmeClients map[string]*lego.Client
|
||||||
|
acmeClientsMu *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefault returns a new, valid, default config.
|
||||||
|
//
|
||||||
|
// Calling this function signifies your acceptance to
|
||||||
|
// the CA's Subscriber Agreement and/or Terms of Service.
|
||||||
|
func NewDefault() *Config {
|
||||||
|
return New(Config{Agreed: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
// New makes a valid config based on cfg and uses
|
||||||
|
// a default certificate cache. All calls to
|
||||||
|
// New() will use the same certificate cache.
|
||||||
|
func New(cfg Config) *Config {
|
||||||
|
return NewWithCache(defaultCache, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithCache makes a valid new config based on cfg
|
||||||
|
// and uses the provided certificate cache.
|
||||||
|
func NewWithCache(certCache *Cache, cfg Config) *Config {
|
||||||
|
// avoid nil pointers with sensible defaults
|
||||||
|
if certCache == nil {
|
||||||
|
certCache = defaultCache
|
||||||
|
}
|
||||||
|
if certCache.storage == nil {
|
||||||
|
certCache.storage = DefaultStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill in default values
|
||||||
|
if cfg.CA == "" {
|
||||||
|
cfg.CA = CA
|
||||||
|
}
|
||||||
|
if cfg.Email == "" {
|
||||||
|
cfg.Email = Email
|
||||||
|
}
|
||||||
|
if cfg.OnDemand == nil {
|
||||||
|
cfg.OnDemand = OnDemand
|
||||||
|
}
|
||||||
|
if !cfg.Agreed {
|
||||||
|
cfg.Agreed = Agreed
|
||||||
|
}
|
||||||
|
if !cfg.DisableHTTPChallenge {
|
||||||
|
cfg.DisableHTTPChallenge = DisableHTTPChallenge
|
||||||
|
}
|
||||||
|
if !cfg.DisableTLSALPNChallenge {
|
||||||
|
cfg.DisableTLSALPNChallenge = DisableTLSALPNChallenge
|
||||||
|
}
|
||||||
|
if cfg.RenewDurationBefore == 0 {
|
||||||
|
cfg.RenewDurationBefore = RenewDurationBefore
|
||||||
|
}
|
||||||
|
if cfg.RenewDurationBeforeAtStartup == 0 {
|
||||||
|
cfg.RenewDurationBeforeAtStartup = RenewDurationBeforeAtStartup
|
||||||
|
}
|
||||||
|
if cfg.OnEvent == nil {
|
||||||
|
cfg.OnEvent = OnEvent
|
||||||
|
}
|
||||||
|
if cfg.ListenHost == "" {
|
||||||
|
cfg.ListenHost = ListenHost
|
||||||
|
}
|
||||||
|
if cfg.AltHTTPPort == 0 {
|
||||||
|
cfg.AltHTTPPort = AltHTTPPort
|
||||||
|
}
|
||||||
|
if cfg.AltTLSALPNPort == 0 {
|
||||||
|
cfg.AltTLSALPNPort = AltTLSALPNPort
|
||||||
|
}
|
||||||
|
if cfg.DNSProvider == nil {
|
||||||
|
cfg.DNSProvider = DNSProvider
|
||||||
|
}
|
||||||
|
if cfg.KeyType == "" {
|
||||||
|
cfg.KeyType = KeyType
|
||||||
|
}
|
||||||
|
if cfg.OnDemand == nil {
|
||||||
|
cfg.OnDemand = OnDemand
|
||||||
|
}
|
||||||
|
if !cfg.MustStaple {
|
||||||
|
cfg.MustStaple = MustStaple
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no sync facility is provided, we'll default to
|
||||||
|
// a file system synchronizer backed by the storage
|
||||||
|
// given to certCache (if it is one), or just a simple
|
||||||
|
// in-memory sync facility otherwise (strictly speaking,
|
||||||
|
// a sync is not required; only if running multiple
|
||||||
|
// instances for the same domain names concurrently)
|
||||||
|
if cfg.Sync == nil {
|
||||||
|
if ccfs, ok := certCache.storage.(FileStorage); ok {
|
||||||
|
cfg.Sync = NewFileStorageLocker(ccfs)
|
||||||
|
} else {
|
||||||
|
cfg.Sync = NewMemoryLocker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure the unexported fields are valid
|
||||||
|
cfg.certificates = make(map[string]string)
|
||||||
|
cfg.certCache = certCache
|
||||||
|
cfg.acmeClients = make(map[string]*lego.Client)
|
||||||
|
cfg.acmeClientsMu = new(sync.Mutex)
|
||||||
|
|
||||||
|
return &cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manage causes the certificates for domainNames to be managed
|
||||||
|
// according to cfg.
|
||||||
|
func (cfg *Config) Manage(domainNames []string) error {
|
||||||
|
for _, domainName := range domainNames {
|
||||||
|
// if on-demand is configured, simply whitelist this name
|
||||||
|
if cfg.OnDemand != nil {
|
||||||
|
if !cfg.OnDemand.whitelistContains(domainName) {
|
||||||
|
cfg.OnDemand.HostWhitelist = append(cfg.OnDemand.HostWhitelist, domainName)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// try loading an existing certificate; if it doesn't
|
||||||
|
// exist yet, obtain one and try loading it again
|
||||||
|
cert, err := cfg.CacheManagedCertificate(domainName)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(ErrNotExist); ok {
|
||||||
|
// if it doesn't exist, get it, then try loading it again
|
||||||
|
err := cfg.ObtainCert(domainName, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: obtaining certificate: %v", domainName, err)
|
||||||
|
}
|
||||||
|
cert, err = cfg.CacheManagedCertificate(domainName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: caching certificate after obtaining it: %v", domainName, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s: caching certificate: %v", domainName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for existing certificates, make sure it is renewed
|
||||||
|
if cert.NeedsRenewal() {
|
||||||
|
err := cfg.RenewCert(domainName, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: renewing certificate: %v", domainName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObtainCert obtains a certificate for name using cfg, as long
|
||||||
|
// as a certificate does not already exist in storage for that
|
||||||
|
// name. The name must qualify and cfg must be flagged as Managed.
|
||||||
|
// This function is a no-op if storage already has a certificate
|
||||||
|
// for name.
|
||||||
|
//
|
||||||
|
// It only obtains and stores certificates (and their keys),
|
||||||
|
// it does not load them into memory. If interactive is true,
|
||||||
|
// the user may be shown a prompt.
|
||||||
|
func (cfg *Config) ObtainCert(name string, interactive bool) error {
|
||||||
|
skip, err := cfg.preObtainOrRenewChecks(name, interactive)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if skip {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// we expect this to be a new site; if the
|
||||||
|
// cert already exists, then no-op
|
||||||
|
if cfg.certCache.storage.Exists(prefixSiteCert(cfg.CA, name)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := cfg.newACMEClient(interactive)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Obtain(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewCert renews the certificate for name using cfg. It stows the
|
||||||
|
// renewed certificate and its assets in storage if successful.
|
||||||
|
func (cfg *Config) RenewCert(name string, interactive bool) error {
|
||||||
|
skip, err := cfg.preObtainOrRenewChecks(name, interactive)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if skip {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
client, err := cfg.newACMEClient(interactive)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return client.Renew(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeCert revokes the certificate for domain via ACME protocol.
|
||||||
|
func (cfg *Config) RevokeCert(domain string, interactive bool) error {
|
||||||
|
client, err := cfg.newACMEClient(interactive)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return client.Revoke(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSConfig returns a TLS configuration that
|
||||||
|
// can be used to configure TLS listeners. It
|
||||||
|
// supports the TLS-ALPN challenge and serves
|
||||||
|
// up certificates managed by cfg.
|
||||||
|
func (cfg *Config) TLSConfig() *tls.Config {
|
||||||
|
return &tls.Config{
|
||||||
|
GetCertificate: cfg.GetCertificate,
|
||||||
|
NextProtos: []string{"h2", "http/1.1", tlsalpn01.ACMETLS1Protocol},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewAllCerts triggers a renewal check of all
|
||||||
|
// certificates in the cache. It only renews
|
||||||
|
// certificates if they need to be renewed.
|
||||||
|
// func (cfg *Config) RenewAllCerts(interactive bool) error {
|
||||||
|
// return cfg.certCache.RenewManagedCertificates(interactive)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// preObtainOrRenewChecks perform a few simple checks before
|
||||||
|
// obtaining or renewing a certificate with ACME, and returns
|
||||||
|
// whether this name should be skipped (like if it's not
|
||||||
|
// managed TLS) as well as any error. It ensures that the
|
||||||
|
// config is Managed, that the name qualifies for a certificate,
|
||||||
|
// and that an email address is available.
|
||||||
|
func (cfg *Config) preObtainOrRenewChecks(name string, allowPrompts bool) (bool, error) {
|
||||||
|
if !HostQualifies(name) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Email == "" {
|
||||||
|
var err error
|
||||||
|
cfg.Email, err = cfg.getEmail(allowPrompts)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
155
vendor/github.com/mholt/certmagic/crypto.go
generated
vendored
Normal file
155
vendor/github.com/mholt/certmagic/crypto.go
generated
vendored
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/certificate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// encodePrivateKey marshals a EC or RSA private key into a PEM-encoded array of bytes.
|
||||||
|
func encodePrivateKey(key crypto.PrivateKey) ([]byte, error) {
|
||||||
|
var pemType string
|
||||||
|
var keyBytes []byte
|
||||||
|
switch key := key.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
var err error
|
||||||
|
pemType = "EC"
|
||||||
|
keyBytes, err = x509.MarshalECPrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
pemType = "RSA"
|
||||||
|
keyBytes = x509.MarshalPKCS1PrivateKey(key)
|
||||||
|
}
|
||||||
|
pemKey := pem.Block{Type: pemType + " PRIVATE KEY", Bytes: keyBytes}
|
||||||
|
return pem.EncodeToMemory(&pemKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodePrivateKey loads a PEM-encoded ECC/RSA private key from an array of bytes.
|
||||||
|
func decodePrivateKey(keyPEMBytes []byte) (crypto.PrivateKey, error) {
|
||||||
|
keyBlock, _ := pem.Decode(keyPEMBytes)
|
||||||
|
switch keyBlock.Type {
|
||||||
|
case "RSA PRIVATE KEY":
|
||||||
|
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
||||||
|
case "EC PRIVATE KEY":
|
||||||
|
return x509.ParseECPrivateKey(keyBlock.Bytes)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unknown private key type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCertsFromPEMBundle parses a certificate bundle from top to bottom and returns
|
||||||
|
// a slice of x509 certificates. This function will error if no certificates are found.
|
||||||
|
func parseCertsFromPEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
||||||
|
var certificates []*x509.Certificate
|
||||||
|
var certDERBlock *pem.Block
|
||||||
|
for {
|
||||||
|
certDERBlock, bundle = pem.Decode(bundle)
|
||||||
|
if certDERBlock == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if certDERBlock.Type == "CERTIFICATE" {
|
||||||
|
cert, err := x509.ParseCertificate(certDERBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certificates = append(certificates, cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(certificates) == 0 {
|
||||||
|
return nil, fmt.Errorf("no certificates found in bundle")
|
||||||
|
}
|
||||||
|
return certificates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fastHash hashes input using a hashing algorithm that
|
||||||
|
// is fast, and returns the hash as a hex-encoded string.
|
||||||
|
// Do not use this for cryptographic purposes.
|
||||||
|
func fastHash(input []byte) string {
|
||||||
|
h := fnv.New32a()
|
||||||
|
h.Write(input)
|
||||||
|
return fmt.Sprintf("%x", h.Sum32())
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveCertResource saves the certificate resource to disk. This
|
||||||
|
// includes the certificate file itself, the private key, and the
|
||||||
|
// metadata file.
|
||||||
|
func (cfg *Config) saveCertResource(cert *certificate.Resource) error {
|
||||||
|
metaBytes, err := json.MarshalIndent(&cert, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encoding certificate metadata: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
all := []keyValue{
|
||||||
|
{
|
||||||
|
key: prefixSiteCert(cfg.CA, cert.Domain),
|
||||||
|
value: cert.Certificate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: prefixSiteKey(cfg.CA, cert.Domain),
|
||||||
|
value: cert.PrivateKey,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: prefixSiteMeta(cfg.CA, cert.Domain),
|
||||||
|
value: metaBytes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return storeTx(cfg.certCache.storage, all)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) loadCertResource(domain string) (certificate.Resource, error) {
|
||||||
|
var certRes certificate.Resource
|
||||||
|
certBytes, err := cfg.certCache.storage.Load(prefixSiteCert(cfg.CA, domain))
|
||||||
|
if err != nil {
|
||||||
|
return certRes, err
|
||||||
|
}
|
||||||
|
keyBytes, err := cfg.certCache.storage.Load(prefixSiteKey(cfg.CA, domain))
|
||||||
|
if err != nil {
|
||||||
|
return certRes, err
|
||||||
|
}
|
||||||
|
metaBytes, err := cfg.certCache.storage.Load(prefixSiteMeta(cfg.CA, domain))
|
||||||
|
if err != nil {
|
||||||
|
return certRes, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(metaBytes, &certRes)
|
||||||
|
if err != nil {
|
||||||
|
return certRes, fmt.Errorf("decoding certificate metadata: %v", err)
|
||||||
|
}
|
||||||
|
certRes.Certificate = certBytes
|
||||||
|
certRes.PrivateKey = keyBytes
|
||||||
|
return certRes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashCertificateChain computes the unique hash of certChain,
|
||||||
|
// which is the chain of DER-encoded bytes. It returns the
|
||||||
|
// hex encoding of the hash.
|
||||||
|
func hashCertificateChain(certChain [][]byte) string {
|
||||||
|
h := sha256.New()
|
||||||
|
for _, certInChain := range certChain {
|
||||||
|
h.Write(certInChain)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%x", h.Sum(nil))
|
||||||
|
}
|
||||||
126
vendor/github.com/mholt/certmagic/filestorage.go
generated
vendored
Normal file
126
vendor/github.com/mholt/certmagic/filestorage.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileStorage facilitates forming file paths derived from a root
|
||||||
|
// directory. It is used to get file paths in a consistent,
|
||||||
|
// cross-platform way or persisting ACME assets on the file system.
|
||||||
|
type FileStorage struct {
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists returns true if key exists in fs.
|
||||||
|
func (fs FileStorage) Exists(key string) bool {
|
||||||
|
_, err := os.Stat(fs.filename(key))
|
||||||
|
return !os.IsNotExist(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store saves value at key.
|
||||||
|
func (fs FileStorage) Store(key string, value []byte) error {
|
||||||
|
filename := fs.filename(key)
|
||||||
|
err := os.MkdirAll(filepath.Dir(filename), 0700)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(filename, value, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load retrieves the value at key.
|
||||||
|
func (fs FileStorage) Load(key string) ([]byte, error) {
|
||||||
|
contents, err := ioutil.ReadFile(fs.filename(key))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, ErrNotExist(err)
|
||||||
|
}
|
||||||
|
return contents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the value at key.
|
||||||
|
// TODO: Delete any empty folders caused by this operation
|
||||||
|
func (fs FileStorage) Delete(key string) error {
|
||||||
|
err := os.Remove(fs.filename(key))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return ErrNotExist(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns all keys that match prefix.
|
||||||
|
func (fs FileStorage) List(prefix string) ([]string, error) {
|
||||||
|
d, err := os.Open(fs.filename(prefix))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, ErrNotExist(err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer d.Close()
|
||||||
|
return d.Readdirnames(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns information about key.
|
||||||
|
func (fs FileStorage) Stat(key string) (KeyInfo, error) {
|
||||||
|
fi, err := os.Stat(fs.filename(key))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return KeyInfo{}, ErrNotExist(err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return KeyInfo{}, err
|
||||||
|
}
|
||||||
|
return KeyInfo{
|
||||||
|
Key: key,
|
||||||
|
Modified: fi.ModTime(),
|
||||||
|
Size: fi.Size(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs FileStorage) filename(key string) string {
|
||||||
|
return filepath.Join(fs.Path, filepath.FromSlash(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// homeDir returns the best guess of the current user's home
|
||||||
|
// directory from environment variables. If unknown, "." (the
|
||||||
|
// current directory) is returned instead.
|
||||||
|
func homeDir() string {
|
||||||
|
home := os.Getenv("HOME")
|
||||||
|
if home == "" && runtime.GOOS == "windows" {
|
||||||
|
drive := os.Getenv("HOMEDRIVE")
|
||||||
|
path := os.Getenv("HOMEPATH")
|
||||||
|
home = drive + path
|
||||||
|
if drive == "" || path == "" {
|
||||||
|
home = os.Getenv("USERPROFILE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if home == "" {
|
||||||
|
home = "."
|
||||||
|
}
|
||||||
|
return home
|
||||||
|
}
|
||||||
|
|
||||||
|
func dataDir() string {
|
||||||
|
baseDir := filepath.Join(homeDir(), ".local", "share")
|
||||||
|
if xdgData := os.Getenv("XDG_DATA_HOME"); xdgData != "" {
|
||||||
|
baseDir = xdgData
|
||||||
|
}
|
||||||
|
return filepath.Join(baseDir, "certmagic")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Storage = FileStorage{}
|
||||||
146
vendor/github.com/mholt/certmagic/filestoragesync.go
generated
vendored
Normal file
146
vendor/github.com/mholt/certmagic/filestoragesync.go
generated
vendored
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileStorageLocker implements the Locker interface
|
||||||
|
// using the file system. An empty value is NOT VALID,
|
||||||
|
// so you must use NewFileStorageLocker() to get one.
|
||||||
|
type FileStorageLocker struct {
|
||||||
|
fs FileStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileStorageLocker returns a valid Locker backed by fs.
|
||||||
|
func NewFileStorageLocker(fs FileStorage) *FileStorageLocker {
|
||||||
|
return &FileStorageLocker{fs: fs}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryLock attempts to get a lock for name, otherwise it returns
|
||||||
|
// a Waiter value to wait until the other process is finished.
|
||||||
|
func (l *FileStorageLocker) TryLock(name string) (Waiter, error) {
|
||||||
|
fileStorageNameLocksMu.Lock()
|
||||||
|
defer fileStorageNameLocksMu.Unlock()
|
||||||
|
|
||||||
|
// see if lock already exists within this process
|
||||||
|
fw, ok := fileStorageNameLocks[name]
|
||||||
|
if ok {
|
||||||
|
// lock already created within process, let caller wait on it
|
||||||
|
return fw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to persist lock to disk by creating lock file
|
||||||
|
|
||||||
|
// parent dir must exist
|
||||||
|
lockDir := l.lockDir()
|
||||||
|
if err := os.MkdirAll(lockDir, 0700); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fw = &FileStorageWaiter{
|
||||||
|
filename: filepath.Join(lockDir, safeKey(name)+".lock"),
|
||||||
|
wg: new(sync.WaitGroup),
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the file in a special mode such that an
|
||||||
|
// error is returned if it already exists
|
||||||
|
lf, err := os.OpenFile(fw.filename, os.O_CREATE|os.O_EXCL, 0644)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsExist(err) {
|
||||||
|
// another process has the lock; use it to wait
|
||||||
|
return fw, nil
|
||||||
|
}
|
||||||
|
// otherwise, this was some unexpected error
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lf.Close()
|
||||||
|
|
||||||
|
// looks like we get the lock
|
||||||
|
fw.wg.Add(1)
|
||||||
|
fileStorageNameLocks[name] = fw
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock releases the lock for name.
|
||||||
|
func (l *FileStorageLocker) Unlock(name string) error {
|
||||||
|
fileStorageNameLocksMu.Lock()
|
||||||
|
defer fileStorageNameLocksMu.Unlock()
|
||||||
|
|
||||||
|
fw, ok := fileStorageNameLocks[name]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("FileStorageLocker: no lock to release for %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove lock file
|
||||||
|
os.Remove(fw.filename)
|
||||||
|
|
||||||
|
// if parent folder is now empty, remove it too to keep it tidy
|
||||||
|
dir, err := os.Open(l.lockDir()) // OK to ignore error here
|
||||||
|
if err == nil {
|
||||||
|
items, _ := dir.Readdirnames(3) // OK to ignore error here
|
||||||
|
if len(items) == 0 {
|
||||||
|
os.Remove(dir.Name())
|
||||||
|
}
|
||||||
|
dir.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up in memory
|
||||||
|
fw.wg.Done()
|
||||||
|
delete(fileStorageNameLocks, name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *FileStorageLocker) lockDir() string {
|
||||||
|
return filepath.Join(l.fs.Path, "locks")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileStorageWaiter waits for a file to disappear; it
|
||||||
|
// polls the file system to check for the existence of
|
||||||
|
// a file. It also uses a WaitGroup to optimize the
|
||||||
|
// polling in the case when this process is the only
|
||||||
|
// one waiting. (Other processes that are waiting
|
||||||
|
// for the lock will still block, but must wait
|
||||||
|
// for the poll intervals to get their answer.)
|
||||||
|
type FileStorageWaiter struct {
|
||||||
|
filename string
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait waits until the lock is released.
|
||||||
|
func (fw *FileStorageWaiter) Wait() {
|
||||||
|
start := time.Now()
|
||||||
|
fw.wg.Wait()
|
||||||
|
for time.Since(start) < 1*time.Hour {
|
||||||
|
_, err := os.Stat(fw.filename)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileStorageNameLocks = make(map[string]*FileStorageWaiter)
|
||||||
|
var fileStorageNameLocksMu sync.Mutex
|
||||||
|
|
||||||
|
var _ Locker = &FileStorageLocker{}
|
||||||
|
var _ Waiter = &FileStorageWaiter{}
|
||||||
400
vendor/github.com/mholt/certmagic/handshake.go
generated
vendored
Normal file
400
vendor/github.com/mholt/certmagic/handshake.go
generated
vendored
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/challenge/tlsalpn01"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetCertificate gets a certificate to satisfy clientHello. In getting
|
||||||
|
// the certificate, it abides the rules and settings defined in the
|
||||||
|
// Config that matches clientHello.ServerName. It first checks the in-
|
||||||
|
// memory cache, then, if the config enables "OnDemand", it accesses
|
||||||
|
// disk, then accesses the network if it must obtain a new certificate
|
||||||
|
// via ACME.
|
||||||
|
//
|
||||||
|
// This method is safe for use as a tls.Config.GetCertificate callback.
|
||||||
|
func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
if cfg.OnEvent != nil {
|
||||||
|
cfg.OnEvent("tls_handshake_started", clientHello)
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case: serve up the certificate for a TLS-ALPN ACME challenge
|
||||||
|
// (https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05)
|
||||||
|
for _, proto := range clientHello.SupportedProtos {
|
||||||
|
if proto == tlsalpn01.ACMETLS1Protocol {
|
||||||
|
cfg.certCache.mu.RLock()
|
||||||
|
challengeCert, ok := cfg.certCache.cache[tlsALPNCertKeyName(clientHello.ServerName)]
|
||||||
|
cfg.certCache.mu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
// see if this challenge was started in a cluster; try distributed challenge solver
|
||||||
|
// (note that the tls.Config's ALPN settings must include the ACME TLS-ALPN challenge
|
||||||
|
// protocol string, otherwise a valid certificate will not solve the challenge; we
|
||||||
|
// should already have taken care of that when we made the tls.Config)
|
||||||
|
challengeCert, ok, err := cfg.tryDistributedChallengeSolver(clientHello)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR][%s] TLS-ALPN: %v", clientHello.ServerName, err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return &challengeCert.Certificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no certificate to complete TLS-ALPN challenge for SNI name: %s", clientHello.ServerName)
|
||||||
|
}
|
||||||
|
return &challengeCert.Certificate, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the certificate and serve it up
|
||||||
|
cert, err := cfg.getCertDuringHandshake(strings.ToLower(clientHello.ServerName), true, true)
|
||||||
|
if err == nil && cfg.OnEvent != nil {
|
||||||
|
cfg.OnEvent("tls_handshake_completed", clientHello)
|
||||||
|
}
|
||||||
|
return &cert.Certificate, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCertificate gets a certificate that matches name (a server name)
|
||||||
|
// from the in-memory cache, according to the lookup table associated with
|
||||||
|
// cfg. The lookup then points to a certificate in the Instance certificate
|
||||||
|
// cache.
|
||||||
|
//
|
||||||
|
// If there is no exact match for name, it will be checked against names of
|
||||||
|
// the form '*.example.com' (wildcard certificates) according to RFC 6125.
|
||||||
|
// If a match is found, matched will be true. If no matches are found, matched
|
||||||
|
// will be false and a "default" certificate will be returned with defaulted
|
||||||
|
// set to true. If defaulted is false, then no certificates were available.
|
||||||
|
//
|
||||||
|
// The logic in this function is adapted from the Go standard library,
|
||||||
|
// which is by the Go Authors.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent use.
|
||||||
|
func (cfg *Config) getCertificate(name string) (cert Certificate, matched, defaulted bool) {
|
||||||
|
var certKey string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
// Not going to trim trailing dots here since RFC 3546 says,
|
||||||
|
// "The hostname is represented ... without a trailing dot."
|
||||||
|
// Just normalize to lowercase.
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
|
||||||
|
cfg.certCache.mu.RLock()
|
||||||
|
defer cfg.certCache.mu.RUnlock()
|
||||||
|
|
||||||
|
// exact match? great, let's use it
|
||||||
|
if certKey, ok = cfg.certificates[name]; ok {
|
||||||
|
cert = cfg.certCache.cache[certKey]
|
||||||
|
matched = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// try replacing labels in the name with wildcards until we get a match
|
||||||
|
labels := strings.Split(name, ".")
|
||||||
|
for i := range labels {
|
||||||
|
labels[i] = "*"
|
||||||
|
candidate := strings.Join(labels, ".")
|
||||||
|
if certKey, ok = cfg.certificates[candidate]; ok {
|
||||||
|
cert = cfg.certCache.cache[certKey]
|
||||||
|
matched = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the certCache directly to see if the SNI name is
|
||||||
|
// already the key of the certificate it wants; this implies
|
||||||
|
// that the SNI can contain the hash of a specific cert
|
||||||
|
// (chain) it wants and we will still be able to serveit up
|
||||||
|
// (this behavior, by the way, could be controversial as to
|
||||||
|
// whether it complies with RFC 6066 about SNI, but I think
|
||||||
|
// it does, soooo...)
|
||||||
|
if directCert, ok := cfg.certCache.cache[name]; ok {
|
||||||
|
cert = directCert
|
||||||
|
matched = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if nothing matches, use a "default" certificate (See issues
|
||||||
|
// mholt/caddy#2035 and mholt/caddy#1303; any change to this
|
||||||
|
// behavior must account for hosts defined like ":443" or
|
||||||
|
// "0.0.0.0:443" where the hostname is empty or a catch-all
|
||||||
|
// IP or something.)
|
||||||
|
if certKey, ok := cfg.certificates[""]; ok {
|
||||||
|
cert = cfg.certCache.cache[certKey]
|
||||||
|
defaulted = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCertDuringHandshake will get a certificate for name. It first tries
|
||||||
|
// the in-memory cache. If no certificate for name is in the cache, the
|
||||||
|
// config most closely corresponding to name will be loaded. If that config
|
||||||
|
// allows it (OnDemand==true) and if loadIfNecessary == true, it goes to disk
|
||||||
|
// to load it into the cache and serve it. If it's not on disk and if
|
||||||
|
// obtainIfNecessary == true, the certificate will be obtained from the CA,
|
||||||
|
// cached, and served. If obtainIfNecessary is true, then loadIfNecessary
|
||||||
|
// must also be set to true. An error will be returned if and only if no
|
||||||
|
// certificate is available.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent use.
|
||||||
|
func (cfg *Config) getCertDuringHandshake(name string, loadIfNecessary, obtainIfNecessary bool) (Certificate, error) {
|
||||||
|
// First check our in-memory cache to see if we've already loaded it
|
||||||
|
cert, matched, defaulted := cfg.getCertificate(name)
|
||||||
|
if matched {
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If OnDemand is enabled, then we might be able to load or
|
||||||
|
// obtain a needed certificate
|
||||||
|
if cfg.OnDemand != nil && loadIfNecessary {
|
||||||
|
// Then check to see if we have one on disk
|
||||||
|
loadedCert, err := cfg.CacheManagedCertificate(name)
|
||||||
|
if err == nil {
|
||||||
|
loadedCert, err = cfg.handshakeMaintenance(name, loadedCert)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Maintaining newly-loaded certificate for %s: %v", name, err)
|
||||||
|
}
|
||||||
|
return loadedCert, nil
|
||||||
|
}
|
||||||
|
if obtainIfNecessary {
|
||||||
|
// By this point, we need to ask the CA for a certificate
|
||||||
|
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
|
||||||
|
// Make sure the certificate should be obtained based on config
|
||||||
|
err := cfg.checkIfCertShouldBeObtained(name)
|
||||||
|
if err != nil {
|
||||||
|
return Certificate{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name has to qualify for a certificate
|
||||||
|
if !HostQualifies(name) {
|
||||||
|
return cert, fmt.Errorf("hostname '%s' does not qualify for certificate", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain certificate from the CA
|
||||||
|
return cfg.obtainOnDemandCertificate(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to the default certificate if there is one
|
||||||
|
if defaulted {
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Certificate{}, fmt.Errorf("no certificate available for %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return fmt.Errorf("not configured for on-demand certificate issuance")
|
||||||
|
}
|
||||||
|
return cfg.OnDemand.Allowed(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// obtainOnDemandCertificate obtains a certificate for name for the given
|
||||||
|
// name. If another goroutine has already started obtaining a cert for
|
||||||
|
// name, it will wait and use what the other goroutine obtained.
|
||||||
|
//
|
||||||
|
// This function is safe for use by multiple concurrent goroutines.
|
||||||
|
func (cfg *Config) obtainOnDemandCertificate(name string) (Certificate, error) {
|
||||||
|
// We must protect this process from happening concurrently, so synchronize.
|
||||||
|
obtainCertWaitChansMu.Lock()
|
||||||
|
wait, ok := obtainCertWaitChans[name]
|
||||||
|
if ok {
|
||||||
|
// lucky us -- another goroutine is already obtaining the certificate.
|
||||||
|
// wait for it to finish obtaining the cert and then we'll use it.
|
||||||
|
obtainCertWaitChansMu.Unlock()
|
||||||
|
<-wait
|
||||||
|
return cfg.getCertDuringHandshake(name, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// looks like it's up to us to do all the work and obtain the cert.
|
||||||
|
// make a chan others can wait on if needed
|
||||||
|
wait = make(chan struct{})
|
||||||
|
obtainCertWaitChans[name] = wait
|
||||||
|
obtainCertWaitChansMu.Unlock()
|
||||||
|
|
||||||
|
// obtain the certificate
|
||||||
|
log.Printf("[INFO] Obtaining new certificate for %s", name)
|
||||||
|
err := cfg.ObtainCert(name, false)
|
||||||
|
|
||||||
|
// 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!
|
||||||
|
obtainCertWaitChansMu.Lock()
|
||||||
|
close(wait)
|
||||||
|
delete(obtainCertWaitChans, name)
|
||||||
|
obtainCertWaitChansMu.Unlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Failed to solve challenge, so don't allow another on-demand
|
||||||
|
// issue for this name to be attempted for a little while.
|
||||||
|
failedIssuanceMu.Lock()
|
||||||
|
failedIssuance[name] = time.Now()
|
||||||
|
go func(name string) {
|
||||||
|
time.Sleep(5 * time.Minute)
|
||||||
|
failedIssuanceMu.Lock()
|
||||||
|
delete(failedIssuance, name)
|
||||||
|
failedIssuanceMu.Unlock()
|
||||||
|
}(name)
|
||||||
|
failedIssuanceMu.Unlock()
|
||||||
|
return Certificate{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success - update counters and stuff
|
||||||
|
atomic.AddInt32(&cfg.OnDemand.obtainedCount, 1)
|
||||||
|
lastIssueTimeMu.Lock()
|
||||||
|
lastIssueTime = time.Now()
|
||||||
|
lastIssueTimeMu.Unlock()
|
||||||
|
|
||||||
|
// certificate is already on disk; now just start over to load it and serve it
|
||||||
|
return cfg.getCertDuringHandshake(name, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handshakeMaintenance performs a check on cert for expiration and OCSP
|
||||||
|
// validity.
|
||||||
|
//
|
||||||
|
// This function is safe for use by multiple concurrent goroutines.
|
||||||
|
func (cfg *Config) handshakeMaintenance(name string, cert Certificate) (Certificate, error) {
|
||||||
|
// Check cert expiration
|
||||||
|
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
||||||
|
if timeLeft < cfg.RenewDurationBefore {
|
||||||
|
log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", cert.Names, timeLeft)
|
||||||
|
return cfg.renewDynamicCertificate(name, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check OCSP staple validity
|
||||||
|
if cert.OCSP != nil {
|
||||||
|
refreshTime := cert.OCSP.ThisUpdate.Add(cert.OCSP.NextUpdate.Sub(cert.OCSP.ThisUpdate) / 2)
|
||||||
|
if time.Now().After(refreshTime) {
|
||||||
|
err := cfg.certCache.stapleOCSP(&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.
|
||||||
|
log.Printf("[ERROR] Getting OCSP for %s: %v", name, err)
|
||||||
|
}
|
||||||
|
cfg.certCache.mu.Lock()
|
||||||
|
cfg.certCache.cache[cert.Hash] = cert
|
||||||
|
cfg.certCache.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// renewDynamicCertificate renews the certificate for name using cfg. It returns the
|
||||||
|
// certificate to use and an error, if any. name should already be lower-cased before
|
||||||
|
// calling this function. name is the name obtained directly from the handshake's
|
||||||
|
// ClientHello.
|
||||||
|
//
|
||||||
|
// This function is safe for use by multiple concurrent goroutines.
|
||||||
|
func (cfg *Config) renewDynamicCertificate(name string, currentCert Certificate) (Certificate, error) {
|
||||||
|
obtainCertWaitChansMu.Lock()
|
||||||
|
wait, ok := obtainCertWaitChans[name]
|
||||||
|
if ok {
|
||||||
|
// lucky us -- another goroutine is already renewing the certificate.
|
||||||
|
// wait for it to finish, then we'll use the new one.
|
||||||
|
obtainCertWaitChansMu.Unlock()
|
||||||
|
<-wait
|
||||||
|
return cfg.getCertDuringHandshake(name, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// looks like it's up to us to do all the work and renew the cert
|
||||||
|
wait = make(chan struct{})
|
||||||
|
obtainCertWaitChans[name] = wait
|
||||||
|
obtainCertWaitChansMu.Unlock()
|
||||||
|
|
||||||
|
// renew and reload the certificate
|
||||||
|
log.Printf("[INFO] Renewing certificate for %s", name)
|
||||||
|
err := cfg.RenewCert(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 := currentCert.configs[0].CacheManagedCertificate(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] loading renewed certificate for %s: %v", name, err)
|
||||||
|
} else {
|
||||||
|
// replace the old certificate with the new one
|
||||||
|
err = cfg.certCache.replaceCertificate(currentCert, newCert)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Replacing certificate for %s: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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!
|
||||||
|
obtainCertWaitChansMu.Lock()
|
||||||
|
close(wait)
|
||||||
|
delete(obtainCertWaitChans, name)
|
||||||
|
obtainCertWaitChansMu.Unlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return Certificate{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg.getCertDuringHandshake(name, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryDistributedChallengeSolver is to be called when the clientHello pertains to
|
||||||
|
// a TLS-ALPN challenge and a certificate is required to solve it. This method
|
||||||
|
// checks the distributed store of challenge info files and, if a matching ServerName
|
||||||
|
// is present, it makes a certificate to solve this challenge and returns it.
|
||||||
|
// A boolean true is returned if a valid certificate is returned.
|
||||||
|
func (cfg *Config) tryDistributedChallengeSolver(clientHello *tls.ClientHelloInfo) (Certificate, bool, error) {
|
||||||
|
tokenKey := distributedSolver{}.challengeTokensKey(clientHello.ServerName)
|
||||||
|
chalInfoBytes, err := cfg.certCache.storage.Load(tokenKey)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(ErrNotExist); ok {
|
||||||
|
return Certificate{}, false, nil
|
||||||
|
}
|
||||||
|
return Certificate{}, false, fmt.Errorf("opening distributed challenge token file %s: %v", tokenKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var chalInfo challengeInfo
|
||||||
|
err = json.Unmarshal(chalInfoBytes, &chalInfo)
|
||||||
|
if err != nil {
|
||||||
|
return Certificate{}, false, fmt.Errorf("decoding challenge token file %s (corrupted?): %v", tokenKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := tlsalpn01.ChallengeCert(chalInfo.Domain, chalInfo.KeyAuth)
|
||||||
|
if err != nil {
|
||||||
|
return Certificate{}, false, fmt.Errorf("making TLS-ALPN challenge certificate: %v", err)
|
||||||
|
}
|
||||||
|
if cert == nil {
|
||||||
|
return Certificate{}, false, fmt.Errorf("got nil TLS-ALPN challenge certificate but no error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Certificate{Certificate: *cert}, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// obtainCertWaitChans is used to coordinate obtaining certs for each hostname.
|
||||||
|
var obtainCertWaitChans = make(map[string]chan struct{})
|
||||||
|
var obtainCertWaitChansMu sync.Mutex
|
||||||
111
vendor/github.com/mholt/certmagic/httphandler.go
generated
vendored
Normal file
111
vendor/github.com/mholt/certmagic/httphandler.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/challenge/http01"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPChallengeHandler wraps h in a handler that can solve the ACME
|
||||||
|
// HTTP challenge. cfg is required, and it must have a certificate
|
||||||
|
// cache backed by a functional storage facility, since that is where
|
||||||
|
// the challenge state is stored between initiation and solution.
|
||||||
|
//
|
||||||
|
// If a request is not an ACME HTTP challenge, h willl be invoked.
|
||||||
|
func (cfg *Config) HTTPChallengeHandler(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if cfg.HandleHTTPChallenge(w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleHTTPChallenge uses cfg to solve challenge requests from an ACME
|
||||||
|
// server that were initiated by this instance or any other instance in
|
||||||
|
// this cluster (being, any instances using the same storage cfg does).
|
||||||
|
//
|
||||||
|
// If the HTTP challenge is disabled, this function is a no-op.
|
||||||
|
//
|
||||||
|
// If cfg is nil or if cfg does not have a certificate cache backed by
|
||||||
|
// usable storage, solving the HTTP challenge will fail.
|
||||||
|
//
|
||||||
|
// It returns true if it handled the request; if so, the response has
|
||||||
|
// already been written. If false is returned, this call was a no-op and
|
||||||
|
// the request has not been handled.
|
||||||
|
func (cfg *Config) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
if cfg == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if cfg.DisableHTTPChallenge {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(r.URL.Path, challengeBasePath) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return cfg.distributedHTTPChallengeSolver(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// distributedHTTPChallengeSolver checks to see if this challenge
|
||||||
|
// request was initiated by this or another instance which uses the
|
||||||
|
// same storage as cfg does, and attempts to complete the challenge for
|
||||||
|
// it. It returns true if the request was handled; false otherwise.
|
||||||
|
func (cfg *Config) distributedHTTPChallengeSolver(w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
if cfg == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenKey := distributedSolver{config: cfg}.challengeTokensKey(r.Host)
|
||||||
|
chalInfoBytes, err := cfg.certCache.storage.Load(tokenKey)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(ErrNotExist); !ok {
|
||||||
|
log.Printf("[ERROR][%s] Opening distributed HTTP challenge token file: %v", r.Host, err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var chalInfo challengeInfo
|
||||||
|
err = json.Unmarshal(chalInfoBytes, &chalInfo)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR][%s] Decoding challenge token file %s (corrupted?): %v", r.Host, tokenKey, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return answerHTTPChallenge(w, r, chalInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// answerHTTPChallenge solves the challenge with chalInfo.
|
||||||
|
// Most of this code borrowed from xenolf/lego's built-in HTTP-01
|
||||||
|
// challenge solver in March 2018.
|
||||||
|
func answerHTTPChallenge(w http.ResponseWriter, r *http.Request, chalInfo challengeInfo) bool {
|
||||||
|
challengeReqPath := http01.ChallengePath(chalInfo.Token)
|
||||||
|
if r.URL.Path == challengeReqPath &&
|
||||||
|
strings.HasPrefix(r.Host, chalInfo.Domain) &&
|
||||||
|
r.Method == "GET" {
|
||||||
|
w.Header().Add("Content-Type", "text/plain")
|
||||||
|
w.Write([]byte(chalInfo.KeyAuth))
|
||||||
|
r.Close = true
|
||||||
|
log.Printf("[INFO][%s] Served key authentication (distributed)", chalInfo.Domain)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const challengeBasePath = "/.well-known/acme-challenge"
|
||||||
307
vendor/github.com/mholt/certmagic/maintain.go
generated
vendored
Normal file
307
vendor/github.com/mholt/certmagic/maintain.go
generated
vendored
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ocsp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maintainAssets is a permanently-blocking function
|
||||||
|
// that loops indefinitely and, on a regular schedule, checks
|
||||||
|
// certificates for expiration and initiates a renewal of certs
|
||||||
|
// that are expiring soon. It also updates OCSP stapling and
|
||||||
|
// performs other maintenance of assets. It should only be
|
||||||
|
// called once per process.
|
||||||
|
//
|
||||||
|
// You must pass in the channel which you'll close when
|
||||||
|
// maintenance should stop, to allow this goroutine to clean up
|
||||||
|
// after itself and unblock. (Not that you HAVE to stop it...)
|
||||||
|
func (certCache *Cache) maintainAssets() {
|
||||||
|
renewalTicker := time.NewTicker(certCache.RenewInterval)
|
||||||
|
ocspTicker := time.NewTicker(certCache.OCSPInterval)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-renewalTicker.C:
|
||||||
|
log.Println("[INFO] Scanning for expiring certificates")
|
||||||
|
err := certCache.RenewManagedCertificates(false)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Renewing managed certificates: %v", err)
|
||||||
|
}
|
||||||
|
log.Println("[INFO] Done checking certificates")
|
||||||
|
case <-ocspTicker.C:
|
||||||
|
log.Println("[INFO] Scanning for stale OCSP staples")
|
||||||
|
certCache.updateOCSPStaples()
|
||||||
|
certCache.deleteOldStapleFiles()
|
||||||
|
log.Println("[INFO] Done checking OCSP staples")
|
||||||
|
case <-certCache.stopChan:
|
||||||
|
renewalTicker.Stop()
|
||||||
|
ocspTicker.Stop()
|
||||||
|
log.Println("[INFO] Stopped certificate maintenance routine")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewManagedCertificates renews managed certificates,
|
||||||
|
// including ones loaded on-demand. Note that this is done
|
||||||
|
// automatically on a regular basis; normally you will not
|
||||||
|
// need to call this.
|
||||||
|
func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
|
||||||
|
// we use the queues for a very important reason: to do any and all
|
||||||
|
// operations that could require an exclusive write lock outside
|
||||||
|
// of the read lock! otherwise we get a deadlock, yikes. in other
|
||||||
|
// words, our first iteration through the certificate cache does NOT
|
||||||
|
// perform any operations--only queues them--so that more fine-grained
|
||||||
|
// write locks may be obtained during the actual operations.
|
||||||
|
var renewQueue, reloadQueue, deleteQueue []Certificate
|
||||||
|
|
||||||
|
certCache.mu.RLock()
|
||||||
|
for certKey, cert := range certCache.cache {
|
||||||
|
if len(cert.configs) == 0 {
|
||||||
|
// this is bad if this happens, probably a programmer error (oops)
|
||||||
|
log.Printf("[ERROR] No associated TLS config for certificate with names %v; unable to manage", cert.Names)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !cert.managed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// the list of names on this cert should never be empty... programmer error?
|
||||||
|
if cert.Names == nil || len(cert.Names) == 0 {
|
||||||
|
log.Printf("[WARNING] Certificate keyed by '%s' has no names: %v - removing from cache", certKey, cert.Names)
|
||||||
|
deleteQueue = append(deleteQueue, cert)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if time is up or expires soon, we need to try to renew it
|
||||||
|
if cert.NeedsRenewal() {
|
||||||
|
// see if the certificate in storage has already been renewed, possibly by another
|
||||||
|
// instance that didn't coordinate with this one; if so, just load it (this
|
||||||
|
// might happen if another instance already renewed it - kinda sloppy but checking disk
|
||||||
|
// first is a simple way to possibly drastically reduce rate limit problems)
|
||||||
|
storedCertExpiring, err := managedCertInStorageExpiresSoon(cert)
|
||||||
|
if err != nil {
|
||||||
|
// hmm, weird, but not a big deal, maybe it was deleted or something
|
||||||
|
log.Printf("[NOTICE] Error while checking if certificate for %v in storage is also expiring soon: %v",
|
||||||
|
cert.Names, 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
|
||||||
|
// a likely-unnecessary renewal procedure
|
||||||
|
reloadQueue = append(reloadQueue, cert)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// the certificate in storage has not been renewed yet, so we will do it
|
||||||
|
// NOTE: It is super-important to note that the TLS-ALPN challenge requires
|
||||||
|
// a write lock on the cache in order to complete its challenge, so it is extra
|
||||||
|
// vital that this renew operation does not happen inside our read lock!
|
||||||
|
renewQueue = append(renewQueue, cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
certCache.mu.RUnlock()
|
||||||
|
|
||||||
|
// Reload certificates that merely need to be updated in memory
|
||||||
|
for _, oldCert := range reloadQueue {
|
||||||
|
timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
|
||||||
|
log.Printf("[INFO] Certificate for %v expires in %v, but is already renewed in storage; reloading stored certificate",
|
||||||
|
oldCert.Names, timeLeft)
|
||||||
|
|
||||||
|
err := certCache.reloadManagedCertificate(oldCert)
|
||||||
|
if err != nil {
|
||||||
|
if interactive {
|
||||||
|
return err // operator is present, so report error immediately
|
||||||
|
}
|
||||||
|
log.Printf("[ERROR] Loading renewed certificate: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renewal queue
|
||||||
|
for _, oldCert := range renewQueue {
|
||||||
|
timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
|
||||||
|
log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", oldCert.Names, timeLeft)
|
||||||
|
|
||||||
|
// Get the name which we should use to renew this certificate;
|
||||||
|
// we only support managing certificates with one name per cert,
|
||||||
|
// so this should be easy.
|
||||||
|
renewName := oldCert.Names[0]
|
||||||
|
|
||||||
|
// perform renewal
|
||||||
|
err := oldCert.configs[0].RenewCert(renewName, interactive)
|
||||||
|
if err != nil {
|
||||||
|
if interactive {
|
||||||
|
// Certificate renewal failed and the operator is present. See a discussion about
|
||||||
|
// this in issue mholt/caddy#642. For a while, we only stopped if the certificate
|
||||||
|
// was expired, but in reality, there is no difference between reporting it now
|
||||||
|
// versus later, except that there's somebody present to deal withit right now.
|
||||||
|
// Follow-up: See issue mholt/caddy#1680. Only fail in this case if the certificate
|
||||||
|
// is dangerously close to expiration.
|
||||||
|
timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
|
||||||
|
if timeLeft < oldCert.configs[0].RenewDurationBeforeAtStartup {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("[ERROR] %v", err)
|
||||||
|
if oldCert.configs[0].OnDemand != nil {
|
||||||
|
// loaded dynamically, remove dynamically
|
||||||
|
deleteQueue = append(deleteQueue, oldCert)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// successful renewal, so update in-memory cache by loading
|
||||||
|
// renewed certificate so it will be used with handshakes
|
||||||
|
err = certCache.reloadManagedCertificate(oldCert)
|
||||||
|
if err != nil {
|
||||||
|
if interactive {
|
||||||
|
return err // operator is present, so report error immediately
|
||||||
|
}
|
||||||
|
log.Printf("[ERROR] %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletion queue
|
||||||
|
for _, cert := range deleteQueue {
|
||||||
|
certCache.mu.Lock()
|
||||||
|
// remove any pointers to this certificate from Configs
|
||||||
|
for _, cfg := range cert.configs {
|
||||||
|
for name, certKey := range cfg.certificates {
|
||||||
|
if certKey == cert.Hash {
|
||||||
|
delete(cfg.certificates, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// then delete the certificate from the cache
|
||||||
|
delete(certCache.cache, cert.Hash)
|
||||||
|
certCache.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateOCSPStaples updates the OCSP stapling in all
|
||||||
|
// eligible, cached certificates.
|
||||||
|
//
|
||||||
|
// OCSP maintenance strives to abide the relevant points on
|
||||||
|
// Ryan Sleevi's recommendations for good OCSP support:
|
||||||
|
// https://gist.github.com/sleevi/5efe9ef98961ecfb4da8
|
||||||
|
func (certCache *Cache) updateOCSPStaples() {
|
||||||
|
// Create a temporary place to store updates
|
||||||
|
// until we release the potentially long-lived
|
||||||
|
// read lock and use a short-lived write lock
|
||||||
|
// on the certificate cache.
|
||||||
|
type ocspUpdate struct {
|
||||||
|
rawBytes []byte
|
||||||
|
parsed *ocsp.Response
|
||||||
|
}
|
||||||
|
updated := make(map[string]ocspUpdate)
|
||||||
|
|
||||||
|
certCache.mu.RLock()
|
||||||
|
for certHash, cert := range certCache.cache {
|
||||||
|
// no point in updating OCSP for expired certificates
|
||||||
|
if time.Now().After(cert.NotAfter) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastNextUpdate time.Time
|
||||||
|
if cert.OCSP != nil {
|
||||||
|
lastNextUpdate = cert.OCSP.NextUpdate
|
||||||
|
if freshOCSP(cert.OCSP) {
|
||||||
|
continue // no need to update staple if ours is still fresh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := certCache.stapleOCSP(&cert, nil)
|
||||||
|
if err != nil {
|
||||||
|
if cert.OCSP != nil {
|
||||||
|
// if there was no staple before, that's fine; otherwise we should log the error
|
||||||
|
log.Printf("[ERROR] Checking OCSP: %v", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// By this point, we've obtained the latest OCSP response.
|
||||||
|
// If there was no staple before, or if the response is updated, make
|
||||||
|
// sure we apply the update to all names on the certificate.
|
||||||
|
if cert.OCSP != nil && (lastNextUpdate.IsZero() || lastNextUpdate != cert.OCSP.NextUpdate) {
|
||||||
|
log.Printf("[INFO] Advancing OCSP staple for %v from %s to %s",
|
||||||
|
cert.Names, lastNextUpdate, cert.OCSP.NextUpdate)
|
||||||
|
updated[certHash] = ocspUpdate{rawBytes: cert.Certificate.OCSPStaple, parsed: cert.OCSP}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
certCache.mu.RUnlock()
|
||||||
|
|
||||||
|
// These write locks should be brief since we have all the info we need now.
|
||||||
|
for certKey, update := range updated {
|
||||||
|
certCache.mu.Lock()
|
||||||
|
cert := certCache.cache[certKey]
|
||||||
|
cert.OCSP = update.parsed
|
||||||
|
cert.Certificate.OCSPStaple = update.rawBytes
|
||||||
|
certCache.cache[certKey] = cert
|
||||||
|
certCache.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteOldStapleFiles deletes cached OCSP staples that have expired.
|
||||||
|
// TODO: We should do this for long-expired certificates, too.
|
||||||
|
func (certCache *Cache) deleteOldStapleFiles() {
|
||||||
|
ocspKeys, err := certCache.storage.List(prefixOCSP)
|
||||||
|
if err != nil {
|
||||||
|
// maybe just hasn't been created yet; no big deal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, key := range ocspKeys {
|
||||||
|
ocspBytes, err := certCache.storage.Load(key)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] While deleting old OCSP staples, unable to load staple file: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resp, err := ocsp.ParseResponse(ocspBytes, nil)
|
||||||
|
if err != nil {
|
||||||
|
// contents are invalid; delete it
|
||||||
|
err = certCache.storage.Delete(key)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Purging corrupt staple file %s: %v", key, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if time.Now().After(resp.NextUpdate) {
|
||||||
|
// response has expired; delete it
|
||||||
|
err = certCache.storage.Delete(key)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Purging expired staple file %s: %v", key, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultRenewInterval is how often to check certificates for renewal.
|
||||||
|
DefaultRenewInterval = 12 * time.Hour
|
||||||
|
|
||||||
|
// DefaultRenewDurationBefore is how long before expiration to renew certificates.
|
||||||
|
DefaultRenewDurationBefore = (24 * time.Hour) * 30
|
||||||
|
|
||||||
|
// DefaultRenewDurationBeforeAtStartup is how long before expiration to require
|
||||||
|
// a renewed certificate when the process is first starting up (see mholt/caddy#1680).
|
||||||
|
DefaultRenewDurationBeforeAtStartup = (24 * time.Hour) * 7
|
||||||
|
|
||||||
|
// DefaultOCSPInterval is how often to check if OCSP stapling needs updating.
|
||||||
|
DefaultOCSPInterval = 1 * time.Hour
|
||||||
|
)
|
||||||
85
vendor/github.com/mholt/certmagic/memorysync.go
generated
vendored
Normal file
85
vendor/github.com/mholt/certmagic/memorysync.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MemoryLocker implements the Locker interface
|
||||||
|
// using memory. An empty value is NOT VALID,
|
||||||
|
// so you must use NewMemoryLocker() to get one.
|
||||||
|
type MemoryLocker struct {
|
||||||
|
nameLocks map[string]*MemoryWaiter
|
||||||
|
nameLocksMu *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMemoryLocker returns a valid Locker backed by fs.
|
||||||
|
func NewMemoryLocker() *MemoryLocker {
|
||||||
|
return &MemoryLocker{
|
||||||
|
nameLocks: make(map[string]*MemoryWaiter),
|
||||||
|
nameLocksMu: new(sync.Mutex),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryLock attempts to get a lock for name, otherwise it returns
|
||||||
|
// a Waiter value to wait until the other process is finished.
|
||||||
|
func (l *MemoryLocker) TryLock(name string) (Waiter, error) {
|
||||||
|
l.nameLocksMu.Lock()
|
||||||
|
defer l.nameLocksMu.Unlock()
|
||||||
|
|
||||||
|
// see if lock already exists within this process
|
||||||
|
w, ok := l.nameLocks[name]
|
||||||
|
if ok {
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// we got the lock, so create it
|
||||||
|
w = &MemoryWaiter{wg: new(sync.WaitGroup)}
|
||||||
|
w.wg.Add(1)
|
||||||
|
l.nameLocks[name] = w
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock releases the lock for name.
|
||||||
|
func (l *MemoryLocker) Unlock(name string) error {
|
||||||
|
l.nameLocksMu.Lock()
|
||||||
|
defer l.nameLocksMu.Unlock()
|
||||||
|
|
||||||
|
w, ok := l.nameLocks[name]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("MemoryLocker: no lock to release for %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.wg.Done()
|
||||||
|
delete(l.nameLocks, name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryWaiter implements Waiter in memory.
|
||||||
|
type MemoryWaiter struct {
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait waits until w.wg is done.
|
||||||
|
func (w *MemoryWaiter) Wait() {
|
||||||
|
w.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Locker = &MemoryLocker{}
|
||||||
|
var _ Waiter = &MemoryWaiter{}
|
||||||
209
vendor/github.com/mholt/certmagic/ocsp.go
generated
vendored
Normal file
209
vendor/github.com/mholt/certmagic/ocsp.go
generated
vendored
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ocsp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// If you don't have the PEM blocks already, just pass in nil.
|
||||||
|
//
|
||||||
|
// Errors here are not necessarily fatal, it could just be that the
|
||||||
|
// certificate doesn't have an issuer URL.
|
||||||
|
func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error {
|
||||||
|
if pemBundle == nil {
|
||||||
|
// we need a PEM encoding only for some function calls below
|
||||||
|
bundle := new(bytes.Buffer)
|
||||||
|
for _, derBytes := range cert.Certificate.Certificate {
|
||||||
|
pem.Encode(bundle, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||||
|
}
|
||||||
|
pemBundle = bundle.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
var ocspBytes []byte
|
||||||
|
var ocspResp *ocsp.Response
|
||||||
|
var ocspErr error
|
||||||
|
var gotNewOCSP bool
|
||||||
|
|
||||||
|
// First try to load OCSP staple from storage and see if
|
||||||
|
// we can still use it.
|
||||||
|
ocspStapleKey := prefixOCSPStaple(cert, pemBundle)
|
||||||
|
cachedOCSP, err := certCache.storage.Load(ocspStapleKey)
|
||||||
|
if err == nil {
|
||||||
|
resp, err := ocsp.ParseResponse(cachedOCSP, nil)
|
||||||
|
if err == nil {
|
||||||
|
if freshOCSP(resp) {
|
||||||
|
// staple is still fresh; use it
|
||||||
|
ocspBytes = cachedOCSP
|
||||||
|
ocspResp = resp
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// invalid contents; delete the file
|
||||||
|
// (we do this independently of the maintenance routine because
|
||||||
|
// in this case we know for sure this should be a staple file
|
||||||
|
// because we loaded it by name, whereas the maintenance routine
|
||||||
|
// just iterates the list of files, even if somehow a non-staple
|
||||||
|
// file gets in the folder. in this case we are sure it is corrupt.)
|
||||||
|
err := certCache.storage.Delete(ocspStapleKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[WARNING] Unable to delete invalid OCSP staple file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we couldn't get a fresh staple by reading the cache,
|
||||||
|
// then we need to request it from the OCSP responder
|
||||||
|
if ocspResp == nil || len(ocspBytes) == 0 {
|
||||||
|
ocspBytes, ocspResp, ocspErr = getOCSPForCert(pemBundle)
|
||||||
|
if ocspErr != nil {
|
||||||
|
// An error here is not a problem because a certificate may simply
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
gotNewOCSP = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// By now, we should have a response. If good, staple it to
|
||||||
|
// the certificate. If the OCSP response was not loaded from
|
||||||
|
// storage, we persist it for next time.
|
||||||
|
if ocspResp.Status == ocsp.Good {
|
||||||
|
if ocspResp.NextUpdate.After(cert.NotAfter) {
|
||||||
|
// 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.NotAfter.Sub(ocspResp.NextUpdate))
|
||||||
|
}
|
||||||
|
cert.Certificate.OCSPStaple = ocspBytes
|
||||||
|
cert.OCSP = ocspResp
|
||||||
|
if gotNewOCSP {
|
||||||
|
err := certCache.storage.Store(ocspStapleKey, ocspBytes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to write OCSP staple file for %v: %v", cert.Names, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOCSPForCert takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
||||||
|
// the parsed response, and an error, if any. The returned []byte can be passed directly
|
||||||
|
// into the OCSPStaple property of a tls.Certificate. If the bundle only contains the
|
||||||
|
// issued certificate, this function will try to get the issuer certificate from the
|
||||||
|
// IssuingCertificateURL in the certificate. If the []byte and/or ocsp.Response return
|
||||||
|
// values are nil, the OCSP status may be assumed OCSPUnknown.
|
||||||
|
//
|
||||||
|
// Borrowed from github.com/xenolf/lego
|
||||||
|
func getOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) {
|
||||||
|
// TODO: Perhaps this should be synchronized too, with a Locker?
|
||||||
|
|
||||||
|
certificates, err := parseCertsFromPEMBundle(bundle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect the certificate slice to be ordered downwards the chain.
|
||||||
|
// SRV CRT -> CA. We need to pull the leaf and issuer certs out of it,
|
||||||
|
// which should always be the first two certificates. If there's no
|
||||||
|
// OCSP server listed in the leaf cert, there's nothing to do. And if
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
if len(certificates) == 1 {
|
||||||
|
if len(issuedCert.IssuingCertificateURL) == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("no URL to issuing certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(issuedCert.IssuingCertificateURL[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("getting issuer certificate: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
issuerBytes, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024*1024))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("reading issuer certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
issuerCert, err := x509.ParseCertificate(issuerBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("parsing issuer certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert it into the slice on position 0;
|
||||||
|
// we want it ordered right SRV CRT -> CA
|
||||||
|
certificates = append(certificates, issuerCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
issuerCert := certificates[1]
|
||||||
|
|
||||||
|
ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("creating OCSP request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := bytes.NewReader(ocspReq)
|
||||||
|
req, err := http.Post(issuedCert.OCSPServer[0], "application/ocsp-request", reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("making OCSP request: %v", err)
|
||||||
|
}
|
||||||
|
defer req.Body.Close()
|
||||||
|
|
||||||
|
ocspResBytes, err := ioutil.ReadAll(io.LimitReader(req.Body, 1024*1024))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("reading OCSP response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("parsing OCSP response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ocspResBytes, ocspRes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// freshOCSP returns true if resp is still fresh,
|
||||||
|
// meaning that it is not expedient to get an
|
||||||
|
// updated response from the OCSP server.
|
||||||
|
func freshOCSP(resp *ocsp.Response) bool {
|
||||||
|
nextUpdate := resp.NextUpdate
|
||||||
|
// If there is an OCSP responder certificate, and it expires before the
|
||||||
|
// OCSP response, use its expiration date as the end of the OCSP
|
||||||
|
// response's validity period.
|
||||||
|
if resp.Certificate != nil && resp.Certificate.NotAfter.Before(nextUpdate) {
|
||||||
|
nextUpdate = resp.Certificate.NotAfter
|
||||||
|
}
|
||||||
|
// start checking OCSP staple about halfway through validity period for good measure
|
||||||
|
refreshTime := resp.ThisUpdate.Add(nextUpdate.Sub(resp.ThisUpdate) / 2)
|
||||||
|
return time.Now().Before(refreshTime)
|
||||||
|
}
|
||||||
148
vendor/github.com/mholt/certmagic/solvers.go
generated
vendored
Normal file
148
vendor/github.com/mholt/certmagic/solvers.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/challenge"
|
||||||
|
"github.com/xenolf/lego/challenge/tlsalpn01"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tlsALPNSolver is a type that can solve TLS-ALPN challenges using
|
||||||
|
// an existing listener and our custom, in-memory certificate cache.
|
||||||
|
type tlsALPNSolver struct {
|
||||||
|
certCache *Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present adds the challenge certificate to the cache.
|
||||||
|
func (s tlsALPNSolver) Present(domain, token, keyAuth string) error {
|
||||||
|
cert, err := tlsalpn01.ChallengeCert(domain, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
certHash := hashCertificateChain(cert.Certificate)
|
||||||
|
s.certCache.mu.Lock()
|
||||||
|
s.certCache.cache[tlsALPNCertKeyName(domain)] = Certificate{
|
||||||
|
Certificate: *cert,
|
||||||
|
Names: []string{domain},
|
||||||
|
Hash: certHash, // perhaps not necesssary
|
||||||
|
}
|
||||||
|
s.certCache.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp removes the challenge certificate from the cache.
|
||||||
|
func (s tlsALPNSolver) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
s.certCache.mu.Lock()
|
||||||
|
delete(s.certCache.cache, domain)
|
||||||
|
s.certCache.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tlsALPNCertKeyName returns the key to use when caching a cert
|
||||||
|
// for use with the TLS-ALPN ACME challenge. It is simply to help
|
||||||
|
// avoid conflicts (although at time of writing, there shouldn't
|
||||||
|
// be, since the cert cache is keyed by hash of certificate chain).
|
||||||
|
func tlsALPNCertKeyName(sniName string) string {
|
||||||
|
return sniName + ":acme-tls-alpn"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// configurations. The only requirement is that the instance which
|
||||||
|
// initiates the challenge shares the same storage and locker with
|
||||||
|
// the others in the cluster. The storage backing the certificate
|
||||||
|
// cache in distributedSolver.config is crucial.
|
||||||
|
//
|
||||||
|
// Obviously, the instance which completes the challenge must be
|
||||||
|
// serving on the HTTPChallengePort for the HTTP-01 challenge or the
|
||||||
|
// TLSALPNChallengePort for the TLS-ALPN-01 challenge (or have all
|
||||||
|
// the packets port-forwarded) to receive and handle the request. The
|
||||||
|
// server which receives the challenge must handle it by checking to
|
||||||
|
// see if the challenge token exists in storage, and if so, decode it
|
||||||
|
// and use it to serve up the correct response. HTTPChallengeHandler
|
||||||
|
// in this package as well as the GetCertificate method implemented
|
||||||
|
// by a Config support and even require this behavior.
|
||||||
|
//
|
||||||
|
// In short: the only two requirements for cluster operation are
|
||||||
|
// sharing sync and storage, and using the facilities provided by
|
||||||
|
// this package for solving the challenges.
|
||||||
|
type distributedSolver struct {
|
||||||
|
// The config with a certificate cache
|
||||||
|
// with a reference to the storage to
|
||||||
|
// use which is shared among all the
|
||||||
|
// instances in the cluster - REQUIRED.
|
||||||
|
config *Config
|
||||||
|
|
||||||
|
// Since the distributedSolver is only a
|
||||||
|
// wrapper over an actual solver, place
|
||||||
|
// the actual solver here.
|
||||||
|
providerServer challenge.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present invokes the underlying solver's Present method
|
||||||
|
// and also stores domain, token, and keyAuth to the storage
|
||||||
|
// backing the certificate cache of dhs.config.
|
||||||
|
func (dhs distributedSolver) Present(domain, token, keyAuth string) error {
|
||||||
|
if dhs.providerServer != nil {
|
||||||
|
err := dhs.providerServer.Present(domain, token, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("presenting with standard provider server: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
infoBytes, err := json.Marshal(challengeInfo{
|
||||||
|
Domain: domain,
|
||||||
|
Token: token,
|
||||||
|
KeyAuth: keyAuth,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dhs.config.certCache.storage.Store(dhs.challengeTokensKey(domain), infoBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp invokes the underlying solver's CleanUp method
|
||||||
|
// and also cleans up any assets saved to storage.
|
||||||
|
func (dhs distributedSolver) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
if dhs.providerServer != nil {
|
||||||
|
err := dhs.providerServer.CleanUp(domain, token, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Cleaning up standard provider server: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dhs.config.certCache.storage.Delete(dhs.challengeTokensKey(domain))
|
||||||
|
}
|
||||||
|
|
||||||
|
// challengeTokensPrefix returns the key prefix for challenge info.
|
||||||
|
func (dhs distributedSolver) challengeTokensPrefix() string {
|
||||||
|
return filepath.Join(prefixCA(dhs.config.CA), "challenge_tokens")
|
||||||
|
}
|
||||||
|
|
||||||
|
// challengeTokensKey returns the key to use to store and access
|
||||||
|
// challenge info for domain.
|
||||||
|
func (dhs distributedSolver) challengeTokensKey(domain string) string {
|
||||||
|
return filepath.Join(dhs.challengeTokensPrefix(), safeKey(domain)+".json")
|
||||||
|
}
|
||||||
|
|
||||||
|
type challengeInfo struct {
|
||||||
|
Domain, Token, KeyAuth string
|
||||||
|
}
|
||||||
212
vendor/github.com/mholt/certmagic/storage.go
generated
vendored
Normal file
212
vendor/github.com/mholt/certmagic/storage.go
generated
vendored
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Storage is a type that implements a key-value store.
|
||||||
|
// Keys are prefix-based, with forward slash '/' as separators
|
||||||
|
// and without a leading slash.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
type Storage interface {
|
||||||
|
// Exists returns true if the key exists
|
||||||
|
// and there was no error checking.
|
||||||
|
Exists(key string) bool
|
||||||
|
|
||||||
|
// Store puts value at key.
|
||||||
|
Store(key string, value []byte) error
|
||||||
|
|
||||||
|
// Load retrieves the value at key.
|
||||||
|
Load(key string) ([]byte, error)
|
||||||
|
|
||||||
|
// Delete deletes key.
|
||||||
|
Delete(key string) error
|
||||||
|
|
||||||
|
// List returns all keys that match prefix.
|
||||||
|
List(prefix string) ([]string, error)
|
||||||
|
|
||||||
|
// Stat returns information about key.
|
||||||
|
Stat(key string) (KeyInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyInfo holds information about a key in storage.
|
||||||
|
type KeyInfo struct {
|
||||||
|
Key string
|
||||||
|
Modified time.Time
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeTx stores all the values or none at all.
|
||||||
|
func storeTx(s Storage, all []keyValue) error {
|
||||||
|
for i, kv := range all {
|
||||||
|
err := s.Store(kv.key, kv.value)
|
||||||
|
if err != nil {
|
||||||
|
for j := i - 1; j >= 0; j-- {
|
||||||
|
s.Delete(all[j].key)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyValue pairs a key and a value.
|
||||||
|
type keyValue struct {
|
||||||
|
key string
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
prefixACME = "acme"
|
||||||
|
prefixOCSP = "ocsp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func prefixCA(ca string) string {
|
||||||
|
caURL, err := url.Parse(ca)
|
||||||
|
if err != nil {
|
||||||
|
caURL = &url.URL{Host: ca}
|
||||||
|
}
|
||||||
|
return path.Join(prefixACME, safeKey(caURL.Host))
|
||||||
|
}
|
||||||
|
|
||||||
|
func prefixSite(ca, domain string) string {
|
||||||
|
return path.Join(prefixCA(ca), "sites", safeKey(domain))
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefixSiteCert returns the path to the certificate file for domain.
|
||||||
|
func prefixSiteCert(ca, domain string) string {
|
||||||
|
return path.Join(prefixSite(ca, domain), safeKey(domain)+".crt")
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefixSiteKey returns the path to domain's private key file.
|
||||||
|
func prefixSiteKey(ca, domain string) string {
|
||||||
|
return path.Join(prefixSite(ca, domain), safeKey(domain)+".key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefixSiteMeta returns the path to the domain's asset metadata file.
|
||||||
|
func prefixSiteMeta(ca, domain string) string {
|
||||||
|
return path.Join(prefixSite(ca, domain), safeKey(domain)+".json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func prefixUsers(ca string) string {
|
||||||
|
return path.Join(prefixCA(ca), "users")
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefixUser gets the account folder for the user with email
|
||||||
|
func prefixUser(ca, email string) string {
|
||||||
|
if email == "" {
|
||||||
|
email = emptyEmail
|
||||||
|
}
|
||||||
|
return path.Join(prefixUsers(ca), safeKey(email))
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefixUserReg gets the path to the registration file for the user with the
|
||||||
|
// given email address.
|
||||||
|
func prefixUserReg(ca, email string) string {
|
||||||
|
return safeUserKey(ca, email, "registration", ".json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefixUserKey gets the path to the private key file for the user with the
|
||||||
|
// given email address.
|
||||||
|
func prefixUserKey(ca, email string) string {
|
||||||
|
return safeUserKey(ca, email, "private", ".key")
|
||||||
|
}
|
||||||
|
|
||||||
|
func prefixOCSPStaple(cert *Certificate, pemBundle []byte) string {
|
||||||
|
var ocspFileName string
|
||||||
|
if len(cert.Names) > 0 {
|
||||||
|
firstName := safeKey(cert.Names[0])
|
||||||
|
ocspFileName = firstName + "-"
|
||||||
|
}
|
||||||
|
ocspFileName += fastHash(pemBundle)
|
||||||
|
return path.Join(prefixOCSP, ocspFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeUserKey returns a key for the given email,
|
||||||
|
// with the default filename, and the filename
|
||||||
|
// ending in the given extension.
|
||||||
|
func safeUserKey(ca, email, defaultFilename, extension string) string {
|
||||||
|
if email == "" {
|
||||||
|
email = emptyEmail
|
||||||
|
}
|
||||||
|
email = strings.ToLower(email)
|
||||||
|
filename := emailUsername(email)
|
||||||
|
if filename == "" {
|
||||||
|
filename = defaultFilename
|
||||||
|
}
|
||||||
|
filename = safeKey(filename)
|
||||||
|
return path.Join(prefixUser(ca, email), filename+extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
// emailUsername returns the username portion of an email address (part before
|
||||||
|
// '@') or the original input if it can't find the "@" symbol.
|
||||||
|
func emailUsername(email string) string {
|
||||||
|
at := strings.Index(email, "@")
|
||||||
|
if at == -1 {
|
||||||
|
return email
|
||||||
|
} else if at == 0 {
|
||||||
|
return email[1:]
|
||||||
|
}
|
||||||
|
return email[:at]
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeKey standardizes and sanitizes str for use in a file path.
|
||||||
|
func safeKey(str string) string {
|
||||||
|
str = strings.ToLower(str)
|
||||||
|
str = strings.TrimSpace(str)
|
||||||
|
|
||||||
|
// replace a few specific characters
|
||||||
|
repl := strings.NewReplacer(
|
||||||
|
" ", "_",
|
||||||
|
"+", "_plus_",
|
||||||
|
"*", "wildcard_",
|
||||||
|
"..", "", // prevent directory traversal (regex allows single dots)
|
||||||
|
)
|
||||||
|
str = repl.Replace(str)
|
||||||
|
|
||||||
|
// finally remove all non-word characters
|
||||||
|
return safeKeyRE.ReplaceAllLiteralString(str, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeKeyRE matches any undesirable characters in storage keys.
|
||||||
|
// Note that this allows dots, so you'll have to strip ".." manually.
|
||||||
|
var safeKeyRE = regexp.MustCompile(`[^\w@.-]`)
|
||||||
|
|
||||||
|
// ErrNotExist is returned by Storage implementations when
|
||||||
|
// a resource is not found. It is similar to os.IsNotExist
|
||||||
|
// except this is a type, not a variable.
|
||||||
|
type ErrNotExist interface {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultFileStorage is a convenient, default storage
|
||||||
|
// implementation using the local file system.
|
||||||
|
var defaultFileStorage = FileStorage{Path: dataDir()}
|
||||||
|
|
||||||
|
// DefaultStorage is the default Storage implementation.
|
||||||
|
var DefaultStorage Storage = defaultFileStorage
|
||||||
|
|
||||||
|
// DefaultSync is a default sync to use.
|
||||||
|
var DefaultSync Locker
|
||||||
270
vendor/github.com/mholt/certmagic/user.go
generated
vendored
Normal file
270
vendor/github.com/mholt/certmagic/user.go
generated
vendored
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
// Copyright 2015 Matthew Holt
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certmagic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/lego"
|
||||||
|
"github.com/xenolf/lego/registration"
|
||||||
|
)
|
||||||
|
|
||||||
|
// user represents a Let's Encrypt user account.
|
||||||
|
type user struct {
|
||||||
|
Email string
|
||||||
|
Registration *registration.Resource
|
||||||
|
key crypto.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmail gets u's email.
|
||||||
|
func (u user) GetEmail() string {
|
||||||
|
return u.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRegistration gets u's registration resource.
|
||||||
|
func (u user) GetRegistration() *registration.Resource {
|
||||||
|
return u.Registration
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPrivateKey gets u's private key.
|
||||||
|
func (u user) GetPrivateKey() crypto.PrivateKey {
|
||||||
|
return u.key
|
||||||
|
}
|
||||||
|
|
||||||
|
// newUser creates a new User for the given email address
|
||||||
|
// with a new private key. This function does NOT save the
|
||||||
|
// user to disk or register it via ACME. If you want to use
|
||||||
|
// a user account that might already exist, call getUser
|
||||||
|
// instead. It does NOT prompt the user.
|
||||||
|
func (cfg *Config) newUser(email string) (user, error) {
|
||||||
|
user := user{Email: email}
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return user, fmt.Errorf("generating private key: %v", err)
|
||||||
|
}
|
||||||
|
user.key = privateKey
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEmail 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 returns
|
||||||
|
// empty string. (If user is present, it will warn the user of
|
||||||
|
// the consequences of an empty email.) This function MAY prompt
|
||||||
|
// the user for input. If userPresent is false, the operator
|
||||||
|
// will NOT be prompted and an empty email may be returned.
|
||||||
|
// If the user is prompted, a new User will be created and
|
||||||
|
// stored in storage according to the email address they
|
||||||
|
// provided (which might be blank).
|
||||||
|
func (cfg *Config) getEmail(userPresent bool) (string, error) {
|
||||||
|
// First try memory
|
||||||
|
leEmail := cfg.Email
|
||||||
|
if leEmail == "" {
|
||||||
|
leEmail = Email
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then try to get most recent user email from storage
|
||||||
|
if leEmail == "" {
|
||||||
|
leEmail = cfg.mostRecentUserEmail()
|
||||||
|
cfg.Email = leEmail // save for next time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks like there is no email address readily available,
|
||||||
|
// so we will have to ask the user if we can.
|
||||||
|
if leEmail == "" && userPresent {
|
||||||
|
// evidently, no User data was present in storage;
|
||||||
|
// thus we must make a new User so that we can get
|
||||||
|
// the Terms of Service URL via our ACME client, phew!
|
||||||
|
user, err := cfg.newUser("")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the agreement URL
|
||||||
|
agreementURL := agreementTestURL
|
||||||
|
if agreementURL == "" {
|
||||||
|
// we call acme.NewClient directly because newACMEClient
|
||||||
|
// would require that we already know the user's email
|
||||||
|
caURL := CA
|
||||||
|
if cfg.CA != "" {
|
||||||
|
caURL = cfg.CA
|
||||||
|
}
|
||||||
|
legoConfig := lego.NewConfig(user)
|
||||||
|
legoConfig.CADirURL = caURL
|
||||||
|
legoConfig.UserAgent = UserAgent
|
||||||
|
tempClient, err := lego.NewClient(legoConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("making ACME client to get ToS URL: %v", err)
|
||||||
|
}
|
||||||
|
agreementURL = tempClient.GetToSURL()
|
||||||
|
}
|
||||||
|
|
||||||
|
// prompt the user for an email address and terms agreement
|
||||||
|
reader := bufio.NewReader(stdin)
|
||||||
|
cfg.promptUserAgreement(agreementURL)
|
||||||
|
fmt.Println("Please enter your email address to signify agreement and to be notified")
|
||||||
|
fmt.Println("in case of issues. You can leave it blank, but we don't recommend it.")
|
||||||
|
fmt.Print(" Email address: ")
|
||||||
|
leEmail, err = reader.ReadString('\n')
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return "", fmt.Errorf("reading email address: %v", err)
|
||||||
|
}
|
||||||
|
leEmail = strings.TrimSpace(leEmail)
|
||||||
|
cfg.Email = leEmail
|
||||||
|
cfg.Agreed = true
|
||||||
|
|
||||||
|
// save the new user to preserve this for next time
|
||||||
|
user.Email = leEmail
|
||||||
|
err = cfg.saveUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lower-casing the email is important for consistency
|
||||||
|
return strings.ToLower(leEmail), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUser loads the user with the given email from disk
|
||||||
|
// using the provided storage. If the user does not exist,
|
||||||
|
// it will create a new one, but it does NOT save new
|
||||||
|
// users to the disk or register them via ACME. It does
|
||||||
|
// NOT prompt the user.
|
||||||
|
func (cfg *Config) getUser(email string) (user, error) {
|
||||||
|
var user user
|
||||||
|
|
||||||
|
regBytes, err := cfg.certCache.storage.Load(prefixUserReg(cfg.CA, email))
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(ErrNotExist); ok {
|
||||||
|
// create a new user
|
||||||
|
return cfg.newUser(email)
|
||||||
|
}
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
keyBytes, err := cfg.certCache.storage.Load(prefixUserKey(cfg.CA, email))
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(ErrNotExist); ok {
|
||||||
|
// create a new user
|
||||||
|
return cfg.newUser(email)
|
||||||
|
}
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(regBytes, &user)
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
user.key, err = decodePrivateKey(keyBytes)
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveUser persists a user's key and account registration
|
||||||
|
// to the file system. It does NOT register the user via ACME
|
||||||
|
// or prompt the user. You must also pass in the storage
|
||||||
|
// wherein the user should be saved. It should be the storage
|
||||||
|
// for the CA with which user has an account.
|
||||||
|
func (cfg *Config) saveUser(user user) error {
|
||||||
|
regBytes, err := json.MarshalIndent(&user, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keyBytes, err := encodePrivateKey(user.key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
all := []keyValue{
|
||||||
|
{
|
||||||
|
key: prefixUserReg(cfg.CA, user.Email),
|
||||||
|
value: regBytes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: prefixUserKey(cfg.CA, user.Email),
|
||||||
|
value: keyBytes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return storeTx(cfg.certCache.storage, all)
|
||||||
|
}
|
||||||
|
|
||||||
|
// promptUserAgreement simply outputs the standard user
|
||||||
|
// agreement prompt with the given agreement URL.
|
||||||
|
// It outputs a newline after the message.
|
||||||
|
func (cfg *Config) promptUserAgreement(agreementURL string) {
|
||||||
|
const userAgreementPrompt = `Your sites will be served over HTTPS automatically using Let's Encrypt.
|
||||||
|
By continuing, you agree to the Let's Encrypt Subscriber Agreement at:`
|
||||||
|
fmt.Printf("\n\n%s\n %s\n", userAgreementPrompt, agreementURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// askUserAgreement prompts the user to agree to the agreement
|
||||||
|
// at the given agreement URL via stdin. It returns whether the
|
||||||
|
// user agreed or not.
|
||||||
|
func (cfg *Config) askUserAgreement(agreementURL string) bool {
|
||||||
|
cfg.promptUserAgreement(agreementURL)
|
||||||
|
fmt.Print("Do you agree to the terms? (y/n): ")
|
||||||
|
|
||||||
|
reader := bufio.NewReader(stdin)
|
||||||
|
answer, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
answer = strings.ToLower(strings.TrimSpace(answer))
|
||||||
|
|
||||||
|
return answer == "y" || answer == "yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
// mostRecentUserEmail finds the most recently-written user file
|
||||||
|
// in s. Since this is part of a complex sequence to get a user
|
||||||
|
// account, errors here are discarded to simplify code flow in
|
||||||
|
// the caller, and errors are not important here anyway.
|
||||||
|
func (cfg *Config) mostRecentUserEmail() string {
|
||||||
|
userList, err := cfg.certCache.storage.List(prefixUsers(cfg.CA))
|
||||||
|
if err != nil || len(userList) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
sort.Slice(userList, func(i, j int) bool {
|
||||||
|
iInfo, _ := cfg.certCache.storage.Stat(prefixUser(cfg.CA, userList[i]))
|
||||||
|
jInfo, _ := cfg.certCache.storage.Stat(prefixUser(cfg.CA, userList[j]))
|
||||||
|
return jInfo.Modified.Before(iInfo.Modified)
|
||||||
|
})
|
||||||
|
user, err := cfg.getUser(userList[0])
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return user.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
// agreementTestURL is set during tests to skip requiring
|
||||||
|
// setting up an entire ACME CA endpoint.
|
||||||
|
var agreementTestURL string
|
||||||
|
|
||||||
|
// stdin is used to read the user's input if prompted;
|
||||||
|
// this is changed by tests during tests.
|
||||||
|
var stdin = io.ReadWriter(os.Stdin)
|
||||||
|
|
||||||
|
// The name of the folder for accounts where the email
|
||||||
|
// address was not provided; default 'username' if you will,
|
||||||
|
// but only for local/storage use, not with the CA.
|
||||||
|
const emptyEmail = "default"
|
||||||
69
vendor/github.com/xenolf/lego/acme/api/account.go
generated
vendored
Normal file
69
vendor/github.com/xenolf/lego/acme/api/account.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountService service
|
||||||
|
|
||||||
|
// New Creates a new account.
|
||||||
|
func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
|
||||||
|
var account acme.Account
|
||||||
|
resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account)
|
||||||
|
location := getLocation(resp)
|
||||||
|
|
||||||
|
if len(location) > 0 {
|
||||||
|
a.core.jws.SetKid(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return acme.ExtendedAccount{Location: location}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return acme.ExtendedAccount{Account: account, Location: location}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEAB Creates a new account with an External Account Binding.
|
||||||
|
func (a *AccountService) NewEAB(accMsg acme.Account, kid string, hmacEncoded string) (acme.ExtendedAccount, error) {
|
||||||
|
hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded)
|
||||||
|
if err != nil {
|
||||||
|
return acme.ExtendedAccount{}, fmt.Errorf("acme: could not decode hmac key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
eabJWS, err := a.core.signEABContent(a.core.GetDirectory().NewAccountURL, kid, hmac)
|
||||||
|
if err != nil {
|
||||||
|
return acme.ExtendedAccount{}, fmt.Errorf("acme: error signing eab content: %v", err)
|
||||||
|
}
|
||||||
|
accMsg.ExternalAccountBinding = eabJWS
|
||||||
|
|
||||||
|
return a.New(accMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Retrieves an account.
|
||||||
|
func (a *AccountService) Get(accountURL string) (acme.Account, error) {
|
||||||
|
if len(accountURL) == 0 {
|
||||||
|
return acme.Account{}, errors.New("account[get]: empty URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
var account acme.Account
|
||||||
|
_, err := a.core.post(accountURL, acme.Account{}, &account)
|
||||||
|
if err != nil {
|
||||||
|
return acme.Account{}, err
|
||||||
|
}
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deactivate Deactivates an account.
|
||||||
|
func (a *AccountService) Deactivate(accountURL string) error {
|
||||||
|
if len(accountURL) == 0 {
|
||||||
|
return errors.New("account[deactivate]: empty URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := acme.Account{Status: acme.StatusDeactivated}
|
||||||
|
_, err := a.core.post(accountURL, req, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
151
vendor/github.com/xenolf/lego/acme/api/api.go
generated
vendored
Normal file
151
vendor/github.com/xenolf/lego/acme/api/api.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/acme/api/internal/nonces"
|
||||||
|
"github.com/xenolf/lego/acme/api/internal/secure"
|
||||||
|
"github.com/xenolf/lego/acme/api/internal/sender"
|
||||||
|
"github.com/xenolf/lego/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Core ACME/LE core API.
|
||||||
|
type Core struct {
|
||||||
|
doer *sender.Doer
|
||||||
|
nonceManager *nonces.Manager
|
||||||
|
jws *secure.JWS
|
||||||
|
directory acme.Directory
|
||||||
|
HTTPClient *http.Client
|
||||||
|
|
||||||
|
common service // Reuse a single struct instead of allocating one for each service on the heap.
|
||||||
|
Accounts *AccountService
|
||||||
|
Authorizations *AuthorizationService
|
||||||
|
Certificates *CertificateService
|
||||||
|
Challenges *ChallengeService
|
||||||
|
Orders *OrderService
|
||||||
|
}
|
||||||
|
|
||||||
|
// New Creates a new Core.
|
||||||
|
func New(httpClient *http.Client, userAgent string, caDirURL, kid string, privateKey crypto.PrivateKey) (*Core, error) {
|
||||||
|
doer := sender.NewDoer(httpClient, userAgent)
|
||||||
|
|
||||||
|
dir, err := getDirectory(doer, caDirURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonceManager := nonces.NewManager(doer, dir.NewNonceURL)
|
||||||
|
|
||||||
|
jws := secure.NewJWS(privateKey, kid, nonceManager)
|
||||||
|
|
||||||
|
c := &Core{doer: doer, nonceManager: nonceManager, jws: jws, directory: dir}
|
||||||
|
|
||||||
|
c.common.core = c
|
||||||
|
c.Accounts = (*AccountService)(&c.common)
|
||||||
|
c.Authorizations = (*AuthorizationService)(&c.common)
|
||||||
|
c.Certificates = (*CertificateService)(&c.common)
|
||||||
|
c.Challenges = (*ChallengeService)(&c.common)
|
||||||
|
c.Orders = (*OrderService)(&c.common)
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// post performs an HTTP POST request and parses the response body as JSON,
|
||||||
|
// into the provided respBody object.
|
||||||
|
func (a *Core) post(uri string, reqBody, response interface{}) (*http.Response, error) {
|
||||||
|
content, err := json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to marshal message")
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.retrievablePost(uri, content, response, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// postAsGet performs an HTTP POST ("POST-as-GET") request.
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.3
|
||||||
|
func (a *Core) postAsGet(uri string, response interface{}) (*http.Response, error) {
|
||||||
|
return a.retrievablePost(uri, []byte{}, response, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Core) retrievablePost(uri string, content []byte, response interface{}, retry int) (*http.Response, error) {
|
||||||
|
resp, err := a.signedPost(uri, content, response)
|
||||||
|
if err != nil {
|
||||||
|
// during tests, 5 retries allow to support ~50% of bad nonce.
|
||||||
|
if retry >= 5 {
|
||||||
|
log.Infof("too many retry on a nonce error, retry count: %d", retry)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
switch err.(type) {
|
||||||
|
// Retry once if the nonce was invalidated
|
||||||
|
case *acme.NonceError:
|
||||||
|
log.Infof("nonce error retry: %s", err)
|
||||||
|
resp, err = a.retrievablePost(uri, content, response, retry+1)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Core) signedPost(uri string, content []byte, response interface{}) (*http.Response, error) {
|
||||||
|
signedContent, err := a.jws.SignContent(uri, content)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to post JWS message -> failed to sign content -> %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signedBody := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
|
||||||
|
|
||||||
|
resp, err := a.doer.Post(uri, signedBody, "application/jose+json", response)
|
||||||
|
|
||||||
|
// nonceErr is ignored to keep the root error.
|
||||||
|
nonce, nonceErr := nonces.GetFromResponse(resp)
|
||||||
|
if nonceErr == nil {
|
||||||
|
a.nonceManager.Push(nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Core) signEABContent(newAccountURL, kid string, hmac []byte) ([]byte, error) {
|
||||||
|
eabJWS, err := a.jws.SignEABContent(newAccountURL, kid, hmac)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(eabJWS.FullSerialize()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyAuthorization Gets the key authorization
|
||||||
|
func (a *Core) GetKeyAuthorization(token string) (string, error) {
|
||||||
|
return a.jws.GetKeyAuthorization(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Core) GetDirectory() acme.Directory {
|
||||||
|
return a.directory
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDirectory(do *sender.Doer, caDirURL string) (acme.Directory, error) {
|
||||||
|
var dir acme.Directory
|
||||||
|
if _, err := do.Get(caDirURL, &dir); err != nil {
|
||||||
|
return dir, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dir.NewAccountURL == "" {
|
||||||
|
return dir, errors.New("directory missing new registration URL")
|
||||||
|
}
|
||||||
|
if dir.NewOrderURL == "" {
|
||||||
|
return dir, errors.New("directory missing new order URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir, nil
|
||||||
|
}
|
||||||
34
vendor/github.com/xenolf/lego/acme/api/authorization.go
generated
vendored
Normal file
34
vendor/github.com/xenolf/lego/acme/api/authorization.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthorizationService service
|
||||||
|
|
||||||
|
// Get Gets an authorization.
|
||||||
|
func (c *AuthorizationService) Get(authzURL string) (acme.Authorization, error) {
|
||||||
|
if len(authzURL) == 0 {
|
||||||
|
return acme.Authorization{}, errors.New("authorization[get]: empty URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
var authz acme.Authorization
|
||||||
|
_, err := c.core.postAsGet(authzURL, &authz)
|
||||||
|
if err != nil {
|
||||||
|
return acme.Authorization{}, err
|
||||||
|
}
|
||||||
|
return authz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deactivate Deactivates an authorization.
|
||||||
|
func (c *AuthorizationService) Deactivate(authzURL string) error {
|
||||||
|
if len(authzURL) == 0 {
|
||||||
|
return errors.New("authorization[deactivate]: empty URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
var disabledAuth acme.Authorization
|
||||||
|
_, err := c.core.post(authzURL, acme.Authorization{Status: acme.StatusDeactivated}, &disabledAuth)
|
||||||
|
return err
|
||||||
|
}
|
||||||
99
vendor/github.com/xenolf/lego/acme/api/certificate.go
generated
vendored
Normal file
99
vendor/github.com/xenolf/lego/acme/api/certificate.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/certcrypto"
|
||||||
|
"github.com/xenolf/lego/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxBodySize is the maximum size of body that we will read.
|
||||||
|
const maxBodySize = 1024 * 1024
|
||||||
|
|
||||||
|
type CertificateService service
|
||||||
|
|
||||||
|
// Get Returns the certificate and the issuer certificate.
|
||||||
|
// 'bundle' is only applied if the issuer is provided by the 'up' link.
|
||||||
|
func (c *CertificateService) Get(certURL string, bundle bool) ([]byte, []byte, error) {
|
||||||
|
cert, up, err := c.get(certURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get issuerCert from bundled response from Let's Encrypt
|
||||||
|
// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
|
||||||
|
_, issuer := pem.Decode(cert)
|
||||||
|
if issuer != nil {
|
||||||
|
return cert, issuer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
issuer, err = c.getIssuerFromLink(up)
|
||||||
|
if err != nil {
|
||||||
|
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
|
||||||
|
log.Warnf("acme: Could not bundle issuer certificate [%s]: %v", certURL, err)
|
||||||
|
} else if len(issuer) > 0 {
|
||||||
|
// If bundle is true, we want to return a certificate bundle.
|
||||||
|
// To do this, we append the issuer cert to the issued cert.
|
||||||
|
if bundle {
|
||||||
|
cert = append(cert, issuer...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, issuer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revoke Revokes a certificate.
|
||||||
|
func (c *CertificateService) Revoke(req acme.RevokeCertMessage) error {
|
||||||
|
_, err := c.core.post(c.core.GetDirectory().RevokeCertURL, req, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get Returns the certificate and the "up" link.
|
||||||
|
func (c *CertificateService) get(certURL string) ([]byte, string, error) {
|
||||||
|
if len(certURL) == 0 {
|
||||||
|
return nil, "", errors.New("certificate[get]: empty URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.core.postAsGet(certURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The issuer certificate link may be supplied via an "up" link
|
||||||
|
// in the response headers of a new certificate.
|
||||||
|
// See https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4.2
|
||||||
|
up := getLink(resp.Header, "up")
|
||||||
|
|
||||||
|
return cert, up, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getIssuerFromLink requests the issuer certificate
|
||||||
|
func (c *CertificateService) getIssuerFromLink(up string) ([]byte, error) {
|
||||||
|
if len(up) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("acme: Requesting issuer cert from %s", up)
|
||||||
|
|
||||||
|
cert, _, err := c.get(up)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = x509.ParseCertificate(cert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return certcrypto.PEMEncode(certcrypto.DERCertificateBytes(cert)), nil
|
||||||
|
}
|
||||||
45
vendor/github.com/xenolf/lego/acme/api/challenge.go
generated
vendored
Normal file
45
vendor/github.com/xenolf/lego/acme/api/challenge.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChallengeService service
|
||||||
|
|
||||||
|
// New Creates a challenge.
|
||||||
|
func (c *ChallengeService) New(chlgURL string) (acme.ExtendedChallenge, error) {
|
||||||
|
if len(chlgURL) == 0 {
|
||||||
|
return acme.ExtendedChallenge{}, errors.New("challenge[new]: empty URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Challenge initiation is done by sending a JWS payload containing the trivial JSON object `{}`.
|
||||||
|
// We use an empty struct instance as the postJSON payload here to achieve this result.
|
||||||
|
var chlng acme.ExtendedChallenge
|
||||||
|
resp, err := c.core.post(chlgURL, struct{}{}, &chlng)
|
||||||
|
if err != nil {
|
||||||
|
return acme.ExtendedChallenge{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
chlng.AuthorizationURL = getLink(resp.Header, "up")
|
||||||
|
chlng.RetryAfter = getRetryAfter(resp)
|
||||||
|
return chlng, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Gets a challenge.
|
||||||
|
func (c *ChallengeService) Get(chlgURL string) (acme.ExtendedChallenge, error) {
|
||||||
|
if len(chlgURL) == 0 {
|
||||||
|
return acme.ExtendedChallenge{}, errors.New("challenge[get]: empty URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
var chlng acme.ExtendedChallenge
|
||||||
|
resp, err := c.core.postAsGet(chlgURL, &chlng)
|
||||||
|
if err != nil {
|
||||||
|
return acme.ExtendedChallenge{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
chlng.AuthorizationURL = getLink(resp.Header, "up")
|
||||||
|
chlng.RetryAfter = getRetryAfter(resp)
|
||||||
|
return chlng, nil
|
||||||
|
}
|
||||||
78
vendor/github.com/xenolf/lego/acme/api/internal/nonces/nonce_manager.go
generated
vendored
Normal file
78
vendor/github.com/xenolf/lego/acme/api/internal/nonces/nonce_manager.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package nonces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme/api/internal/sender"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager Manages nonces.
|
||||||
|
type Manager struct {
|
||||||
|
do *sender.Doer
|
||||||
|
nonceURL string
|
||||||
|
nonces []string
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager Creates a new Manager.
|
||||||
|
func NewManager(do *sender.Doer, nonceURL string) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
do: do,
|
||||||
|
nonceURL: nonceURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop Pops a nonce.
|
||||||
|
func (n *Manager) Pop() (string, bool) {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
|
||||||
|
if len(n.nonces) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := n.nonces[len(n.nonces)-1]
|
||||||
|
n.nonces = n.nonces[:len(n.nonces)-1]
|
||||||
|
return nonce, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push Pushes a nonce.
|
||||||
|
func (n *Manager) Push(nonce string) {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
n.nonces = append(n.nonces, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nonce implement jose.NonceSource
|
||||||
|
func (n *Manager) Nonce() (string, error) {
|
||||||
|
if nonce, ok := n.Pop(); ok {
|
||||||
|
return nonce, nil
|
||||||
|
}
|
||||||
|
return n.getNonce()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Manager) getNonce() (string, error) {
|
||||||
|
resp, err := n.do.Head(n.nonceURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get nonce from HTTP HEAD -> %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetFromResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFromResponse Extracts a nonce from a HTTP response.
|
||||||
|
func GetFromResponse(resp *http.Response) (string, error) {
|
||||||
|
if resp == nil {
|
||||||
|
return "", errors.New("nil response")
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := resp.Header.Get("Replay-Nonce")
|
||||||
|
if nonce == "" {
|
||||||
|
return "", fmt.Errorf("server did not respond with a proper nonce header")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nonce, nil
|
||||||
|
}
|
||||||
134
vendor/github.com/xenolf/lego/acme/api/internal/secure/jws.go
generated
vendored
Normal file
134
vendor/github.com/xenolf/lego/acme/api/internal/secure/jws.go
generated
vendored
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package secure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme/api/internal/nonces"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JWS Represents a JWS.
|
||||||
|
type JWS struct {
|
||||||
|
privKey crypto.PrivateKey
|
||||||
|
kid string // Key identifier
|
||||||
|
nonces *nonces.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJWS Create a new JWS.
|
||||||
|
func NewJWS(privateKey crypto.PrivateKey, kid string, nonceManager *nonces.Manager) *JWS {
|
||||||
|
return &JWS{
|
||||||
|
privKey: privateKey,
|
||||||
|
nonces: nonceManager,
|
||||||
|
kid: kid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetKid Sets a key identifier.
|
||||||
|
func (j *JWS) SetKid(kid string) {
|
||||||
|
j.kid = kid
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignContent Signs a content with the JWS.
|
||||||
|
func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, error) {
|
||||||
|
var alg jose.SignatureAlgorithm
|
||||||
|
switch k := j.privKey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
alg = jose.RS256
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
if k.Curve == elliptic.P256() {
|
||||||
|
alg = jose.ES256
|
||||||
|
} else if k.Curve == elliptic.P384() {
|
||||||
|
alg = jose.ES384
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signKey := jose.SigningKey{
|
||||||
|
Algorithm: alg,
|
||||||
|
Key: jose.JSONWebKey{Key: j.privKey, KeyID: j.kid},
|
||||||
|
}
|
||||||
|
|
||||||
|
options := jose.SignerOptions{
|
||||||
|
NonceSource: j.nonces,
|
||||||
|
ExtraHeaders: map[jose.HeaderKey]interface{}{
|
||||||
|
"url": url,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if j.kid == "" {
|
||||||
|
options.EmbedJWK = true
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err := jose.NewSigner(signKey, &options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create jose signer -> %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signed, err := signer.Sign(content)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to sign content -> %v", err)
|
||||||
|
}
|
||||||
|
return signed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignEABContent Signs an external account binding content with the JWS.
|
||||||
|
func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) {
|
||||||
|
jwk := jose.JSONWebKey{Key: j.privKey}
|
||||||
|
jwkJSON, err := jwk.Public().MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("acme: error encoding eab jwk key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err := jose.NewSigner(
|
||||||
|
jose.SigningKey{Algorithm: jose.HS256, Key: hmac},
|
||||||
|
&jose.SignerOptions{
|
||||||
|
EmbedJWK: false,
|
||||||
|
ExtraHeaders: map[jose.HeaderKey]interface{}{
|
||||||
|
"kid": kid,
|
||||||
|
"url": url,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create External Account Binding jose signer -> %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signed, err := signer.Sign(jwkJSON)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to External Account Binding sign content -> %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return signed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyAuthorization Gets the key authorization for a token.
|
||||||
|
func (j *JWS) GetKeyAuthorization(token string) (string, error) {
|
||||||
|
var publicKey crypto.PublicKey
|
||||||
|
switch k := j.privKey.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
publicKey = k.Public()
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
publicKey = k.Public()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the Key Authorization for the challenge
|
||||||
|
jwk := &jose.JSONWebKey{Key: publicKey}
|
||||||
|
if jwk == nil {
|
||||||
|
return "", errors.New("could not generate JWK from key")
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbBytes, err := jwk.Thumbprint(crypto.SHA256)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpad the base64URL
|
||||||
|
keyThumb := base64.RawURLEncoding.EncodeToString(thumbBytes)
|
||||||
|
|
||||||
|
return token + "." + keyThumb, nil
|
||||||
|
}
|
||||||
146
vendor/github.com/xenolf/lego/acme/api/internal/sender/sender.go
generated
vendored
Normal file
146
vendor/github.com/xenolf/lego/acme/api/internal/sender/sender.go
generated
vendored
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package sender
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RequestOption func(*http.Request) error
|
||||||
|
|
||||||
|
func contentType(ct string) RequestOption {
|
||||||
|
return func(req *http.Request) error {
|
||||||
|
req.Header.Set("Content-Type", ct)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Doer struct {
|
||||||
|
httpClient *http.Client
|
||||||
|
userAgent string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDoer Creates a new Doer.
|
||||||
|
func NewDoer(client *http.Client, userAgent string) *Doer {
|
||||||
|
return &Doer{
|
||||||
|
httpClient: client,
|
||||||
|
userAgent: userAgent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get performs a GET request with a proper User-Agent string.
|
||||||
|
// If "response" is not provided, callers should close resp.Body when done reading from it.
|
||||||
|
func (d *Doer) Get(url string, response interface{}) (*http.Response, error) {
|
||||||
|
req, err := d.newRequest(http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.do(req, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head performs a HEAD request with a proper User-Agent string.
|
||||||
|
// The response body (resp.Body) is already closed when this function returns.
|
||||||
|
func (d *Doer) Head(url string) (*http.Response, error) {
|
||||||
|
req, err := d.newRequest(http.MethodHead, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.do(req, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post performs a POST request with a proper User-Agent string.
|
||||||
|
// If "response" is not provided, callers should close resp.Body when done reading from it.
|
||||||
|
func (d *Doer) Post(url string, body io.Reader, bodyType string, response interface{}) (*http.Response, error) {
|
||||||
|
req, err := d.newRequest(http.MethodPost, url, body, contentType(bodyType))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.do(req, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Doer) newRequest(method, uri string, body io.Reader, opts ...RequestOption) (*http.Request, error) {
|
||||||
|
req, err := http.NewRequest(method, uri, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", d.formatUserAgent())
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
err = opt(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Doer) do(req *http.Request, response interface{}) (*http.Response, error) {
|
||||||
|
resp, err := d.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = checkError(req, resp); err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response != nil {
|
||||||
|
raw, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
err = json.Unmarshal(raw, response)
|
||||||
|
if err != nil {
|
||||||
|
return resp, fmt.Errorf("failed to unmarshal %q to type %T: %v", raw, response, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatUserAgent builds and returns the User-Agent string to use in requests.
|
||||||
|
func (d *Doer) formatUserAgent() string {
|
||||||
|
ua := fmt.Sprintf("%s %s (%s; %s; %s)", d.userAgent, ourUserAgent, ourUserAgentComment, runtime.GOOS, runtime.GOARCH)
|
||||||
|
return strings.TrimSpace(ua)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkError(req *http.Request, resp *http.Response) error {
|
||||||
|
if resp.StatusCode >= http.StatusBadRequest {
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%d :: %s :: %s :: %v", resp.StatusCode, req.Method, req.URL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorDetails *acme.ProblemDetails
|
||||||
|
err = json.Unmarshal(body, &errorDetails)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%d ::%s :: %s :: %v :: %s", resp.StatusCode, req.Method, req.URL, err, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
errorDetails.Method = req.Method
|
||||||
|
errorDetails.URL = req.URL.String()
|
||||||
|
|
||||||
|
// Check for errors we handle specifically
|
||||||
|
if errorDetails.HTTPStatus == http.StatusBadRequest && errorDetails.Type == acme.BadNonceErr {
|
||||||
|
return &acme.NonceError{ProblemDetails: errorDetails}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorDetails
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
14
vendor/github.com/xenolf/lego/acme/api/internal/sender/useragent.go
generated
vendored
Normal file
14
vendor/github.com/xenolf/lego/acme/api/internal/sender/useragent.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package sender
|
||||||
|
|
||||||
|
// CODE GENERATED AUTOMATICALLY
|
||||||
|
// THIS FILE MUST NOT BE EDITED BY HAND
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ourUserAgent is the User-Agent of this underlying library package.
|
||||||
|
ourUserAgent = "xenolf-acme/1.2.1"
|
||||||
|
|
||||||
|
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
|
||||||
|
// values: detach|release
|
||||||
|
// NOTE: Update this with each tagged release.
|
||||||
|
ourUserAgentComment = "detach"
|
||||||
|
)
|
||||||
65
vendor/github.com/xenolf/lego/acme/api/order.go
generated
vendored
Normal file
65
vendor/github.com/xenolf/lego/acme/api/order.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderService service
|
||||||
|
|
||||||
|
// New Creates a new order.
|
||||||
|
func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) {
|
||||||
|
var identifiers []acme.Identifier
|
||||||
|
for _, domain := range domains {
|
||||||
|
identifiers = append(identifiers, acme.Identifier{Type: "dns", Value: domain})
|
||||||
|
}
|
||||||
|
|
||||||
|
orderReq := acme.Order{Identifiers: identifiers}
|
||||||
|
|
||||||
|
var order acme.Order
|
||||||
|
resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
|
||||||
|
if err != nil {
|
||||||
|
return acme.ExtendedOrder{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return acme.ExtendedOrder{
|
||||||
|
Location: resp.Header.Get("Location"),
|
||||||
|
Order: order,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Gets an order.
|
||||||
|
func (o *OrderService) Get(orderURL string) (acme.Order, error) {
|
||||||
|
if len(orderURL) == 0 {
|
||||||
|
return acme.Order{}, errors.New("order[get]: empty URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
var order acme.Order
|
||||||
|
_, err := o.core.postAsGet(orderURL, &order)
|
||||||
|
if err != nil {
|
||||||
|
return acme.Order{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return order, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateForCSR Updates an order for a CSR.
|
||||||
|
func (o *OrderService) UpdateForCSR(orderURL string, csr []byte) (acme.Order, error) {
|
||||||
|
csrMsg := acme.CSRMessage{
|
||||||
|
Csr: base64.RawURLEncoding.EncodeToString(csr),
|
||||||
|
}
|
||||||
|
|
||||||
|
var order acme.Order
|
||||||
|
_, err := o.core.post(orderURL, csrMsg, &order)
|
||||||
|
if err != nil {
|
||||||
|
return acme.Order{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.Status == acme.StatusInvalid {
|
||||||
|
return acme.Order{}, order.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return order, nil
|
||||||
|
}
|
||||||
45
vendor/github.com/xenolf/lego/acme/api/service.go
generated
vendored
Normal file
45
vendor/github.com/xenolf/lego/acme/api/service.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
core *Core
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLink get a rel into the Link header
|
||||||
|
func getLink(header http.Header, rel string) string {
|
||||||
|
var linkExpr = regexp.MustCompile(`<(.+?)>;\s*rel="(.+?)"`)
|
||||||
|
|
||||||
|
for _, link := range header["Link"] {
|
||||||
|
for _, m := range linkExpr.FindAllStringSubmatch(link, -1) {
|
||||||
|
if len(m) != 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if m[2] == rel {
|
||||||
|
return m[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLocation get the value of the header Location
|
||||||
|
func getLocation(resp *http.Response) string {
|
||||||
|
if resp == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Header.Get("Location")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRetryAfter get the value of the header Retry-After
|
||||||
|
func getRetryAfter(resp *http.Response) string {
|
||||||
|
if resp == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Header.Get("Retry-After")
|
||||||
|
}
|
||||||
17
vendor/github.com/xenolf/lego/acme/challenges.go
generated
vendored
17
vendor/github.com/xenolf/lego/acme/challenges.go
generated
vendored
@ -1,17 +0,0 @@
|
|||||||
package acme
|
|
||||||
|
|
||||||
// Challenge is a string that identifies a particular type and version of ACME challenge.
|
|
||||||
type Challenge string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// HTTP01 is the "http-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#http
|
|
||||||
// Note: HTTP01ChallengePath returns the URL path to fulfill this challenge
|
|
||||||
HTTP01 = Challenge("http-01")
|
|
||||||
|
|
||||||
// DNS01 is the "dns-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#dns
|
|
||||||
// Note: DNS01Record returns a DNS record which will fulfill this challenge
|
|
||||||
DNS01 = Challenge("dns-01")
|
|
||||||
|
|
||||||
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01
|
|
||||||
TLSALPN01 = Challenge("tls-alpn-01")
|
|
||||||
)
|
|
||||||
957
vendor/github.com/xenolf/lego/acme/client.go
generated
vendored
957
vendor/github.com/xenolf/lego/acme/client.go
generated
vendored
@ -1,957 +0,0 @@
|
|||||||
// Package acme implements the ACME protocol for Let's Encrypt and other conforming providers.
|
|
||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xenolf/lego/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// maxBodySize is the maximum size of body that we will read.
|
|
||||||
maxBodySize = 1024 * 1024
|
|
||||||
|
|
||||||
// overallRequestLimit is the overall number of request per second limited on the
|
|
||||||
// “new-reg”, “new-authz” and “new-cert” endpoints. From the documentation the
|
|
||||||
// limitation is 20 requests per second, but using 20 as value doesn't work but 18 do
|
|
||||||
overallRequestLimit = 18
|
|
||||||
|
|
||||||
statusValid = "valid"
|
|
||||||
statusInvalid = "invalid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// User interface is to be implemented by users of this library.
|
|
||||||
// It is used by the client type to get user specific information.
|
|
||||||
type User interface {
|
|
||||||
GetEmail() string
|
|
||||||
GetRegistration() *RegistrationResource
|
|
||||||
GetPrivateKey() crypto.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface for all challenge solvers to implement.
|
|
||||||
type solver interface {
|
|
||||||
Solve(challenge challenge, domain string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface for challenges like dns, where we can set a record in advance for ALL challenges.
|
|
||||||
// This saves quite a bit of time vs creating the records and solving them serially.
|
|
||||||
type preSolver interface {
|
|
||||||
PreSolve(challenge challenge, domain string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface for challenges like dns, where we can solve all the challenges before to delete them.
|
|
||||||
type cleanup interface {
|
|
||||||
CleanUp(challenge challenge, domain string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type validateFunc func(j *jws, domain, uri string, chlng challenge) error
|
|
||||||
|
|
||||||
// Client is the user-friendy way to ACME
|
|
||||||
type Client struct {
|
|
||||||
directory directory
|
|
||||||
user User
|
|
||||||
jws *jws
|
|
||||||
keyType KeyType
|
|
||||||
solvers map[Challenge]solver
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient creates a new ACME client on behalf of the user. The client will depend on
|
|
||||||
// the ACME directory located at caDirURL for the rest of its actions. A private
|
|
||||||
// key of type keyType (see KeyType contants) will be generated when requesting a new
|
|
||||||
// certificate if one isn't provided.
|
|
||||||
func NewClient(caDirURL string, user User, keyType KeyType) (*Client, error) {
|
|
||||||
privKey := user.GetPrivateKey()
|
|
||||||
if privKey == nil {
|
|
||||||
return nil, errors.New("private key was nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
var dir directory
|
|
||||||
if _, err := getJSON(caDirURL, &dir); err != nil {
|
|
||||||
return nil, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dir.NewAccountURL == "" {
|
|
||||||
return nil, errors.New("directory missing new registration URL")
|
|
||||||
}
|
|
||||||
if dir.NewOrderURL == "" {
|
|
||||||
return nil, errors.New("directory missing new order URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
jws := &jws{privKey: privKey, getNonceURL: dir.NewNonceURL}
|
|
||||||
if reg := user.GetRegistration(); reg != nil {
|
|
||||||
jws.kid = reg.URI
|
|
||||||
}
|
|
||||||
|
|
||||||
// REVIEW: best possibility?
|
|
||||||
// Add all available solvers with the right index as per ACME
|
|
||||||
// spec to this map. Otherwise they won`t be found.
|
|
||||||
solvers := map[Challenge]solver{
|
|
||||||
HTTP01: &httpChallenge{jws: jws, validate: validate, provider: &HTTPProviderServer{}},
|
|
||||||
TLSALPN01: &tlsALPNChallenge{jws: jws, validate: validate, provider: &TLSALPNProviderServer{}},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Client{directory: dir, user: user, jws: jws, keyType: keyType, solvers: solvers}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetChallengeProvider specifies a custom provider p that can solve the given challenge type.
|
|
||||||
func (c *Client) SetChallengeProvider(challenge Challenge, p ChallengeProvider) error {
|
|
||||||
switch challenge {
|
|
||||||
case HTTP01:
|
|
||||||
c.solvers[challenge] = &httpChallenge{jws: c.jws, validate: validate, provider: p}
|
|
||||||
case DNS01:
|
|
||||||
c.solvers[challenge] = &dnsChallenge{jws: c.jws, validate: validate, provider: p}
|
|
||||||
case TLSALPN01:
|
|
||||||
c.solvers[challenge] = &tlsALPNChallenge{jws: c.jws, validate: validate, provider: p}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown challenge %v", challenge)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHTTPAddress specifies a custom interface:port to be used for HTTP based challenges.
|
|
||||||
// If this option is not used, the default port 80 and all interfaces will be used.
|
|
||||||
// To only specify a port and no interface use the ":port" notation.
|
|
||||||
//
|
|
||||||
// NOTE: This REPLACES any custom HTTP provider previously set by calling
|
|
||||||
// c.SetChallengeProvider with the default HTTP challenge provider.
|
|
||||||
func (c *Client) SetHTTPAddress(iface string) error {
|
|
||||||
host, port, err := net.SplitHostPort(iface)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if chlng, ok := c.solvers[HTTP01]; ok {
|
|
||||||
chlng.(*httpChallenge).provider = NewHTTPProviderServer(host, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTLSAddress specifies a custom interface:port to be used for TLS based challenges.
|
|
||||||
// If this option is not used, the default port 443 and all interfaces will be used.
|
|
||||||
// To only specify a port and no interface use the ":port" notation.
|
|
||||||
//
|
|
||||||
// NOTE: This REPLACES any custom TLS-ALPN provider previously set by calling
|
|
||||||
// c.SetChallengeProvider with the default TLS-ALPN challenge provider.
|
|
||||||
func (c *Client) SetTLSAddress(iface string) error {
|
|
||||||
host, port, err := net.SplitHostPort(iface)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if chlng, ok := c.solvers[TLSALPN01]; ok {
|
|
||||||
chlng.(*tlsALPNChallenge).provider = NewTLSALPNProviderServer(host, port)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExcludeChallenges explicitly removes challenges from the pool for solving.
|
|
||||||
func (c *Client) ExcludeChallenges(challenges []Challenge) {
|
|
||||||
// Loop through all challenges and delete the requested one if found.
|
|
||||||
for _, challenge := range challenges {
|
|
||||||
delete(c.solvers, challenge)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetToSURL returns the current ToS URL from the Directory
|
|
||||||
func (c *Client) GetToSURL() string {
|
|
||||||
return c.directory.Meta.TermsOfService
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetExternalAccountRequired returns the External Account Binding requirement of the Directory
|
|
||||||
func (c *Client) GetExternalAccountRequired() bool {
|
|
||||||
return c.directory.Meta.ExternalAccountRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the current account to the ACME server.
|
|
||||||
func (c *Client) Register(tosAgreed bool) (*RegistrationResource, error) {
|
|
||||||
if c == nil || c.user == nil {
|
|
||||||
return nil, errors.New("acme: cannot register a nil client or user")
|
|
||||||
}
|
|
||||||
log.Infof("acme: Registering account for %s", c.user.GetEmail())
|
|
||||||
|
|
||||||
accMsg := accountMessage{}
|
|
||||||
if c.user.GetEmail() != "" {
|
|
||||||
accMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
|
|
||||||
} else {
|
|
||||||
accMsg.Contact = []string{}
|
|
||||||
}
|
|
||||||
accMsg.TermsOfServiceAgreed = tosAgreed
|
|
||||||
|
|
||||||
var serverReg accountMessage
|
|
||||||
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, accMsg, &serverReg)
|
|
||||||
if err != nil {
|
|
||||||
remoteErr, ok := err.(RemoteError)
|
|
||||||
if ok && remoteErr.StatusCode == 409 {
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reg := &RegistrationResource{
|
|
||||||
URI: hdr.Get("Location"),
|
|
||||||
Body: serverReg,
|
|
||||||
}
|
|
||||||
c.jws.kid = reg.URI
|
|
||||||
|
|
||||||
return reg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterWithExternalAccountBinding Register the current account to the ACME server.
|
|
||||||
func (c *Client) RegisterWithExternalAccountBinding(tosAgreed bool, kid string, hmacEncoded string) (*RegistrationResource, error) {
|
|
||||||
if c == nil || c.user == nil {
|
|
||||||
return nil, errors.New("acme: cannot register a nil client or user")
|
|
||||||
}
|
|
||||||
log.Infof("acme: Registering account (EAB) for %s", c.user.GetEmail())
|
|
||||||
|
|
||||||
accMsg := accountMessage{}
|
|
||||||
if c.user.GetEmail() != "" {
|
|
||||||
accMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
|
|
||||||
} else {
|
|
||||||
accMsg.Contact = []string{}
|
|
||||||
}
|
|
||||||
accMsg.TermsOfServiceAgreed = tosAgreed
|
|
||||||
|
|
||||||
hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("acme: could not decode hmac key: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
eabJWS, err := c.jws.signEABContent(c.directory.NewAccountURL, kid, hmac)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("acme: error signing eab content: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
eabPayload := eabJWS.FullSerialize()
|
|
||||||
|
|
||||||
accMsg.ExternalAccountBinding = []byte(eabPayload)
|
|
||||||
|
|
||||||
var serverReg accountMessage
|
|
||||||
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, accMsg, &serverReg)
|
|
||||||
if err != nil {
|
|
||||||
remoteErr, ok := err.(RemoteError)
|
|
||||||
if ok && remoteErr.StatusCode == 409 {
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reg := &RegistrationResource{
|
|
||||||
URI: hdr.Get("Location"),
|
|
||||||
Body: serverReg,
|
|
||||||
}
|
|
||||||
c.jws.kid = reg.URI
|
|
||||||
|
|
||||||
return reg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveAccountByKey will attempt to look up an account using the given account key
|
|
||||||
// and return its registration resource.
|
|
||||||
func (c *Client) ResolveAccountByKey() (*RegistrationResource, error) {
|
|
||||||
log.Infof("acme: Trying to resolve account by key")
|
|
||||||
|
|
||||||
acc := accountMessage{OnlyReturnExisting: true}
|
|
||||||
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, acc, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
accountLink := hdr.Get("Location")
|
|
||||||
if accountLink == "" {
|
|
||||||
return nil, errors.New("Server did not return the account link")
|
|
||||||
}
|
|
||||||
|
|
||||||
var retAccount accountMessage
|
|
||||||
c.jws.kid = accountLink
|
|
||||||
_, err = postJSON(c.jws, accountLink, accountMessage{}, &retAccount)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RegistrationResource{URI: accountLink, Body: retAccount}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRegistration deletes the client's user registration from the ACME
|
|
||||||
// server.
|
|
||||||
func (c *Client) DeleteRegistration() error {
|
|
||||||
if c == nil || c.user == nil {
|
|
||||||
return errors.New("acme: cannot unregister a nil client or user")
|
|
||||||
}
|
|
||||||
log.Infof("acme: Deleting account for %s", c.user.GetEmail())
|
|
||||||
|
|
||||||
accMsg := accountMessage{
|
|
||||||
Status: "deactivated",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := postJSON(c.jws, c.user.GetRegistration().URI, accMsg, nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryRegistration runs a POST request on the client's registration and
|
|
||||||
// returns the result.
|
|
||||||
//
|
|
||||||
// This is similar to the Register function, but acting on an existing
|
|
||||||
// registration link and resource.
|
|
||||||
func (c *Client) QueryRegistration() (*RegistrationResource, error) {
|
|
||||||
if c == nil || c.user == nil {
|
|
||||||
return nil, errors.New("acme: cannot query the registration of a nil client or user")
|
|
||||||
}
|
|
||||||
// Log the URL here instead of the email as the email may not be set
|
|
||||||
log.Infof("acme: Querying account for %s", c.user.GetRegistration().URI)
|
|
||||||
|
|
||||||
accMsg := accountMessage{}
|
|
||||||
|
|
||||||
var serverReg accountMessage
|
|
||||||
_, err := postJSON(c.jws, c.user.GetRegistration().URI, accMsg, &serverReg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reg := &RegistrationResource{Body: serverReg}
|
|
||||||
|
|
||||||
// Location: header is not returned so this needs to be populated off of
|
|
||||||
// existing URI
|
|
||||||
reg.URI = c.user.GetRegistration().URI
|
|
||||||
|
|
||||||
return reg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ObtainCertificateForCSR tries to obtain a certificate matching the CSR passed into it.
|
|
||||||
// The domains are inferred from the CommonName and SubjectAltNames, if any. The private key
|
|
||||||
// for this CSR is not required.
|
|
||||||
// If bundle is true, the []byte contains both the issuer certificate and
|
|
||||||
// your issued certificate as a bundle.
|
|
||||||
// This function will never return a partial certificate. If one domain in the list fails,
|
|
||||||
// the whole certificate will fail.
|
|
||||||
func (c *Client) ObtainCertificateForCSR(csr x509.CertificateRequest, bundle bool) (*CertificateResource, error) {
|
|
||||||
// figure out what domains it concerns
|
|
||||||
// start with the common name
|
|
||||||
domains := []string{csr.Subject.CommonName}
|
|
||||||
|
|
||||||
// loop over the SubjectAltName DNS names
|
|
||||||
DNSNames:
|
|
||||||
for _, sanName := range csr.DNSNames {
|
|
||||||
for _, existingName := range domains {
|
|
||||||
if existingName == sanName {
|
|
||||||
// duplicate; skip this name
|
|
||||||
continue DNSNames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// name is unique
|
|
||||||
domains = append(domains, sanName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bundle {
|
|
||||||
log.Infof("[%s] acme: Obtaining bundled SAN certificate given a CSR", strings.Join(domains, ", "))
|
|
||||||
} else {
|
|
||||||
log.Infof("[%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
order, err := c.createOrderForIdentifiers(domains)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
authz, err := c.getAuthzForOrder(order)
|
|
||||||
if err != nil {
|
|
||||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
|
||||||
/*for _, auth := range authz {
|
|
||||||
c.disableAuthz(auth)
|
|
||||||
}*/
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.solveChallengeForAuthz(authz)
|
|
||||||
if err != nil {
|
|
||||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
|
||||||
|
|
||||||
failures := make(ObtainError)
|
|
||||||
cert, err := c.requestCertificateForCsr(order, bundle, csr.Raw, nil)
|
|
||||||
if err != nil {
|
|
||||||
for _, chln := range authz {
|
|
||||||
failures[chln.Identifier.Value] = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert != nil {
|
|
||||||
// Add the CSR to the certificate so that it can be used for renewals.
|
|
||||||
cert.CSR = pemEncode(&csr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// do not return an empty failures map, because
|
|
||||||
// it would still be a non-nil error value
|
|
||||||
if len(failures) > 0 {
|
|
||||||
return cert, failures
|
|
||||||
}
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ObtainCertificate tries to obtain a single certificate using all domains passed into it.
|
|
||||||
// The first domain in domains is used for the CommonName field of the certificate, all other
|
|
||||||
// domains are added using the Subject Alternate Names extension. A new private key is generated
|
|
||||||
// for every invocation of this function. If you do not want that you can supply your own private key
|
|
||||||
// in the privKey parameter. If this parameter is non-nil it will be used instead of generating a new one.
|
|
||||||
// If bundle is true, the []byte contains both the issuer certificate and
|
|
||||||
// your issued certificate as a bundle.
|
|
||||||
// This function will never return a partial certificate. If one domain in the list fails,
|
|
||||||
// the whole certificate will fail.
|
|
||||||
func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (*CertificateResource, error) {
|
|
||||||
if len(domains) == 0 {
|
|
||||||
return nil, errors.New("no domains to obtain a certificate for")
|
|
||||||
}
|
|
||||||
|
|
||||||
if bundle {
|
|
||||||
log.Infof("[%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
|
|
||||||
} else {
|
|
||||||
log.Infof("[%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
order, err := c.createOrderForIdentifiers(domains)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
authz, err := c.getAuthzForOrder(order)
|
|
||||||
if err != nil {
|
|
||||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
|
||||||
/*for _, auth := range authz {
|
|
||||||
c.disableAuthz(auth)
|
|
||||||
}*/
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.solveChallengeForAuthz(authz)
|
|
||||||
if err != nil {
|
|
||||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
|
||||||
|
|
||||||
failures := make(ObtainError)
|
|
||||||
cert, err := c.requestCertificateForOrder(order, bundle, privKey, mustStaple)
|
|
||||||
if err != nil {
|
|
||||||
for _, auth := range authz {
|
|
||||||
failures[auth.Identifier.Value] = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// do not return an empty failures map, because
|
|
||||||
// it would still be a non-nil error value
|
|
||||||
if len(failures) > 0 {
|
|
||||||
return cert, failures
|
|
||||||
}
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RevokeCertificate takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
|
||||||
func (c *Client) RevokeCertificate(certificate []byte) error {
|
|
||||||
certificates, err := parsePEMBundle(certificate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
x509Cert := certificates[0]
|
|
||||||
if x509Cert.IsCA {
|
|
||||||
return fmt.Errorf("Certificate bundle starts with a CA certificate")
|
|
||||||
}
|
|
||||||
|
|
||||||
encodedCert := base64.URLEncoding.EncodeToString(x509Cert.Raw)
|
|
||||||
|
|
||||||
_, err = postJSON(c.jws, c.directory.RevokeCertURL, revokeCertMessage{Certificate: encodedCert}, nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenewCertificate takes a CertificateResource and tries to renew the certificate.
|
|
||||||
// If the renewal process succeeds, the new certificate will ge returned in a new CertResource.
|
|
||||||
// Please be aware that this function will return a new certificate in ANY case that is not an error.
|
|
||||||
// If the server does not provide us with a new cert on a GET request to the CertURL
|
|
||||||
// this function will start a new-cert flow where a new certificate gets generated.
|
|
||||||
// If bundle is true, the []byte contains both the issuer certificate and
|
|
||||||
// your issued certificate as a bundle.
|
|
||||||
// For private key reuse the PrivateKey property of the passed in CertificateResource should be non-nil.
|
|
||||||
func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple bool) (*CertificateResource, error) {
|
|
||||||
// Input certificate is PEM encoded. Decode it here as we may need the decoded
|
|
||||||
// cert later on in the renewal process. The input may be a bundle or a single certificate.
|
|
||||||
certificates, err := parsePEMBundle(cert.Certificate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
x509Cert := certificates[0]
|
|
||||||
if x509Cert.IsCA {
|
|
||||||
return nil, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", cert.Domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is just meant to be informal for the user.
|
|
||||||
timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC())
|
|
||||||
log.Infof("[%s] acme: Trying renewal with %d hours remaining", cert.Domain, int(timeLeft.Hours()))
|
|
||||||
|
|
||||||
// We always need to request a new certificate to renew.
|
|
||||||
// Start by checking to see if the certificate was based off a CSR, and
|
|
||||||
// use that if it's defined.
|
|
||||||
if len(cert.CSR) > 0 {
|
|
||||||
csr, errP := pemDecodeTox509CSR(cert.CSR)
|
|
||||||
if errP != nil {
|
|
||||||
return nil, errP
|
|
||||||
}
|
|
||||||
newCert, failures := c.ObtainCertificateForCSR(*csr, bundle)
|
|
||||||
return newCert, failures
|
|
||||||
}
|
|
||||||
|
|
||||||
var privKey crypto.PrivateKey
|
|
||||||
if cert.PrivateKey != nil {
|
|
||||||
privKey, err = parsePEMPrivateKey(cert.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var domains []string
|
|
||||||
// check for SAN certificate
|
|
||||||
if len(x509Cert.DNSNames) > 1 {
|
|
||||||
domains = append(domains, x509Cert.Subject.CommonName)
|
|
||||||
for _, sanDomain := range x509Cert.DNSNames {
|
|
||||||
if sanDomain == x509Cert.Subject.CommonName {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
domains = append(domains, sanDomain)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
domains = append(domains, x509Cert.Subject.CommonName)
|
|
||||||
}
|
|
||||||
|
|
||||||
newCert, err := c.ObtainCertificate(domains, bundle, privKey, mustStaple)
|
|
||||||
return newCert, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) createOrderForIdentifiers(domains []string) (orderResource, error) {
|
|
||||||
var identifiers []identifier
|
|
||||||
for _, domain := range domains {
|
|
||||||
identifiers = append(identifiers, identifier{Type: "dns", Value: domain})
|
|
||||||
}
|
|
||||||
|
|
||||||
order := orderMessage{
|
|
||||||
Identifiers: identifiers,
|
|
||||||
}
|
|
||||||
|
|
||||||
var response orderMessage
|
|
||||||
hdr, err := postJSON(c.jws, c.directory.NewOrderURL, order, &response)
|
|
||||||
if err != nil {
|
|
||||||
return orderResource{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
orderRes := orderResource{
|
|
||||||
URL: hdr.Get("Location"),
|
|
||||||
Domains: domains,
|
|
||||||
orderMessage: response,
|
|
||||||
}
|
|
||||||
return orderRes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// an authz with the solver we have chosen and the index of the challenge associated with it
|
|
||||||
type selectedAuthSolver struct {
|
|
||||||
authz authorization
|
|
||||||
challengeIndex int
|
|
||||||
solver solver
|
|
||||||
}
|
|
||||||
|
|
||||||
// Looks through the challenge combinations to find a solvable match.
|
|
||||||
// Then solves the challenges in series and returns.
|
|
||||||
func (c *Client) solveChallengeForAuthz(authorizations []authorization) error {
|
|
||||||
failures := make(ObtainError)
|
|
||||||
|
|
||||||
authSolvers := []*selectedAuthSolver{}
|
|
||||||
|
|
||||||
// loop through the resources, basically through the domains. First pass just selects a solver for each authz.
|
|
||||||
for _, authz := range authorizations {
|
|
||||||
if authz.Status == statusValid {
|
|
||||||
// Boulder might recycle recent validated authz (see issue #267)
|
|
||||||
log.Infof("[%s] acme: Authorization already valid; skipping challenge", authz.Identifier.Value)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if i, solvr := c.chooseSolver(authz, authz.Identifier.Value); solvr != nil {
|
|
||||||
authSolvers = append(authSolvers, &selectedAuthSolver{
|
|
||||||
authz: authz,
|
|
||||||
challengeIndex: i,
|
|
||||||
solver: solvr,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
failures[authz.Identifier.Value] = fmt.Errorf("[%s] acme: Could not determine solvers", authz.Identifier.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for all valid presolvers, first submit the challenges so they have max time to propagate
|
|
||||||
for _, item := range authSolvers {
|
|
||||||
authz := item.authz
|
|
||||||
i := item.challengeIndex
|
|
||||||
if presolver, ok := item.solver.(preSolver); ok {
|
|
||||||
if err := presolver.PreSolve(authz.Challenges[i], authz.Identifier.Value); err != nil {
|
|
||||||
failures[authz.Identifier.Value] = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
// clean all created TXT records
|
|
||||||
for _, item := range authSolvers {
|
|
||||||
if clean, ok := item.solver.(cleanup); ok {
|
|
||||||
if failures[item.authz.Identifier.Value] != nil {
|
|
||||||
// already failed in previous loop
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err := clean.CleanUp(item.authz.Challenges[item.challengeIndex], item.authz.Identifier.Value)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Error cleaning up %s: %v ", item.authz.Identifier.Value, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// finally solve all challenges for real
|
|
||||||
for _, item := range authSolvers {
|
|
||||||
authz := item.authz
|
|
||||||
i := item.challengeIndex
|
|
||||||
if failures[authz.Identifier.Value] != nil {
|
|
||||||
// already failed in previous loop
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := item.solver.Solve(authz.Challenges[i], authz.Identifier.Value); err != nil {
|
|
||||||
failures[authz.Identifier.Value] = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// be careful not to return an empty failures map, for
|
|
||||||
// even an empty ObtainError is a non-nil error value
|
|
||||||
if len(failures) > 0 {
|
|
||||||
return failures
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks all challenges from the server in order and returns the first matching solver.
|
|
||||||
func (c *Client) chooseSolver(auth authorization, domain string) (int, solver) {
|
|
||||||
for i, challenge := range auth.Challenges {
|
|
||||||
if solver, ok := c.solvers[Challenge(challenge.Type)]; ok {
|
|
||||||
return i, solver
|
|
||||||
}
|
|
||||||
log.Infof("[%s] acme: Could not find solver for: %s", domain, challenge.Type)
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the challenges needed to proof our identifier to the ACME server.
|
|
||||||
func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, error) {
|
|
||||||
resc, errc := make(chan authorization), make(chan domainError)
|
|
||||||
|
|
||||||
delay := time.Second / overallRequestLimit
|
|
||||||
|
|
||||||
for _, authzURL := range order.Authorizations {
|
|
||||||
time.Sleep(delay)
|
|
||||||
|
|
||||||
go func(authzURL string) {
|
|
||||||
var authz authorization
|
|
||||||
_, err := postAsGet(c.jws, authzURL, &authz)
|
|
||||||
if err != nil {
|
|
||||||
errc <- domainError{Domain: authz.Identifier.Value, Error: err}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resc <- authz
|
|
||||||
}(authzURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
var responses []authorization
|
|
||||||
failures := make(ObtainError)
|
|
||||||
for i := 0; i < len(order.Authorizations); i++ {
|
|
||||||
select {
|
|
||||||
case res := <-resc:
|
|
||||||
responses = append(responses, res)
|
|
||||||
case err := <-errc:
|
|
||||||
failures[err.Domain] = err.Error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logAuthz(order)
|
|
||||||
|
|
||||||
close(resc)
|
|
||||||
close(errc)
|
|
||||||
|
|
||||||
// be careful to not return an empty failures map;
|
|
||||||
// even if empty, they become non-nil error values
|
|
||||||
if len(failures) > 0 {
|
|
||||||
return responses, failures
|
|
||||||
}
|
|
||||||
return responses, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func logAuthz(order orderResource) {
|
|
||||||
for i, auth := range order.Authorizations {
|
|
||||||
log.Infof("[%s] AuthURL: %s", order.Identifiers[i].Value, auth)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanAuthz loops through the passed in slice and disables any auths which are not "valid"
|
|
||||||
func (c *Client) disableAuthz(authURL string) error {
|
|
||||||
var disabledAuth authorization
|
|
||||||
_, err := postJSON(c.jws, authURL, deactivateAuthMessage{Status: "deactivated"}, &disabledAuth)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) requestCertificateForOrder(order orderResource, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (*CertificateResource, error) {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if privKey == nil {
|
|
||||||
privKey, err = generatePrivateKey(c.keyType)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine certificate name(s) based on the authorization resources
|
|
||||||
commonName := order.Domains[0]
|
|
||||||
|
|
||||||
// ACME draft Section 7.4 "Applying for Certificate Issuance"
|
|
||||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4
|
|
||||||
// says:
|
|
||||||
// Clients SHOULD NOT make any assumptions about the sort order of
|
|
||||||
// "identifiers" or "authorizations" elements in the returned order
|
|
||||||
// object.
|
|
||||||
san := []string{commonName}
|
|
||||||
for _, auth := range order.Identifiers {
|
|
||||||
if auth.Value != commonName {
|
|
||||||
san = append(san, auth.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: should the CSR be customizable?
|
|
||||||
csr, err := generateCsr(privKey, commonName, san, mustStaple)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.requestCertificateForCsr(order, bundle, csr, pemEncode(privKey))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr []byte, privateKeyPem []byte) (*CertificateResource, error) {
|
|
||||||
commonName := order.Domains[0]
|
|
||||||
|
|
||||||
csrString := base64.RawURLEncoding.EncodeToString(csr)
|
|
||||||
var retOrder orderMessage
|
|
||||||
_, err := postJSON(c.jws, order.Finalize, csrMessage{Csr: csrString}, &retOrder)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if retOrder.Status == statusInvalid {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
certRes := CertificateResource{
|
|
||||||
Domain: commonName,
|
|
||||||
CertURL: retOrder.Certificate,
|
|
||||||
PrivateKey: privateKeyPem,
|
|
||||||
}
|
|
||||||
|
|
||||||
if retOrder.Status == statusValid {
|
|
||||||
// if the certificate is available right away, short cut!
|
|
||||||
ok, err := c.checkCertResponse(retOrder, &certRes, bundle)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
return &certRes, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stopTimer := time.NewTimer(30 * time.Second)
|
|
||||||
defer stopTimer.Stop()
|
|
||||||
retryTick := time.NewTicker(500 * time.Millisecond)
|
|
||||||
defer retryTick.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-stopTimer.C:
|
|
||||||
return nil, errors.New("certificate polling timed out")
|
|
||||||
case <-retryTick.C:
|
|
||||||
_, err := postAsGet(c.jws, order.URL, &retOrder)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
done, err := c.checkCertResponse(retOrder, &certRes, bundle)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if done {
|
|
||||||
return &certRes, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkCertResponse checks to see if the certificate is ready and a link is contained in the
|
|
||||||
// response. if so, loads it into certRes and returns true. If the cert
|
|
||||||
// is not yet ready, it returns false. The certRes input
|
|
||||||
// should already have the Domain (common name) field populated. If bundle is
|
|
||||||
// true, the certificate will be bundled with the issuer's cert.
|
|
||||||
func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResource, bundle bool) (bool, error) {
|
|
||||||
switch order.Status {
|
|
||||||
case statusValid:
|
|
||||||
resp, err := postAsGet(c.jws, order.Certificate, nil)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err := ioutil.ReadAll(limitReader(resp.Body, maxBodySize))
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The issuer certificate link may be supplied via an "up" link
|
|
||||||
// in the response headers of a new certificate. See
|
|
||||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4.2
|
|
||||||
links := parseLinks(resp.Header["Link"])
|
|
||||||
if link, ok := links["up"]; ok {
|
|
||||||
issuerCert, err := c.getIssuerCertificate(link)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
|
|
||||||
log.Warnf("[%s] acme: Could not bundle issuer certificate: %v", certRes.Domain, err)
|
|
||||||
} else {
|
|
||||||
issuerCert = pemEncode(derCertificateBytes(issuerCert))
|
|
||||||
|
|
||||||
// If bundle is true, we want to return a certificate bundle.
|
|
||||||
// To do this, we append the issuer cert to the issued cert.
|
|
||||||
if bundle {
|
|
||||||
cert = append(cert, issuerCert...)
|
|
||||||
}
|
|
||||||
|
|
||||||
certRes.IssuerCertificate = issuerCert
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Get issuerCert from bundled response from Let's Encrypt
|
|
||||||
// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
|
|
||||||
_, rest := pem.Decode(cert)
|
|
||||||
if rest != nil {
|
|
||||||
certRes.IssuerCertificate = rest
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
certRes.Certificate = cert
|
|
||||||
certRes.CertURL = order.Certificate
|
|
||||||
certRes.CertStableURL = order.Certificate
|
|
||||||
log.Infof("[%s] Server responded with a certificate.", certRes.Domain)
|
|
||||||
return true, nil
|
|
||||||
|
|
||||||
case "processing":
|
|
||||||
return false, nil
|
|
||||||
case statusInvalid:
|
|
||||||
return false, errors.New("order has invalid state: invalid")
|
|
||||||
default:
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getIssuerCertificate requests the issuer certificate
|
|
||||||
func (c *Client) getIssuerCertificate(url string) ([]byte, error) {
|
|
||||||
log.Infof("acme: Requesting issuer cert from %s", url)
|
|
||||||
resp, err := postAsGet(c.jws, url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
issuerBytes, err := ioutil.ReadAll(limitReader(resp.Body, maxBodySize))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = x509.ParseCertificate(issuerBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return issuerBytes, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseLinks(links []string) map[string]string {
|
|
||||||
aBrkt := regexp.MustCompile("[<>]")
|
|
||||||
slver := regexp.MustCompile("(.+) *= *\"(.+)\"")
|
|
||||||
linkMap := make(map[string]string)
|
|
||||||
|
|
||||||
for _, link := range links {
|
|
||||||
|
|
||||||
link = aBrkt.ReplaceAllString(link, "")
|
|
||||||
parts := strings.Split(link, ";")
|
|
||||||
|
|
||||||
matches := slver.FindStringSubmatch(parts[1])
|
|
||||||
if len(matches) > 0 {
|
|
||||||
linkMap[matches[2]] = parts[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return linkMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate makes the ACME server start validating a
|
|
||||||
// challenge response, only returning once it is done.
|
|
||||||
func validate(j *jws, domain, uri string, c challenge) error {
|
|
||||||
var chlng challenge
|
|
||||||
|
|
||||||
// Challenge initiation is done by sending a JWS payload containing the
|
|
||||||
// trivial JSON object `{}`. We use an empty struct instance as the postJSON
|
|
||||||
// payload here to achieve this result.
|
|
||||||
hdr, err := postJSON(j, uri, struct{}{}, &chlng)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// After the path is sent, the ACME server will access our server.
|
|
||||||
// Repeatedly check the server for an updated status on our request.
|
|
||||||
for {
|
|
||||||
switch chlng.Status {
|
|
||||||
case statusValid:
|
|
||||||
log.Infof("[%s] The server validated our request", domain)
|
|
||||||
return nil
|
|
||||||
case "pending":
|
|
||||||
case "processing":
|
|
||||||
case statusInvalid:
|
|
||||||
return handleChallengeError(chlng)
|
|
||||||
default:
|
|
||||||
return errors.New("the server returned an unexpected state")
|
|
||||||
}
|
|
||||||
|
|
||||||
ra, err := strconv.Atoi(hdr.Get("Retry-After"))
|
|
||||||
if err != nil {
|
|
||||||
// The ACME server MUST return a Retry-After.
|
|
||||||
// If it doesn't, we'll just poll hard.
|
|
||||||
ra = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Duration(ra) * time.Second)
|
|
||||||
|
|
||||||
resp, err := postAsGet(j, uri, &chlng)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
hdr = resp.Header
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
284
vendor/github.com/xenolf/lego/acme/commons.go
generated
vendored
Normal file
284
vendor/github.com/xenolf/lego/acme/commons.go
generated
vendored
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
// Package acme contains all objects related the ACME endpoints.
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-16
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Challenge statuses
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.6
|
||||||
|
const (
|
||||||
|
StatusPending = "pending"
|
||||||
|
StatusInvalid = "invalid"
|
||||||
|
StatusValid = "valid"
|
||||||
|
StatusProcessing = "processing"
|
||||||
|
StatusDeactivated = "deactivated"
|
||||||
|
StatusExpired = "expired"
|
||||||
|
StatusRevoked = "revoked"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Directory the ACME directory object.
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.1
|
||||||
|
type Directory struct {
|
||||||
|
NewNonceURL string `json:"newNonce"`
|
||||||
|
NewAccountURL string `json:"newAccount"`
|
||||||
|
NewOrderURL string `json:"newOrder"`
|
||||||
|
NewAuthzURL string `json:"newAuthz"`
|
||||||
|
RevokeCertURL string `json:"revokeCert"`
|
||||||
|
KeyChangeURL string `json:"keyChange"`
|
||||||
|
Meta Meta `json:"meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta the ACME meta object (related to Directory).
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.1
|
||||||
|
type Meta struct {
|
||||||
|
// termsOfService (optional, string):
|
||||||
|
// A URL identifying the current terms of service.
|
||||||
|
TermsOfService string `json:"termsOfService"`
|
||||||
|
|
||||||
|
// website (optional, string):
|
||||||
|
// An HTTP or HTTPS URL locating a website providing more information about the ACME server.
|
||||||
|
Website string `json:"website"`
|
||||||
|
|
||||||
|
// caaIdentities (optional, array of string):
|
||||||
|
// The hostnames that the ACME server recognizes as referring to itself
|
||||||
|
// for the purposes of CAA record validation as defined in [RFC6844].
|
||||||
|
// Each string MUST represent the same sequence of ASCII code points
|
||||||
|
// that the server will expect to see as the "Issuer Domain Name" in a CAA issue or issuewild property tag.
|
||||||
|
// This allows clients to determine the correct issuer domain name to use when configuring CAA records.
|
||||||
|
CaaIdentities []string `json:"caaIdentities"`
|
||||||
|
|
||||||
|
// externalAccountRequired (optional, boolean):
|
||||||
|
// If this field is present and set to "true",
|
||||||
|
// then the CA requires that all new- account requests include an "externalAccountBinding" field
|
||||||
|
// associating the new account with an external account.
|
||||||
|
ExternalAccountRequired bool `json:"externalAccountRequired"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtendedAccount a extended Account.
|
||||||
|
type ExtendedAccount struct {
|
||||||
|
Account
|
||||||
|
// Contains the value of the response header `Location`
|
||||||
|
Location string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account the ACME account Object.
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.2
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.3
|
||||||
|
type Account struct {
|
||||||
|
// status (required, string):
|
||||||
|
// The status of this account.
|
||||||
|
// Possible values are: "valid", "deactivated", and "revoked".
|
||||||
|
// The value "deactivated" should be used to indicate client-initiated deactivation
|
||||||
|
// whereas "revoked" should be used to indicate server- initiated deactivation. (See Section 7.1.6)
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
|
||||||
|
// contact (optional, array of string):
|
||||||
|
// An array of URLs that the server can use to contact the client for issues related to this account.
|
||||||
|
// For example, the server may wish to notify the client about server-initiated revocation or certificate expiration.
|
||||||
|
// For information on supported URL schemes, see Section 7.3
|
||||||
|
Contact []string `json:"contact,omitempty"`
|
||||||
|
|
||||||
|
// termsOfServiceAgreed (optional, boolean):
|
||||||
|
// Including this field in a new-account request,
|
||||||
|
// with a value of true, indicates the client's agreement with the terms of service.
|
||||||
|
// This field is not updateable by the client.
|
||||||
|
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
|
||||||
|
|
||||||
|
// orders (required, string):
|
||||||
|
// A URL from which a list of orders submitted by this account can be fetched via a POST-as-GET request,
|
||||||
|
// as described in Section 7.1.2.1.
|
||||||
|
Orders string `json:"orders,omitempty"`
|
||||||
|
|
||||||
|
// onlyReturnExisting (optional, boolean):
|
||||||
|
// If this field is present with the value "true",
|
||||||
|
// then the server MUST NOT create a new account if one does not already exist.
|
||||||
|
// This allows a client to look up an account URL based on an account key (see Section 7.3.1).
|
||||||
|
OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"`
|
||||||
|
|
||||||
|
// externalAccountBinding (optional, object):
|
||||||
|
// An optional field for binding the new account with an existing non-ACME account (see Section 7.3.4).
|
||||||
|
ExternalAccountBinding json.RawMessage `json:"externalAccountBinding,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtendedOrder a extended Order.
|
||||||
|
type ExtendedOrder struct {
|
||||||
|
Order
|
||||||
|
// The order URL, contains the value of the response header `Location`
|
||||||
|
Location string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order the ACME order Object.
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.3
|
||||||
|
type Order struct {
|
||||||
|
// status (required, string):
|
||||||
|
// The status of this order.
|
||||||
|
// Possible values are: "pending", "ready", "processing", "valid", and "invalid".
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
|
||||||
|
// expires (optional, string):
|
||||||
|
// The timestamp after which the server will consider this order invalid,
|
||||||
|
// encoded in the format specified in RFC 3339 [RFC3339].
|
||||||
|
// This field is REQUIRED for objects with "pending" or "valid" in the status field.
|
||||||
|
Expires string `json:"expires,omitempty"`
|
||||||
|
|
||||||
|
// identifiers (required, array of object):
|
||||||
|
// An array of identifier objects that the order pertains to.
|
||||||
|
Identifiers []Identifier `json:"identifiers"`
|
||||||
|
|
||||||
|
// notBefore (optional, string):
|
||||||
|
// The requested value of the notBefore field in the certificate,
|
||||||
|
// in the date format defined in [RFC3339].
|
||||||
|
NotBefore string `json:"notBefore,omitempty"`
|
||||||
|
|
||||||
|
// notAfter (optional, string):
|
||||||
|
// The requested value of the notAfter field in the certificate,
|
||||||
|
// in the date format defined in [RFC3339].
|
||||||
|
NotAfter string `json:"notAfter,omitempty"`
|
||||||
|
|
||||||
|
// error (optional, object):
|
||||||
|
// The error that occurred while processing the order, if any.
|
||||||
|
// This field is structured as a problem document [RFC7807].
|
||||||
|
Error *ProblemDetails `json:"error,omitempty"`
|
||||||
|
|
||||||
|
// authorizations (required, array of string):
|
||||||
|
// For pending orders,
|
||||||
|
// the authorizations that the client needs to complete before the requested certificate can be issued (see Section 7.5),
|
||||||
|
// including unexpired authorizations that the client has completed in the past for identifiers specified in the order.
|
||||||
|
// The authorizations required are dictated by server policy
|
||||||
|
// and there may not be a 1:1 relationship between the order identifiers and the authorizations required.
|
||||||
|
// For final orders (in the "valid" or "invalid" state), the authorizations that were completed.
|
||||||
|
// Each entry is a URL from which an authorization can be fetched with a POST-as-GET request.
|
||||||
|
Authorizations []string `json:"authorizations,omitempty"`
|
||||||
|
|
||||||
|
// finalize (required, string):
|
||||||
|
// A URL that a CSR must be POSTed to once all of the order's authorizations are satisfied to finalize the order.
|
||||||
|
// The result of a successful finalization will be the population of the certificate URL for the order.
|
||||||
|
Finalize string `json:"finalize,omitempty"`
|
||||||
|
|
||||||
|
// certificate (optional, string):
|
||||||
|
// A URL for the certificate that has been issued in response to this order
|
||||||
|
Certificate string `json:"certificate,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorization the ACME authorization object.
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.4
|
||||||
|
type Authorization struct {
|
||||||
|
// status (required, string):
|
||||||
|
// The status of this authorization.
|
||||||
|
// Possible values are: "pending", "valid", "invalid", "deactivated", "expired", and "revoked".
|
||||||
|
Status string `json:"status"`
|
||||||
|
|
||||||
|
// expires (optional, string):
|
||||||
|
// The timestamp after which the server will consider this authorization invalid,
|
||||||
|
// encoded in the format specified in RFC 3339 [RFC3339].
|
||||||
|
// This field is REQUIRED for objects with "valid" in the "status" field.
|
||||||
|
Expires time.Time `json:"expires,omitempty"`
|
||||||
|
|
||||||
|
// identifier (required, object):
|
||||||
|
// The identifier that the account is authorized to represent
|
||||||
|
Identifier Identifier `json:"identifier,omitempty"`
|
||||||
|
|
||||||
|
// challenges (required, array of objects):
|
||||||
|
// For pending authorizations, the challenges that the client can fulfill in order to prove possession of the identifier.
|
||||||
|
// For valid authorizations, the challenge that was validated.
|
||||||
|
// For invalid authorizations, the challenge that was attempted and failed.
|
||||||
|
// Each array entry is an object with parameters required to validate the challenge.
|
||||||
|
// A client should attempt to fulfill one of these challenges,
|
||||||
|
// and a server should consider any one of the challenges sufficient to make the authorization valid.
|
||||||
|
Challenges []Challenge `json:"challenges,omitempty"`
|
||||||
|
|
||||||
|
// wildcard (optional, boolean):
|
||||||
|
// For authorizations created as a result of a newOrder request containing a DNS identifier
|
||||||
|
// with a value that contained a wildcard prefix this field MUST be present, and true.
|
||||||
|
Wildcard bool `json:"wildcard,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtendedChallenge a extended Challenge.
|
||||||
|
type ExtendedChallenge struct {
|
||||||
|
Challenge
|
||||||
|
// Contains the value of the response header `Retry-After`
|
||||||
|
RetryAfter string `json:"-"`
|
||||||
|
// Contains the value of the response header `Link` rel="up"
|
||||||
|
AuthorizationURL string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Challenge the ACME challenge object.
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.5
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8
|
||||||
|
type Challenge struct {
|
||||||
|
// type (required, string):
|
||||||
|
// The type of challenge encoded in the object.
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// url (required, string):
|
||||||
|
// The URL to which a response can be posted.
|
||||||
|
URL string `json:"url"`
|
||||||
|
|
||||||
|
// status (required, string):
|
||||||
|
// The status of this challenge. Possible values are: "pending", "processing", "valid", and "invalid".
|
||||||
|
Status string `json:"status"`
|
||||||
|
|
||||||
|
// validated (optional, string):
|
||||||
|
// The time at which the server validated this challenge,
|
||||||
|
// encoded in the format specified in RFC 3339 [RFC3339].
|
||||||
|
// This field is REQUIRED if the "status" field is "valid".
|
||||||
|
Validated time.Time `json:"validated,omitempty"`
|
||||||
|
|
||||||
|
// error (optional, object):
|
||||||
|
// Error that occurred while the server was validating the challenge, if any,
|
||||||
|
// structured as a problem document [RFC7807].
|
||||||
|
// Multiple errors can be indicated by using subproblems Section 6.7.1.
|
||||||
|
// A challenge object with an error MUST have status equal to "invalid".
|
||||||
|
Error *ProblemDetails `json:"error,omitempty"`
|
||||||
|
|
||||||
|
// token (required, string):
|
||||||
|
// A random value that uniquely identifies the challenge.
|
||||||
|
// This value MUST have at least 128 bits of entropy.
|
||||||
|
// It MUST NOT contain any characters outside the base64url alphabet,
|
||||||
|
// and MUST NOT include base64 padding characters ("=").
|
||||||
|
// See [RFC4086] for additional information on randomness requirements.
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.3
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.4
|
||||||
|
Token string `json:"token"`
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.1
|
||||||
|
KeyAuthorization string `json:"keyAuthorization"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier the ACME identifier object.
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-9.7.7
|
||||||
|
type Identifier struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSRMessage Certificate Signing Request
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.4
|
||||||
|
type CSRMessage struct {
|
||||||
|
// csr (required, string):
|
||||||
|
// A CSR encoding the parameters for the certificate being requested [RFC2986].
|
||||||
|
// The CSR is sent in the base64url-encoded version of the DER format.
|
||||||
|
// (Note: Because this field uses base64url, and does not include headers, it is different from PEM.).
|
||||||
|
Csr string `json:"csr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeCertMessage a certificate revocation message
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.6
|
||||||
|
// - https://tools.ietf.org/html/rfc5280#section-5.3.1
|
||||||
|
type RevokeCertMessage struct {
|
||||||
|
// certificate (required, string):
|
||||||
|
// The certificate to be revoked, in the base64url-encoded version of the DER format.
|
||||||
|
// (Note: Because this field uses base64url, and does not include headers, it is different from PEM.)
|
||||||
|
Certificate string `json:"certificate"`
|
||||||
|
|
||||||
|
// reason (optional, int):
|
||||||
|
// One of the revocation reasonCodes defined in Section 5.3.1 of [RFC5280] to be used when generating OCSP responses and CRLs.
|
||||||
|
// If this field is not set the server SHOULD omit the reasonCode CRL entry extension when generating OCSP responses and CRLs.
|
||||||
|
// The server MAY disallow a subset of reasonCodes from being used by the user.
|
||||||
|
// If a request contains a disallowed reasonCode the server MUST reject it with the error type "urn:ietf:params:acme:error:badRevocationReason".
|
||||||
|
// The problem document detail SHOULD indicate which reasonCodes are allowed.
|
||||||
|
Reason *uint `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
334
vendor/github.com/xenolf/lego/acme/crypto.go
generated
vendored
334
vendor/github.com/xenolf/lego/acme/crypto.go
generated
vendored
@ -1,334 +0,0 @@
|
|||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/asn1"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ocsp"
|
|
||||||
jose "gopkg.in/square/go-jose.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KeyType represents the key algo as well as the key size or curve to use.
|
|
||||||
type KeyType string
|
|
||||||
type derCertificateBytes []byte
|
|
||||||
|
|
||||||
// Constants for all key types we support.
|
|
||||||
const (
|
|
||||||
EC256 = KeyType("P256")
|
|
||||||
EC384 = KeyType("P384")
|
|
||||||
RSA2048 = KeyType("2048")
|
|
||||||
RSA4096 = KeyType("4096")
|
|
||||||
RSA8192 = KeyType("8192")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// OCSPGood means that the certificate is valid.
|
|
||||||
OCSPGood = ocsp.Good
|
|
||||||
// OCSPRevoked means that the certificate has been deliberately revoked.
|
|
||||||
OCSPRevoked = ocsp.Revoked
|
|
||||||
// OCSPUnknown means that the OCSP responder doesn't know about the certificate.
|
|
||||||
OCSPUnknown = ocsp.Unknown
|
|
||||||
// OCSPServerFailed means that the OCSP responder failed to process the request.
|
|
||||||
OCSPServerFailed = ocsp.ServerFailed
|
|
||||||
)
|
|
||||||
|
|
||||||
// Constants for OCSP must staple
|
|
||||||
var (
|
|
||||||
tlsFeatureExtensionOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
|
|
||||||
ocspMustStapleFeature = []byte{0x30, 0x03, 0x02, 0x01, 0x05}
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetOCSPForCert takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
|
||||||
// the parsed response, and an error, if any. The returned []byte can be passed directly
|
|
||||||
// into the OCSPStaple property of a tls.Certificate. If the bundle only contains the
|
|
||||||
// issued certificate, this function will try to get the issuer certificate from the
|
|
||||||
// IssuingCertificateURL in the certificate. If the []byte and/or ocsp.Response return
|
|
||||||
// values are nil, the OCSP status may be assumed OCSPUnknown.
|
|
||||||
func GetOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) {
|
|
||||||
certificates, err := parsePEMBundle(bundle)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We expect the certificate slice to be ordered downwards the chain.
|
|
||||||
// SRV CRT -> CA. We need to pull the leaf and issuer certs out of it,
|
|
||||||
// which should always be the first two certificates. If there's no
|
|
||||||
// OCSP server listed in the leaf cert, there's nothing to do. And if
|
|
||||||
// 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, errors.New("no OCSP server specified in cert")
|
|
||||||
}
|
|
||||||
if len(certificates) == 1 {
|
|
||||||
// TODO: build fallback. If this fails, check the remaining array entries.
|
|
||||||
if len(issuedCert.IssuingCertificateURL) == 0 {
|
|
||||||
return nil, nil, errors.New("no issuing certificate URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, errC := httpGet(issuedCert.IssuingCertificateURL[0])
|
|
||||||
if errC != nil {
|
|
||||||
return nil, nil, errC
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
issuerBytes, errC := ioutil.ReadAll(limitReader(resp.Body, 1024*1024))
|
|
||||||
if errC != nil {
|
|
||||||
return nil, nil, errC
|
|
||||||
}
|
|
||||||
|
|
||||||
issuerCert, errC := x509.ParseCertificate(issuerBytes)
|
|
||||||
if errC != nil {
|
|
||||||
return nil, nil, errC
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert it into the slice on position 0
|
|
||||||
// We want it ordered right SRV CRT -> CA
|
|
||||||
certificates = append(certificates, issuerCert)
|
|
||||||
}
|
|
||||||
issuerCert := certificates[1]
|
|
||||||
|
|
||||||
// Finally kick off the OCSP request.
|
|
||||||
ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := bytes.NewReader(ocspReq)
|
|
||||||
req, err := httpPost(issuedCert.OCSPServer[0], "application/ocsp-request", reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer req.Body.Close()
|
|
||||||
|
|
||||||
ocspResBytes, err := ioutil.ReadAll(limitReader(req.Body, 1024*1024))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ocspResBytes, ocspRes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKeyAuthorization(token string, key interface{}) (string, error) {
|
|
||||||
var publicKey crypto.PublicKey
|
|
||||||
switch k := key.(type) {
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
publicKey = k.Public()
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
publicKey = k.Public()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the Key Authorization for the challenge
|
|
||||||
jwk := &jose.JSONWebKey{Key: publicKey}
|
|
||||||
if jwk == nil {
|
|
||||||
return "", errors.New("could not generate JWK from key")
|
|
||||||
}
|
|
||||||
thumbBytes, err := jwk.Thumbprint(crypto.SHA256)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpad the base64URL
|
|
||||||
keyThumb := base64.RawURLEncoding.EncodeToString(thumbBytes)
|
|
||||||
|
|
||||||
return token + "." + keyThumb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsePEMBundle parses a certificate bundle from top to bottom and returns
|
|
||||||
// a slice of x509 certificates. This function will error if no certificates are found.
|
|
||||||
func parsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
|
||||||
var certificates []*x509.Certificate
|
|
||||||
var certDERBlock *pem.Block
|
|
||||||
|
|
||||||
for {
|
|
||||||
certDERBlock, bundle = pem.Decode(bundle)
|
|
||||||
if certDERBlock == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if certDERBlock.Type == "CERTIFICATE" {
|
|
||||||
cert, err := x509.ParseCertificate(certDERBlock.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
certificates = append(certificates, cert)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(certificates) == 0 {
|
|
||||||
return nil, errors.New("no certificates were found while parsing the bundle")
|
|
||||||
}
|
|
||||||
|
|
||||||
return certificates, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
|
||||||
keyBlock, _ := pem.Decode(key)
|
|
||||||
|
|
||||||
switch keyBlock.Type {
|
|
||||||
case "RSA PRIVATE KEY":
|
|
||||||
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
|
||||||
case "EC PRIVATE KEY":
|
|
||||||
return x509.ParseECPrivateKey(keyBlock.Bytes)
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown PEM header value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generatePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
|
|
||||||
|
|
||||||
switch keyType {
|
|
||||||
case EC256:
|
|
||||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
case EC384:
|
|
||||||
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
|
||||||
case RSA2048:
|
|
||||||
return rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
case RSA4096:
|
|
||||||
return rsa.GenerateKey(rand.Reader, 4096)
|
|
||||||
case RSA8192:
|
|
||||||
return rsa.GenerateKey(rand.Reader, 8192)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("invalid KeyType: %s", keyType)
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateCsr(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
|
|
||||||
template := x509.CertificateRequest{
|
|
||||||
Subject: pkix.Name{CommonName: domain},
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(san) > 0 {
|
|
||||||
template.DNSNames = san
|
|
||||||
}
|
|
||||||
|
|
||||||
if mustStaple {
|
|
||||||
template.ExtraExtensions = append(template.ExtraExtensions, pkix.Extension{
|
|
||||||
Id: tlsFeatureExtensionOID,
|
|
||||||
Value: ocspMustStapleFeature,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pemEncode(data interface{}) []byte {
|
|
||||||
var pemBlock *pem.Block
|
|
||||||
switch key := data.(type) {
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
keyBytes, _ := x509.MarshalECPrivateKey(key)
|
|
||||||
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
|
|
||||||
case *x509.CertificateRequest:
|
|
||||||
pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw}
|
|
||||||
case derCertificateBytes:
|
|
||||||
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.(derCertificateBytes))}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pem.EncodeToMemory(pemBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pemDecode(data []byte) (*pem.Block, error) {
|
|
||||||
pemBlock, _ := pem.Decode(data)
|
|
||||||
if pemBlock == nil {
|
|
||||||
return nil, fmt.Errorf("Pem decode did not yield a valid block. Is the certificate in the right format?")
|
|
||||||
}
|
|
||||||
|
|
||||||
return pemBlock, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func pemDecodeTox509CSR(pem []byte) (*x509.CertificateRequest, error) {
|
|
||||||
pemBlock, err := pemDecode(pem)
|
|
||||||
if pemBlock == nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if pemBlock.Type != "CERTIFICATE REQUEST" {
|
|
||||||
return nil, fmt.Errorf("PEM block is not a certificate request")
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509.ParseCertificateRequest(pemBlock.Bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPEMCertExpiration returns the "NotAfter" date of a PEM encoded certificate.
|
|
||||||
// The certificate has to be PEM encoded. Any other encodings like DER will fail.
|
|
||||||
func GetPEMCertExpiration(cert []byte) (time.Time, error) {
|
|
||||||
pemBlock, err := pemDecode(cert)
|
|
||||||
if pemBlock == nil {
|
|
||||||
return time.Time{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return getCertExpiration(pemBlock.Bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCertExpiration returns the "NotAfter" date of a DER encoded certificate.
|
|
||||||
func getCertExpiration(cert []byte) (time.Time, error) {
|
|
||||||
pCert, err := x509.ParseCertificate(cert)
|
|
||||||
if err != nil {
|
|
||||||
return time.Time{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pCert.NotAfter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generatePemCert(privKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
|
|
||||||
derBytes, err := generateDerCert(privKey, time.Time{}, domain, extensions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain string, extensions []pkix.Extension) ([]byte, error) {
|
|
||||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
||||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if expiration.IsZero() {
|
|
||||||
expiration = time.Now().Add(365)
|
|
||||||
}
|
|
||||||
|
|
||||||
template := x509.Certificate{
|
|
||||||
SerialNumber: serialNumber,
|
|
||||||
Subject: pkix.Name{
|
|
||||||
CommonName: "ACME Challenge TEMP",
|
|
||||||
},
|
|
||||||
NotBefore: time.Now(),
|
|
||||||
NotAfter: expiration,
|
|
||||||
|
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment,
|
|
||||||
BasicConstraintsValid: true,
|
|
||||||
DNSNames: []string{domain},
|
|
||||||
ExtraExtensions: extensions,
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func limitReader(rd io.ReadCloser, numBytes int64) io.ReadCloser {
|
|
||||||
return http.MaxBytesReader(nil, rd, numBytes)
|
|
||||||
}
|
|
||||||
343
vendor/github.com/xenolf/lego/acme/dns_challenge.go
generated
vendored
343
vendor/github.com/xenolf/lego/acme/dns_challenge.go
generated
vendored
@ -1,343 +0,0 @@
|
|||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"github.com/xenolf/lego/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type preCheckDNSFunc func(fqdn, value string) (bool, error)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// PreCheckDNS checks DNS propagation before notifying ACME that
|
|
||||||
// the DNS challenge is ready.
|
|
||||||
PreCheckDNS preCheckDNSFunc = checkDNSPropagation
|
|
||||||
fqdnToZone = map[string]string{}
|
|
||||||
muFqdnToZone sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultResolvConf = "/etc/resolv.conf"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultPropagationTimeout default propagation timeout
|
|
||||||
DefaultPropagationTimeout = 60 * time.Second
|
|
||||||
|
|
||||||
// DefaultPollingInterval default polling interval
|
|
||||||
DefaultPollingInterval = 2 * time.Second
|
|
||||||
|
|
||||||
// DefaultTTL default TTL
|
|
||||||
DefaultTTL = 120
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultNameservers = []string{
|
|
||||||
"google-public-dns-a.google.com:53",
|
|
||||||
"google-public-dns-b.google.com:53",
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecursiveNameservers are used to pre-check DNS propagation
|
|
||||||
var RecursiveNameservers = getNameservers(defaultResolvConf, defaultNameservers)
|
|
||||||
|
|
||||||
// DNSTimeout is used to override the default DNS timeout of 10 seconds.
|
|
||||||
var DNSTimeout = 10 * time.Second
|
|
||||||
|
|
||||||
// getNameservers attempts to get systems nameservers before falling back to the defaults
|
|
||||||
func getNameservers(path string, defaults []string) []string {
|
|
||||||
config, err := dns.ClientConfigFromFile(path)
|
|
||||||
if err != nil || len(config.Servers) == 0 {
|
|
||||||
return defaults
|
|
||||||
}
|
|
||||||
|
|
||||||
systemNameservers := []string{}
|
|
||||||
for _, server := range config.Servers {
|
|
||||||
// ensure all servers have a port number
|
|
||||||
if _, _, err := net.SplitHostPort(server); err != nil {
|
|
||||||
systemNameservers = append(systemNameservers, net.JoinHostPort(server, "53"))
|
|
||||||
} else {
|
|
||||||
systemNameservers = append(systemNameservers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return systemNameservers
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNS01Record returns a DNS record which will fulfill the `dns-01` challenge
|
|
||||||
func DNS01Record(domain, keyAuth string) (fqdn string, value string, ttl int) {
|
|
||||||
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
|
|
||||||
// base64URL encoding without padding
|
|
||||||
value = base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
|
|
||||||
ttl = DefaultTTL
|
|
||||||
fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// dnsChallenge implements the dns-01 challenge according to ACME 7.5
|
|
||||||
type dnsChallenge struct {
|
|
||||||
jws *jws
|
|
||||||
validate validateFunc
|
|
||||||
provider ChallengeProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreSolve just submits the txt record to the dns provider. It does not validate record propagation, or
|
|
||||||
// do anything at all with the acme server.
|
|
||||||
func (s *dnsChallenge) PreSolve(chlng challenge, domain string) error {
|
|
||||||
log.Infof("[%s] acme: Preparing to solve DNS-01", domain)
|
|
||||||
|
|
||||||
if s.provider == nil {
|
|
||||||
return errors.New("no DNS Provider configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the Key Authorization for the challenge
|
|
||||||
keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.provider.Present(domain, chlng.Token, keyAuth)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error presenting token: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
|
|
||||||
log.Infof("[%s] acme: Trying to solve DNS-01", domain)
|
|
||||||
|
|
||||||
// Generate the Key Authorization for the challenge
|
|
||||||
keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fqdn, value, _ := DNS01Record(domain, keyAuth)
|
|
||||||
|
|
||||||
log.Infof("[%s] Checking DNS record propagation using %+v", domain, RecursiveNameservers)
|
|
||||||
|
|
||||||
var timeout, interval time.Duration
|
|
||||||
switch provider := s.provider.(type) {
|
|
||||||
case ChallengeProviderTimeout:
|
|
||||||
timeout, interval = provider.Timeout()
|
|
||||||
default:
|
|
||||||
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
err = WaitFor(timeout, interval, func() (bool, error) {
|
|
||||||
return PreCheckDNS(fqdn, value)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.validate(s.jws, domain, chlng.URL, challenge{Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanUp cleans the challenge
|
|
||||||
func (s *dnsChallenge) CleanUp(chlng challenge, domain string) error {
|
|
||||||
keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.provider.CleanUp(domain, chlng.Token, keyAuth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
|
|
||||||
func checkDNSPropagation(fqdn, value string) (bool, error) {
|
|
||||||
// Initial attempt to resolve at the recursive NS
|
|
||||||
r, err := dnsQuery(fqdn, dns.TypeTXT, RecursiveNameservers, true)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Rcode == dns.RcodeSuccess {
|
|
||||||
// If we see a CNAME here then use the alias
|
|
||||||
for _, rr := range r.Answer {
|
|
||||||
if cn, ok := rr.(*dns.CNAME); ok {
|
|
||||||
if cn.Hdr.Name == fqdn {
|
|
||||||
fqdn = cn.Target
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
authoritativeNss, err := lookupNameservers(fqdn)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return checkAuthoritativeNss(fqdn, value, authoritativeNss)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Rcode != dns.RcodeSuccess {
|
|
||||||
return false, fmt.Errorf("NS %s returned %s for %s", ns, dns.RcodeToString[r.Rcode], fqdn)
|
|
||||||
}
|
|
||||||
|
|
||||||
var found bool
|
|
||||||
for _, rr := range r.Answer {
|
|
||||||
if txt, ok := rr.(*dns.TXT); ok {
|
|
||||||
if strings.Join(txt.Txt, "") == value {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return false, fmt.Errorf("NS %s did not return the expected TXT record [fqdn: %s]", ns, fqdn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// dnsQuery will query a nameserver, iterating through the supplied servers as it retries
|
|
||||||
// The nameserver should include a port, to facilitate testing where we talk to a mock dns server.
|
|
||||||
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (in *dns.Msg, err error) {
|
|
||||||
m := new(dns.Msg)
|
|
||||||
m.SetQuestion(fqdn, rtype)
|
|
||||||
m.SetEdns0(4096, false)
|
|
||||||
|
|
||||||
if !recursive {
|
|
||||||
m.RecursionDesired = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Will retry the request based on the number of servers (n+1)
|
|
||||||
for i := 1; i <= len(nameservers)+1; i++ {
|
|
||||||
ns := nameservers[i%len(nameservers)]
|
|
||||||
udp := &dns.Client{Net: "udp", Timeout: DNSTimeout}
|
|
||||||
in, _, err = udp.Exchange(m, ns)
|
|
||||||
|
|
||||||
if err == dns.ErrTruncated {
|
|
||||||
tcp := &dns.Client{Net: "tcp", Timeout: DNSTimeout}
|
|
||||||
// If the TCP request succeeds, the err will reset to nil
|
|
||||||
in, _, err = tcp.Exchange(m, ns)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookupNameservers returns the authoritative nameservers for the given fqdn.
|
|
||||||
func lookupNameservers(fqdn string) ([]string, error) {
|
|
||||||
var authoritativeNss []string
|
|
||||||
|
|
||||||
zone, err := FindZoneByFqdn(fqdn, RecursiveNameservers)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not determine the zone: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := dnsQuery(zone, dns.TypeNS, RecursiveNameservers, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rr := range r.Answer {
|
|
||||||
if ns, ok := rr.(*dns.NS); ok {
|
|
||||||
authoritativeNss = append(authoritativeNss, strings.ToLower(ns.Ns))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(authoritativeNss) > 0 {
|
|
||||||
return authoritativeNss, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("could not determine authoritative nameservers")
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindZoneByFqdn determines the zone apex for the given fqdn by recursing up the
|
|
||||||
// domain labels until the nameserver returns a SOA record in the answer section.
|
|
||||||
func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) {
|
|
||||||
muFqdnToZone.Lock()
|
|
||||||
defer muFqdnToZone.Unlock()
|
|
||||||
|
|
||||||
// Do we have it cached?
|
|
||||||
if zone, ok := fqdnToZone[fqdn]; ok {
|
|
||||||
return zone, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
labelIndexes := dns.Split(fqdn)
|
|
||||||
for _, index := range labelIndexes {
|
|
||||||
domain := fqdn[index:]
|
|
||||||
|
|
||||||
in, err := dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
|
||||||
if in.Rcode != dns.RcodeNameError && in.Rcode != dns.RcodeSuccess {
|
|
||||||
return "", fmt.Errorf("unexpected response code '%s' for %s",
|
|
||||||
dns.RcodeToString[in.Rcode], domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we got a SOA RR in the answer section
|
|
||||||
if in.Rcode == dns.RcodeSuccess {
|
|
||||||
|
|
||||||
// CNAME records cannot/should not exist at the root of a zone.
|
|
||||||
// So we skip a domain when a CNAME is found.
|
|
||||||
if dnsMsgContainsCNAME(in) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ans := range in.Answer {
|
|
||||||
if soa, ok := ans.(*dns.SOA); ok {
|
|
||||||
zone := soa.Hdr.Name
|
|
||||||
fqdnToZone[fqdn] = zone
|
|
||||||
return zone, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("could not find the start of authority")
|
|
||||||
}
|
|
||||||
|
|
||||||
// dnsMsgContainsCNAME checks for a CNAME answer in msg
|
|
||||||
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
|
|
||||||
for _, ans := range msg.Answer {
|
|
||||||
if _, ok := ans.(*dns.CNAME); ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
|
||||||
func ClearFqdnCache() {
|
|
||||||
fqdnToZone = map[string]string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToFqdn converts the name into a fqdn appending a trailing dot.
|
|
||||||
func ToFqdn(name string) string {
|
|
||||||
n := len(name)
|
|
||||||
if n == 0 || name[n-1] == '.' {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return name + "."
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnFqdn converts the fqdn into a name removing the trailing dot.
|
|
||||||
func UnFqdn(name string) string {
|
|
||||||
n := len(name)
|
|
||||||
if n != 0 && name[n-1] == '.' {
|
|
||||||
return name[:n-1]
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
55
vendor/github.com/xenolf/lego/acme/dns_challenge_manual.go
generated
vendored
55
vendor/github.com/xenolf/lego/acme/dns_challenge_manual.go
generated
vendored
@ -1,55 +0,0 @@
|
|||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/xenolf/lego/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
dnsTemplate = "%s %d IN TXT \"%s\""
|
|
||||||
)
|
|
||||||
|
|
||||||
// DNSProviderManual is an implementation of the ChallengeProvider interface
|
|
||||||
type DNSProviderManual struct{}
|
|
||||||
|
|
||||||
// NewDNSProviderManual returns a DNSProviderManual instance.
|
|
||||||
func NewDNSProviderManual() (*DNSProviderManual, error) {
|
|
||||||
return &DNSProviderManual{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Present prints instructions for manually creating the TXT record
|
|
||||||
func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
|
|
||||||
fqdn, value, ttl := DNS01Record(domain, keyAuth)
|
|
||||||
dnsRecord := fmt.Sprintf(dnsTemplate, fqdn, ttl, value)
|
|
||||||
|
|
||||||
authZone, err := FindZoneByFqdn(fqdn, RecursiveNameservers)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("acme: Please create the following TXT record in your %s zone:", authZone)
|
|
||||||
log.Infof("acme: %s", dnsRecord)
|
|
||||||
log.Infof("acme: Press 'Enter' when you are done")
|
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
_, _ = reader.ReadString('\n')
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanUp prints instructions for manually removing the TXT record
|
|
||||||
func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
|
|
||||||
fqdn, _, ttl := DNS01Record(domain, keyAuth)
|
|
||||||
dnsRecord := fmt.Sprintf(dnsTemplate, fqdn, ttl, "...")
|
|
||||||
|
|
||||||
authZone, err := FindZoneByFqdn(fqdn, RecursiveNameservers)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("acme: You can now remove this TXT record from your %s zone:", authZone)
|
|
||||||
log.Infof("acme: %s", dnsRecord)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
91
vendor/github.com/xenolf/lego/acme/error.go
generated
vendored
91
vendor/github.com/xenolf/lego/acme/error.go
generated
vendored
@ -1,91 +0,0 @@
|
|||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
tosAgreementError = "Terms of service have changed"
|
|
||||||
invalidNonceError = "urn:ietf:params:acme:error:badNonce"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RemoteError is the base type for all errors specific to the ACME protocol.
|
|
||||||
type RemoteError struct {
|
|
||||||
StatusCode int `json:"status,omitempty"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Detail string `json:"detail"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e RemoteError) Error() string {
|
|
||||||
return fmt.Sprintf("acme: Error %d - %s - %s", e.StatusCode, e.Type, e.Detail)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TOSError represents the error which is returned if the user needs to
|
|
||||||
// accept the TOS.
|
|
||||||
// TODO: include the new TOS url if we can somehow obtain it.
|
|
||||||
type TOSError struct {
|
|
||||||
RemoteError
|
|
||||||
}
|
|
||||||
|
|
||||||
// NonceError represents the error which is returned if the
|
|
||||||
// nonce sent by the client was not accepted by the server.
|
|
||||||
type NonceError struct {
|
|
||||||
RemoteError
|
|
||||||
}
|
|
||||||
|
|
||||||
type domainError struct {
|
|
||||||
Domain string
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ObtainError is returned when there are specific errors available
|
|
||||||
// per domain. For example in ObtainCertificate
|
|
||||||
type ObtainError map[string]error
|
|
||||||
|
|
||||||
func (e ObtainError) Error() string {
|
|
||||||
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
|
|
||||||
for dom, err := range e {
|
|
||||||
buffer.WriteString(fmt.Sprintf("[%s] %s\n", dom, err))
|
|
||||||
}
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleHTTPError(resp *http.Response) error {
|
|
||||||
var errorDetail RemoteError
|
|
||||||
|
|
||||||
contentType := resp.Header.Get("Content-Type")
|
|
||||||
if contentType == "application/json" || strings.HasPrefix(contentType, "application/problem+json") {
|
|
||||||
err := json.NewDecoder(resp.Body).Decode(&errorDetail)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
detailBytes, err := ioutil.ReadAll(limitReader(resp.Body, maxBodySize))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
errorDetail.Detail = string(detailBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
errorDetail.StatusCode = resp.StatusCode
|
|
||||||
|
|
||||||
// Check for errors we handle specifically
|
|
||||||
if errorDetail.StatusCode == http.StatusForbidden && errorDetail.Detail == tosAgreementError {
|
|
||||||
return TOSError{errorDetail}
|
|
||||||
}
|
|
||||||
|
|
||||||
if errorDetail.StatusCode == http.StatusBadRequest && errorDetail.Type == invalidNonceError {
|
|
||||||
return NonceError{errorDetail}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errorDetail
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleChallengeError(chlng challenge) error {
|
|
||||||
return chlng.Error
|
|
||||||
}
|
|
||||||
58
vendor/github.com/xenolf/lego/acme/errors.go
generated
vendored
Normal file
58
vendor/github.com/xenolf/lego/acme/errors.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errors types
|
||||||
|
const (
|
||||||
|
errNS = "urn:ietf:params:acme:error:"
|
||||||
|
BadNonceErr = errNS + "badNonce"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProblemDetails the problem details object
|
||||||
|
// - https://tools.ietf.org/html/rfc7807#section-3.1
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.3.3
|
||||||
|
type ProblemDetails struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Detail string `json:"detail,omitempty"`
|
||||||
|
HTTPStatus int `json:"status,omitempty"`
|
||||||
|
Instance string `json:"instance,omitempty"`
|
||||||
|
SubProblems []SubProblem `json:"subproblems,omitempty"`
|
||||||
|
|
||||||
|
// additional values to have a better error message (Not defined by the RFC)
|
||||||
|
Method string `json:"method,omitempty"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubProblem a "subproblems"
|
||||||
|
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.7.1
|
||||||
|
type SubProblem struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Detail string `json:"detail,omitempty"`
|
||||||
|
Identifier Identifier `json:"identifier,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProblemDetails) Error() string {
|
||||||
|
msg := fmt.Sprintf("acme: error: %d", p.HTTPStatus)
|
||||||
|
if len(p.Method) != 0 || len(p.URL) != 0 {
|
||||||
|
msg += fmt.Sprintf(" :: %s :: %s", p.Method, p.URL)
|
||||||
|
}
|
||||||
|
msg += fmt.Sprintf(" :: %s :: %s", p.Type, p.Detail)
|
||||||
|
|
||||||
|
for _, sub := range p.SubProblems {
|
||||||
|
msg += fmt.Sprintf(", problem: %q :: %s", sub.Type, sub.Detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.Instance) == 0 {
|
||||||
|
msg += ", url: " + p.Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// NonceError represents the error which is returned
|
||||||
|
// if the nonce sent by the client was not accepted by the server.
|
||||||
|
type NonceError struct {
|
||||||
|
*ProblemDetails
|
||||||
|
}
|
||||||
212
vendor/github.com/xenolf/lego/acme/http.go
generated
vendored
212
vendor/github.com/xenolf/lego/acme/http.go
generated
vendored
@ -1,212 +0,0 @@
|
|||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// UserAgent (if non-empty) will be tacked onto the User-Agent string in requests.
|
|
||||||
UserAgent string
|
|
||||||
|
|
||||||
// HTTPClient is an HTTP client with a reasonable timeout value and
|
|
||||||
// potentially a custom *x509.CertPool based on the caCertificatesEnvVar
|
|
||||||
// environment variable (see the `initCertPool` function)
|
|
||||||
HTTPClient = http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
DialContext: (&net.Dialer{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
KeepAlive: 30 * time.Second,
|
|
||||||
}).DialContext,
|
|
||||||
TLSHandshakeTimeout: 15 * time.Second,
|
|
||||||
ResponseHeaderTimeout: 15 * time.Second,
|
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
ServerName: os.Getenv(caServerNameEnvVar),
|
|
||||||
RootCAs: initCertPool(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ourUserAgent is the User-Agent of this underlying library package.
|
|
||||||
// NOTE: Update this with each tagged release.
|
|
||||||
ourUserAgent = "xenolf-acme/1.2.1"
|
|
||||||
|
|
||||||
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
|
|
||||||
// values: detach|release
|
|
||||||
// NOTE: Update this with each tagged release.
|
|
||||||
ourUserAgentComment = "detach"
|
|
||||||
|
|
||||||
// caCertificatesEnvVar is the environment variable name that can be used to
|
|
||||||
// specify the path to PEM encoded CA Certificates that can be used to
|
|
||||||
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
|
|
||||||
// the system-wide trusted root list.
|
|
||||||
caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"
|
|
||||||
|
|
||||||
// caServerNameEnvVar is the environment variable name that can be used to
|
|
||||||
// specify the CA server name that can be used to
|
|
||||||
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
|
|
||||||
// the system-wide trusted root list.
|
|
||||||
caServerNameEnvVar = "LEGO_CA_SERVER_NAME"
|
|
||||||
)
|
|
||||||
|
|
||||||
// initCertPool creates a *x509.CertPool populated with the PEM certificates
|
|
||||||
// found in the filepath specified in the caCertificatesEnvVar OS environment
|
|
||||||
// variable. If the caCertificatesEnvVar is not set then initCertPool will
|
|
||||||
// return nil. If there is an error creating a *x509.CertPool from the provided
|
|
||||||
// caCertificatesEnvVar value then initCertPool will panic.
|
|
||||||
func initCertPool() *x509.CertPool {
|
|
||||||
if customCACertsPath := os.Getenv(caCertificatesEnvVar); customCACertsPath != "" {
|
|
||||||
customCAs, err := ioutil.ReadFile(customCACertsPath)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("error reading %s=%q: %v",
|
|
||||||
caCertificatesEnvVar, customCACertsPath, err))
|
|
||||||
}
|
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
|
|
||||||
panic(fmt.Sprintf("error creating x509 cert pool from %s=%q: %v",
|
|
||||||
caCertificatesEnvVar, customCACertsPath, err))
|
|
||||||
}
|
|
||||||
return certPool
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// httpHead performs a HEAD request with a proper User-Agent string.
|
|
||||||
// The response body (resp.Body) is already closed when this function returns.
|
|
||||||
func httpHead(url string) (resp *http.Response, err error) {
|
|
||||||
req, err := http.NewRequest(http.MethodHead, url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to head %q: %v", url, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("User-Agent", userAgent())
|
|
||||||
|
|
||||||
resp, err = HTTPClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return resp, fmt.Errorf("failed to do head %q: %v", url, err)
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// httpPost performs a POST request with a proper User-Agent string.
|
|
||||||
// Callers should close resp.Body when done reading from it.
|
|
||||||
func httpPost(url string, bodyType string, body io.Reader) (resp *http.Response, err error) {
|
|
||||||
req, err := http.NewRequest(http.MethodPost, url, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to post %q: %v", url, err)
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", bodyType)
|
|
||||||
req.Header.Set("User-Agent", userAgent())
|
|
||||||
|
|
||||||
return HTTPClient.Do(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// httpGet performs a GET request with a proper User-Agent string.
|
|
||||||
// Callers should close resp.Body when done reading from it.
|
|
||||||
func httpGet(url string) (resp *http.Response, err error) {
|
|
||||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get %q: %v", url, err)
|
|
||||||
}
|
|
||||||
req.Header.Set("User-Agent", userAgent())
|
|
||||||
|
|
||||||
return HTTPClient.Do(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getJSON performs an HTTP GET request and parses the response body
|
|
||||||
// as JSON, into the provided respBody object.
|
|
||||||
func getJSON(uri string, respBody interface{}) (http.Header, error) {
|
|
||||||
resp, err := httpGet(uri)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get json %q: %v", uri, err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode >= http.StatusBadRequest {
|
|
||||||
return resp.Header, handleHTTPError(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
|
|
||||||
}
|
|
||||||
|
|
||||||
// postJSON performs an HTTP POST request and parses the response body
|
|
||||||
// as JSON, into the provided respBody object.
|
|
||||||
func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, error) {
|
|
||||||
jsonBytes, err := json.Marshal(reqBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to marshal network message")
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := post(j, uri, jsonBytes, respBody)
|
|
||||||
if resp == nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return resp.Header, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func postAsGet(j *jws, uri string, respBody interface{}) (*http.Response, error) {
|
|
||||||
return post(j, uri, []byte{}, respBody)
|
|
||||||
}
|
|
||||||
|
|
||||||
func post(j *jws, uri string, reqBody []byte, respBody interface{}) (*http.Response, error) {
|
|
||||||
resp, err := j.post(uri, reqBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to post JWS message. -> %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode >= http.StatusBadRequest {
|
|
||||||
err = handleHTTPError(resp)
|
|
||||||
switch err.(type) {
|
|
||||||
case NonceError:
|
|
||||||
// Retry once if the nonce was invalidated
|
|
||||||
|
|
||||||
retryResp, errP := j.post(uri, reqBody)
|
|
||||||
if errP != nil {
|
|
||||||
return nil, fmt.Errorf("failed to post JWS message. -> %v", errP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if retryResp.StatusCode >= http.StatusBadRequest {
|
|
||||||
return retryResp, handleHTTPError(retryResp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if respBody == nil {
|
|
||||||
return retryResp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return retryResp, json.NewDecoder(retryResp.Body).Decode(respBody)
|
|
||||||
default:
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if respBody == nil {
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, json.NewDecoder(resp.Body).Decode(respBody)
|
|
||||||
}
|
|
||||||
|
|
||||||
// userAgent builds and returns the User-Agent string to use in requests.
|
|
||||||
func userAgent() string {
|
|
||||||
ua := fmt.Sprintf("%s %s (%s; %s; %s)", UserAgent, ourUserAgent, ourUserAgentComment, runtime.GOOS, runtime.GOARCH)
|
|
||||||
return strings.TrimSpace(ua)
|
|
||||||
}
|
|
||||||
42
vendor/github.com/xenolf/lego/acme/http_challenge.go
generated
vendored
42
vendor/github.com/xenolf/lego/acme/http_challenge.go
generated
vendored
@ -1,42 +0,0 @@
|
|||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/xenolf/lego/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type httpChallenge struct {
|
|
||||||
jws *jws
|
|
||||||
validate validateFunc
|
|
||||||
provider ChallengeProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP01ChallengePath returns the URL path for the `http-01` challenge
|
|
||||||
func HTTP01ChallengePath(token string) string {
|
|
||||||
return "/.well-known/acme-challenge/" + token
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *httpChallenge) Solve(chlng challenge, domain string) error {
|
|
||||||
|
|
||||||
log.Infof("[%s] acme: Trying to solve HTTP-01", domain)
|
|
||||||
|
|
||||||
// Generate the Key Authorization for the challenge
|
|
||||||
keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.provider.Present(domain, chlng.Token, keyAuth)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("[%s] error presenting token: %v", domain, err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
err := s.provider.CleanUp(domain, chlng.Token, keyAuth)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("[%s] error cleaning up: %v", domain, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return s.validate(s.jws, domain, chlng.URL, challenge{Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
|
||||||
}
|
|
||||||
167
vendor/github.com/xenolf/lego/acme/jws.go
generated
vendored
167
vendor/github.com/xenolf/lego/acme/jws.go
generated
vendored
@ -1,167 +0,0 @@
|
|||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rsa"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type jws struct {
|
|
||||||
getNonceURL string
|
|
||||||
privKey crypto.PrivateKey
|
|
||||||
kid string
|
|
||||||
nonces nonceManager
|
|
||||||
}
|
|
||||||
|
|
||||||
// Posts a JWS signed message to the specified URL.
|
|
||||||
// It does NOT close the response body, so the caller must
|
|
||||||
// do that if no error was returned.
|
|
||||||
func (j *jws) post(url string, content []byte) (*http.Response, error) {
|
|
||||||
signedContent, err := j.signContent(url, content)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to sign content -> %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
data := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
|
|
||||||
resp, err := httpPost(url, "application/jose+json", data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to HTTP POST to %s -> %s", url, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
nonce, nonceErr := getNonceFromResponse(resp)
|
|
||||||
if nonceErr == nil {
|
|
||||||
j.nonces.Push(nonce)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *jws) signContent(url string, content []byte) (*jose.JSONWebSignature, error) {
|
|
||||||
|
|
||||||
var alg jose.SignatureAlgorithm
|
|
||||||
switch k := j.privKey.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
alg = jose.RS256
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
if k.Curve == elliptic.P256() {
|
|
||||||
alg = jose.ES256
|
|
||||||
} else if k.Curve == elliptic.P384() {
|
|
||||||
alg = jose.ES384
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonKey := jose.JSONWebKey{
|
|
||||||
Key: j.privKey,
|
|
||||||
KeyID: j.kid,
|
|
||||||
}
|
|
||||||
|
|
||||||
signKey := jose.SigningKey{
|
|
||||||
Algorithm: alg,
|
|
||||||
Key: jsonKey,
|
|
||||||
}
|
|
||||||
options := jose.SignerOptions{
|
|
||||||
NonceSource: j,
|
|
||||||
ExtraHeaders: make(map[jose.HeaderKey]interface{}),
|
|
||||||
}
|
|
||||||
options.ExtraHeaders["url"] = url
|
|
||||||
if j.kid == "" {
|
|
||||||
options.EmbedJWK = true
|
|
||||||
}
|
|
||||||
|
|
||||||
signer, err := jose.NewSigner(signKey, &options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create jose signer -> %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
signed, err := signer.Sign(content)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to sign content -> %s", err.Error())
|
|
||||||
}
|
|
||||||
return signed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *jws) signEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) {
|
|
||||||
jwk := jose.JSONWebKey{Key: j.privKey}
|
|
||||||
jwkJSON, err := jwk.Public().MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("acme: error encoding eab jwk key: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
signer, err := jose.NewSigner(
|
|
||||||
jose.SigningKey{Algorithm: jose.HS256, Key: hmac},
|
|
||||||
&jose.SignerOptions{
|
|
||||||
EmbedJWK: false,
|
|
||||||
ExtraHeaders: map[jose.HeaderKey]interface{}{
|
|
||||||
"kid": kid,
|
|
||||||
"url": url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create External Account Binding jose signer -> %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
signed, err := signer.Sign(jwkJSON)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to External Account Binding sign content -> %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return signed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *jws) Nonce() (string, error) {
|
|
||||||
if nonce, ok := j.nonces.Pop(); ok {
|
|
||||||
return nonce, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return getNonce(j.getNonceURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
type nonceManager struct {
|
|
||||||
nonces []string
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *nonceManager) Pop() (string, bool) {
|
|
||||||
n.Lock()
|
|
||||||
defer n.Unlock()
|
|
||||||
|
|
||||||
if len(n.nonces) == 0 {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
nonce := n.nonces[len(n.nonces)-1]
|
|
||||||
n.nonces = n.nonces[:len(n.nonces)-1]
|
|
||||||
return nonce, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *nonceManager) Push(nonce string) {
|
|
||||||
n.Lock()
|
|
||||||
defer n.Unlock()
|
|
||||||
n.nonces = append(n.nonces, nonce)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNonce(url string) (string, error) {
|
|
||||||
resp, err := httpHead(url)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to get nonce from HTTP HEAD -> %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return getNonceFromResponse(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNonceFromResponse(resp *http.Response) (string, error) {
|
|
||||||
nonce := resp.Header.Get("Replay-Nonce")
|
|
||||||
if nonce == "" {
|
|
||||||
return "", fmt.Errorf("server did not respond with a proper nonce header")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nonce, nil
|
|
||||||
}
|
|
||||||
103
vendor/github.com/xenolf/lego/acme/messages.go
generated
vendored
103
vendor/github.com/xenolf/lego/acme/messages.go
generated
vendored
@ -1,103 +0,0 @@
|
|||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RegistrationResource represents all important informations about a registration
|
|
||||||
// of which the client needs to keep track itself.
|
|
||||||
type RegistrationResource struct {
|
|
||||||
Body accountMessage `json:"body,omitempty"`
|
|
||||||
URI string `json:"uri,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type directory struct {
|
|
||||||
NewNonceURL string `json:"newNonce"`
|
|
||||||
NewAccountURL string `json:"newAccount"`
|
|
||||||
NewOrderURL string `json:"newOrder"`
|
|
||||||
RevokeCertURL string `json:"revokeCert"`
|
|
||||||
KeyChangeURL string `json:"keyChange"`
|
|
||||||
Meta struct {
|
|
||||||
TermsOfService string `json:"termsOfService"`
|
|
||||||
Website string `json:"website"`
|
|
||||||
CaaIdentities []string `json:"caaIdentities"`
|
|
||||||
ExternalAccountRequired bool `json:"externalAccountRequired"`
|
|
||||||
} `json:"meta"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type accountMessage struct {
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
Contact []string `json:"contact,omitempty"`
|
|
||||||
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
|
|
||||||
Orders string `json:"orders,omitempty"`
|
|
||||||
OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"`
|
|
||||||
ExternalAccountBinding json.RawMessage `json:"externalAccountBinding,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type orderResource struct {
|
|
||||||
URL string `json:"url,omitempty"`
|
|
||||||
Domains []string `json:"domains,omitempty"`
|
|
||||||
orderMessage `json:"body,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type orderMessage struct {
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
Expires string `json:"expires,omitempty"`
|
|
||||||
Identifiers []identifier `json:"identifiers"`
|
|
||||||
NotBefore string `json:"notBefore,omitempty"`
|
|
||||||
NotAfter string `json:"notAfter,omitempty"`
|
|
||||||
Authorizations []string `json:"authorizations,omitempty"`
|
|
||||||
Finalize string `json:"finalize,omitempty"`
|
|
||||||
Certificate string `json:"certificate,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type authorization struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Expires time.Time `json:"expires"`
|
|
||||||
Identifier identifier `json:"identifier"`
|
|
||||||
Challenges []challenge `json:"challenges"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type identifier struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type challenge struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
Validated time.Time `json:"validated"`
|
|
||||||
KeyAuthorization string `json:"keyAuthorization"`
|
|
||||||
Error RemoteError `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type csrMessage struct {
|
|
||||||
Csr string `json:"csr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type revokeCertMessage struct {
|
|
||||||
Certificate string `json:"certificate"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type deactivateAuthMessage struct {
|
|
||||||
Status string `jsom:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertificateResource represents a CA issued certificate.
|
|
||||||
// PrivateKey, Certificate and IssuerCertificate are all
|
|
||||||
// already PEM encoded and can be directly written to disk.
|
|
||||||
// Certificate may be a certificate bundle, depending on the
|
|
||||||
// options supplied to create it.
|
|
||||||
type CertificateResource struct {
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
CertURL string `json:"certUrl"`
|
|
||||||
CertStableURL string `json:"certStableUrl"`
|
|
||||||
AccountRef string `json:"accountRef,omitempty"`
|
|
||||||
PrivateKey []byte `json:"-"`
|
|
||||||
Certificate []byte `json:"-"`
|
|
||||||
IssuerCertificate []byte `json:"-"`
|
|
||||||
CSR []byte `json:"-"`
|
|
||||||
}
|
|
||||||
104
vendor/github.com/xenolf/lego/acme/tls_alpn_challenge.go
generated
vendored
104
vendor/github.com/xenolf/lego/acme/tls_alpn_challenge.go
generated
vendored
@ -1,104 +0,0 @@
|
|||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/asn1"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/xenolf/lego/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
|
|
||||||
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1
|
|
||||||
var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
|
||||||
|
|
||||||
type tlsALPNChallenge struct {
|
|
||||||
jws *jws
|
|
||||||
validate validateFunc
|
|
||||||
provider ChallengeProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
// Solve manages the provider to validate and solve the challenge.
|
|
||||||
func (t *tlsALPNChallenge) Solve(chlng challenge, domain string) error {
|
|
||||||
log.Infof("[%s] acme: Trying to solve TLS-ALPN-01", domain)
|
|
||||||
|
|
||||||
// Generate the Key Authorization for the challenge
|
|
||||||
keyAuth, err := getKeyAuthorization(chlng.Token, t.jws.privKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.provider.Present(domain, chlng.Token, keyAuth)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("[%s] error presenting token: %v", domain, err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
err := t.provider.CleanUp(domain, chlng.Token, keyAuth)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("[%s] error cleaning up: %v", domain, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return t.validate(t.jws, domain, chlng.URL, challenge{Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLSALPNChallengeBlocks returns PEM blocks (certPEMBlock, keyPEMBlock) with the acmeValidation-v1 extension
|
|
||||||
// and domain name for the `tls-alpn-01` challenge.
|
|
||||||
func TLSALPNChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
|
|
||||||
// Compute the SHA-256 digest of the key authorization.
|
|
||||||
zBytes := sha256.Sum256([]byte(keyAuth))
|
|
||||||
|
|
||||||
value, err := asn1.Marshal(zBytes[:sha256.Size])
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the keyAuth digest as the acmeValidation-v1 extension
|
|
||||||
// (marked as critical such that it won't be used by non-ACME software).
|
|
||||||
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-3
|
|
||||||
extensions := []pkix.Extension{
|
|
||||||
{
|
|
||||||
Id: idPeAcmeIdentifierV1,
|
|
||||||
Critical: true,
|
|
||||||
Value: value,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a new RSA key for the certificates.
|
|
||||||
tempPrivKey, err := generatePrivateKey(RSA2048)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rsaPrivKey := tempPrivKey.(*rsa.PrivateKey)
|
|
||||||
|
|
||||||
// Generate the PEM certificate using the provided private key, domain, and extra extensions.
|
|
||||||
tempCertPEM, err := generatePemCert(rsaPrivKey, domain, extensions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode the private key into a PEM format. We'll need to use it to generate the x509 keypair.
|
|
||||||
rsaPrivPEM := pemEncode(rsaPrivKey)
|
|
||||||
|
|
||||||
return tempCertPEM, rsaPrivPEM, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLSALPNChallengeCert returns a certificate with the acmeValidation-v1 extension
|
|
||||||
// and domain name for the `tls-alpn-01` challenge.
|
|
||||||
func TLSALPNChallengeCert(domain, keyAuth string) (*tls.Certificate, error) {
|
|
||||||
tempCertPEM, rsaPrivPEM, err := TLSALPNChallengeBlocks(domain, keyAuth)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &certificate, nil
|
|
||||||
}
|
|
||||||
21
vendor/github.com/xenolf/lego/certcrypto/LICENSE
generated
vendored
Normal file
21
vendor/github.com/xenolf/lego/certcrypto/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-2017 Sebastian Erhart
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
252
vendor/github.com/xenolf/lego/certcrypto/crypto.go
generated
vendored
Normal file
252
vendor/github.com/xenolf/lego/certcrypto/crypto.go
generated
vendored
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
package certcrypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ocsp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constants for all key types we support.
|
||||||
|
const (
|
||||||
|
EC256 = KeyType("P256")
|
||||||
|
EC384 = KeyType("P384")
|
||||||
|
RSA2048 = KeyType("2048")
|
||||||
|
RSA4096 = KeyType("4096")
|
||||||
|
RSA8192 = KeyType("8192")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// OCSPGood means that the certificate is valid.
|
||||||
|
OCSPGood = ocsp.Good
|
||||||
|
// OCSPRevoked means that the certificate has been deliberately revoked.
|
||||||
|
OCSPRevoked = ocsp.Revoked
|
||||||
|
// OCSPUnknown means that the OCSP responder doesn't know about the certificate.
|
||||||
|
OCSPUnknown = ocsp.Unknown
|
||||||
|
// OCSPServerFailed means that the OCSP responder failed to process the request.
|
||||||
|
OCSPServerFailed = ocsp.ServerFailed
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constants for OCSP must staple
|
||||||
|
var (
|
||||||
|
tlsFeatureExtensionOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
|
||||||
|
ocspMustStapleFeature = []byte{0x30, 0x03, 0x02, 0x01, 0x05}
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyType represents the key algo as well as the key size or curve to use.
|
||||||
|
type KeyType string
|
||||||
|
|
||||||
|
type DERCertificateBytes []byte
|
||||||
|
|
||||||
|
// ParsePEMBundle parses a certificate bundle from top to bottom and returns
|
||||||
|
// a slice of x509 certificates. This function will error if no certificates are found.
|
||||||
|
func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
||||||
|
var certificates []*x509.Certificate
|
||||||
|
var certDERBlock *pem.Block
|
||||||
|
|
||||||
|
for {
|
||||||
|
certDERBlock, bundle = pem.Decode(bundle)
|
||||||
|
if certDERBlock == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if certDERBlock.Type == "CERTIFICATE" {
|
||||||
|
cert, err := x509.ParseCertificate(certDERBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certificates = append(certificates, cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(certificates) == 0 {
|
||||||
|
return nil, errors.New("no certificates were found while parsing the bundle")
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
||||||
|
keyBlock, _ := pem.Decode(key)
|
||||||
|
|
||||||
|
switch keyBlock.Type {
|
||||||
|
case "RSA PRIVATE KEY":
|
||||||
|
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
||||||
|
case "EC PRIVATE KEY":
|
||||||
|
return x509.ParseECPrivateKey(keyBlock.Bytes)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown PEM header value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GeneratePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
|
||||||
|
switch keyType {
|
||||||
|
case EC256:
|
||||||
|
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
case EC384:
|
||||||
|
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||||
|
case RSA2048:
|
||||||
|
return rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
case RSA4096:
|
||||||
|
return rsa.GenerateKey(rand.Reader, 4096)
|
||||||
|
case RSA8192:
|
||||||
|
return rsa.GenerateKey(rand.Reader, 8192)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("invalid KeyType: %s", keyType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateCSR(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
|
||||||
|
template := x509.CertificateRequest{
|
||||||
|
Subject: pkix.Name{CommonName: domain},
|
||||||
|
DNSNames: san,
|
||||||
|
}
|
||||||
|
|
||||||
|
if mustStaple {
|
||||||
|
template.ExtraExtensions = append(template.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: tlsFeatureExtensionOID,
|
||||||
|
Value: ocspMustStapleFeature,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PEMEncode(data interface{}) []byte {
|
||||||
|
var pemBlock *pem.Block
|
||||||
|
switch key := data.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
keyBytes, _ := x509.MarshalECPrivateKey(key)
|
||||||
|
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
|
||||||
|
case *x509.CertificateRequest:
|
||||||
|
pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw}
|
||||||
|
case DERCertificateBytes:
|
||||||
|
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.(DERCertificateBytes))}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pem.EncodeToMemory(pemBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pemDecode(data []byte) (*pem.Block, error) {
|
||||||
|
pemBlock, _ := pem.Decode(data)
|
||||||
|
if pemBlock == nil {
|
||||||
|
return nil, fmt.Errorf("PEM decode did not yield a valid block. Is the certificate in the right format?")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pemBlock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PemDecodeTox509CSR(pem []byte) (*x509.CertificateRequest, error) {
|
||||||
|
pemBlock, err := pemDecode(pem)
|
||||||
|
if pemBlock == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pemBlock.Type != "CERTIFICATE REQUEST" {
|
||||||
|
return nil, fmt.Errorf("PEM block is not a certificate request")
|
||||||
|
}
|
||||||
|
|
||||||
|
return x509.ParseCertificateRequest(pemBlock.Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePEMCertificate returns Certificate from a PEM encoded certificate.
|
||||||
|
// The certificate has to be PEM encoded. Any other encodings like DER will fail.
|
||||||
|
func ParsePEMCertificate(cert []byte) (*x509.Certificate, error) {
|
||||||
|
pemBlock, err := pemDecode(cert)
|
||||||
|
if pemBlock == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// from a DER encoded certificate
|
||||||
|
return x509.ParseCertificate(pemBlock.Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExtractDomains(cert *x509.Certificate) []string {
|
||||||
|
domains := []string{cert.Subject.CommonName}
|
||||||
|
|
||||||
|
// Check for SAN certificate
|
||||||
|
for _, sanDomain := range cert.DNSNames {
|
||||||
|
if sanDomain == cert.Subject.CommonName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
domains = append(domains, sanDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
|
||||||
|
domains := []string{csr.Subject.CommonName}
|
||||||
|
|
||||||
|
// loop over the SubjectAltName DNS names
|
||||||
|
for _, sanName := range csr.DNSNames {
|
||||||
|
if containsSAN(domains, sanName) {
|
||||||
|
// Duplicate; skip this name
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name is unique
|
||||||
|
domains = append(domains, sanName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsSAN(domains []string, sanName string) bool {
|
||||||
|
for _, existingName := range domains {
|
||||||
|
if existingName == sanName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
|
||||||
|
derBytes, err := generateDerCert(privateKey, time.Time{}, domain, extensions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateDerCert(privateKey *rsa.PrivateKey, expiration time.Time, domain string, extensions []pkix.Extension) ([]byte, error) {
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if expiration.IsZero() {
|
||||||
|
expiration = time.Now().Add(365)
|
||||||
|
}
|
||||||
|
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: "ACME Challenge TEMP",
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: expiration,
|
||||||
|
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment,
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
DNSNames: []string{domain},
|
||||||
|
ExtraExtensions: extensions,
|
||||||
|
}
|
||||||
|
|
||||||
|
return x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
||||||
|
}
|
||||||
21
vendor/github.com/xenolf/lego/certificate/LICENSE
generated
vendored
Normal file
21
vendor/github.com/xenolf/lego/certificate/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-2017 Sebastian Erhart
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
69
vendor/github.com/xenolf/lego/certificate/authorization.go
generated
vendored
Normal file
69
vendor/github.com/xenolf/lego/certificate/authorization.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package certificate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// overallRequestLimit is the overall number of request per second
|
||||||
|
// limited on the "new-reg", "new-authz" and "new-cert" endpoints.
|
||||||
|
// From the documentation the limitation is 20 requests per second,
|
||||||
|
// but using 20 as value doesn't work but 18 do
|
||||||
|
overallRequestLimit = 18
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authorization, error) {
|
||||||
|
resc, errc := make(chan acme.Authorization), make(chan domainError)
|
||||||
|
|
||||||
|
delay := time.Second / overallRequestLimit
|
||||||
|
|
||||||
|
for _, authzURL := range order.Authorizations {
|
||||||
|
time.Sleep(delay)
|
||||||
|
|
||||||
|
go func(authzURL string) {
|
||||||
|
authz, err := c.core.Authorizations.Get(authzURL)
|
||||||
|
if err != nil {
|
||||||
|
errc <- domainError{Domain: authz.Identifier.Value, Error: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resc <- authz
|
||||||
|
}(authzURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
var responses []acme.Authorization
|
||||||
|
failures := make(obtainError)
|
||||||
|
for i := 0; i < len(order.Authorizations); i++ {
|
||||||
|
select {
|
||||||
|
case res := <-resc:
|
||||||
|
responses = append(responses, res)
|
||||||
|
case err := <-errc:
|
||||||
|
failures[err.Domain] = err.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, auth := range order.Authorizations {
|
||||||
|
log.Infof("[%s] AuthURL: %s", order.Identifiers[i].Value, auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
close(resc)
|
||||||
|
close(errc)
|
||||||
|
|
||||||
|
// be careful to not return an empty failures map;
|
||||||
|
// even if empty, they become non-nil error values
|
||||||
|
if len(failures) > 0 {
|
||||||
|
return responses, failures
|
||||||
|
}
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) {
|
||||||
|
for _, auth := range order.Authorizations {
|
||||||
|
if err := c.core.Authorizations.Deactivate(auth); err != nil {
|
||||||
|
log.Infof("Unable to deactivated authorizations: %s", auth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
493
vendor/github.com/xenolf/lego/certificate/certificates.go
generated
vendored
Normal file
493
vendor/github.com/xenolf/lego/certificate/certificates.go
generated
vendored
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
package certificate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/acme/api"
|
||||||
|
"github.com/xenolf/lego/certcrypto"
|
||||||
|
"github.com/xenolf/lego/challenge"
|
||||||
|
"github.com/xenolf/lego/log"
|
||||||
|
"golang.org/x/crypto/ocsp"
|
||||||
|
"golang.org/x/net/idna"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxBodySize is the maximum size of body that we will read.
|
||||||
|
const maxBodySize = 1024 * 1024
|
||||||
|
|
||||||
|
// Resource represents a CA issued certificate.
|
||||||
|
// PrivateKey, Certificate and IssuerCertificate are all
|
||||||
|
// already PEM encoded and can be directly written to disk.
|
||||||
|
// Certificate may be a certificate bundle,
|
||||||
|
// depending on the options supplied to create it.
|
||||||
|
type Resource struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
CertURL string `json:"certUrl"`
|
||||||
|
CertStableURL string `json:"certStableUrl"`
|
||||||
|
PrivateKey []byte `json:"-"`
|
||||||
|
Certificate []byte `json:"-"`
|
||||||
|
IssuerCertificate []byte `json:"-"`
|
||||||
|
CSR []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObtainRequest The request to obtain certificate.
|
||||||
|
//
|
||||||
|
// The first domain in domains is used for the CommonName field of the certificate,
|
||||||
|
// all other domains are added using the Subject Alternate Names extension.
|
||||||
|
//
|
||||||
|
// A new private key is generated for every invocation of the function Obtain.
|
||||||
|
// If you do not want that you can supply your own private key in the privateKey parameter.
|
||||||
|
// If this parameter is non-nil it will be used instead of generating a new one.
|
||||||
|
//
|
||||||
|
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
|
type ObtainRequest struct {
|
||||||
|
Domains []string
|
||||||
|
Bundle bool
|
||||||
|
PrivateKey crypto.PrivateKey
|
||||||
|
MustStaple bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type resolver interface {
|
||||||
|
Solve(authorizations []acme.Authorization) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Certifier struct {
|
||||||
|
core *api.Core
|
||||||
|
keyType certcrypto.KeyType
|
||||||
|
resolver resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCertifier(core *api.Core, keyType certcrypto.KeyType, resolver resolver) *Certifier {
|
||||||
|
return &Certifier{
|
||||||
|
core: core,
|
||||||
|
keyType: keyType,
|
||||||
|
resolver: resolver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain tries to obtain a single certificate using all domains passed into it.
|
||||||
|
//
|
||||||
|
// This function will never return a partial certificate.
|
||||||
|
// If one domain in the list fails, the whole certificate will fail.
|
||||||
|
func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
|
||||||
|
if len(request.Domains) == 0 {
|
||||||
|
return nil, errors.New("no domains to obtain a certificate for")
|
||||||
|
}
|
||||||
|
|
||||||
|
domains := sanitizeDomain(request.Domains)
|
||||||
|
|
||||||
|
if request.Bundle {
|
||||||
|
log.Infof("[%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
|
||||||
|
} else {
|
||||||
|
log.Infof("[%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err := c.core.Orders.New(domains)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
authz, err := c.getAuthorizations(order)
|
||||||
|
if err != nil {
|
||||||
|
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||||
|
c.deactivateAuthorizations(order)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.resolver.Solve(authz)
|
||||||
|
if err != nil {
|
||||||
|
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||||
|
|
||||||
|
failures := make(obtainError)
|
||||||
|
cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple)
|
||||||
|
if err != nil {
|
||||||
|
for _, auth := range authz {
|
||||||
|
failures[challenge.GetTargetedDomain(auth)] = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not return an empty failures map, because
|
||||||
|
// it would still be a non-nil error value
|
||||||
|
if len(failures) > 0 {
|
||||||
|
return cert, failures
|
||||||
|
}
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
|
||||||
|
//
|
||||||
|
// The domains are inferred from the CommonName and SubjectAltNames, if any.
|
||||||
|
// The private key for this CSR is not required.
|
||||||
|
//
|
||||||
|
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
|
//
|
||||||
|
// This function will never return a partial certificate.
|
||||||
|
// If one domain in the list fails, the whole certificate will fail.
|
||||||
|
func (c *Certifier) ObtainForCSR(csr x509.CertificateRequest, bundle bool) (*Resource, error) {
|
||||||
|
// figure out what domains it concerns
|
||||||
|
// start with the common name
|
||||||
|
domains := certcrypto.ExtractDomainsCSR(&csr)
|
||||||
|
|
||||||
|
if bundle {
|
||||||
|
log.Infof("[%s] acme: Obtaining bundled SAN certificate given a CSR", strings.Join(domains, ", "))
|
||||||
|
} else {
|
||||||
|
log.Infof("[%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err := c.core.Orders.New(domains)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
authz, err := c.getAuthorizations(order)
|
||||||
|
if err != nil {
|
||||||
|
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||||
|
c.deactivateAuthorizations(order)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.resolver.Solve(authz)
|
||||||
|
if err != nil {
|
||||||
|
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||||
|
|
||||||
|
failures := make(obtainError)
|
||||||
|
cert, err := c.getForCSR(domains, order, bundle, csr.Raw, nil)
|
||||||
|
if err != nil {
|
||||||
|
for _, auth := range authz {
|
||||||
|
failures[challenge.GetTargetedDomain(auth)] = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert != nil {
|
||||||
|
// Add the CSR to the certificate so that it can be used for renewals.
|
||||||
|
cert.CSR = certcrypto.PEMEncode(&csr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not return an empty failures map,
|
||||||
|
// because it would still be a non-nil error value
|
||||||
|
if len(failures) > 0 {
|
||||||
|
return cert, failures
|
||||||
|
}
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool) (*Resource, error) {
|
||||||
|
if privateKey == nil {
|
||||||
|
var err error
|
||||||
|
privateKey, err = certcrypto.GeneratePrivateKey(c.keyType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine certificate name(s) based on the authorization resources
|
||||||
|
commonName := domains[0]
|
||||||
|
|
||||||
|
// ACME draft Section 7.4 "Applying for Certificate Issuance"
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4
|
||||||
|
// says:
|
||||||
|
// Clients SHOULD NOT make any assumptions about the sort order of
|
||||||
|
// "identifiers" or "authorizations" elements in the returned order
|
||||||
|
// object.
|
||||||
|
san := []string{commonName}
|
||||||
|
for _, auth := range order.Identifiers {
|
||||||
|
if auth.Value != commonName {
|
||||||
|
san = append(san, auth.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should the CSR be customizable?
|
||||||
|
csr, err := certcrypto.GenerateCSR(privateKey, commonName, san, mustStaple)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.getForCSR(domains, order, bundle, csr, certcrypto.PEMEncode(privateKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle bool, csr []byte, privateKeyPem []byte) (*Resource, error) {
|
||||||
|
respOrder, err := c.core.Orders.UpdateForCSR(order.Finalize, csr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
commonName := domains[0]
|
||||||
|
certRes := &Resource{
|
||||||
|
Domain: commonName,
|
||||||
|
CertURL: respOrder.Certificate,
|
||||||
|
PrivateKey: privateKeyPem,
|
||||||
|
}
|
||||||
|
|
||||||
|
if respOrder.Status == acme.StatusValid {
|
||||||
|
// if the certificate is available right away, short cut!
|
||||||
|
ok, err := c.checkResponse(respOrder, certRes, bundle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return certRes, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.waitForCertificate(certRes, order.Location, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Certifier) waitForCertificate(certRes *Resource, orderURL string, bundle bool) (*Resource, error) {
|
||||||
|
stopTimer := time.NewTimer(30 * time.Second)
|
||||||
|
defer stopTimer.Stop()
|
||||||
|
retryTick := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer retryTick.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stopTimer.C:
|
||||||
|
return nil, errors.New("certificate polling timed out")
|
||||||
|
case <-retryTick.C:
|
||||||
|
order, err := c.core.Orders.Get(orderURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
done, err := c.checkResponse(order, certRes, bundle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if done {
|
||||||
|
return certRes, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkResponse checks to see if the certificate is ready and a link is contained in the response.
|
||||||
|
//
|
||||||
|
// If so, loads it into certRes and returns true.
|
||||||
|
// If the cert is not yet ready, it returns false.
|
||||||
|
//
|
||||||
|
// The certRes input should already have the Domain (common name) field populated.
|
||||||
|
//
|
||||||
|
// If bundle is true, the certificate will be bundled with the issuer's cert.
|
||||||
|
func (c *Certifier) checkResponse(order acme.Order, certRes *Resource, bundle bool) (bool, error) {
|
||||||
|
valid, err := checkOrderStatus(order)
|
||||||
|
if err != nil || !valid {
|
||||||
|
return valid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, issuer, err := c.core.Certificates.Get(order.Certificate, bundle)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("[%s] Server responded with a certificate.", certRes.Domain)
|
||||||
|
|
||||||
|
certRes.IssuerCertificate = issuer
|
||||||
|
certRes.Certificate = cert
|
||||||
|
certRes.CertURL = order.Certificate
|
||||||
|
certRes.CertStableURL = order.Certificate
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revoke takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
||||||
|
func (c *Certifier) Revoke(cert []byte) error {
|
||||||
|
certificates, err := certcrypto.ParsePEMBundle(cert)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
x509Cert := certificates[0]
|
||||||
|
if x509Cert.IsCA {
|
||||||
|
return fmt.Errorf("certificate bundle starts with a CA certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
revokeMsg := acme.RevokeCertMessage{
|
||||||
|
Certificate: base64.RawURLEncoding.EncodeToString(x509Cert.Raw),
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.core.Certificates.Revoke(revokeMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renew takes a Resource and tries to renew the certificate.
|
||||||
|
//
|
||||||
|
// If the renewal process succeeds, the new certificate will ge returned in a new CertResource.
|
||||||
|
// Please be aware that this function will return a new certificate in ANY case that is not an error.
|
||||||
|
// If the server does not provide us with a new cert on a GET request to the CertURL
|
||||||
|
// this function will start a new-cert flow where a new certificate gets generated.
|
||||||
|
//
|
||||||
|
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
|
//
|
||||||
|
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
|
||||||
|
func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool) (*Resource, error) {
|
||||||
|
// Input certificate is PEM encoded.
|
||||||
|
// Decode it here as we may need the decoded cert later on in the renewal process.
|
||||||
|
// The input may be a bundle or a single certificate.
|
||||||
|
certificates, err := certcrypto.ParsePEMBundle(certRes.Certificate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
x509Cert := certificates[0]
|
||||||
|
if x509Cert.IsCA {
|
||||||
|
return nil, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", certRes.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is just meant to be informal for the user.
|
||||||
|
timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC())
|
||||||
|
log.Infof("[%s] acme: Trying renewal with %d hours remaining", certRes.Domain, int(timeLeft.Hours()))
|
||||||
|
|
||||||
|
// We always need to request a new certificate to renew.
|
||||||
|
// Start by checking to see if the certificate was based off a CSR,
|
||||||
|
// and use that if it's defined.
|
||||||
|
if len(certRes.CSR) > 0 {
|
||||||
|
csr, errP := certcrypto.PemDecodeTox509CSR(certRes.CSR)
|
||||||
|
if errP != nil {
|
||||||
|
return nil, errP
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.ObtainForCSR(*csr, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
var privateKey crypto.PrivateKey
|
||||||
|
if certRes.PrivateKey != nil {
|
||||||
|
privateKey, err = certcrypto.ParsePEMPrivateKey(certRes.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query := ObtainRequest{
|
||||||
|
Domains: certcrypto.ExtractDomains(x509Cert),
|
||||||
|
Bundle: bundle,
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
MustStaple: mustStaple,
|
||||||
|
}
|
||||||
|
return c.Obtain(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOCSP takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
||||||
|
// the parsed response, and an error, if any.
|
||||||
|
//
|
||||||
|
// The returned []byte can be passed directly into the OCSPStaple property of a tls.Certificate.
|
||||||
|
// If the bundle only contains the issued certificate,
|
||||||
|
// this function will try to get the issuer certificate from the IssuingCertificateURL in the certificate.
|
||||||
|
//
|
||||||
|
// If the []byte and/or ocsp.Response return values are nil, the OCSP status may be assumed OCSPUnknown.
|
||||||
|
func (c *Certifier) GetOCSP(bundle []byte) ([]byte, *ocsp.Response, error) {
|
||||||
|
certificates, err := certcrypto.ParsePEMBundle(bundle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect the certificate slice to be ordered downwards the chain.
|
||||||
|
// SRV CRT -> CA. We need to pull the leaf and issuer certs out of it,
|
||||||
|
// which should always be the first two certificates.
|
||||||
|
// If there's no OCSP server listed in the leaf cert, there's nothing to do.
|
||||||
|
// And if 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, errors.New("no OCSP server specified in cert")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(certificates) == 1 {
|
||||||
|
// TODO: build fallback. If this fails, check the remaining array entries.
|
||||||
|
if len(issuedCert.IssuingCertificateURL) == 0 {
|
||||||
|
return nil, nil, errors.New("no issuing certificate URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, errC := c.core.HTTPClient.Get(issuedCert.IssuingCertificateURL[0])
|
||||||
|
if errC != nil {
|
||||||
|
return nil, nil, errC
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
issuerBytes, errC := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||||
|
if errC != nil {
|
||||||
|
return nil, nil, errC
|
||||||
|
}
|
||||||
|
|
||||||
|
issuerCert, errC := x509.ParseCertificate(issuerBytes)
|
||||||
|
if errC != nil {
|
||||||
|
return nil, nil, errC
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert it into the slice on position 0
|
||||||
|
// We want it ordered right SRV CRT -> CA
|
||||||
|
certificates = append(certificates, issuerCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
issuerCert := certificates[1]
|
||||||
|
|
||||||
|
// Finally kick off the OCSP request.
|
||||||
|
ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.core.HTTPClient.Post(issuedCert.OCSPServer[0], "application/ocsp-request", bytes.NewReader(ocspReq))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
ocspResBytes, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ocspResBytes, ocspRes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkOrderStatus(order acme.Order) (bool, error) {
|
||||||
|
switch order.Status {
|
||||||
|
case acme.StatusValid:
|
||||||
|
return true, nil
|
||||||
|
case acme.StatusInvalid:
|
||||||
|
return false, order.Error
|
||||||
|
default:
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.4
|
||||||
|
// The domain name MUST be encoded
|
||||||
|
// in the form in which it would appear in a certificate. That is, it
|
||||||
|
// MUST be encoded according to the rules in Section 7 of [RFC5280].
|
||||||
|
//
|
||||||
|
// https://tools.ietf.org/html/rfc5280#section-7
|
||||||
|
func sanitizeDomain(domains []string) []string {
|
||||||
|
var sanitizedDomains []string
|
||||||
|
for _, domain := range domains {
|
||||||
|
sanitizedDomain, err := idna.ToASCII(domain)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("skip domain %q: unable to sanitize (punnycode): %v", domain, err)
|
||||||
|
} else {
|
||||||
|
sanitizedDomains = append(sanitizedDomains, sanitizedDomain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sanitizedDomains
|
||||||
|
}
|
||||||
30
vendor/github.com/xenolf/lego/certificate/errors.go
generated
vendored
Normal file
30
vendor/github.com/xenolf/lego/certificate/errors.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package certificate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// obtainError is returned when there are specific errors available per domain.
|
||||||
|
type obtainError map[string]error
|
||||||
|
|
||||||
|
func (e obtainError) Error() string {
|
||||||
|
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
|
||||||
|
|
||||||
|
var domains []string
|
||||||
|
for domain := range e {
|
||||||
|
domains = append(domains, domain)
|
||||||
|
}
|
||||||
|
sort.Strings(domains)
|
||||||
|
|
||||||
|
for _, domain := range domains {
|
||||||
|
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
|
||||||
|
}
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type domainError struct {
|
||||||
|
Domain string
|
||||||
|
Error error
|
||||||
|
}
|
||||||
21
vendor/github.com/xenolf/lego/challenge/LICENSE
generated
vendored
Normal file
21
vendor/github.com/xenolf/lego/challenge/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-2017 Sebastian Erhart
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
44
vendor/github.com/xenolf/lego/challenge/challenges.go
generated
vendored
Normal file
44
vendor/github.com/xenolf/lego/challenge/challenges.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package challenge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type is a string that identifies a particular challenge type and version of ACME challenge.
|
||||||
|
type Type string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HTTP01 is the "http-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.3
|
||||||
|
// Note: ChallengePath returns the URL path to fulfill this challenge
|
||||||
|
HTTP01 = Type("http-01")
|
||||||
|
|
||||||
|
// DNS01 is the "dns-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.4
|
||||||
|
// Note: GetRecord returns a DNS record which will fulfill this challenge
|
||||||
|
DNS01 = Type("dns-01")
|
||||||
|
|
||||||
|
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05
|
||||||
|
TLSALPN01 = Type("tls-alpn-01")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t Type) String() string {
|
||||||
|
return string(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindChallenge(chlgType Type, authz acme.Authorization) (acme.Challenge, error) {
|
||||||
|
for _, chlg := range authz.Challenges {
|
||||||
|
if chlg.Type == string(chlgType) {
|
||||||
|
return chlg, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acme.Challenge{}, fmt.Errorf("[%s] acme: unable to find challenge %s", GetTargetedDomain(authz), chlgType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTargetedDomain(authz acme.Authorization) string {
|
||||||
|
if authz.Wildcard {
|
||||||
|
return "*." + authz.Identifier.Value
|
||||||
|
}
|
||||||
|
return authz.Identifier.Value
|
||||||
|
}
|
||||||
174
vendor/github.com/xenolf/lego/challenge/dns01/dns_challenge.go
generated
vendored
Normal file
174
vendor/github.com/xenolf/lego/challenge/dns01/dns_challenge.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package dns01
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/acme/api"
|
||||||
|
"github.com/xenolf/lego/challenge"
|
||||||
|
"github.com/xenolf/lego/log"
|
||||||
|
"github.com/xenolf/lego/platform/wait"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultPropagationTimeout default propagation timeout
|
||||||
|
DefaultPropagationTimeout = 60 * time.Second
|
||||||
|
|
||||||
|
// DefaultPollingInterval default polling interval
|
||||||
|
DefaultPollingInterval = 2 * time.Second
|
||||||
|
|
||||||
|
// DefaultTTL default TTL
|
||||||
|
DefaultTTL = 120
|
||||||
|
)
|
||||||
|
|
||||||
|
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||||
|
|
||||||
|
type ChallengeOption func(*Challenge) error
|
||||||
|
|
||||||
|
// CondOption Conditional challenge option.
|
||||||
|
func CondOption(condition bool, opt ChallengeOption) ChallengeOption {
|
||||||
|
if !condition {
|
||||||
|
// NoOp options
|
||||||
|
return func(*Challenge) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Challenge implements the dns-01 challenge
|
||||||
|
type Challenge struct {
|
||||||
|
core *api.Core
|
||||||
|
validate ValidateFunc
|
||||||
|
provider challenge.Provider
|
||||||
|
preCheck preCheck
|
||||||
|
dnsTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider, opts ...ChallengeOption) *Challenge {
|
||||||
|
chlg := &Challenge{
|
||||||
|
core: core,
|
||||||
|
validate: validate,
|
||||||
|
provider: provider,
|
||||||
|
preCheck: newPreCheck(),
|
||||||
|
dnsTimeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
err := opt(chlg)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("challenge option error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chlg
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreSolve just submits the txt record to the dns provider.
|
||||||
|
// It does not validate record propagation, or do anything at all with the acme server.
|
||||||
|
func (c *Challenge) PreSolve(authz acme.Authorization) error {
|
||||||
|
domain := challenge.GetTargetedDomain(authz)
|
||||||
|
log.Infof("[%s] acme: Preparing to solve DNS-01", domain)
|
||||||
|
|
||||||
|
chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.provider == nil {
|
||||||
|
return fmt.Errorf("[%s] acme: no DNS Provider configured", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the Key Authorization for the challenge
|
||||||
|
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[%s] acme: error presenting token: %s", domain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||||
|
domain := challenge.GetTargetedDomain(authz)
|
||||||
|
log.Infof("[%s] acme: Trying to solve DNS-01", domain)
|
||||||
|
|
||||||
|
chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the Key Authorization for the challenge
|
||||||
|
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fqdn, value := GetRecord(authz.Identifier.Value, keyAuth)
|
||||||
|
|
||||||
|
var timeout, interval time.Duration
|
||||||
|
switch provider := c.provider.(type) {
|
||||||
|
case challenge.ProviderTimeout:
|
||||||
|
timeout, interval = provider.Timeout()
|
||||||
|
default:
|
||||||
|
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("[%s] acme: Checking DNS record propagation using %+v", domain, recursiveNameservers)
|
||||||
|
|
||||||
|
err = wait.For(timeout, interval, func() (bool, error) {
|
||||||
|
stop, errP := c.preCheck.call(fqdn, value)
|
||||||
|
if !stop || errP != nil {
|
||||||
|
log.Infof("[%s] acme: Waiting for DNS record propagation.", domain)
|
||||||
|
}
|
||||||
|
return stop, errP
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
chlng.KeyAuthorization = keyAuth
|
||||||
|
return c.validate(c.core, authz.Identifier.Value, chlng)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp cleans the challenge.
|
||||||
|
func (c *Challenge) CleanUp(authz acme.Authorization) error {
|
||||||
|
chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Challenge) Sequential() (bool, time.Duration) {
|
||||||
|
if p, ok := c.provider.(sequential); ok {
|
||||||
|
return ok, p.Sequential()
|
||||||
|
}
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type sequential interface {
|
||||||
|
Sequential() time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecord returns a DNS record which will fulfill the `dns-01` challenge
|
||||||
|
func GetRecord(domain, keyAuth string) (fqdn string, value string) {
|
||||||
|
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
|
||||||
|
// base64URL encoding without padding
|
||||||
|
value = base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
|
||||||
|
fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
|
||||||
|
return
|
||||||
|
}
|
||||||
52
vendor/github.com/xenolf/lego/challenge/dns01/dns_challenge_manual.go
generated
vendored
Normal file
52
vendor/github.com/xenolf/lego/challenge/dns01/dns_challenge_manual.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package dns01
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dnsTemplate = `%s %d IN TXT "%s"`
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSProviderManual is an implementation of the ChallengeProvider interface
|
||||||
|
type DNSProviderManual struct{}
|
||||||
|
|
||||||
|
// NewDNSProviderManual returns a DNSProviderManual instance.
|
||||||
|
func NewDNSProviderManual() (*DNSProviderManual, error) {
|
||||||
|
return &DNSProviderManual{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present prints instructions for manually creating the TXT record
|
||||||
|
func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
|
||||||
|
fqdn, value := GetRecord(domain, keyAuth)
|
||||||
|
|
||||||
|
authZone, err := FindZoneByFqdn(fqdn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("lego: Please create the following TXT record in your %s zone:\n", authZone)
|
||||||
|
fmt.Printf(dnsTemplate+"\n", fqdn, DefaultTTL, value)
|
||||||
|
fmt.Printf("lego: Press 'Enter' when you are done\n")
|
||||||
|
|
||||||
|
_, err = bufio.NewReader(os.Stdin).ReadBytes('\n')
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp prints instructions for manually removing the TXT record
|
||||||
|
func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
fqdn, _ := GetRecord(domain, keyAuth)
|
||||||
|
|
||||||
|
authZone, err := FindZoneByFqdn(fqdn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("lego: You can now remove this TXT record from your %s zone:\n", authZone)
|
||||||
|
fmt.Printf(dnsTemplate+"\n", fqdn, DefaultTTL, "...")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
19
vendor/github.com/xenolf/lego/challenge/dns01/fqdn.go
generated
vendored
Normal file
19
vendor/github.com/xenolf/lego/challenge/dns01/fqdn.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package dns01
|
||||||
|
|
||||||
|
// ToFqdn converts the name into a fqdn appending a trailing dot.
|
||||||
|
func ToFqdn(name string) string {
|
||||||
|
n := len(name)
|
||||||
|
if n == 0 || name[n-1] == '.' {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return name + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnFqdn converts the fqdn into a name removing the trailing dot.
|
||||||
|
func UnFqdn(name string) string {
|
||||||
|
n := len(name)
|
||||||
|
if n != 0 && name[n-1] == '.' {
|
||||||
|
return name[:n-1]
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
232
vendor/github.com/xenolf/lego/challenge/dns01/nameserver.go
generated
vendored
Normal file
232
vendor/github.com/xenolf/lego/challenge/dns01/nameserver.go
generated
vendored
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
package dns01
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultResolvConf = "/etc/resolv.conf"
|
||||||
|
|
||||||
|
// dnsTimeout is used to override the default DNS timeout of 10 seconds.
|
||||||
|
var dnsTimeout = 10 * time.Second
|
||||||
|
|
||||||
|
var (
|
||||||
|
fqdnToZone = map[string]string{}
|
||||||
|
muFqdnToZone sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultNameservers = []string{
|
||||||
|
"google-public-dns-a.google.com:53",
|
||||||
|
"google-public-dns-b.google.com:53",
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursiveNameservers are used to pre-check DNS propagation
|
||||||
|
var recursiveNameservers = getNameservers(defaultResolvConf, defaultNameservers)
|
||||||
|
|
||||||
|
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
||||||
|
func ClearFqdnCache() {
|
||||||
|
muFqdnToZone.Lock()
|
||||||
|
fqdnToZone = map[string]string{}
|
||||||
|
muFqdnToZone.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddDNSTimeout(timeout time.Duration) ChallengeOption {
|
||||||
|
return func(_ *Challenge) error {
|
||||||
|
dnsTimeout = timeout
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddRecursiveNameservers(nameservers []string) ChallengeOption {
|
||||||
|
return func(_ *Challenge) error {
|
||||||
|
recursiveNameservers = ParseNameservers(nameservers)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNameservers attempts to get systems nameservers before falling back to the defaults
|
||||||
|
func getNameservers(path string, defaults []string) []string {
|
||||||
|
config, err := dns.ClientConfigFromFile(path)
|
||||||
|
if err != nil || len(config.Servers) == 0 {
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseNameservers(config.Servers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseNameservers(servers []string) []string {
|
||||||
|
var resolvers []string
|
||||||
|
for _, resolver := range servers {
|
||||||
|
// ensure all servers have a port number
|
||||||
|
if _, _, err := net.SplitHostPort(resolver); err != nil {
|
||||||
|
resolvers = append(resolvers, net.JoinHostPort(resolver, "53"))
|
||||||
|
} else {
|
||||||
|
resolvers = append(resolvers, resolver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolvers
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookupNameservers returns the authoritative nameservers for the given fqdn.
|
||||||
|
func lookupNameservers(fqdn string) ([]string, error) {
|
||||||
|
var authoritativeNss []string
|
||||||
|
|
||||||
|
zone, err := FindZoneByFqdn(fqdn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not determine the zone: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range r.Answer {
|
||||||
|
if ns, ok := rr.(*dns.NS); ok {
|
||||||
|
authoritativeNss = append(authoritativeNss, strings.ToLower(ns.Ns))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(authoritativeNss) > 0 {
|
||||||
|
return authoritativeNss, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("could not determine authoritative nameservers")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindZoneByFqdn determines the zone apex for the given fqdn
|
||||||
|
// by recursing up the domain labels until the nameserver returns a SOA record in the answer section.
|
||||||
|
func FindZoneByFqdn(fqdn string) (string, error) {
|
||||||
|
return FindZoneByFqdnCustom(fqdn, recursiveNameservers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindZoneByFqdnCustom determines the zone apex for the given fqdn
|
||||||
|
// by recursing up the domain labels until the nameserver returns a SOA record in the answer section.
|
||||||
|
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||||
|
muFqdnToZone.Lock()
|
||||||
|
defer muFqdnToZone.Unlock()
|
||||||
|
|
||||||
|
// Do we have it cached?
|
||||||
|
if zone, ok := fqdnToZone[fqdn]; ok {
|
||||||
|
return zone, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var in *dns.Msg
|
||||||
|
|
||||||
|
labelIndexes := dns.Split(fqdn)
|
||||||
|
for _, index := range labelIndexes {
|
||||||
|
domain := fqdn[index:]
|
||||||
|
|
||||||
|
in, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if in == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch in.Rcode {
|
||||||
|
case dns.RcodeSuccess:
|
||||||
|
// Check if we got a SOA RR in the answer section
|
||||||
|
|
||||||
|
if len(in.Answer) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// CNAME records cannot/should not exist at the root of a zone.
|
||||||
|
// So we skip a domain when a CNAME is found.
|
||||||
|
if dnsMsgContainsCNAME(in) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ans := range in.Answer {
|
||||||
|
if soa, ok := ans.(*dns.SOA); ok {
|
||||||
|
zone := soa.Hdr.Name
|
||||||
|
fqdnToZone[fqdn] = zone
|
||||||
|
return zone, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case dns.RcodeNameError:
|
||||||
|
// NXDOMAIN
|
||||||
|
default:
|
||||||
|
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
||||||
|
return "", fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// dnsMsgContainsCNAME checks for a CNAME answer in msg
|
||||||
|
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
|
||||||
|
for _, ans := range msg.Answer {
|
||||||
|
if _, ok := ans.(*dns.CNAME); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
|
||||||
|
m := createDNSMsg(fqdn, rtype, recursive)
|
||||||
|
|
||||||
|
var in *dns.Msg
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for _, ns := range nameservers {
|
||||||
|
in, err = sendDNSQuery(m, ns)
|
||||||
|
if err == nil && len(in.Answer) > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return in, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetQuestion(fqdn, rtype)
|
||||||
|
m.SetEdns0(4096, false)
|
||||||
|
|
||||||
|
if !recursive {
|
||||||
|
m.RecursionDesired = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) {
|
||||||
|
udp := &dns.Client{Net: "udp", Timeout: dnsTimeout}
|
||||||
|
in, _, err := udp.Exchange(m, ns)
|
||||||
|
|
||||||
|
if in != nil && in.Truncated {
|
||||||
|
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
||||||
|
// If the TCP request succeeds, the err will reset to nil
|
||||||
|
in, _, err = tcp.Exchange(m, ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
return in, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDNSError(msg *dns.Msg, err error) string {
|
||||||
|
var parts []string
|
||||||
|
|
||||||
|
if msg != nil {
|
||||||
|
parts = append(parts, dns.RcodeToString[msg.Rcode])
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
parts = append(parts, fmt.Sprintf("%v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) > 0 {
|
||||||
|
return ": " + strings.Join(parts, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
110
vendor/github.com/xenolf/lego/challenge/dns01/precheck.go
generated
vendored
Normal file
110
vendor/github.com/xenolf/lego/challenge/dns01/precheck.go
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package dns01
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PreCheckFunc checks DNS propagation before notifying ACME that the DNS challenge is ready.
|
||||||
|
type PreCheckFunc func(fqdn, value string) (bool, error)
|
||||||
|
|
||||||
|
func AddPreCheck(preCheck PreCheckFunc) ChallengeOption {
|
||||||
|
// Prevent race condition
|
||||||
|
check := preCheck
|
||||||
|
return func(chlg *Challenge) error {
|
||||||
|
chlg.preCheck.checkFunc = check
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisableCompletePropagationRequirement() ChallengeOption {
|
||||||
|
return func(chlg *Challenge) error {
|
||||||
|
chlg.preCheck.requireCompletePropagation = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type preCheck struct {
|
||||||
|
// checks DNS propagation before notifying ACME that the DNS challenge is ready.
|
||||||
|
checkFunc PreCheckFunc
|
||||||
|
// require the TXT record to be propagated to all authoritative name servers
|
||||||
|
requireCompletePropagation bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPreCheck() preCheck {
|
||||||
|
return preCheck{
|
||||||
|
requireCompletePropagation: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p preCheck) call(fqdn, value string) (bool, error) {
|
||||||
|
if p.checkFunc == nil {
|
||||||
|
return p.checkDNSPropagation(fqdn, value)
|
||||||
|
}
|
||||||
|
return p.checkFunc(fqdn, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
|
||||||
|
func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) {
|
||||||
|
// Initial attempt to resolve at the recursive NS
|
||||||
|
r, err := dnsQuery(fqdn, dns.TypeTXT, recursiveNameservers, true)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.requireCompletePropagation {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Rcode == dns.RcodeSuccess {
|
||||||
|
// If we see a CNAME here then use the alias
|
||||||
|
for _, rr := range r.Answer {
|
||||||
|
if cn, ok := rr.(*dns.CNAME); ok {
|
||||||
|
if cn.Hdr.Name == fqdn {
|
||||||
|
fqdn = cn.Target
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authoritativeNss, err := lookupNameservers(fqdn)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkAuthoritativeNss(fqdn, value, authoritativeNss)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Rcode != dns.RcodeSuccess {
|
||||||
|
return false, fmt.Errorf("NS %s returned %s for %s", ns, dns.RcodeToString[r.Rcode], fqdn)
|
||||||
|
}
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
for _, rr := range r.Answer {
|
||||||
|
if txt, ok := rr.(*dns.TXT); ok {
|
||||||
|
if strings.Join(txt.Txt, "") == value {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return false, fmt.Errorf("NS %s did not return the expected TXT record [fqdn: %s]", ns, fqdn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
65
vendor/github.com/xenolf/lego/challenge/http01/http_challenge.go
generated
vendored
Normal file
65
vendor/github.com/xenolf/lego/challenge/http01/http_challenge.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package http01
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/acme/api"
|
||||||
|
"github.com/xenolf/lego/challenge"
|
||||||
|
"github.com/xenolf/lego/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||||
|
|
||||||
|
// ChallengePath returns the URL path for the `http-01` challenge
|
||||||
|
func ChallengePath(token string) string {
|
||||||
|
return "/.well-known/acme-challenge/" + token
|
||||||
|
}
|
||||||
|
|
||||||
|
type Challenge struct {
|
||||||
|
core *api.Core
|
||||||
|
validate ValidateFunc
|
||||||
|
provider challenge.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
|
||||||
|
return &Challenge{
|
||||||
|
core: core,
|
||||||
|
validate: validate,
|
||||||
|
provider: provider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Challenge) SetProvider(provider challenge.Provider) {
|
||||||
|
c.provider = provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||||
|
domain := challenge.GetTargetedDomain(authz)
|
||||||
|
log.Infof("[%s] acme: Trying to solve HTTP-01", domain)
|
||||||
|
|
||||||
|
chlng, err := challenge.FindChallenge(challenge.HTTP01, authz)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the Key Authorization for the challenge
|
||||||
|
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[%s] acme: error presenting token: %v", domain, err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("[%s] acme: error cleaning up: %v", domain, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
chlng.KeyAuthorization = keyAuth
|
||||||
|
return c.validate(c.core, authz.Identifier.Value, chlng)
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package acme
|
package http01
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -9,31 +9,31 @@ import (
|
|||||||
"github.com/xenolf/lego/log"
|
"github.com/xenolf/lego/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPProviderServer implements ChallengeProvider for `http-01` challenge
|
// ProviderServer implements ChallengeProvider for `http-01` challenge
|
||||||
// It may be instantiated without using the NewHTTPProviderServer function if
|
// It may be instantiated without using the NewProviderServer function if
|
||||||
// you want only to use the default values.
|
// you want only to use the default values.
|
||||||
type HTTPProviderServer struct {
|
type ProviderServer struct {
|
||||||
iface string
|
iface string
|
||||||
port string
|
port string
|
||||||
done chan bool
|
done chan bool
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHTTPProviderServer creates a new HTTPProviderServer on the selected interface and port.
|
// NewProviderServer creates a new ProviderServer on the selected interface and port.
|
||||||
// Setting iface and / or port to an empty string will make the server fall back to
|
// Setting iface and / or port to an empty string will make the server fall back to
|
||||||
// the "any" interface and port 80 respectively.
|
// the "any" interface and port 80 respectively.
|
||||||
func NewHTTPProviderServer(iface, port string) *HTTPProviderServer {
|
func NewProviderServer(iface, port string) *ProviderServer {
|
||||||
return &HTTPProviderServer{iface: iface, port: port}
|
return &ProviderServer{iface: iface, port: port}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present starts a web server and makes the token available at `HTTP01ChallengePath(token)` for web requests.
|
// Present starts a web server and makes the token available at `ChallengePath(token)` for web requests.
|
||||||
func (s *HTTPProviderServer) Present(domain, token, keyAuth string) error {
|
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||||
if s.port == "" {
|
if s.port == "" {
|
||||||
s.port = "80"
|
s.port = "80"
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
s.listener, err = net.Listen("tcp", net.JoinHostPort(s.iface, s.port))
|
s.listener, err = net.Listen("tcp", s.GetAddress())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not start HTTP server for challenge -> %v", err)
|
return fmt.Errorf("could not start HTTP server for challenge -> %v", err)
|
||||||
}
|
}
|
||||||
@ -43,8 +43,12 @@ func (s *HTTPProviderServer) Present(domain, token, keyAuth string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp closes the HTTP server and removes the token from `HTTP01ChallengePath(token)`
|
func (s *ProviderServer) GetAddress() string {
|
||||||
func (s *HTTPProviderServer) CleanUp(domain, token, keyAuth string) error {
|
return net.JoinHostPort(s.iface, s.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp closes the HTTP server and removes the token from `ChallengePath(token)`
|
||||||
|
func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||||
if s.listener == nil {
|
if s.listener == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -53,8 +57,8 @@ func (s *HTTPProviderServer) CleanUp(domain, token, keyAuth string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPProviderServer) serve(domain, token, keyAuth string) {
|
func (s *ProviderServer) serve(domain, token, keyAuth string) {
|
||||||
path := HTTP01ChallengePath(token)
|
path := ChallengePath(token)
|
||||||
|
|
||||||
// The handler validates the HOST header and request type.
|
// The handler validates the HOST header and request type.
|
||||||
// For validation it then writes the token the server returned with the challenge
|
// For validation it then writes the token the server returned with the challenge
|
||||||
@ -80,12 +84,12 @@ func (s *HTTPProviderServer) serve(domain, token, keyAuth string) {
|
|||||||
|
|
||||||
httpServer := &http.Server{Handler: mux}
|
httpServer := &http.Server{Handler: mux}
|
||||||
|
|
||||||
// Once httpServer is shut down we don't want any lingering
|
// Once httpServer is shut down
|
||||||
// connections, so disable KeepAlives.
|
// we don't want any lingering connections, so disable KeepAlives.
|
||||||
httpServer.SetKeepAlivesEnabled(false)
|
httpServer.SetKeepAlivesEnabled(false)
|
||||||
|
|
||||||
err := httpServer.Serve(s.listener)
|
err := httpServer.Serve(s.listener)
|
||||||
if err != nil {
|
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
s.done <- true
|
s.done <- true
|
||||||
@ -1,28 +1,28 @@
|
|||||||
package acme
|
package challenge
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// ChallengeProvider enables implementing a custom challenge
|
// Provider enables implementing a custom challenge
|
||||||
// provider. Present presents the solution to a challenge available to
|
// provider. Present presents the solution to a challenge available to
|
||||||
// be solved. CleanUp will be called by the challenge if Present ends
|
// be solved. CleanUp will be called by the challenge if Present ends
|
||||||
// in a non-error state.
|
// in a non-error state.
|
||||||
type ChallengeProvider interface {
|
type Provider interface {
|
||||||
Present(domain, token, keyAuth string) error
|
Present(domain, token, keyAuth string) error
|
||||||
CleanUp(domain, token, keyAuth string) error
|
CleanUp(domain, token, keyAuth string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChallengeProviderTimeout allows for implementing a
|
// ProviderTimeout allows for implementing a
|
||||||
// ChallengeProvider where an unusually long timeout is required when
|
// Provider where an unusually long timeout is required when
|
||||||
// waiting for an ACME challenge to be satisfied, such as when
|
// waiting for an ACME challenge to be satisfied, such as when
|
||||||
// checking for DNS record progagation. If an implementor of a
|
// checking for DNS record propagation. If an implementor of a
|
||||||
// ChallengeProvider provides a Timeout method, then the return values
|
// Provider provides a Timeout method, then the return values
|
||||||
// of the Timeout method will be used when appropriate by the acme
|
// of the Timeout method will be used when appropriate by the acme
|
||||||
// package. The interval value is the time between checks.
|
// package. The interval value is the time between checks.
|
||||||
//
|
//
|
||||||
// The default values used for timeout and interval are 60 seconds and
|
// The default values used for timeout and interval are 60 seconds and
|
||||||
// 2 seconds respectively. These are used when no Timeout method is
|
// 2 seconds respectively. These are used when no Timeout method is
|
||||||
// defined for the ChallengeProvider.
|
// defined for the Provider.
|
||||||
type ChallengeProviderTimeout interface {
|
type ProviderTimeout interface {
|
||||||
ChallengeProvider
|
Provider
|
||||||
Timeout() (timeout, interval time.Duration)
|
Timeout() (timeout, interval time.Duration)
|
||||||
}
|
}
|
||||||
25
vendor/github.com/xenolf/lego/challenge/resolver/errors.go
generated
vendored
Normal file
25
vendor/github.com/xenolf/lego/challenge/resolver/errors.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// obtainError is returned when there are specific errors available per domain.
|
||||||
|
type obtainError map[string]error
|
||||||
|
|
||||||
|
func (e obtainError) Error() string {
|
||||||
|
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
|
||||||
|
|
||||||
|
var domains []string
|
||||||
|
for domain := range e {
|
||||||
|
domains = append(domains, domain)
|
||||||
|
}
|
||||||
|
sort.Strings(domains)
|
||||||
|
|
||||||
|
for _, domain := range domains {
|
||||||
|
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
|
||||||
|
}
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
172
vendor/github.com/xenolf/lego/challenge/resolver/prober.go
generated
vendored
Normal file
172
vendor/github.com/xenolf/lego/challenge/resolver/prober.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/challenge"
|
||||||
|
"github.com/xenolf/lego/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interface for all challenge solvers to implement.
|
||||||
|
type solver interface {
|
||||||
|
Solve(authorization acme.Authorization) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface for challenges like dns, where we can set a record in advance for ALL challenges.
|
||||||
|
// This saves quite a bit of time vs creating the records and solving them serially.
|
||||||
|
type preSolver interface {
|
||||||
|
PreSolve(authorization acme.Authorization) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface for challenges like dns, where we can solve all the challenges before to delete them.
|
||||||
|
type cleanup interface {
|
||||||
|
CleanUp(authorization acme.Authorization) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type sequential interface {
|
||||||
|
Sequential() (bool, time.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// an authz with the solver we have chosen and the index of the challenge associated with it
|
||||||
|
type selectedAuthSolver struct {
|
||||||
|
authz acme.Authorization
|
||||||
|
solver solver
|
||||||
|
}
|
||||||
|
|
||||||
|
type Prober struct {
|
||||||
|
solverManager *SolverManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProber(solverManager *SolverManager) *Prober {
|
||||||
|
return &Prober{
|
||||||
|
solverManager: solverManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve Looks through the challenge combinations to find a solvable match.
|
||||||
|
// Then solves the challenges in series and returns.
|
||||||
|
func (p *Prober) Solve(authorizations []acme.Authorization) error {
|
||||||
|
failures := make(obtainError)
|
||||||
|
|
||||||
|
var authSolvers []*selectedAuthSolver
|
||||||
|
var authSolversSequential []*selectedAuthSolver
|
||||||
|
|
||||||
|
// Loop through the resources, basically through the domains.
|
||||||
|
// First pass just selects a solver for each authz.
|
||||||
|
for _, authz := range authorizations {
|
||||||
|
domain := challenge.GetTargetedDomain(authz)
|
||||||
|
if authz.Status == acme.StatusValid {
|
||||||
|
// Boulder might recycle recent validated authz (see issue #267)
|
||||||
|
log.Infof("[%s] acme: authorization already valid; skipping challenge", domain)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if solvr := p.solverManager.chooseSolver(authz); solvr != nil {
|
||||||
|
authSolver := &selectedAuthSolver{authz: authz, solver: solvr}
|
||||||
|
|
||||||
|
switch s := solvr.(type) {
|
||||||
|
case sequential:
|
||||||
|
if ok, _ := s.Sequential(); ok {
|
||||||
|
authSolversSequential = append(authSolversSequential, authSolver)
|
||||||
|
} else {
|
||||||
|
authSolvers = append(authSolvers, authSolver)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
authSolvers = append(authSolvers, authSolver)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
failures[domain] = fmt.Errorf("[%s] acme: could not determine solvers", domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parallelSolve(authSolvers, failures)
|
||||||
|
|
||||||
|
sequentialSolve(authSolversSequential, failures)
|
||||||
|
|
||||||
|
// Be careful not to return an empty failures map,
|
||||||
|
// for even an empty obtainError is a non-nil error value
|
||||||
|
if len(failures) > 0 {
|
||||||
|
return failures
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||||
|
for i, authSolver := range authSolvers {
|
||||||
|
// Submit the challenge
|
||||||
|
domain := challenge.GetTargetedDomain(authSolver.authz)
|
||||||
|
|
||||||
|
if solvr, ok := authSolver.solver.(preSolver); ok {
|
||||||
|
err := solvr.PreSolve(authSolver.authz)
|
||||||
|
if err != nil {
|
||||||
|
failures[domain] = err
|
||||||
|
cleanUp(authSolver.solver, authSolver.authz)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve challenge
|
||||||
|
err := authSolver.solver.Solve(authSolver.authz)
|
||||||
|
if err != nil {
|
||||||
|
failures[authSolver.authz.Identifier.Value] = err
|
||||||
|
cleanUp(authSolver.solver, authSolver.authz)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean challenge
|
||||||
|
cleanUp(authSolver.solver, authSolver.authz)
|
||||||
|
|
||||||
|
if len(authSolvers)-1 > i {
|
||||||
|
solvr := authSolver.solver.(sequential)
|
||||||
|
_, interval := solvr.Sequential()
|
||||||
|
log.Infof("sequence: wait for %s", interval)
|
||||||
|
time.Sleep(interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||||
|
// For all valid preSolvers, first submit the challenges so they have max time to propagate
|
||||||
|
for _, authSolver := range authSolvers {
|
||||||
|
authz := authSolver.authz
|
||||||
|
if solvr, ok := authSolver.solver.(preSolver); ok {
|
||||||
|
err := solvr.PreSolve(authz)
|
||||||
|
if err != nil {
|
||||||
|
failures[challenge.GetTargetedDomain(authz)] = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// Clean all created TXT records
|
||||||
|
for _, authSolver := range authSolvers {
|
||||||
|
cleanUp(authSolver.solver, authSolver.authz)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Finally solve all challenges for real
|
||||||
|
for _, authSolver := range authSolvers {
|
||||||
|
authz := authSolver.authz
|
||||||
|
if failures[authz.Identifier.Value] != nil {
|
||||||
|
// already failed in previous loop
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := authSolver.solver.Solve(authz)
|
||||||
|
if err != nil {
|
||||||
|
failures[authz.Identifier.Value] = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanUp(solvr solver, authz acme.Authorization) {
|
||||||
|
if solvr, ok := solvr.(cleanup); ok {
|
||||||
|
domain := challenge.GetTargetedDomain(authz)
|
||||||
|
err := solvr.CleanUp(authz)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("[%s] acme: error cleaning up: %v ", domain, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
201
vendor/github.com/xenolf/lego/challenge/resolver/solver_manager.go
generated
vendored
Normal file
201
vendor/github.com/xenolf/lego/challenge/resolver/solver_manager.go
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/acme/api"
|
||||||
|
"github.com/xenolf/lego/challenge"
|
||||||
|
"github.com/xenolf/lego/challenge/dns01"
|
||||||
|
"github.com/xenolf/lego/challenge/http01"
|
||||||
|
"github.com/xenolf/lego/challenge/tlsalpn01"
|
||||||
|
"github.com/xenolf/lego/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type byType []acme.Challenge
|
||||||
|
|
||||||
|
func (a byType) Len() int { return len(a) }
|
||||||
|
func (a byType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a byType) Less(i, j int) bool { return a[i].Type < a[j].Type }
|
||||||
|
|
||||||
|
type SolverManager struct {
|
||||||
|
core *api.Core
|
||||||
|
solvers map[challenge.Type]solver
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSolversManager(core *api.Core) *SolverManager {
|
||||||
|
solvers := map[challenge.Type]solver{
|
||||||
|
challenge.HTTP01: http01.NewChallenge(core, validate, &http01.ProviderServer{}),
|
||||||
|
challenge.TLSALPN01: tlsalpn01.NewChallenge(core, validate, &tlsalpn01.ProviderServer{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SolverManager{
|
||||||
|
solvers: solvers,
|
||||||
|
core: core,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHTTP01Address specifies a custom interface:port to be used for HTTP based challenges.
|
||||||
|
// If this option is not used, the default port 80 and all interfaces will be used.
|
||||||
|
// To only specify a port and no interface use the ":port" notation.
|
||||||
|
//
|
||||||
|
// NOTE: This REPLACES any custom HTTP provider previously set by calling
|
||||||
|
// c.SetProvider with the default HTTP challenge provider.
|
||||||
|
func (c *SolverManager) SetHTTP01Address(iface string) error {
|
||||||
|
host, port, err := net.SplitHostPort(iface)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if chlng, ok := c.solvers[challenge.HTTP01]; ok {
|
||||||
|
chlng.(*http01.Challenge).SetProvider(http01.NewProviderServer(host, port))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTLSALPN01Address specifies a custom interface:port to be used for TLS based challenges.
|
||||||
|
// If this option is not used, the default port 443 and all interfaces will be used.
|
||||||
|
// To only specify a port and no interface use the ":port" notation.
|
||||||
|
//
|
||||||
|
// NOTE: This REPLACES any custom TLS-ALPN provider previously set by calling
|
||||||
|
// c.SetProvider with the default TLS-ALPN challenge provider.
|
||||||
|
func (c *SolverManager) SetTLSALPN01Address(iface string) error {
|
||||||
|
host, port, err := net.SplitHostPort(iface)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if chlng, ok := c.solvers[challenge.TLSALPN01]; ok {
|
||||||
|
chlng.(*tlsalpn01.Challenge).SetProvider(tlsalpn01.NewProviderServer(host, port))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHTTP01Provider specifies a custom provider p that can solve the given HTTP-01 challenge.
|
||||||
|
func (c *SolverManager) SetHTTP01Provider(p challenge.Provider) error {
|
||||||
|
c.solvers[challenge.HTTP01] = http01.NewChallenge(c.core, validate, p)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTLSALPN01Provider specifies a custom provider p that can solve the given TLS-ALPN-01 challenge.
|
||||||
|
func (c *SolverManager) SetTLSALPN01Provider(p challenge.Provider) error {
|
||||||
|
c.solvers[challenge.TLSALPN01] = tlsalpn01.NewChallenge(c.core, validate, p)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDNS01Provider specifies a custom provider p that can solve the given DNS-01 challenge.
|
||||||
|
func (c *SolverManager) SetDNS01Provider(p challenge.Provider, opts ...dns01.ChallengeOption) error {
|
||||||
|
c.solvers[challenge.DNS01] = dns01.NewChallenge(c.core, validate, p, opts...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude explicitly removes challenges from the pool for solving.
|
||||||
|
func (c *SolverManager) Exclude(challenges []challenge.Type) {
|
||||||
|
// Loop through all challenges and delete the requested one if found.
|
||||||
|
for _, chlg := range challenges {
|
||||||
|
delete(c.solvers, chlg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks all challenges from the server in order and returns the first matching solver.
|
||||||
|
func (c *SolverManager) chooseSolver(authz acme.Authorization) solver {
|
||||||
|
// Allow to have a deterministic challenge order
|
||||||
|
sort.Sort(sort.Reverse(byType(authz.Challenges)))
|
||||||
|
|
||||||
|
domain := challenge.GetTargetedDomain(authz)
|
||||||
|
for _, chlg := range authz.Challenges {
|
||||||
|
if solvr, ok := c.solvers[challenge.Type(chlg.Type)]; ok {
|
||||||
|
log.Infof("[%s] acme: use %s solver", domain, chlg.Type)
|
||||||
|
return solvr
|
||||||
|
}
|
||||||
|
log.Infof("[%s] acme: Could not find solver for: %s", domain, chlg.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validate(core *api.Core, domain string, chlg acme.Challenge) error {
|
||||||
|
chlng, err := core.Challenges.New(chlg.URL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to initiate challenge: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
valid, err := checkChallengeStatus(chlng)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid {
|
||||||
|
log.Infof("[%s] The server validated our request", domain)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// After the path is sent, the ACME server will access our server.
|
||||||
|
// Repeatedly check the server for an updated status on our request.
|
||||||
|
for {
|
||||||
|
authz, err := core.Authorizations.Get(chlng.AuthorizationURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
valid, err := checkAuthorizationStatus(authz)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid {
|
||||||
|
log.Infof("[%s] The server validated our request", domain)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ra, err := strconv.Atoi(chlng.RetryAfter)
|
||||||
|
if err != nil {
|
||||||
|
// The ACME server MUST return a Retry-After.
|
||||||
|
// If it doesn't, we'll just poll hard.
|
||||||
|
// Boulder does not implement the ability to retry challenges or the Retry-After header.
|
||||||
|
// https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md#section-82
|
||||||
|
ra = 5
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(ra) * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) {
|
||||||
|
switch chlng.Status {
|
||||||
|
case acme.StatusValid:
|
||||||
|
return true, nil
|
||||||
|
case acme.StatusPending, acme.StatusProcessing:
|
||||||
|
return false, nil
|
||||||
|
case acme.StatusInvalid:
|
||||||
|
return false, chlng.Error
|
||||||
|
default:
|
||||||
|
return false, errors.New("the server returned an unexpected state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAuthorizationStatus(authz acme.Authorization) (bool, error) {
|
||||||
|
switch authz.Status {
|
||||||
|
case acme.StatusValid:
|
||||||
|
return true, nil
|
||||||
|
case acme.StatusPending, acme.StatusProcessing:
|
||||||
|
return false, nil
|
||||||
|
case acme.StatusDeactivated, acme.StatusExpired, acme.StatusRevoked:
|
||||||
|
return false, fmt.Errorf("the authorization state %s", authz.Status)
|
||||||
|
case acme.StatusInvalid:
|
||||||
|
for _, chlg := range authz.Challenges {
|
||||||
|
if chlg.Status == acme.StatusInvalid && chlg.Error != nil {
|
||||||
|
return false, chlg.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("the authorization state %s", authz.Status)
|
||||||
|
default:
|
||||||
|
return false, errors.New("the server returned an unexpected state")
|
||||||
|
}
|
||||||
|
}
|
||||||
129
vendor/github.com/xenolf/lego/challenge/tlsalpn01/tls_alpn_challenge.go
generated
vendored
Normal file
129
vendor/github.com/xenolf/lego/challenge/tlsalpn01/tls_alpn_challenge.go
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package tlsalpn01
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/acme/api"
|
||||||
|
"github.com/xenolf/lego/certcrypto"
|
||||||
|
"github.com/xenolf/lego/challenge"
|
||||||
|
"github.com/xenolf/lego/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
|
||||||
|
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1
|
||||||
|
var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
||||||
|
|
||||||
|
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||||
|
|
||||||
|
type Challenge struct {
|
||||||
|
core *api.Core
|
||||||
|
validate ValidateFunc
|
||||||
|
provider challenge.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
|
||||||
|
return &Challenge{
|
||||||
|
core: core,
|
||||||
|
validate: validate,
|
||||||
|
provider: provider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Challenge) SetProvider(provider challenge.Provider) {
|
||||||
|
c.provider = provider
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve manages the provider to validate and solve the challenge.
|
||||||
|
func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||||
|
domain := authz.Identifier.Value
|
||||||
|
log.Infof("[%s] acme: Trying to solve TLS-ALPN-01", challenge.GetTargetedDomain(authz))
|
||||||
|
|
||||||
|
chlng, err := challenge.FindChallenge(challenge.TLSALPN01, authz)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the Key Authorization for the challenge
|
||||||
|
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.provider.Present(domain, chlng.Token, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[%s] acme: error presenting token: %v", challenge.GetTargetedDomain(authz), err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := c.provider.CleanUp(domain, chlng.Token, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("[%s] acme: error cleaning up: %v", challenge.GetTargetedDomain(authz), err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
chlng.KeyAuthorization = keyAuth
|
||||||
|
return c.validate(c.core, domain, chlng)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChallengeBlocks returns PEM blocks (certPEMBlock, keyPEMBlock) with the acmeValidation-v1 extension
|
||||||
|
// and domain name for the `tls-alpn-01` challenge.
|
||||||
|
func ChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
|
||||||
|
// Compute the SHA-256 digest of the key authorization.
|
||||||
|
zBytes := sha256.Sum256([]byte(keyAuth))
|
||||||
|
|
||||||
|
value, err := asn1.Marshal(zBytes[:sha256.Size])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the keyAuth digest as the acmeValidation-v1 extension
|
||||||
|
// (marked as critical such that it won't be used by non-ACME software).
|
||||||
|
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-3
|
||||||
|
extensions := []pkix.Extension{
|
||||||
|
{
|
||||||
|
Id: idPeAcmeIdentifierV1,
|
||||||
|
Critical: true,
|
||||||
|
Value: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new RSA key for the certificates.
|
||||||
|
tempPrivateKey, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rsaPrivateKey := tempPrivateKey.(*rsa.PrivateKey)
|
||||||
|
|
||||||
|
// Generate the PEM certificate using the provided private key, domain, and extra extensions.
|
||||||
|
tempCertPEM, err := certcrypto.GeneratePemCert(rsaPrivateKey, domain, extensions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the private key into a PEM format. We'll need to use it to generate the x509 keypair.
|
||||||
|
rsaPrivatePEM := certcrypto.PEMEncode(rsaPrivateKey)
|
||||||
|
|
||||||
|
return tempCertPEM, rsaPrivatePEM, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChallengeCert returns a certificate with the acmeValidation-v1 extension
|
||||||
|
// and domain name for the `tls-alpn-01` challenge.
|
||||||
|
func ChallengeCert(domain, keyAuth string) (*tls.Certificate, error) {
|
||||||
|
tempCertPEM, rsaPrivatePEM, err := ChallengeBlocks(domain, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := tls.X509KeyPair(tempCertPEM, rsaPrivatePEM)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cert, nil
|
||||||
|
}
|
||||||
@ -1,49 +1,54 @@
|
|||||||
package acme
|
package tlsalpn01
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ACMETLS1Protocol is the ALPN Protocol ID for the ACME-TLS/1 Protocol.
|
// ACMETLS1Protocol is the ALPN Protocol ID for the ACME-TLS/1 Protocol.
|
||||||
ACMETLS1Protocol = "acme-tls/1"
|
ACMETLS1Protocol = "acme-tls/1"
|
||||||
|
|
||||||
// defaultTLSPort is the port that the TLSALPNProviderServer will default to
|
// defaultTLSPort is the port that the ProviderServer will default to
|
||||||
// when no other port is provided.
|
// when no other port is provided.
|
||||||
defaultTLSPort = "443"
|
defaultTLSPort = "443"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TLSALPNProviderServer implements ChallengeProvider for `TLS-ALPN-01`
|
// ProviderServer implements ChallengeProvider for `TLS-ALPN-01` challenge.
|
||||||
// challenge. It may be instantiated without using the NewTLSALPNProviderServer
|
// It may be instantiated without using the NewProviderServer
|
||||||
// if you want only to use the default values.
|
// if you want only to use the default values.
|
||||||
type TLSALPNProviderServer struct {
|
type ProviderServer struct {
|
||||||
iface string
|
iface string
|
||||||
port string
|
port string
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTLSALPNProviderServer creates a new TLSALPNProviderServer on the selected
|
// NewProviderServer creates a new ProviderServer on the selected interface and port.
|
||||||
// interface and port. Setting iface and / or port to an empty string will make
|
// Setting iface and / or port to an empty string will make the server fall back to
|
||||||
// the server fall back to the "any" interface and port 443 respectively.
|
// the "any" interface and port 443 respectively.
|
||||||
func NewTLSALPNProviderServer(iface, port string) *TLSALPNProviderServer {
|
func NewProviderServer(iface, port string) *ProviderServer {
|
||||||
return &TLSALPNProviderServer{iface: iface, port: port}
|
return &ProviderServer{iface: iface, port: port}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProviderServer) GetAddress() string {
|
||||||
|
return net.JoinHostPort(s.iface, s.port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present generates a certificate with a SHA-256 digest of the keyAuth provided
|
// Present generates a certificate with a SHA-256 digest of the keyAuth provided
|
||||||
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN
|
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN spec.
|
||||||
// spec.
|
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||||
func (t *TLSALPNProviderServer) Present(domain, token, keyAuth string) error {
|
if s.port == "" {
|
||||||
if t.port == "" {
|
|
||||||
// Fallback to port 443 if the port was not provided.
|
// Fallback to port 443 if the port was not provided.
|
||||||
t.port = defaultTLSPort
|
s.port = defaultTLSPort
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the challenge certificate using the provided keyAuth and domain.
|
// Generate the challenge certificate using the provided keyAuth and domain.
|
||||||
cert, err := TLSALPNChallengeCert(domain, keyAuth)
|
cert, err := ChallengeCert(domain, keyAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -59,15 +64,15 @@ func (t *TLSALPNProviderServer) Present(domain, token, keyAuth string) error {
|
|||||||
tlsConf.NextProtos = []string{ACMETLS1Protocol}
|
tlsConf.NextProtos = []string{ACMETLS1Protocol}
|
||||||
|
|
||||||
// Create the listener with the created tls.Config.
|
// Create the listener with the created tls.Config.
|
||||||
t.listener, err = tls.Listen("tcp", net.JoinHostPort(t.iface, t.port), tlsConf)
|
s.listener, err = tls.Listen("tcp", s.GetAddress(), tlsConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not start HTTPS server for challenge -> %v", err)
|
return fmt.Errorf("could not start HTTPS server for challenge -> %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shut the server down when we're finished.
|
// Shut the server down when we're finished.
|
||||||
go func() {
|
go func() {
|
||||||
err := http.Serve(t.listener, nil)
|
err := http.Serve(s.listener, nil)
|
||||||
if err != nil {
|
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -76,13 +81,13 @@ func (t *TLSALPNProviderServer) Present(domain, token, keyAuth string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp closes the HTTPS server.
|
// CleanUp closes the HTTPS server.
|
||||||
func (t *TLSALPNProviderServer) CleanUp(domain, token, keyAuth string) error {
|
func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||||
if t.listener == nil {
|
if s.listener == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server was created, close it.
|
// Server was created, close it.
|
||||||
if err := t.listener.Close(); err != nil && err != http.ErrServerClosed {
|
if err := s.listener.Close(); err != nil && err != http.ErrServerClosed {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
21
vendor/github.com/xenolf/lego/lego/LICENSE
generated
vendored
Normal file
21
vendor/github.com/xenolf/lego/lego/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-2017 Sebastian Erhart
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
73
vendor/github.com/xenolf/lego/lego/client.go
generated
vendored
Normal file
73
vendor/github.com/xenolf/lego/lego/client.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package lego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme/api"
|
||||||
|
"github.com/xenolf/lego/certificate"
|
||||||
|
"github.com/xenolf/lego/challenge/resolver"
|
||||||
|
"github.com/xenolf/lego/registration"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is the user-friendly way to ACME
|
||||||
|
type Client struct {
|
||||||
|
Certificate *certificate.Certifier
|
||||||
|
Challenge *resolver.SolverManager
|
||||||
|
Registration *registration.Registrar
|
||||||
|
core *api.Core
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new ACME client on behalf of the user.
|
||||||
|
// The client will depend on the ACME directory located at CADirURL for the rest of its actions.
|
||||||
|
// A private key of type keyType (see KeyType constants) will be generated when requesting a new certificate if one isn't provided.
|
||||||
|
func NewClient(config *Config) (*Client, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("a configuration must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := url.Parse(config.CADirURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.HTTPClient == nil {
|
||||||
|
return nil, errors.New("the HTTP client cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey := config.User.GetPrivateKey()
|
||||||
|
if privateKey == nil {
|
||||||
|
return nil, errors.New("private key was nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
var kid string
|
||||||
|
if reg := config.User.GetRegistration(); reg != nil {
|
||||||
|
kid = reg.URI
|
||||||
|
}
|
||||||
|
|
||||||
|
core, err := api.New(config.HTTPClient, config.UserAgent, config.CADirURL, kid, privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
solversManager := resolver.NewSolversManager(core)
|
||||||
|
|
||||||
|
prober := resolver.NewProber(solversManager)
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
Certificate: certificate.NewCertifier(core, config.KeyType, prober),
|
||||||
|
Challenge: solversManager,
|
||||||
|
Registration: registration.NewRegistrar(core, config.User),
|
||||||
|
core: core,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToSURL returns the current ToS URL from the Directory
|
||||||
|
func (c *Client) GetToSURL() string {
|
||||||
|
return c.core.GetDirectory().Meta.TermsOfService
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExternalAccountRequired returns the External Account Binding requirement of the Directory
|
||||||
|
func (c *Client) GetExternalAccountRequired() bool {
|
||||||
|
return c.core.GetDirectory().Meta.ExternalAccountRequired
|
||||||
|
}
|
||||||
96
vendor/github.com/xenolf/lego/lego/client_config.go
generated
vendored
Normal file
96
vendor/github.com/xenolf/lego/lego/client_config.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package lego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/certcrypto"
|
||||||
|
"github.com/xenolf/lego/registration"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// caCertificatesEnvVar is the environment variable name that can be used to
|
||||||
|
// specify the path to PEM encoded CA Certificates that can be used to
|
||||||
|
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
|
||||||
|
// the system-wide trusted root list.
|
||||||
|
caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"
|
||||||
|
|
||||||
|
// caServerNameEnvVar is the environment variable name that can be used to
|
||||||
|
// specify the CA server name that can be used to
|
||||||
|
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
|
||||||
|
// the system-wide trusted root list.
|
||||||
|
caServerNameEnvVar = "LEGO_CA_SERVER_NAME"
|
||||||
|
|
||||||
|
// LEDirectoryProduction URL to the Let's Encrypt production
|
||||||
|
LEDirectoryProduction = "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
|
|
||||||
|
// LEDirectoryStaging URL to the Let's Encrypt staging
|
||||||
|
LEDirectoryStaging = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
CADirURL string
|
||||||
|
User registration.User
|
||||||
|
KeyType certcrypto.KeyType
|
||||||
|
UserAgent string
|
||||||
|
HTTPClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig(user registration.User) *Config {
|
||||||
|
return &Config{
|
||||||
|
CADirURL: LEDirectoryProduction,
|
||||||
|
User: user,
|
||||||
|
KeyType: certcrypto.RSA2048,
|
||||||
|
HTTPClient: createDefaultHTTPClient(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createDefaultHTTPClient Creates an HTTP client with a reasonable timeout value
|
||||||
|
// and potentially a custom *x509.CertPool
|
||||||
|
// based on the caCertificatesEnvVar environment variable (see the `initCertPool` function)
|
||||||
|
func createDefaultHTTPClient() *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).DialContext,
|
||||||
|
TLSHandshakeTimeout: 15 * time.Second,
|
||||||
|
ResponseHeaderTimeout: 15 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
ServerName: os.Getenv(caServerNameEnvVar),
|
||||||
|
RootCAs: initCertPool(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initCertPool creates a *x509.CertPool populated with the PEM certificates
|
||||||
|
// found in the filepath specified in the caCertificatesEnvVar OS environment
|
||||||
|
// variable. If the caCertificatesEnvVar is not set then initCertPool will
|
||||||
|
// return nil. If there is an error creating a *x509.CertPool from the provided
|
||||||
|
// caCertificatesEnvVar value then initCertPool will panic.
|
||||||
|
func initCertPool() *x509.CertPool {
|
||||||
|
if customCACertsPath := os.Getenv(caCertificatesEnvVar); customCACertsPath != "" {
|
||||||
|
customCAs, err := ioutil.ReadFile(customCACertsPath)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error reading %s=%q: %v",
|
||||||
|
caCertificatesEnvVar, customCACertsPath, err))
|
||||||
|
}
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
|
||||||
|
panic(fmt.Sprintf("error creating x509 cert pool from %s=%q: %v",
|
||||||
|
caCertificatesEnvVar, customCACertsPath, err))
|
||||||
|
}
|
||||||
|
return certPool
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
12
vendor/github.com/xenolf/lego/log/logger.go
generated
vendored
12
vendor/github.com/xenolf/lego/log/logger.go
generated
vendored
@ -6,7 +6,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Logger is an optional custom logger.
|
// Logger is an optional custom logger.
|
||||||
var Logger = log.New(os.Stdout, "", log.LstdFlags)
|
var Logger StdLogger = log.New(os.Stdout, "", log.LstdFlags)
|
||||||
|
|
||||||
|
// StdLogger interface for Standard Logger.
|
||||||
|
type StdLogger interface {
|
||||||
|
Fatal(args ...interface{})
|
||||||
|
Fatalln(args ...interface{})
|
||||||
|
Fatalf(format string, args ...interface{})
|
||||||
|
Print(args ...interface{})
|
||||||
|
Println(args ...interface{})
|
||||||
|
Printf(format string, args ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
// Fatal writes a log entry.
|
// Fatal writes a log entry.
|
||||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
||||||
|
|||||||
21
vendor/github.com/xenolf/lego/platform/wait/LICENSE
generated
vendored
Normal file
21
vendor/github.com/xenolf/lego/platform/wait/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-2017 Sebastian Erhart
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package acme
|
package wait
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -7,8 +7,8 @@ import (
|
|||||||
"github.com/xenolf/lego/log"
|
"github.com/xenolf/lego/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WaitFor polls the given function 'f', once every 'interval', up to 'timeout'.
|
// For polls the given function 'f', once every 'interval', up to 'timeout'.
|
||||||
func WaitFor(timeout, interval time.Duration, f func() (bool, error)) error {
|
func For(timeout, interval time.Duration, f func() (bool, error)) error {
|
||||||
log.Infof("Wait [timeout: %s, interval: %s]", timeout, interval)
|
log.Infof("Wait [timeout: %s, interval: %s]", timeout, interval)
|
||||||
|
|
||||||
var lastErr string
|
var lastErr string
|
||||||
21
vendor/github.com/xenolf/lego/registration/LICENSE
generated
vendored
Normal file
21
vendor/github.com/xenolf/lego/registration/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-2017 Sebastian Erhart
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
146
vendor/github.com/xenolf/lego/registration/registar.go
generated
vendored
Normal file
146
vendor/github.com/xenolf/lego/registration/registar.go
generated
vendored
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package registration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/acme/api"
|
||||||
|
"github.com/xenolf/lego/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resource represents all important information about a registration
|
||||||
|
// of which the client needs to keep track itself.
|
||||||
|
// Deprecated: will be remove in the future (acme.ExtendedAccount).
|
||||||
|
type Resource struct {
|
||||||
|
Body acme.Account `json:"body,omitempty"`
|
||||||
|
URI string `json:"uri,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegisterOptions struct {
|
||||||
|
TermsOfServiceAgreed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegisterEABOptions struct {
|
||||||
|
TermsOfServiceAgreed bool
|
||||||
|
Kid string
|
||||||
|
HmacEncoded string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Registrar struct {
|
||||||
|
core *api.Core
|
||||||
|
user User
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistrar(core *api.Core, user User) *Registrar {
|
||||||
|
return &Registrar{
|
||||||
|
core: core,
|
||||||
|
user: user,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the current account to the ACME server.
|
||||||
|
func (r *Registrar) Register(options RegisterOptions) (*Resource, error) {
|
||||||
|
if r == nil || r.user == nil {
|
||||||
|
return nil, errors.New("acme: cannot register a nil client or user")
|
||||||
|
}
|
||||||
|
|
||||||
|
accMsg := acme.Account{
|
||||||
|
TermsOfServiceAgreed: options.TermsOfServiceAgreed,
|
||||||
|
Contact: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.user.GetEmail() != "" {
|
||||||
|
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
||||||
|
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := r.core.Accounts.New(accMsg)
|
||||||
|
if err != nil {
|
||||||
|
// FIXME seems impossible
|
||||||
|
errorDetails, ok := err.(acme.ProblemDetails)
|
||||||
|
if !ok || errorDetails.HTTPStatus != http.StatusConflict {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Resource{URI: account.Location, Body: account.Account}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterWithExternalAccountBinding Register the current account to the ACME server.
|
||||||
|
func (r *Registrar) RegisterWithExternalAccountBinding(options RegisterEABOptions) (*Resource, error) {
|
||||||
|
accMsg := acme.Account{
|
||||||
|
TermsOfServiceAgreed: options.TermsOfServiceAgreed,
|
||||||
|
Contact: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.user.GetEmail() != "" {
|
||||||
|
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
||||||
|
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := r.core.Accounts.NewEAB(accMsg, options.Kid, options.HmacEncoded)
|
||||||
|
if err != nil {
|
||||||
|
errorDetails, ok := err.(acme.ProblemDetails)
|
||||||
|
// FIXME seems impossible
|
||||||
|
if !ok || errorDetails.HTTPStatus != http.StatusConflict {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Resource{URI: account.Location, Body: account.Account}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRegistration runs a POST request on the client's registration and returns the result.
|
||||||
|
//
|
||||||
|
// This is similar to the Register function,
|
||||||
|
// but acting on an existing registration link and resource.
|
||||||
|
func (r *Registrar) QueryRegistration() (*Resource, error) {
|
||||||
|
if r == nil || r.user == nil {
|
||||||
|
return nil, errors.New("acme: cannot query the registration of a nil client or user")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the URL here instead of the email as the email may not be set
|
||||||
|
log.Infof("acme: Querying account for %s", r.user.GetRegistration().URI)
|
||||||
|
|
||||||
|
account, err := r.core.Accounts.Get(r.user.GetRegistration().URI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Resource{
|
||||||
|
Body: account,
|
||||||
|
// Location: header is not returned so this needs to be populated off of existing URI
|
||||||
|
URI: r.user.GetRegistration().URI,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRegistration deletes the client's user registration from the ACME server.
|
||||||
|
func (r *Registrar) DeleteRegistration() error {
|
||||||
|
if r == nil || r.user == nil {
|
||||||
|
return errors.New("acme: cannot unregister a nil client or user")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("acme: Deleting account for %s", r.user.GetEmail())
|
||||||
|
|
||||||
|
return r.core.Accounts.Deactivate(r.user.GetRegistration().URI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveAccountByKey will attempt to look up an account using the given account key
|
||||||
|
// and return its registration resource.
|
||||||
|
func (r *Registrar) ResolveAccountByKey() (*Resource, error) {
|
||||||
|
log.Infof("acme: Trying to resolve account by key")
|
||||||
|
|
||||||
|
accMsg := acme.Account{OnlyReturnExisting: true}
|
||||||
|
accountTransit, err := r.core.Accounts.New(accMsg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := r.core.Accounts.Get(accountTransit.Location)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Resource{URI: accountTransit.Location, Body: account}, nil
|
||||||
|
}
|
||||||
13
vendor/github.com/xenolf/lego/registration/user.go
generated
vendored
Normal file
13
vendor/github.com/xenolf/lego/registration/user.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package registration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// User interface is to be implemented by users of this library.
|
||||||
|
// It is used by the client type to get user specific information.
|
||||||
|
type User interface {
|
||||||
|
GetEmail() string
|
||||||
|
GetRegistration() *Resource
|
||||||
|
GetPrivateKey() crypto.PrivateKey
|
||||||
|
}
|
||||||
21
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/LICENSE
generated
vendored
Normal file
21
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-2017 Sebastian Erhart
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
54
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/acceptfunc.go
generated
vendored
Normal file
54
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/acceptfunc.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
// MsgAcceptFunc is used early in the server code to accept or reject a message with RcodeFormatError.
|
||||||
|
// It returns a MsgAcceptAction to indicate what should happen with the message.
|
||||||
|
type MsgAcceptFunc func(dh Header) MsgAcceptAction
|
||||||
|
|
||||||
|
// DefaultMsgAcceptFunc checks the request and will reject if:
|
||||||
|
//
|
||||||
|
// * isn't a request (don't respond in that case).
|
||||||
|
// * opcode isn't OpcodeQuery or OpcodeNotify
|
||||||
|
// * Zero bit isn't zero
|
||||||
|
// * has more than 1 question in the question section
|
||||||
|
// * has more than 0 RRs in the Answer section
|
||||||
|
// * 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.
|
||||||
|
type MsgAcceptAction int
|
||||||
|
|
||||||
|
const (
|
||||||
|
MsgAccept MsgAcceptAction = iota // Accept the message
|
||||||
|
MsgReject // Reject the message with a RcodeFormatError
|
||||||
|
MsgIgnore // Ignore the error and send nothing back.
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultMsgAcceptFunc = func(dh Header) MsgAcceptAction {
|
||||||
|
if isResponse := dh.Bits&_QR != 0; isResponse {
|
||||||
|
return MsgIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow dynamic updates, because then the sections can contain a whole bunch of RRs.
|
||||||
|
opcode := int(dh.Bits>>11) & 0xF
|
||||||
|
if opcode != OpcodeQuery && opcode != OpcodeNotify {
|
||||||
|
return MsgReject
|
||||||
|
}
|
||||||
|
|
||||||
|
if isZero := dh.Bits&_Z != 0; isZero {
|
||||||
|
return MsgReject
|
||||||
|
}
|
||||||
|
if dh.Qdcount != 1 {
|
||||||
|
return MsgReject
|
||||||
|
}
|
||||||
|
if dh.Ancount != 0 {
|
||||||
|
return MsgReject
|
||||||
|
}
|
||||||
|
if dh.Nscount != 0 {
|
||||||
|
return MsgReject
|
||||||
|
}
|
||||||
|
if dh.Arcount > 2 {
|
||||||
|
return MsgReject
|
||||||
|
}
|
||||||
|
return MsgAccept
|
||||||
|
}
|
||||||
496
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/client.go
generated
vendored
Normal file
496
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/client.go
generated
vendored
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
// A client implementation.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dnsTimeout time.Duration = 2 * time.Second
|
||||||
|
tcpIdleTimeout time.Duration = 8 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Conn represents a connection to a DNS server.
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn // a net.Conn holding the connection
|
||||||
|
UDPSize uint16 // minimum receive buffer for UDP messages
|
||||||
|
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)
|
||||||
|
tsigRequestMAC string
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Client defines parameters for a DNS client.
|
||||||
|
type Client struct {
|
||||||
|
Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP)
|
||||||
|
UDPSize uint16 // minimum receive buffer for UDP messages
|
||||||
|
TLSConfig *tls.Config // TLS connection configuration
|
||||||
|
Dialer *net.Dialer // a net.Dialer used to set local address, timeouts and more
|
||||||
|
// 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 the deprecated 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)
|
||||||
|
SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass
|
||||||
|
group singleflight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange performs a synchronous UDP query. It sends the message m to the address
|
||||||
|
// contained in a and waits for a reply. Exchange does not retry a failed query, nor
|
||||||
|
// will it fall back to TCP in case of truncation.
|
||||||
|
// See client.Exchange for more information on setting larger buffer sizes.
|
||||||
|
func Exchange(m *Msg, a string) (r *Msg, err error) {
|
||||||
|
client := Client{Net: "udp"}
|
||||||
|
r, _, err = client.Exchange(m, a)
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) dialTimeout() time.Duration {
|
||||||
|
if c.Timeout != 0 {
|
||||||
|
return c.Timeout
|
||||||
|
}
|
||||||
|
if c.DialTimeout != 0 {
|
||||||
|
return c.DialTimeout
|
||||||
|
}
|
||||||
|
return dnsTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) readTimeout() time.Duration {
|
||||||
|
if c.ReadTimeout != 0 {
|
||||||
|
return c.ReadTimeout
|
||||||
|
}
|
||||||
|
return dnsTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) writeTimeout() time.Duration {
|
||||||
|
if c.WriteTimeout != 0 {
|
||||||
|
return c.WriteTimeout
|
||||||
|
}
|
||||||
|
return dnsTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address on the named network.
|
||||||
|
func (c *Client) Dial(address string) (conn *Conn, err error) {
|
||||||
|
// create a new dialer with the appropriate timeout
|
||||||
|
var d net.Dialer
|
||||||
|
if c.Dialer == nil {
|
||||||
|
d = net.Dialer{Timeout: c.getTimeoutForRequest(c.dialTimeout())}
|
||||||
|
} else {
|
||||||
|
d = *c.Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
network := c.Net
|
||||||
|
if network == "" {
|
||||||
|
network = "udp"
|
||||||
|
}
|
||||||
|
|
||||||
|
useTLS := strings.HasPrefix(network, "tcp") && strings.HasSuffix(network, "-tls")
|
||||||
|
|
||||||
|
conn = new(Conn)
|
||||||
|
if useTLS {
|
||||||
|
network = strings.TrimSuffix(network, "-tls")
|
||||||
|
|
||||||
|
conn.Conn, err = tls.DialWithDialer(&d, network, address, c.TLSConfig)
|
||||||
|
} else {
|
||||||
|
conn.Conn, err = d.Dial(network, address)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange performs a synchronous query. It sends the message m to the address
|
||||||
|
// contained in a and waits for a reply. Basic use pattern with a *dns.Client:
|
||||||
|
//
|
||||||
|
// c := new(dns.Client)
|
||||||
|
// in, rtt, err := c.Exchange(message, "127.0.0.1:53")
|
||||||
|
//
|
||||||
|
// Exchange does not retry a failed query, nor will it fall back to TCP in
|
||||||
|
// case of truncation.
|
||||||
|
// It is up to the caller to create a message that allows for larger responses to be
|
||||||
|
// returned. Specifically this means adding an EDNS0 OPT RR that will advertise a larger
|
||||||
|
// buffer, see SetEdns0. Messages without an OPT RR will fallback to the historic limit
|
||||||
|
// of 512 bytes
|
||||||
|
// To specify a local address or a timeout, the caller has to set the `Client.Dialer`
|
||||||
|
// attribute appropriately
|
||||||
|
func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, err error) {
|
||||||
|
if !c.SingleInflight {
|
||||||
|
return c.exchange(m, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := "nop"
|
||||||
|
if t1, ok := TypeToString[m.Question[0].Qtype]; ok {
|
||||||
|
t = t1
|
||||||
|
}
|
||||||
|
cl := "nop"
|
||||||
|
if cl1, ok := ClassToString[m.Question[0].Qclass]; ok {
|
||||||
|
cl = cl1
|
||||||
|
}
|
||||||
|
r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) {
|
||||||
|
return c.exchange(m, address)
|
||||||
|
})
|
||||||
|
if r != nil && shared {
|
||||||
|
r = r.Copy()
|
||||||
|
}
|
||||||
|
return r, rtt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
|
||||||
|
var co *Conn
|
||||||
|
|
||||||
|
co, err = c.Dial(a)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
defer co.Close()
|
||||||
|
|
||||||
|
opt := m.IsEdns0()
|
||||||
|
// If EDNS0 is used use that for size.
|
||||||
|
if opt != nil && opt.UDPSize() >= MinMsgSize {
|
||||||
|
co.UDPSize = opt.UDPSize()
|
||||||
|
}
|
||||||
|
// Otherwise use the client's configured UDP size.
|
||||||
|
if opt == nil && c.UDPSize >= MinMsgSize {
|
||||||
|
co.UDPSize = c.UDPSize
|
||||||
|
}
|
||||||
|
|
||||||
|
co.TsigSecret = c.TsigSecret
|
||||||
|
t := time.Now()
|
||||||
|
// write with the appropriate write timeout
|
||||||
|
co.SetWriteDeadline(t.Add(c.getTimeoutForRequest(c.writeTimeout())))
|
||||||
|
if err = co.WriteMsg(m); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
co.SetReadDeadline(time.Now().Add(c.getTimeoutForRequest(c.readTimeout())))
|
||||||
|
r, err = co.ReadMsg()
|
||||||
|
if err == nil && r.Id != m.Id {
|
||||||
|
err = ErrId
|
||||||
|
}
|
||||||
|
rtt = time.Since(t)
|
||||||
|
return r, rtt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMsg reads a message from the connection co.
|
||||||
|
// If the received message contains a TSIG record the transaction signature
|
||||||
|
// is verified. This method always tries to return the message, however if an
|
||||||
|
// error is returned there are no guarantees that the returned message is a
|
||||||
|
// valid representation of the packet read.
|
||||||
|
func (co *Conn) ReadMsg() (*Msg, error) {
|
||||||
|
p, err := co.ReadMsgHeader(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := new(Msg)
|
||||||
|
if err := m.Unpack(p); err != nil {
|
||||||
|
// If an error was returned, we still want to allow the user to use
|
||||||
|
// the message, but naively they can just check err if they don't want
|
||||||
|
// to use an erroneous message
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
if t := m.IsTsig(); t != nil {
|
||||||
|
if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
|
||||||
|
return m, ErrSecret
|
||||||
|
}
|
||||||
|
// Need to work on the original message p, as that was used to calculate the tsig.
|
||||||
|
err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
|
||||||
|
}
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMsgHeader reads a DNS message, parses and populates hdr (when hdr is not nil).
|
||||||
|
// Returns message as a byte slice to be parsed with Msg.Unpack later on.
|
||||||
|
// Note that error handling on the message body is not possible as only the header is parsed.
|
||||||
|
func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
p []byte
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
switch t := co.Conn.(type) {
|
||||||
|
case *net.TCPConn, *tls.Conn:
|
||||||
|
r := t.(io.Reader)
|
||||||
|
|
||||||
|
// First two bytes specify the length of the entire message.
|
||||||
|
l, err := tcpMsgLen(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p = make([]byte, l)
|
||||||
|
n, err = tcpRead(r, p)
|
||||||
|
default:
|
||||||
|
if co.UDPSize > MinMsgSize {
|
||||||
|
p = make([]byte, co.UDPSize)
|
||||||
|
} else {
|
||||||
|
p = make([]byte, MinMsgSize)
|
||||||
|
}
|
||||||
|
n, err = co.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if n < headerSize {
|
||||||
|
return nil, ErrShortRead
|
||||||
|
}
|
||||||
|
|
||||||
|
p = p[:n]
|
||||||
|
if hdr != nil {
|
||||||
|
dh, _, err := unpackMsgHdr(p, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
*hdr = dh
|
||||||
|
}
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// tcpMsgLen is a helper func to read first two bytes of stream as uint16 packet length.
|
||||||
|
func tcpMsgLen(t io.Reader) (int, error) {
|
||||||
|
p := []byte{0, 0}
|
||||||
|
n, err := t.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// As seen with my local router/switch, returns 1 byte on the above read,
|
||||||
|
// resulting a a ShortRead. Just write it out (instead of loop) and read the
|
||||||
|
// other byte.
|
||||||
|
if n == 1 {
|
||||||
|
n1, err := t.Read(p[1:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
n += n1
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != 2 {
|
||||||
|
return 0, ErrShortRead
|
||||||
|
}
|
||||||
|
l := binary.BigEndian.Uint16(p)
|
||||||
|
if l == 0 {
|
||||||
|
return 0, ErrShortRead
|
||||||
|
}
|
||||||
|
return int(l), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tcpRead calls TCPConn.Read enough times to fill allocated buffer.
|
||||||
|
func tcpRead(t io.Reader, p []byte) (int, error) {
|
||||||
|
n, err := t.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
for n < len(p) {
|
||||||
|
j, err := t.Read(p[n:])
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += j
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements the net.Conn read method.
|
||||||
|
func (co *Conn) Read(p []byte) (n int, err error) {
|
||||||
|
if co.Conn == nil {
|
||||||
|
return 0, ErrConnEmpty
|
||||||
|
}
|
||||||
|
if len(p) < 2 {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
switch t := co.Conn.(type) {
|
||||||
|
case *net.TCPConn, *tls.Conn:
|
||||||
|
r := t.(io.Reader)
|
||||||
|
|
||||||
|
l, err := tcpMsgLen(r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if l > len(p) {
|
||||||
|
return int(l), io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
return tcpRead(r, p[:l])
|
||||||
|
}
|
||||||
|
// UDP connection
|
||||||
|
n, err = co.Conn.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg sends a message through the connection co.
|
||||||
|
// If the message m contains a TSIG record the transaction
|
||||||
|
// signature is calculated.
|
||||||
|
func (co *Conn) WriteMsg(m *Msg) (err error) {
|
||||||
|
var out []byte
|
||||||
|
if t := m.IsTsig(); t != nil {
|
||||||
|
mac := ""
|
||||||
|
if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
|
||||||
|
return ErrSecret
|
||||||
|
}
|
||||||
|
out, mac, err = TsigGenerate(m, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
|
||||||
|
// Set for the next read, although only used in zone transfers
|
||||||
|
co.tsigRequestMAC = mac
|
||||||
|
} else {
|
||||||
|
out, err = m.Pack()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = co.Write(out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements the net.Conn Write method.
|
||||||
|
func (co *Conn) Write(p []byte) (n int, err error) {
|
||||||
|
switch t := co.Conn.(type) {
|
||||||
|
case *net.TCPConn, *tls.Conn:
|
||||||
|
w := t.(io.Writer)
|
||||||
|
|
||||||
|
lp := len(p)
|
||||||
|
if lp < 2 {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
if lp > MaxMsgSize {
|
||||||
|
return 0, &Error{err: "message too large"}
|
||||||
|
}
|
||||||
|
l := make([]byte, 2, lp+2)
|
||||||
|
binary.BigEndian.PutUint16(l, uint16(lp))
|
||||||
|
p = append(l, p...)
|
||||||
|
n, err := io.Copy(w, bytes.NewReader(p))
|
||||||
|
return int(n), err
|
||||||
|
}
|
||||||
|
n, err = co.Conn.Write(p)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the appropriate timeout for a specific request
|
||||||
|
func (c *Client) getTimeoutForRequest(timeout time.Duration) time.Duration {
|
||||||
|
var requestTimeout time.Duration
|
||||||
|
if c.Timeout != 0 {
|
||||||
|
requestTimeout = c.Timeout
|
||||||
|
} else {
|
||||||
|
requestTimeout = timeout
|
||||||
|
}
|
||||||
|
// net.Dialer.Timeout has priority if smaller than the timeouts computed so
|
||||||
|
// far
|
||||||
|
if c.Dialer != nil && c.Dialer.Timeout != 0 {
|
||||||
|
if c.Dialer.Timeout < requestTimeout {
|
||||||
|
requestTimeout = c.Dialer.Timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requestTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address on the named network.
|
||||||
|
func Dial(network, address string) (conn *Conn, err error) {
|
||||||
|
conn = new(Conn)
|
||||||
|
conn.Conn, err = net.Dial(network, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExchangeContext performs a synchronous UDP query, like Exchange. It
|
||||||
|
// additionally obeys deadlines from the passed Context.
|
||||||
|
func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) {
|
||||||
|
client := Client{Net: "udp"}
|
||||||
|
r, _, err = client.ExchangeContext(ctx, m, a)
|
||||||
|
// ignorint rtt to leave the original ExchangeContext API unchanged, but
|
||||||
|
// this function will go away
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExchangeConn performs a synchronous query. It sends the message m via the connection
|
||||||
|
// c and waits for a reply. The connection c is not closed by ExchangeConn.
|
||||||
|
// This function is going away, but can easily be mimicked:
|
||||||
|
//
|
||||||
|
// co := &dns.Conn{Conn: c} // c is your net.Conn
|
||||||
|
// 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)
|
||||||
|
co.Conn = c
|
||||||
|
if err = co.WriteMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r, err = co.ReadMsg()
|
||||||
|
if err == nil && r.Id != m.Id {
|
||||||
|
err = ErrId
|
||||||
|
}
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialTimeout acts like Dial but takes a timeout.
|
||||||
|
func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) {
|
||||||
|
client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}}
|
||||||
|
conn, err = client.Dial(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialWithTLS connects to the address on the named network with TLS.
|
||||||
|
func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, err error) {
|
||||||
|
if !strings.HasSuffix(network, "-tls") {
|
||||||
|
network += "-tls"
|
||||||
|
}
|
||||||
|
client := Client{Net: network, TLSConfig: tlsConfig}
|
||||||
|
conn, err = client.Dial(address)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialTimeoutWithTLS acts like DialWithTLS but takes a timeout.
|
||||||
|
func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout time.Duration) (conn *Conn, err error) {
|
||||||
|
if !strings.HasSuffix(network, "-tls") {
|
||||||
|
network += "-tls"
|
||||||
|
}
|
||||||
|
client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}, TLSConfig: tlsConfig}
|
||||||
|
conn, err = client.Dial(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExchangeContext acts like Exchange, but honors the deadline on the provided
|
||||||
|
// context, if present. If there is both a context deadline and a configured
|
||||||
|
// timeout on the client, the earliest of the two takes effect.
|
||||||
|
func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
|
||||||
|
var timeout time.Duration
|
||||||
|
if deadline, ok := ctx.Deadline(); !ok {
|
||||||
|
timeout = 0
|
||||||
|
} else {
|
||||||
|
timeout = time.Until(deadline)
|
||||||
|
}
|
||||||
|
// not passing the context to the underlying calls, as the API does not support
|
||||||
|
// context. For timeouts you should set up Client.Dialer and call Client.Exchange.
|
||||||
|
// TODO(tmthrgd,miekg): this is a race condition.
|
||||||
|
c.Dialer = &net.Dialer{Timeout: timeout}
|
||||||
|
return c.Exchange(m, a)
|
||||||
|
}
|
||||||
139
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/clientconfig.go
generated
vendored
Normal file
139
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/clientconfig.go
generated
vendored
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientConfig wraps the contents of the /etc/resolv.conf file.
|
||||||
|
type ClientConfig struct {
|
||||||
|
Servers []string // servers to use
|
||||||
|
Search []string // suffixes to append to local name
|
||||||
|
Port string // what port to use
|
||||||
|
Ndots int // number of dots in name to trigger absolute lookup
|
||||||
|
Timeout int // seconds before giving up on packet
|
||||||
|
Attempts int // lost packets before giving up on server, not used in the package dns
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientConfigFromFile parses a resolv.conf(5) like file and returns
|
||||||
|
// a *ClientConfig.
|
||||||
|
func ClientConfigFromFile(resolvconf string) (*ClientConfig, error) {
|
||||||
|
file, err := os.Open(resolvconf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
return ClientConfigFromReader(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientConfigFromReader works like ClientConfigFromFile but takes an io.Reader as argument
|
||||||
|
func ClientConfigFromReader(resolvconf io.Reader) (*ClientConfig, error) {
|
||||||
|
c := new(ClientConfig)
|
||||||
|
scanner := bufio.NewScanner(resolvconf)
|
||||||
|
c.Servers = make([]string, 0)
|
||||||
|
c.Search = make([]string, 0)
|
||||||
|
c.Port = "53"
|
||||||
|
c.Ndots = 1
|
||||||
|
c.Timeout = 5
|
||||||
|
c.Attempts = 2
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
line := scanner.Text()
|
||||||
|
f := strings.Fields(line)
|
||||||
|
if len(f) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch f[0] {
|
||||||
|
case "nameserver": // add one name server
|
||||||
|
if len(f) > 1 {
|
||||||
|
// One more check: make sure server name is
|
||||||
|
// just an IP address. Otherwise we need DNS
|
||||||
|
// to look it up.
|
||||||
|
name := f[1]
|
||||||
|
c.Servers = append(c.Servers, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "domain": // set search path to just this domain
|
||||||
|
if len(f) > 1 {
|
||||||
|
c.Search = make([]string, 1)
|
||||||
|
c.Search[0] = f[1]
|
||||||
|
} else {
|
||||||
|
c.Search = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "search": // set search path to given servers
|
||||||
|
c.Search = make([]string, len(f)-1)
|
||||||
|
for i := 0; i < len(c.Search); i++ {
|
||||||
|
c.Search[i] = f[i+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
case "options": // magic options
|
||||||
|
for i := 1; i < len(f); i++ {
|
||||||
|
s := f[i]
|
||||||
|
switch {
|
||||||
|
case len(s) >= 6 && s[:6] == "ndots:":
|
||||||
|
n, _ := strconv.Atoi(s[6:])
|
||||||
|
if n < 0 {
|
||||||
|
n = 0
|
||||||
|
} else if n > 15 {
|
||||||
|
n = 15
|
||||||
|
}
|
||||||
|
c.Ndots = n
|
||||||
|
case len(s) >= 8 && s[:8] == "timeout:":
|
||||||
|
n, _ := strconv.Atoi(s[8:])
|
||||||
|
if n < 1 {
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
c.Timeout = n
|
||||||
|
case len(s) >= 9 && s[:9] == "attempts:":
|
||||||
|
n, _ := strconv.Atoi(s[9:])
|
||||||
|
if n < 1 {
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
c.Attempts = n
|
||||||
|
case s == "rotate":
|
||||||
|
/* not imp */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameList returns all of the names that should be queried based on the
|
||||||
|
// config. It is based off of go's net/dns name building, but it does not
|
||||||
|
// check the length of the resulting names.
|
||||||
|
func (c *ClientConfig) NameList(name string) []string {
|
||||||
|
// if this domain is already fully qualified, no append needed.
|
||||||
|
if IsFqdn(name) {
|
||||||
|
return []string{name}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if the name has more labels than Ndots. Do this before making
|
||||||
|
// the domain fully qualified.
|
||||||
|
hasNdots := CountLabel(name) > c.Ndots
|
||||||
|
// Make the domain fully qualified.
|
||||||
|
name = Fqdn(name)
|
||||||
|
|
||||||
|
// Make a list of names based off search.
|
||||||
|
names := []string{}
|
||||||
|
|
||||||
|
// If name has enough dots, try that first.
|
||||||
|
if hasNdots {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
for _, s := range c.Search {
|
||||||
|
names = append(names, Fqdn(name+s))
|
||||||
|
}
|
||||||
|
// If we didn't have enough dots, try after suffixes.
|
||||||
|
if !hasNdots {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
198
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/compress_generate.go
generated
vendored
Normal file
198
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/compress_generate.go
generated
vendored
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
//+build ignore
|
||||||
|
|
||||||
|
// compression_generate.go is meant to run with go generate. It will use
|
||||||
|
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||||
|
// it will look to see if there are (compressible) names, if so it will add that
|
||||||
|
// type to compressionLenHelperType and comressionLenSearchType which "fake" the
|
||||||
|
// compression so that Len() is fast.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"go/importer"
|
||||||
|
"go/types"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var packageHdr = `
|
||||||
|
// Code generated by "go run compress_generate.go"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
// getTypeStruct will take a type and the package scope, and return the
|
||||||
|
// (innermost) struct if the type is considered a RR type (currently defined as
|
||||||
|
// those structs beginning with a RR_Header, could be redefined as implementing
|
||||||
|
// the RR interface). The bool return value indicates if embedded structs were
|
||||||
|
// resolved.
|
||||||
|
func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
||||||
|
st, ok := t.Underlying().(*types.Struct)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
||||||
|
return st, false
|
||||||
|
}
|
||||||
|
if st.Field(0).Anonymous() {
|
||||||
|
st, _ := getTypeStruct(st.Field(0).Type(), scope)
|
||||||
|
return st, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Import and type-check the package
|
||||||
|
pkg, err := importer.Default().Import("github.com/miekg/dns")
|
||||||
|
fatalIfErr(err)
|
||||||
|
scope := pkg.Scope()
|
||||||
|
|
||||||
|
var domainTypes []string // Types that have a domain name in them (either compressible or not).
|
||||||
|
var cdomainTypes []string // Types that have a compressible domain name in them (subset of domainType)
|
||||||
|
Names:
|
||||||
|
for _, name := range scope.Names() {
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
if o == nil || !o.Exported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
st, _ := getTypeStruct(o.Type(), scope)
|
||||||
|
if st == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if name == "PrivateRR" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" {
|
||||||
|
log.Fatalf("Constant Type%s does not exist.", o.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < st.NumFields(); i++ {
|
||||||
|
if _, ok := st.Field(i).Type().(*types.Slice); ok {
|
||||||
|
if st.Tag(i) == `dns:"domain-name"` {
|
||||||
|
domainTypes = append(domainTypes, o.Name())
|
||||||
|
continue Names
|
||||||
|
}
|
||||||
|
if st.Tag(i) == `dns:"cdomain-name"` {
|
||||||
|
cdomainTypes = append(cdomainTypes, o.Name())
|
||||||
|
domainTypes = append(domainTypes, o.Name())
|
||||||
|
continue Names
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case st.Tag(i) == `dns:"domain-name"`:
|
||||||
|
domainTypes = append(domainTypes, o.Name())
|
||||||
|
continue Names
|
||||||
|
case st.Tag(i) == `dns:"cdomain-name"`:
|
||||||
|
cdomainTypes = append(cdomainTypes, o.Name())
|
||||||
|
domainTypes = append(domainTypes, o.Name())
|
||||||
|
continue Names
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
b.WriteString(packageHdr)
|
||||||
|
|
||||||
|
// compressionLenHelperType - all types that have domain-name/cdomain-name can be used for compressing names
|
||||||
|
|
||||||
|
fmt.Fprint(b, "func compressionLenHelperType(c map[string]struct{}, r RR, initLen int) int {\n")
|
||||||
|
fmt.Fprint(b, "currentLen := initLen\n")
|
||||||
|
fmt.Fprint(b, "switch x := r.(type) {\n")
|
||||||
|
for _, name := range domainTypes {
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
st, _ := getTypeStruct(o.Type(), scope)
|
||||||
|
|
||||||
|
fmt.Fprintf(b, "case *%s:\n", name)
|
||||||
|
for i := 1; i < st.NumFields(); i++ {
|
||||||
|
out := func(s string) {
|
||||||
|
fmt.Fprintf(b, "currentLen -= len(x.%s) + 1\n", st.Field(i).Name())
|
||||||
|
fmt.Fprintf(b, "currentLen += compressionLenHelper(c, x.%s, currentLen)\n", st.Field(i).Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := st.Field(i).Type().(*types.Slice); ok {
|
||||||
|
switch st.Tag(i) {
|
||||||
|
case `dns:"domain-name"`:
|
||||||
|
fallthrough
|
||||||
|
case `dns:"cdomain-name"`:
|
||||||
|
// For HIP we need to slice over the elements in this slice.
|
||||||
|
fmt.Fprintf(b, `for i := range x.%s {
|
||||||
|
currentLen -= len(x.%s[i]) + 1
|
||||||
|
}
|
||||||
|
`, st.Field(i).Name(), st.Field(i).Name())
|
||||||
|
fmt.Fprintf(b, `for i := range x.%s {
|
||||||
|
currentLen += compressionLenHelper(c, x.%s[i], currentLen)
|
||||||
|
}
|
||||||
|
`, st.Field(i).Name(), st.Field(i).Name())
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case st.Tag(i) == `dns:"cdomain-name"`:
|
||||||
|
fallthrough
|
||||||
|
case st.Tag(i) == `dns:"domain-name"`:
|
||||||
|
out(st.Field(i).Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintln(b, "}\nreturn currentLen - initLen\n}\n\n")
|
||||||
|
|
||||||
|
// compressionLenSearchType - search cdomain-tags types for compressible names.
|
||||||
|
|
||||||
|
fmt.Fprint(b, "func compressionLenSearchType(c map[string]struct{}, r RR) (int, bool, int) {\n")
|
||||||
|
fmt.Fprint(b, "switch x := r.(type) {\n")
|
||||||
|
for _, name := range cdomainTypes {
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
st, _ := getTypeStruct(o.Type(), scope)
|
||||||
|
|
||||||
|
fmt.Fprintf(b, "case *%s:\n", name)
|
||||||
|
j := 1
|
||||||
|
for i := 1; i < st.NumFields(); i++ {
|
||||||
|
out := func(s string, j int) {
|
||||||
|
fmt.Fprintf(b, "k%d, ok%d, sz%d := compressionLenSearch(c, x.%s)\n", j, j, j, st.Field(i).Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are no slice types with names that can be compressed.
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case st.Tag(i) == `dns:"cdomain-name"`:
|
||||||
|
out(st.Field(i).Name(), j)
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k := "k1"
|
||||||
|
ok := "ok1"
|
||||||
|
sz := "sz1"
|
||||||
|
for i := 2; i < j; i++ {
|
||||||
|
k += fmt.Sprintf(" + k%d", i)
|
||||||
|
ok += fmt.Sprintf(" && ok%d", i)
|
||||||
|
sz += fmt.Sprintf(" + sz%d", i)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "return %s, %s, %s\n", k, ok, sz)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(b, "}\nreturn 0, false, 0\n}\n\n")
|
||||||
|
|
||||||
|
// gofmt
|
||||||
|
res, err := format.Source(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
b.WriteTo(os.Stderr)
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create("zcompress.go")
|
||||||
|
fatalIfErr(err)
|
||||||
|
defer f.Close()
|
||||||
|
f.Write(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatalIfErr(err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
43
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/dane.go
generated
vendored
Normal file
43
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/dane.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CertificateToDANE converts a certificate to a hex string as used in the TLSA or SMIMEA records.
|
||||||
|
func CertificateToDANE(selector, matchingType uint8, cert *x509.Certificate) (string, error) {
|
||||||
|
switch matchingType {
|
||||||
|
case 0:
|
||||||
|
switch selector {
|
||||||
|
case 0:
|
||||||
|
return hex.EncodeToString(cert.Raw), nil
|
||||||
|
case 1:
|
||||||
|
return hex.EncodeToString(cert.RawSubjectPublicKeyInfo), nil
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
h := sha256.New()
|
||||||
|
switch selector {
|
||||||
|
case 0:
|
||||||
|
h.Write(cert.Raw)
|
||||||
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
case 1:
|
||||||
|
h.Write(cert.RawSubjectPublicKeyInfo)
|
||||||
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
h := sha512.New()
|
||||||
|
switch selector {
|
||||||
|
case 0:
|
||||||
|
h.Write(cert.Raw)
|
||||||
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
case 1:
|
||||||
|
h.Write(cert.RawSubjectPublicKeyInfo)
|
||||||
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("dns: bad MatchingType or Selector")
|
||||||
|
}
|
||||||
288
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/defaults.go
generated
vendored
Normal file
288
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/defaults.go
generated
vendored
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const hexDigit = "0123456789abcdef"
|
||||||
|
|
||||||
|
// Everything is assumed in ClassINET.
|
||||||
|
|
||||||
|
// SetReply creates a reply message from a request message.
|
||||||
|
func (dns *Msg) SetReply(request *Msg) *Msg {
|
||||||
|
dns.Id = request.Id
|
||||||
|
dns.Response = true
|
||||||
|
dns.Opcode = request.Opcode
|
||||||
|
if dns.Opcode == OpcodeQuery {
|
||||||
|
dns.RecursionDesired = request.RecursionDesired // Copy rd bit
|
||||||
|
dns.CheckingDisabled = request.CheckingDisabled // Copy cd bit
|
||||||
|
}
|
||||||
|
dns.Rcode = RcodeSuccess
|
||||||
|
if len(request.Question) > 0 {
|
||||||
|
dns.Question = make([]Question, 1)
|
||||||
|
dns.Question[0] = request.Question[0]
|
||||||
|
}
|
||||||
|
return dns
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetQuestion creates a question message, it sets the Question
|
||||||
|
// section, generates an Id and sets the RecursionDesired (RD)
|
||||||
|
// bit to true.
|
||||||
|
func (dns *Msg) SetQuestion(z string, t uint16) *Msg {
|
||||||
|
dns.Id = Id()
|
||||||
|
dns.RecursionDesired = true
|
||||||
|
dns.Question = make([]Question, 1)
|
||||||
|
dns.Question[0] = Question{z, t, ClassINET}
|
||||||
|
return dns
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNotify creates a notify message, it sets the Question
|
||||||
|
// section, generates an Id and sets the Authoritative (AA)
|
||||||
|
// bit to true.
|
||||||
|
func (dns *Msg) SetNotify(z string) *Msg {
|
||||||
|
dns.Opcode = OpcodeNotify
|
||||||
|
dns.Authoritative = true
|
||||||
|
dns.Id = Id()
|
||||||
|
dns.Question = make([]Question, 1)
|
||||||
|
dns.Question[0] = Question{z, TypeSOA, ClassINET}
|
||||||
|
return dns
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRcode creates an error message suitable for the request.
|
||||||
|
func (dns *Msg) SetRcode(request *Msg, rcode int) *Msg {
|
||||||
|
dns.SetReply(request)
|
||||||
|
dns.Rcode = rcode
|
||||||
|
return dns
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRcodeFormatError creates a message with FormError set.
|
||||||
|
func (dns *Msg) SetRcodeFormatError(request *Msg) *Msg {
|
||||||
|
dns.Rcode = RcodeFormatError
|
||||||
|
dns.Opcode = OpcodeQuery
|
||||||
|
dns.Response = true
|
||||||
|
dns.Authoritative = false
|
||||||
|
dns.Id = request.Id
|
||||||
|
return dns
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdate makes the message a dynamic update message. It
|
||||||
|
// sets the ZONE section to: z, TypeSOA, ClassINET.
|
||||||
|
func (dns *Msg) SetUpdate(z string) *Msg {
|
||||||
|
dns.Id = Id()
|
||||||
|
dns.Response = false
|
||||||
|
dns.Opcode = OpcodeUpdate
|
||||||
|
dns.Compress = false // BIND9 cannot handle compression
|
||||||
|
dns.Question = make([]Question, 1)
|
||||||
|
dns.Question[0] = Question{z, TypeSOA, ClassINET}
|
||||||
|
return dns
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIxfr creates message for requesting an IXFR.
|
||||||
|
func (dns *Msg) SetIxfr(z string, serial uint32, ns, mbox string) *Msg {
|
||||||
|
dns.Id = Id()
|
||||||
|
dns.Question = make([]Question, 1)
|
||||||
|
dns.Ns = make([]RR, 1)
|
||||||
|
s := new(SOA)
|
||||||
|
s.Hdr = RR_Header{z, TypeSOA, ClassINET, defaultTtl, 0}
|
||||||
|
s.Serial = serial
|
||||||
|
s.Ns = ns
|
||||||
|
s.Mbox = mbox
|
||||||
|
dns.Question[0] = Question{z, TypeIXFR, ClassINET}
|
||||||
|
dns.Ns[0] = s
|
||||||
|
return dns
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAxfr creates message for requesting an AXFR.
|
||||||
|
func (dns *Msg) SetAxfr(z string) *Msg {
|
||||||
|
dns.Id = Id()
|
||||||
|
dns.Question = make([]Question, 1)
|
||||||
|
dns.Question[0] = Question{z, TypeAXFR, ClassINET}
|
||||||
|
return dns
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTsig appends a TSIG RR to the message.
|
||||||
|
// This is only a skeleton TSIG RR that is added as the last RR in the
|
||||||
|
// additional section. The Tsig is calculated when the message is being send.
|
||||||
|
func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg {
|
||||||
|
t := new(TSIG)
|
||||||
|
t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0}
|
||||||
|
t.Algorithm = algo
|
||||||
|
t.Fudge = fudge
|
||||||
|
t.TimeSigned = uint64(timesigned)
|
||||||
|
t.OrigId = dns.Id
|
||||||
|
dns.Extra = append(dns.Extra, t)
|
||||||
|
return dns
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEdns0 appends a EDNS0 OPT RR to the message.
|
||||||
|
// TSIG should always the last RR in a message.
|
||||||
|
func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg {
|
||||||
|
e := new(OPT)
|
||||||
|
e.Hdr.Name = "."
|
||||||
|
e.Hdr.Rrtype = TypeOPT
|
||||||
|
e.SetUDPSize(udpsize)
|
||||||
|
if do {
|
||||||
|
e.SetDo()
|
||||||
|
}
|
||||||
|
dns.Extra = append(dns.Extra, e)
|
||||||
|
return dns
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTsig checks if the message has a TSIG record as the last record
|
||||||
|
// in the additional section. It returns the TSIG record found or nil.
|
||||||
|
func (dns *Msg) IsTsig() *TSIG {
|
||||||
|
if len(dns.Extra) > 0 {
|
||||||
|
if dns.Extra[len(dns.Extra)-1].Header().Rrtype == TypeTSIG {
|
||||||
|
return dns.Extra[len(dns.Extra)-1].(*TSIG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEdns0 checks if the message has a EDNS0 (OPT) record, any EDNS0
|
||||||
|
// record in the additional section will do. It returns the OPT record
|
||||||
|
// found or nil.
|
||||||
|
func (dns *Msg) IsEdns0() *OPT {
|
||||||
|
// EDNS0 is at the end of the additional section, start there.
|
||||||
|
// We might want to change this to *only* look at the last two
|
||||||
|
// records. So we see TSIG and/or OPT - this a slightly bigger
|
||||||
|
// change though.
|
||||||
|
for i := len(dns.Extra) - 1; i >= 0; i-- {
|
||||||
|
if dns.Extra[i].Header().Rrtype == TypeOPT {
|
||||||
|
return dns.Extra[i].(*OPT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDomainName checks if s is a valid domain name, it returns the number of
|
||||||
|
// labels and true, when a domain name is valid. Note that non fully qualified
|
||||||
|
// domain name is considered valid, in this case the last label is counted in
|
||||||
|
// the number of labels. When false is returned the number of labels is not
|
||||||
|
// defined. Also note that this function is extremely liberal; almost any
|
||||||
|
// string is a valid domain name as the DNS is 8 bit protocol. It checks if each
|
||||||
|
// label fits in 63 characters, but there is no length check for the entire
|
||||||
|
// string s. I.e. a domain name longer than 255 characters is considered valid.
|
||||||
|
func IsDomainName(s string) (labels int, ok bool) {
|
||||||
|
_, labels, err := packDomainName(s, nil, 0, nil, false)
|
||||||
|
return labels, err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSubDomain checks if child is indeed a child of the parent. If child and parent
|
||||||
|
// are the same domain true is returned as well.
|
||||||
|
func IsSubDomain(parent, child string) bool {
|
||||||
|
// Entire child is contained in parent
|
||||||
|
return CompareDomainName(parent, child) == CountLabel(parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMsg sanity checks buf and returns an error if it isn't a valid DNS packet.
|
||||||
|
// The checking is performed on the binary payload.
|
||||||
|
func IsMsg(buf []byte) error {
|
||||||
|
// Header
|
||||||
|
if len(buf) < 12 {
|
||||||
|
return errors.New("dns: bad message header")
|
||||||
|
}
|
||||||
|
// Header: Opcode
|
||||||
|
// TODO(miek): more checks here, e.g. check all header bits.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFqdn checks if a domain name is fully qualified.
|
||||||
|
func IsFqdn(s string) bool {
|
||||||
|
l := len(s)
|
||||||
|
if l == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s[l-1] == '.'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
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
|
||||||
|
|
||||||
|
for _, rr := range rrset[1:] {
|
||||||
|
curRRHeader := rr.Header()
|
||||||
|
if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName {
|
||||||
|
// Mismatch between the records, so this is not a valid rrset for
|
||||||
|
//signing/verifying
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fqdn return the fully qualified domain name from s.
|
||||||
|
// If s is already fully qualified, it behaves as the identity function.
|
||||||
|
func Fqdn(s string) string {
|
||||||
|
if IsFqdn(s) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from the official Go code.
|
||||||
|
|
||||||
|
// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
|
||||||
|
// address suitable for reverse DNS (PTR) record lookups or an error if it fails
|
||||||
|
// to parse the IP address.
|
||||||
|
func ReverseAddr(addr string) (arpa string, err error) {
|
||||||
|
ip := net.ParseIP(addr)
|
||||||
|
if ip == nil {
|
||||||
|
return "", &Error{err: "unrecognized address: " + addr}
|
||||||
|
}
|
||||||
|
if ip.To4() != nil {
|
||||||
|
return strconv.Itoa(int(ip[15])) + "." + strconv.Itoa(int(ip[14])) + "." + strconv.Itoa(int(ip[13])) + "." +
|
||||||
|
strconv.Itoa(int(ip[12])) + ".in-addr.arpa.", nil
|
||||||
|
}
|
||||||
|
// Must be IPv6
|
||||||
|
buf := make([]byte, 0, len(ip)*4+len("ip6.arpa."))
|
||||||
|
// Add it, in reverse, to the buffer
|
||||||
|
for i := len(ip) - 1; i >= 0; i-- {
|
||||||
|
v := ip[i]
|
||||||
|
buf = append(buf, hexDigit[v&0xF])
|
||||||
|
buf = append(buf, '.')
|
||||||
|
buf = append(buf, hexDigit[v>>4])
|
||||||
|
buf = append(buf, '.')
|
||||||
|
}
|
||||||
|
// Append "ip6.arpa." and return (buf already has the final .)
|
||||||
|
buf = append(buf, "ip6.arpa."...)
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation for the type t.
|
||||||
|
func (t Type) String() string {
|
||||||
|
if t1, ok := TypeToString[uint16(t)]; ok {
|
||||||
|
return t1
|
||||||
|
}
|
||||||
|
return "TYPE" + strconv.Itoa(int(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation for the class c.
|
||||||
|
func (c Class) String() string {
|
||||||
|
if s, ok := ClassToString[uint16(c)]; ok {
|
||||||
|
// Only emit mnemonics when they are unambiguous, specically ANY is in both.
|
||||||
|
if _, ok := StringToType[s]; !ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "CLASS" + strconv.Itoa(int(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation for the name n.
|
||||||
|
func (n Name) String() string {
|
||||||
|
return sprintName(string(n))
|
||||||
|
}
|
||||||
97
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/dns.go
generated
vendored
Normal file
97
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/dns.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
const (
|
||||||
|
year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits.
|
||||||
|
defaultTtl = 3600 // Default internal TTL.
|
||||||
|
|
||||||
|
// DefaultMsgSize is the standard default for messages larger than 512 bytes.
|
||||||
|
DefaultMsgSize = 4096
|
||||||
|
// MinMsgSize is the minimal size of a DNS packet.
|
||||||
|
MinMsgSize = 512
|
||||||
|
// MaxMsgSize is the largest possible DNS packet.
|
||||||
|
MaxMsgSize = 65535
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error represents a DNS error.
|
||||||
|
type Error struct{ err string }
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
if e == nil {
|
||||||
|
return "dns: <nil>"
|
||||||
|
}
|
||||||
|
return "dns: " + e.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// An RR represents a resource record.
|
||||||
|
type RR interface {
|
||||||
|
// Header returns the header of an resource record. The header contains
|
||||||
|
// everything up to the rdata.
|
||||||
|
Header() *RR_Header
|
||||||
|
// String returns the text representation of the resource record.
|
||||||
|
String() string
|
||||||
|
|
||||||
|
// copy returns a copy of the RR
|
||||||
|
copy() RR
|
||||||
|
// len returns the length (in octets) of the uncompressed RR in wire format.
|
||||||
|
len() int
|
||||||
|
// pack packs an RR into wire format.
|
||||||
|
pack([]byte, int, map[string]int, bool) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RR_Header is the header all DNS resource records share.
|
||||||
|
type RR_Header struct {
|
||||||
|
Name string `dns:"cdomain-name"`
|
||||||
|
Rrtype uint16
|
||||||
|
Class uint16
|
||||||
|
Ttl uint32
|
||||||
|
Rdlength uint16 // Length of data after header.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header returns itself. This is here to make RR_Header implements the RR interface.
|
||||||
|
func (h *RR_Header) Header() *RR_Header { return h }
|
||||||
|
|
||||||
|
// Just to implement the RR interface.
|
||||||
|
func (h *RR_Header) copy() RR { return nil }
|
||||||
|
|
||||||
|
func (h *RR_Header) String() string {
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if h.Rrtype == TypeOPT {
|
||||||
|
s = ";"
|
||||||
|
// and maybe other things
|
||||||
|
}
|
||||||
|
|
||||||
|
s += sprintName(h.Name) + "\t"
|
||||||
|
s += strconv.FormatInt(int64(h.Ttl), 10) + "\t"
|
||||||
|
s += Class(h.Class).String() + "\t"
|
||||||
|
s += Type(h.Rrtype).String() + "\t"
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RR_Header) len() int {
|
||||||
|
l := len(h.Name) + 1
|
||||||
|
l += 10 // rrtype(2) + class(2) + ttl(4) + rdlength(2)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRFC3597 converts a known RR to the unknown RR representation from RFC 3597.
|
||||||
|
func (rr *RFC3597) ToRFC3597(r RR) error {
|
||||||
|
buf := make([]byte, r.len()*2)
|
||||||
|
off, err := PackRR(r, buf, 0, nil, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf = buf[:off]
|
||||||
|
if int(r.Header().Rdlength) > off {
|
||||||
|
return ErrBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
rfc3597, _, err := unpackRFC3597(*r.Header(), buf, off-int(r.Header().Rdlength))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*rr = *rfc3597.(*RFC3597)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
801
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/dnssec.go
generated
vendored
Normal file
801
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/dnssec.go
generated
vendored
Normal file
@ -0,0 +1,801 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
_ "crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
_ "crypto/sha1"
|
||||||
|
_ "crypto/sha256"
|
||||||
|
_ "crypto/sha512"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSSEC encryption algorithm codes.
|
||||||
|
const (
|
||||||
|
_ uint8 = iota
|
||||||
|
RSAMD5
|
||||||
|
DH
|
||||||
|
DSA
|
||||||
|
_ // Skip 4, RFC 6725, section 2.1
|
||||||
|
RSASHA1
|
||||||
|
DSANSEC3SHA1
|
||||||
|
RSASHA1NSEC3SHA1
|
||||||
|
RSASHA256
|
||||||
|
_ // Skip 9, RFC 6725, section 2.1
|
||||||
|
RSASHA512
|
||||||
|
_ // Skip 11, RFC 6725, section 2.1
|
||||||
|
ECCGOST
|
||||||
|
ECDSAP256SHA256
|
||||||
|
ECDSAP384SHA384
|
||||||
|
ED25519
|
||||||
|
ED448
|
||||||
|
INDIRECT uint8 = 252
|
||||||
|
PRIVATEDNS uint8 = 253 // Private (experimental keys)
|
||||||
|
PRIVATEOID uint8 = 254
|
||||||
|
)
|
||||||
|
|
||||||
|
// AlgorithmToString is a map of algorithm IDs to algorithm names.
|
||||||
|
var AlgorithmToString = map[uint8]string{
|
||||||
|
RSAMD5: "RSAMD5",
|
||||||
|
DH: "DH",
|
||||||
|
DSA: "DSA",
|
||||||
|
RSASHA1: "RSASHA1",
|
||||||
|
DSANSEC3SHA1: "DSA-NSEC3-SHA1",
|
||||||
|
RSASHA1NSEC3SHA1: "RSASHA1-NSEC3-SHA1",
|
||||||
|
RSASHA256: "RSASHA256",
|
||||||
|
RSASHA512: "RSASHA512",
|
||||||
|
ECCGOST: "ECC-GOST",
|
||||||
|
ECDSAP256SHA256: "ECDSAP256SHA256",
|
||||||
|
ECDSAP384SHA384: "ECDSAP384SHA384",
|
||||||
|
ED25519: "ED25519",
|
||||||
|
ED448: "ED448",
|
||||||
|
INDIRECT: "INDIRECT",
|
||||||
|
PRIVATEDNS: "PRIVATEDNS",
|
||||||
|
PRIVATEOID: "PRIVATEOID",
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToAlgorithm is the reverse of AlgorithmToString.
|
||||||
|
var StringToAlgorithm = reverseInt8(AlgorithmToString)
|
||||||
|
|
||||||
|
// AlgorithmToHash is a map of algorithm crypto hash IDs to crypto.Hash's.
|
||||||
|
var AlgorithmToHash = map[uint8]crypto.Hash{
|
||||||
|
RSAMD5: crypto.MD5, // Deprecated in RFC 6725
|
||||||
|
DSA: crypto.SHA1,
|
||||||
|
RSASHA1: crypto.SHA1,
|
||||||
|
RSASHA1NSEC3SHA1: crypto.SHA1,
|
||||||
|
RSASHA256: crypto.SHA256,
|
||||||
|
ECDSAP256SHA256: crypto.SHA256,
|
||||||
|
ECDSAP384SHA384: crypto.SHA384,
|
||||||
|
RSASHA512: crypto.SHA512,
|
||||||
|
ED25519: crypto.Hash(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSSEC hashing algorithm codes.
|
||||||
|
const (
|
||||||
|
_ uint8 = iota
|
||||||
|
SHA1 // RFC 4034
|
||||||
|
SHA256 // RFC 4509
|
||||||
|
GOST94 // RFC 5933
|
||||||
|
SHA384 // Experimental
|
||||||
|
SHA512 // Experimental
|
||||||
|
)
|
||||||
|
|
||||||
|
// HashToString is a map of hash IDs to names.
|
||||||
|
var HashToString = map[uint8]string{
|
||||||
|
SHA1: "SHA1",
|
||||||
|
SHA256: "SHA256",
|
||||||
|
GOST94: "GOST94",
|
||||||
|
SHA384: "SHA384",
|
||||||
|
SHA512: "SHA512",
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToHash is a map of names to hash IDs.
|
||||||
|
var StringToHash = reverseInt8(HashToString)
|
||||||
|
|
||||||
|
// DNSKEY flag values.
|
||||||
|
const (
|
||||||
|
SEP = 1
|
||||||
|
REVOKE = 1 << 7
|
||||||
|
ZONE = 1 << 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// The RRSIG needs to be converted to wireformat with some of the rdata (the signature) missing.
|
||||||
|
type rrsigWireFmt struct {
|
||||||
|
TypeCovered uint16
|
||||||
|
Algorithm uint8
|
||||||
|
Labels uint8
|
||||||
|
OrigTtl uint32
|
||||||
|
Expiration uint32
|
||||||
|
Inception uint32
|
||||||
|
KeyTag uint16
|
||||||
|
SignerName string `dns:"domain-name"`
|
||||||
|
/* No Signature */
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for converting DNSKEY's rdata to wirefmt.
|
||||||
|
type dnskeyWireFmt struct {
|
||||||
|
Flags uint16
|
||||||
|
Protocol uint8
|
||||||
|
Algorithm uint8
|
||||||
|
PublicKey string `dns:"base64"`
|
||||||
|
/* 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 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var keytag int
|
||||||
|
switch k.Algorithm {
|
||||||
|
case RSAMD5:
|
||||||
|
// Look at the bottom two bytes of the modules, which the last
|
||||||
|
// item in the pubkey. We could do this faster by looking directly
|
||||||
|
// at the base64 values. But I'm lazy.
|
||||||
|
modulus, _ := fromBase64([]byte(k.PublicKey))
|
||||||
|
if len(modulus) > 1 {
|
||||||
|
x := binary.BigEndian.Uint16(modulus[len(modulus)-2:])
|
||||||
|
keytag = int(x)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
keywire := new(dnskeyWireFmt)
|
||||||
|
keywire.Flags = k.Flags
|
||||||
|
keywire.Protocol = k.Protocol
|
||||||
|
keywire.Algorithm = k.Algorithm
|
||||||
|
keywire.PublicKey = k.PublicKey
|
||||||
|
wire := make([]byte, DefaultMsgSize)
|
||||||
|
n, err := packKeyWire(keywire, wire)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
wire = wire[:n]
|
||||||
|
for i, v := range wire {
|
||||||
|
if i&1 != 0 {
|
||||||
|
keytag += int(v) // must be larger than uint32
|
||||||
|
} else {
|
||||||
|
keytag += int(v) << 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keytag += keytag >> 16 & 0xFFFF
|
||||||
|
keytag &= 0xFFFF
|
||||||
|
}
|
||||||
|
return uint16(keytag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDS converts a DNSKEY record to a DS record.
|
||||||
|
func (k *DNSKEY) ToDS(h uint8) *DS {
|
||||||
|
if k == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ds := new(DS)
|
||||||
|
ds.Hdr.Name = k.Hdr.Name
|
||||||
|
ds.Hdr.Class = k.Hdr.Class
|
||||||
|
ds.Hdr.Rrtype = TypeDS
|
||||||
|
ds.Hdr.Ttl = k.Hdr.Ttl
|
||||||
|
ds.Algorithm = k.Algorithm
|
||||||
|
ds.DigestType = h
|
||||||
|
ds.KeyTag = k.KeyTag()
|
||||||
|
|
||||||
|
keywire := new(dnskeyWireFmt)
|
||||||
|
keywire.Flags = k.Flags
|
||||||
|
keywire.Protocol = k.Protocol
|
||||||
|
keywire.Algorithm = k.Algorithm
|
||||||
|
keywire.PublicKey = k.PublicKey
|
||||||
|
wire := make([]byte, DefaultMsgSize)
|
||||||
|
n, err := packKeyWire(keywire, wire)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
wire = wire[:n]
|
||||||
|
|
||||||
|
owner := make([]byte, 255)
|
||||||
|
off, err1 := PackDomainName(strings.ToLower(k.Hdr.Name), owner, 0, nil, false)
|
||||||
|
if err1 != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
owner = owner[:off]
|
||||||
|
// RFC4034:
|
||||||
|
// digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA);
|
||||||
|
// "|" denotes concatenation
|
||||||
|
// DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key.
|
||||||
|
|
||||||
|
var hash crypto.Hash
|
||||||
|
switch h {
|
||||||
|
case SHA1:
|
||||||
|
hash = crypto.SHA1
|
||||||
|
case SHA256:
|
||||||
|
hash = crypto.SHA256
|
||||||
|
case SHA384:
|
||||||
|
hash = crypto.SHA384
|
||||||
|
case SHA512:
|
||||||
|
hash = crypto.SHA512
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s := hash.New()
|
||||||
|
s.Write(owner)
|
||||||
|
s.Write(wire)
|
||||||
|
ds.Digest = hex.EncodeToString(s.Sum(nil))
|
||||||
|
return ds
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToCDNSKEY converts a DNSKEY record to a CDNSKEY record.
|
||||||
|
func (k *DNSKEY) ToCDNSKEY() *CDNSKEY {
|
||||||
|
c := &CDNSKEY{DNSKEY: *k}
|
||||||
|
c.Hdr = k.Hdr
|
||||||
|
c.Hdr.Rrtype = TypeCDNSKEY
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToCDS converts a DS record to a CDS record.
|
||||||
|
func (d *DS) ToCDS() *CDS {
|
||||||
|
c := &CDS{DS: *d}
|
||||||
|
c.Hdr = d.Hdr
|
||||||
|
c.Hdr.Rrtype = TypeCDS
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs an RRSet. The signature needs to be filled in with the values:
|
||||||
|
// Inception, Expiration, KeyTag, SignerName and Algorithm. The rest is copied
|
||||||
|
// from the RRset. Sign returns a non-nill error when the signing went OK.
|
||||||
|
// There is no check if RRSet is a proper (RFC 2181) RRSet. If OrigTTL is non
|
||||||
|
// zero, it is used as-is, otherwise the TTL of the RRset is used as the
|
||||||
|
// OrigTTL.
|
||||||
|
func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
|
||||||
|
if k == nil {
|
||||||
|
return ErrPrivKey
|
||||||
|
}
|
||||||
|
// s.Inception and s.Expiration may be 0 (rollover etc.), the rest must be set
|
||||||
|
if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
|
||||||
|
rr.Hdr.Rrtype = TypeRRSIG
|
||||||
|
rr.Hdr.Name = rrset[0].Header().Name
|
||||||
|
rr.Hdr.Class = rrset[0].Header().Class
|
||||||
|
if rr.OrigTtl == 0 { // If set don't override
|
||||||
|
rr.OrigTtl = rrset[0].Header().Ttl
|
||||||
|
}
|
||||||
|
rr.TypeCovered = rrset[0].Header().Rrtype
|
||||||
|
rr.Labels = uint8(CountLabel(rrset[0].Header().Name))
|
||||||
|
|
||||||
|
if strings.HasPrefix(rrset[0].Header().Name, "*") {
|
||||||
|
rr.Labels-- // wildcard, remove from label count
|
||||||
|
}
|
||||||
|
|
||||||
|
sigwire := new(rrsigWireFmt)
|
||||||
|
sigwire.TypeCovered = rr.TypeCovered
|
||||||
|
sigwire.Algorithm = rr.Algorithm
|
||||||
|
sigwire.Labels = rr.Labels
|
||||||
|
sigwire.OrigTtl = rr.OrigTtl
|
||||||
|
sigwire.Expiration = rr.Expiration
|
||||||
|
sigwire.Inception = rr.Inception
|
||||||
|
sigwire.KeyTag = rr.KeyTag
|
||||||
|
// For signing, lowercase this name
|
||||||
|
sigwire.SignerName = strings.ToLower(rr.SignerName)
|
||||||
|
|
||||||
|
// Create the desired binary blob
|
||||||
|
signdata := make([]byte, DefaultMsgSize)
|
||||||
|
n, err := packSigWire(sigwire, signdata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
signdata = signdata[:n]
|
||||||
|
wire, err := rawSignatureData(rrset, rr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, ok := AlgorithmToHash[rr.Algorithm]
|
||||||
|
if !ok {
|
||||||
|
return ErrAlg
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rr.Algorithm {
|
||||||
|
case ED25519:
|
||||||
|
// ed25519 signs the raw message and performs hashing internally.
|
||||||
|
// All other supported signature schemes operate over the pre-hashed
|
||||||
|
// message, and thus ed25519 must be handled separately here.
|
||||||
|
//
|
||||||
|
// The raw message is passed directly into sign and crypto.Hash(0) is
|
||||||
|
// used to signal to the crypto.Signer that the data has not been hashed.
|
||||||
|
signature, err := sign(k, append(signdata, wire...), crypto.Hash(0), rr.Algorithm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rr.Signature = toBase64(signature)
|
||||||
|
default:
|
||||||
|
h := hash.New()
|
||||||
|
h.Write(signdata)
|
||||||
|
h.Write(wire)
|
||||||
|
|
||||||
|
signature, err := sign(k, h.Sum(nil), hash, rr.Algorithm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rr.Signature = toBase64(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte, error) {
|
||||||
|
signature, err := k.Sign(rand.Reader, hashed, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch alg {
|
||||||
|
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
|
||||||
|
return signature, nil
|
||||||
|
|
||||||
|
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||||
|
ecdsaSignature := &struct {
|
||||||
|
R, S *big.Int
|
||||||
|
}{}
|
||||||
|
if _, err := asn1.Unmarshal(signature, ecdsaSignature); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var intlen int
|
||||||
|
switch alg {
|
||||||
|
case ECDSAP256SHA256:
|
||||||
|
intlen = 32
|
||||||
|
case ECDSAP384SHA384:
|
||||||
|
intlen = 48
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := intToBytes(ecdsaSignature.R, intlen)
|
||||||
|
signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...)
|
||||||
|
return signature, nil
|
||||||
|
|
||||||
|
// There is no defined interface for what a DSA backed crypto.Signer returns
|
||||||
|
case DSA, DSANSEC3SHA1:
|
||||||
|
// t := divRoundUp(divRoundUp(p.PublicKey.Y.BitLen(), 8)-64, 8)
|
||||||
|
// signature := []byte{byte(t)}
|
||||||
|
// signature = append(signature, intToBytes(r1, 20)...)
|
||||||
|
// signature = append(signature, intToBytes(s1, 20)...)
|
||||||
|
// rr.Signature = signature
|
||||||
|
|
||||||
|
case ED25519:
|
||||||
|
return signature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrAlg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify validates an RRSet with the signature and key. This is only the
|
||||||
|
// cryptographic test, the signature validity period must be checked separately.
|
||||||
|
// This function copies the rdata of some RRs (to lowercase domain names) for the validation to work.
|
||||||
|
func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
||||||
|
// First the easy checks
|
||||||
|
if !IsRRset(rrset) {
|
||||||
|
return ErrRRset
|
||||||
|
}
|
||||||
|
if rr.KeyTag != k.KeyTag() {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
if rr.Hdr.Class != k.Hdr.Class {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
if rr.Algorithm != k.Algorithm {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
if !strings.EqualFold(rr.SignerName, k.Hdr.Name) {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
if k.Protocol != 3 {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRRset checked that we have at least one RR and that the RRs in
|
||||||
|
// the set have consistent type, class, and name. Also check that type and
|
||||||
|
// class matches the RRSIG record.
|
||||||
|
if rrset[0].Header().Class != rr.Hdr.Class {
|
||||||
|
return ErrRRset
|
||||||
|
}
|
||||||
|
if rrset[0].Header().Rrtype != rr.TypeCovered {
|
||||||
|
return ErrRRset
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 4035 5.3.2. Reconstructing the Signed Data
|
||||||
|
// Copy the sig, except the rrsig data
|
||||||
|
sigwire := new(rrsigWireFmt)
|
||||||
|
sigwire.TypeCovered = rr.TypeCovered
|
||||||
|
sigwire.Algorithm = rr.Algorithm
|
||||||
|
sigwire.Labels = rr.Labels
|
||||||
|
sigwire.OrigTtl = rr.OrigTtl
|
||||||
|
sigwire.Expiration = rr.Expiration
|
||||||
|
sigwire.Inception = rr.Inception
|
||||||
|
sigwire.KeyTag = rr.KeyTag
|
||||||
|
sigwire.SignerName = strings.ToLower(rr.SignerName)
|
||||||
|
// Create the desired binary blob
|
||||||
|
signeddata := make([]byte, DefaultMsgSize)
|
||||||
|
n, err := packSigWire(sigwire, signeddata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
signeddata = signeddata[:n]
|
||||||
|
wire, err := rawSignatureData(rrset, rr)
|
||||||
|
if err != nil {
|
||||||
|
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?
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, ok := AlgorithmToHash[rr.Algorithm]
|
||||||
|
if !ok {
|
||||||
|
return ErrAlg
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rr.Algorithm {
|
||||||
|
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, RSAMD5:
|
||||||
|
// TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere??
|
||||||
|
pubkey := k.publicKeyRSA() // Get the key
|
||||||
|
if pubkey == nil {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
|
||||||
|
h := hash.New()
|
||||||
|
h.Write(signeddata)
|
||||||
|
h.Write(wire)
|
||||||
|
return rsa.VerifyPKCS1v15(pubkey, hash, h.Sum(nil), sigbuf)
|
||||||
|
|
||||||
|
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||||
|
pubkey := k.publicKeyECDSA()
|
||||||
|
if pubkey == nil {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split sigbuf into the r and s coordinates
|
||||||
|
r := new(big.Int).SetBytes(sigbuf[:len(sigbuf)/2])
|
||||||
|
s := new(big.Int).SetBytes(sigbuf[len(sigbuf)/2:])
|
||||||
|
|
||||||
|
h := hash.New()
|
||||||
|
h.Write(signeddata)
|
||||||
|
h.Write(wire)
|
||||||
|
if ecdsa.Verify(pubkey, h.Sum(nil), r, s) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrSig
|
||||||
|
|
||||||
|
case ED25519:
|
||||||
|
pubkey := k.publicKeyED25519()
|
||||||
|
if pubkey == nil {
|
||||||
|
return ErrKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if ed25519.Verify(pubkey, append(signeddata, wire...), sigbuf) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrSig
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ErrAlg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidityPeriod uses RFC1982 serial arithmetic to calculate
|
||||||
|
// if a signature period is valid. If t is the zero time, the
|
||||||
|
// current time is taken other t is. Returns true if the signature
|
||||||
|
// is valid at the given time, otherwise returns false.
|
||||||
|
func (rr *RRSIG) ValidityPeriod(t time.Time) bool {
|
||||||
|
var utc int64
|
||||||
|
if t.IsZero() {
|
||||||
|
utc = time.Now().UTC().Unix()
|
||||||
|
} else {
|
||||||
|
utc = t.UTC().Unix()
|
||||||
|
}
|
||||||
|
modi := (int64(rr.Inception) - utc) / year68
|
||||||
|
mode := (int64(rr.Expiration) - utc) / year68
|
||||||
|
ti := int64(rr.Inception) + modi*year68
|
||||||
|
te := int64(rr.Expiration) + mode*year68
|
||||||
|
return ti <= utc && utc <= te
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the signatures base64 encodedig sigdata as a byte slice.
|
||||||
|
func (rr *RRSIG) sigBuf() []byte {
|
||||||
|
sigbuf, err := fromBase64([]byte(rr.Signature))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return sigbuf
|
||||||
|
}
|
||||||
|
|
||||||
|
// publicKeyRSA returns the RSA public key from a DNSKEY record.
|
||||||
|
func (k *DNSKEY) publicKeyRSA() *rsa.PublicKey {
|
||||||
|
keybuf, err := fromBase64([]byte(k.PublicKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keybuf) < 1+1+64 {
|
||||||
|
// Exponent must be at least 1 byte and modulus at least 64
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 2537/3110, section 2. RSA Public KEY Resource Records
|
||||||
|
// Length is in the 0th byte, unless its zero, then it
|
||||||
|
// it in bytes 1 and 2 and its a 16 bit number
|
||||||
|
explen := uint16(keybuf[0])
|
||||||
|
keyoff := 1
|
||||||
|
if explen == 0 {
|
||||||
|
explen = uint16(keybuf[1])<<8 | uint16(keybuf[2])
|
||||||
|
keyoff = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if explen > 4 || explen == 0 || keybuf[keyoff] == 0 {
|
||||||
|
// Exponent larger than supported by the crypto package,
|
||||||
|
// empty, or contains prohibited leading zero.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
modoff := keyoff + int(explen)
|
||||||
|
modlen := len(keybuf) - modoff
|
||||||
|
if modlen < 64 || modlen > 512 || keybuf[modoff] == 0 {
|
||||||
|
// Modulus is too small, large, or contains prohibited leading zero.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey := new(rsa.PublicKey)
|
||||||
|
|
||||||
|
expo := uint64(0)
|
||||||
|
for i := 0; i < int(explen); i++ {
|
||||||
|
expo <<= 8
|
||||||
|
expo |= uint64(keybuf[keyoff+i])
|
||||||
|
}
|
||||||
|
if expo > 1<<31-1 {
|
||||||
|
// Larger exponent than supported by the crypto package.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pubkey.E = int(expo)
|
||||||
|
|
||||||
|
pubkey.N = big.NewInt(0)
|
||||||
|
pubkey.N.SetBytes(keybuf[modoff:])
|
||||||
|
|
||||||
|
return pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
// publicKeyECDSA returns the Curve public key from the DNSKEY record.
|
||||||
|
func (k *DNSKEY) publicKeyECDSA() *ecdsa.PublicKey {
|
||||||
|
keybuf, err := fromBase64([]byte(k.PublicKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pubkey := new(ecdsa.PublicKey)
|
||||||
|
switch k.Algorithm {
|
||||||
|
case ECDSAP256SHA256:
|
||||||
|
pubkey.Curve = elliptic.P256()
|
||||||
|
if len(keybuf) != 64 {
|
||||||
|
// wrongly encoded key
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case ECDSAP384SHA384:
|
||||||
|
pubkey.Curve = elliptic.P384()
|
||||||
|
if len(keybuf) != 96 {
|
||||||
|
// Wrongly encoded key
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pubkey.X = big.NewInt(0)
|
||||||
|
pubkey.X.SetBytes(keybuf[:len(keybuf)/2])
|
||||||
|
pubkey.Y = big.NewInt(0)
|
||||||
|
pubkey.Y.SetBytes(keybuf[len(keybuf)/2:])
|
||||||
|
return pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *DNSKEY) publicKeyDSA() *dsa.PublicKey {
|
||||||
|
keybuf, err := fromBase64([]byte(k.PublicKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(keybuf) < 22 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, keybuf := int(keybuf[0]), keybuf[1:]
|
||||||
|
size := 64 + t*8
|
||||||
|
q, keybuf := keybuf[:20], keybuf[20:]
|
||||||
|
if len(keybuf) != 3*size {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p, keybuf := keybuf[:size], keybuf[size:]
|
||||||
|
g, y := keybuf[:size], keybuf[size:]
|
||||||
|
pubkey := new(dsa.PublicKey)
|
||||||
|
pubkey.Parameters.Q = big.NewInt(0).SetBytes(q)
|
||||||
|
pubkey.Parameters.P = big.NewInt(0).SetBytes(p)
|
||||||
|
pubkey.Parameters.G = big.NewInt(0).SetBytes(g)
|
||||||
|
pubkey.Y = big.NewInt(0).SetBytes(y)
|
||||||
|
return pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *DNSKEY) publicKeyED25519() ed25519.PublicKey {
|
||||||
|
keybuf, err := fromBase64([]byte(k.PublicKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(keybuf) != ed25519.PublicKeySize {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return keybuf
|
||||||
|
}
|
||||||
|
|
||||||
|
type wireSlice [][]byte
|
||||||
|
|
||||||
|
func (p wireSlice) Len() int { return len(p) }
|
||||||
|
func (p wireSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
func (p wireSlice) Less(i, j int) bool {
|
||||||
|
_, ioff, _ := UnpackDomainName(p[i], 0)
|
||||||
|
_, joff, _ := UnpackDomainName(p[j], 0)
|
||||||
|
return bytes.Compare(p[i][ioff+10:], p[j][joff+10:]) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the raw signature data.
|
||||||
|
func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) {
|
||||||
|
wires := make(wireSlice, len(rrset))
|
||||||
|
for i, r := range rrset {
|
||||||
|
r1 := r.copy()
|
||||||
|
r1.Header().Ttl = s.OrigTtl
|
||||||
|
labels := SplitDomainName(r1.Header().Name)
|
||||||
|
// 6.2. Canonical RR Form. (4) - wildcards
|
||||||
|
if len(labels) > int(s.Labels) {
|
||||||
|
// Wildcard
|
||||||
|
r1.Header().Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "."
|
||||||
|
}
|
||||||
|
// RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase
|
||||||
|
r1.Header().Name = strings.ToLower(r1.Header().Name)
|
||||||
|
// 6.2. Canonical RR Form. (3) - domain rdata to lowercase.
|
||||||
|
// NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
|
||||||
|
// HINFO, MINFO, MX, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
|
||||||
|
// SRV, DNAME, A6
|
||||||
|
//
|
||||||
|
// RFC 6840 - Clarifications and Implementation Notes for DNS Security (DNSSEC):
|
||||||
|
// Section 6.2 of [RFC4034] also erroneously lists HINFO as a record
|
||||||
|
// that needs conversion to lowercase, and twice at that. Since HINFO
|
||||||
|
// records contain no domain names, they are not subject to case
|
||||||
|
// conversion.
|
||||||
|
switch x := r1.(type) {
|
||||||
|
case *NS:
|
||||||
|
x.Ns = strings.ToLower(x.Ns)
|
||||||
|
case *MD:
|
||||||
|
x.Md = strings.ToLower(x.Md)
|
||||||
|
case *MF:
|
||||||
|
x.Mf = strings.ToLower(x.Mf)
|
||||||
|
case *CNAME:
|
||||||
|
x.Target = strings.ToLower(x.Target)
|
||||||
|
case *SOA:
|
||||||
|
x.Ns = strings.ToLower(x.Ns)
|
||||||
|
x.Mbox = strings.ToLower(x.Mbox)
|
||||||
|
case *MB:
|
||||||
|
x.Mb = strings.ToLower(x.Mb)
|
||||||
|
case *MG:
|
||||||
|
x.Mg = strings.ToLower(x.Mg)
|
||||||
|
case *MR:
|
||||||
|
x.Mr = strings.ToLower(x.Mr)
|
||||||
|
case *PTR:
|
||||||
|
x.Ptr = strings.ToLower(x.Ptr)
|
||||||
|
case *MINFO:
|
||||||
|
x.Rmail = strings.ToLower(x.Rmail)
|
||||||
|
x.Email = strings.ToLower(x.Email)
|
||||||
|
case *MX:
|
||||||
|
x.Mx = strings.ToLower(x.Mx)
|
||||||
|
case *RP:
|
||||||
|
x.Mbox = strings.ToLower(x.Mbox)
|
||||||
|
x.Txt = strings.ToLower(x.Txt)
|
||||||
|
case *AFSDB:
|
||||||
|
x.Hostname = strings.ToLower(x.Hostname)
|
||||||
|
case *RT:
|
||||||
|
x.Host = strings.ToLower(x.Host)
|
||||||
|
case *SIG:
|
||||||
|
x.SignerName = strings.ToLower(x.SignerName)
|
||||||
|
case *PX:
|
||||||
|
x.Map822 = strings.ToLower(x.Map822)
|
||||||
|
x.Mapx400 = strings.ToLower(x.Mapx400)
|
||||||
|
case *NAPTR:
|
||||||
|
x.Replacement = strings.ToLower(x.Replacement)
|
||||||
|
case *KX:
|
||||||
|
x.Exchanger = strings.ToLower(x.Exchanger)
|
||||||
|
case *SRV:
|
||||||
|
x.Target = strings.ToLower(x.Target)
|
||||||
|
case *DNAME:
|
||||||
|
x.Target = strings.ToLower(x.Target)
|
||||||
|
}
|
||||||
|
// 6.2. Canonical RR Form. (5) - origTTL
|
||||||
|
wire := make([]byte, r1.len()+1) // +1 to be safe(r)
|
||||||
|
off, err1 := PackRR(r1, wire, 0, nil, false)
|
||||||
|
if err1 != nil {
|
||||||
|
return nil, err1
|
||||||
|
}
|
||||||
|
wire = wire[:off]
|
||||||
|
wires[i] = wire
|
||||||
|
}
|
||||||
|
sort.Sort(wires)
|
||||||
|
for i, wire := range wires {
|
||||||
|
if i > 0 && bytes.Equal(wire, wires[i-1]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf = append(buf, wire...)
|
||||||
|
}
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func packSigWire(sw *rrsigWireFmt, msg []byte) (int, error) {
|
||||||
|
// copied from zmsg.go RRSIG packing
|
||||||
|
off, err := packUint16(sw.TypeCovered, msg, 0)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packUint8(sw.Algorithm, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packUint8(sw.Labels, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packUint32(sw.OrigTtl, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packUint32(sw.Expiration, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packUint32(sw.Inception, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packUint16(sw.KeyTag, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = PackDomainName(sw.SignerName, msg, off, nil, false)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func packKeyWire(dw *dnskeyWireFmt, msg []byte) (int, error) {
|
||||||
|
// copied from zmsg.go DNSKEY packing
|
||||||
|
off, err := packUint16(dw.Flags, msg, 0)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packUint8(dw.Protocol, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packUint8(dw.Algorithm, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
off, err = packStringBase64(dw.PublicKey, msg, off)
|
||||||
|
if err != nil {
|
||||||
|
return off, err
|
||||||
|
}
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
178
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/dnssec_keygen.go
generated
vendored
Normal file
178
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/dnssec_keygen.go
generated
vendored
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generate generates a DNSKEY of the given bit size.
|
||||||
|
// The public part is put inside the DNSKEY record.
|
||||||
|
// The Algorithm in the key must be set as this will define
|
||||||
|
// what kind of DNSKEY will be generated.
|
||||||
|
// The ECDSA algorithms imply a fixed keysize, in that case
|
||||||
|
// bits should be set to the size of the algorithm.
|
||||||
|
func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
|
||||||
|
switch k.Algorithm {
|
||||||
|
case DSA, DSANSEC3SHA1:
|
||||||
|
if bits != 1024 {
|
||||||
|
return nil, ErrKeySize
|
||||||
|
}
|
||||||
|
case RSAMD5, RSASHA1, RSASHA256, RSASHA1NSEC3SHA1:
|
||||||
|
if bits < 512 || bits > 4096 {
|
||||||
|
return nil, ErrKeySize
|
||||||
|
}
|
||||||
|
case RSASHA512:
|
||||||
|
if bits < 1024 || bits > 4096 {
|
||||||
|
return nil, ErrKeySize
|
||||||
|
}
|
||||||
|
case ECDSAP256SHA256:
|
||||||
|
if bits != 256 {
|
||||||
|
return nil, ErrKeySize
|
||||||
|
}
|
||||||
|
case ECDSAP384SHA384:
|
||||||
|
if bits != 384 {
|
||||||
|
return nil, ErrKeySize
|
||||||
|
}
|
||||||
|
case ED25519:
|
||||||
|
if bits != 256 {
|
||||||
|
return nil, ErrKeySize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k.Algorithm {
|
||||||
|
case DSA, DSANSEC3SHA1:
|
||||||
|
params := new(dsa.Parameters)
|
||||||
|
if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
priv := new(dsa.PrivateKey)
|
||||||
|
priv.PublicKey.Parameters = *params
|
||||||
|
err := dsa.GenerateKey(priv, rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k.setPublicKeyDSA(params.Q, params.P, params.G, priv.PublicKey.Y)
|
||||||
|
return priv, nil
|
||||||
|
case RSAMD5, RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1:
|
||||||
|
priv, err := rsa.GenerateKey(rand.Reader, bits)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k.setPublicKeyRSA(priv.PublicKey.E, priv.PublicKey.N)
|
||||||
|
return priv, nil
|
||||||
|
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||||
|
var c elliptic.Curve
|
||||||
|
switch k.Algorithm {
|
||||||
|
case ECDSAP256SHA256:
|
||||||
|
c = elliptic.P256()
|
||||||
|
case ECDSAP384SHA384:
|
||||||
|
c = elliptic.P384()
|
||||||
|
}
|
||||||
|
priv, err := ecdsa.GenerateKey(c, rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k.setPublicKeyECDSA(priv.PublicKey.X, priv.PublicKey.Y)
|
||||||
|
return priv, nil
|
||||||
|
case ED25519:
|
||||||
|
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k.setPublicKeyED25519(pub)
|
||||||
|
return priv, nil
|
||||||
|
default:
|
||||||
|
return nil, ErrAlg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the public key (the value E and N)
|
||||||
|
func (k *DNSKEY) setPublicKeyRSA(_E int, _N *big.Int) bool {
|
||||||
|
if _E == 0 || _N == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
buf := exponentToBuf(_E)
|
||||||
|
buf = append(buf, _N.Bytes()...)
|
||||||
|
k.PublicKey = toBase64(buf)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the public key for Elliptic Curves
|
||||||
|
func (k *DNSKEY) setPublicKeyECDSA(_X, _Y *big.Int) bool {
|
||||||
|
if _X == nil || _Y == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var intlen int
|
||||||
|
switch k.Algorithm {
|
||||||
|
case ECDSAP256SHA256:
|
||||||
|
intlen = 32
|
||||||
|
case ECDSAP384SHA384:
|
||||||
|
intlen = 48
|
||||||
|
}
|
||||||
|
k.PublicKey = toBase64(curveToBuf(_X, _Y, intlen))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the public key for DSA
|
||||||
|
func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool {
|
||||||
|
if _Q == nil || _P == nil || _G == nil || _Y == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
buf := dsaToBuf(_Q, _P, _G, _Y)
|
||||||
|
k.PublicKey = toBase64(buf)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the public key for Ed25519
|
||||||
|
func (k *DNSKEY) setPublicKeyED25519(_K ed25519.PublicKey) bool {
|
||||||
|
if _K == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
k.PublicKey = toBase64(_K)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the public key (the values E and N) for RSA
|
||||||
|
// RFC 3110: Section 2. RSA Public KEY Resource Records
|
||||||
|
func exponentToBuf(_E int) []byte {
|
||||||
|
var buf []byte
|
||||||
|
i := big.NewInt(int64(_E)).Bytes()
|
||||||
|
if len(i) < 256 {
|
||||||
|
buf = make([]byte, 1, 1+len(i))
|
||||||
|
buf[0] = uint8(len(i))
|
||||||
|
} else {
|
||||||
|
buf = make([]byte, 3, 3+len(i))
|
||||||
|
buf[0] = 0
|
||||||
|
buf[1] = uint8(len(i) >> 8)
|
||||||
|
buf[2] = uint8(len(i))
|
||||||
|
}
|
||||||
|
buf = append(buf, i...)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the public key for X and Y for Curve. The two
|
||||||
|
// values are just concatenated.
|
||||||
|
func curveToBuf(_X, _Y *big.Int, intlen int) []byte {
|
||||||
|
buf := intToBytes(_X, intlen)
|
||||||
|
buf = append(buf, intToBytes(_Y, intlen)...)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the public key for X and Y for Curve. The two
|
||||||
|
// values are just concatenated.
|
||||||
|
func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte {
|
||||||
|
t := divRoundUp(divRoundUp(_G.BitLen(), 8)-64, 8)
|
||||||
|
buf := []byte{byte(t)}
|
||||||
|
buf = append(buf, intToBytes(_Q, 20)...)
|
||||||
|
buf = append(buf, intToBytes(_P, 64+t*8)...)
|
||||||
|
buf = append(buf, intToBytes(_G, 64+t*8)...)
|
||||||
|
buf = append(buf, intToBytes(_Y, 64+t*8)...)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
352
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/dnssec_keyscan.go
generated
vendored
Normal file
352
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/dnssec_keyscan.go
generated
vendored
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto"
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPrivateKey returns a PrivateKey by parsing the string s.
|
||||||
|
// s should be in the same form of the BIND private key files.
|
||||||
|
func (k *DNSKEY) NewPrivateKey(s string) (crypto.PrivateKey, error) {
|
||||||
|
if s == "" || s[len(s)-1] != '\n' { // We need a closing newline
|
||||||
|
return k.ReadPrivateKey(strings.NewReader(s+"\n"), "")
|
||||||
|
}
|
||||||
|
return k.ReadPrivateKey(strings.NewReader(s), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPrivateKey reads a private key from the io.Reader q. The string file is
|
||||||
|
// only used in error reporting.
|
||||||
|
// The public key must be known, because some cryptographic algorithms embed
|
||||||
|
// the public inside the privatekey.
|
||||||
|
func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, error) {
|
||||||
|
m, err := parseKey(q, file)
|
||||||
|
if m == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, ok := m["private-key-format"]; !ok {
|
||||||
|
return nil, ErrPrivKey
|
||||||
|
}
|
||||||
|
if m["private-key-format"] != "v1.2" && m["private-key-format"] != "v1.3" {
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrPrivKey
|
||||||
|
}
|
||||||
|
switch uint8(algo) {
|
||||||
|
case DSA:
|
||||||
|
priv, err := readPrivateKeyDSA(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pub := k.publicKeyDSA()
|
||||||
|
if pub == nil {
|
||||||
|
return nil, ErrKey
|
||||||
|
}
|
||||||
|
priv.PublicKey = *pub
|
||||||
|
return priv, nil
|
||||||
|
case RSAMD5:
|
||||||
|
fallthrough
|
||||||
|
case RSASHA1:
|
||||||
|
fallthrough
|
||||||
|
case RSASHA1NSEC3SHA1:
|
||||||
|
fallthrough
|
||||||
|
case RSASHA256:
|
||||||
|
fallthrough
|
||||||
|
case RSASHA512:
|
||||||
|
priv, err := readPrivateKeyRSA(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pub := k.publicKeyRSA()
|
||||||
|
if pub == nil {
|
||||||
|
return nil, ErrKey
|
||||||
|
}
|
||||||
|
priv.PublicKey = *pub
|
||||||
|
return priv, nil
|
||||||
|
case ECCGOST:
|
||||||
|
return nil, ErrPrivKey
|
||||||
|
case ECDSAP256SHA256:
|
||||||
|
fallthrough
|
||||||
|
case ECDSAP384SHA384:
|
||||||
|
priv, err := readPrivateKeyECDSA(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pub := k.publicKeyECDSA()
|
||||||
|
if pub == nil {
|
||||||
|
return nil, ErrKey
|
||||||
|
}
|
||||||
|
priv.PublicKey = *pub
|
||||||
|
return priv, nil
|
||||||
|
case ED25519:
|
||||||
|
return readPrivateKeyED25519(m)
|
||||||
|
default:
|
||||||
|
return nil, ErrPrivKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a private key (file) string and create a public key. Return the private key.
|
||||||
|
func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) {
|
||||||
|
p := new(rsa.PrivateKey)
|
||||||
|
p.Primes = []*big.Int{nil, nil}
|
||||||
|
for k, v := range m {
|
||||||
|
switch k {
|
||||||
|
case "modulus", "publicexponent", "privateexponent", "prime1", "prime2":
|
||||||
|
v1, err := fromBase64([]byte(v))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch k {
|
||||||
|
case "modulus":
|
||||||
|
p.PublicKey.N = big.NewInt(0)
|
||||||
|
p.PublicKey.N.SetBytes(v1)
|
||||||
|
case "publicexponent":
|
||||||
|
i := big.NewInt(0)
|
||||||
|
i.SetBytes(v1)
|
||||||
|
p.PublicKey.E = int(i.Int64()) // int64 should be large enough
|
||||||
|
case "privateexponent":
|
||||||
|
p.D = big.NewInt(0)
|
||||||
|
p.D.SetBytes(v1)
|
||||||
|
case "prime1":
|
||||||
|
p.Primes[0] = big.NewInt(0)
|
||||||
|
p.Primes[0].SetBytes(v1)
|
||||||
|
case "prime2":
|
||||||
|
p.Primes[1] = big.NewInt(0)
|
||||||
|
p.Primes[1].SetBytes(v1)
|
||||||
|
}
|
||||||
|
case "exponent1", "exponent2", "coefficient":
|
||||||
|
// not used in Go (yet)
|
||||||
|
case "created", "publish", "activate":
|
||||||
|
// not used in Go (yet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPrivateKeyDSA(m map[string]string) (*dsa.PrivateKey, error) {
|
||||||
|
p := new(dsa.PrivateKey)
|
||||||
|
p.X = big.NewInt(0)
|
||||||
|
for k, v := range m {
|
||||||
|
switch k {
|
||||||
|
case "private_value(x)":
|
||||||
|
v1, err := fromBase64([]byte(v))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.X.SetBytes(v1)
|
||||||
|
case "created", "publish", "activate":
|
||||||
|
/* not used in Go (yet) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) {
|
||||||
|
p := new(ecdsa.PrivateKey)
|
||||||
|
p.D = big.NewInt(0)
|
||||||
|
// TODO: validate that the required flags are present
|
||||||
|
for k, v := range m {
|
||||||
|
switch k {
|
||||||
|
case "privatekey":
|
||||||
|
v1, err := fromBase64([]byte(v))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.D.SetBytes(v1)
|
||||||
|
case "created", "publish", "activate":
|
||||||
|
/* not used in Go (yet) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPrivateKeyED25519(m map[string]string) (ed25519.PrivateKey, error) {
|
||||||
|
var p ed25519.PrivateKey
|
||||||
|
// TODO: validate that the required flags are present
|
||||||
|
for k, v := range m {
|
||||||
|
switch k {
|
||||||
|
case "privatekey":
|
||||||
|
p1, err := fromBase64([]byte(v))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(p1) != ed25519.SeedSize {
|
||||||
|
return nil, ErrPrivKey
|
||||||
|
}
|
||||||
|
p = ed25519.NewKeyFromSeed(p1)
|
||||||
|
case "created", "publish", "activate":
|
||||||
|
/* not used in Go (yet) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseKey reads a private key from r. It returns a map[string]string,
|
||||||
|
// with the key-value pairs, or an error when the file is not correct.
|
||||||
|
func parseKey(r io.Reader, file string) (map[string]string, error) {
|
||||||
|
m := make(map[string]string)
|
||||||
|
var k string
|
||||||
|
|
||||||
|
c := newKLexer(r)
|
||||||
|
|
||||||
|
for l, ok := c.Next(); ok; l, ok = c.Next() {
|
||||||
|
// It should alternate
|
||||||
|
switch l.value {
|
||||||
|
case zKey:
|
||||||
|
k = l.token
|
||||||
|
case zValue:
|
||||||
|
if k == "" {
|
||||||
|
return nil, &ParseError{file, "no private key seen", l}
|
||||||
|
}
|
||||||
|
|
||||||
|
m[strings.ToLower(k)] = l.token
|
||||||
|
k = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surface any read errors from r.
|
||||||
|
if err := c.Err(); err != nil {
|
||||||
|
return nil, &ParseError{file: file, err: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type klexer struct {
|
||||||
|
br io.ByteReader
|
||||||
|
|
||||||
|
readErr error
|
||||||
|
|
||||||
|
line int
|
||||||
|
column int
|
||||||
|
|
||||||
|
key bool
|
||||||
|
|
||||||
|
eol bool // end-of-line
|
||||||
|
}
|
||||||
|
|
||||||
|
func newKLexer(r io.Reader) *klexer {
|
||||||
|
br, ok := r.(io.ByteReader)
|
||||||
|
if !ok {
|
||||||
|
br = bufio.NewReaderSize(r, 1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &klexer{
|
||||||
|
br: br,
|
||||||
|
|
||||||
|
line: 1,
|
||||||
|
|
||||||
|
key: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kl *klexer) Err() error {
|
||||||
|
if kl.readErr == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return kl.readErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// readByte returns the next byte from the input
|
||||||
|
func (kl *klexer) readByte() (byte, bool) {
|
||||||
|
if kl.readErr != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := kl.br.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
kl.readErr = err
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// delay the newline handling until the next token is delivered,
|
||||||
|
// fixes off-by-one errors when reporting a parse error.
|
||||||
|
if kl.eol {
|
||||||
|
kl.line++
|
||||||
|
kl.column = 0
|
||||||
|
kl.eol = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == '\n' {
|
||||||
|
kl.eol = true
|
||||||
|
} else {
|
||||||
|
kl.column++
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kl *klexer) Next() (lex, bool) {
|
||||||
|
var (
|
||||||
|
l lex
|
||||||
|
|
||||||
|
str strings.Builder
|
||||||
|
|
||||||
|
commt bool
|
||||||
|
)
|
||||||
|
|
||||||
|
for x, ok := kl.readByte(); ok; x, ok = kl.readByte() {
|
||||||
|
l.line, l.column = kl.line, kl.column
|
||||||
|
|
||||||
|
switch x {
|
||||||
|
case ':':
|
||||||
|
if commt || !kl.key {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
kl.key = false
|
||||||
|
|
||||||
|
// Next token is a space, eat it
|
||||||
|
kl.readByte()
|
||||||
|
|
||||||
|
l.value = zKey
|
||||||
|
l.token = str.String()
|
||||||
|
return l, true
|
||||||
|
case ';':
|
||||||
|
commt = true
|
||||||
|
case '\n':
|
||||||
|
if commt {
|
||||||
|
// Reset a comment
|
||||||
|
commt = false
|
||||||
|
}
|
||||||
|
|
||||||
|
kl.key = true
|
||||||
|
|
||||||
|
l.value = zValue
|
||||||
|
l.token = str.String()
|
||||||
|
return l, true
|
||||||
|
default:
|
||||||
|
if commt {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
str.WriteByte(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if kl.readErr != nil && kl.readErr != io.EOF {
|
||||||
|
// Don't return any tokens after a read error occurs.
|
||||||
|
return lex{value: zEOF}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if str.Len() > 0 {
|
||||||
|
// Send remainder
|
||||||
|
l.value = zValue
|
||||||
|
l.token = str.String()
|
||||||
|
return l, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return lex{value: zEOF}, false
|
||||||
|
}
|
||||||
93
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/dnssec_privkey.go
generated
vendored
Normal file
93
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/dnssec_privkey.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
const format = "Private-key-format: v1.3\n"
|
||||||
|
|
||||||
|
// PrivateKeyString converts a PrivateKey to a string. This string has the same
|
||||||
|
// format as the private-key-file of BIND9 (Private-key-format: v1.3).
|
||||||
|
// It needs some info from the key (the algorithm), so its a method of the DNSKEY
|
||||||
|
// It supports rsa.PrivateKey, ecdsa.PrivateKey and dsa.PrivateKey
|
||||||
|
func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string {
|
||||||
|
algorithm := strconv.Itoa(int(r.Algorithm))
|
||||||
|
algorithm += " (" + AlgorithmToString[r.Algorithm] + ")"
|
||||||
|
|
||||||
|
switch p := p.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
modulus := toBase64(p.PublicKey.N.Bytes())
|
||||||
|
e := big.NewInt(int64(p.PublicKey.E))
|
||||||
|
publicExponent := toBase64(e.Bytes())
|
||||||
|
privateExponent := toBase64(p.D.Bytes())
|
||||||
|
prime1 := toBase64(p.Primes[0].Bytes())
|
||||||
|
prime2 := toBase64(p.Primes[1].Bytes())
|
||||||
|
// Calculate Exponent1/2 and Coefficient as per: http://en.wikipedia.org/wiki/RSA#Using_the_Chinese_remainder_algorithm
|
||||||
|
// and from: http://code.google.com/p/go/issues/detail?id=987
|
||||||
|
one := big.NewInt(1)
|
||||||
|
p1 := big.NewInt(0).Sub(p.Primes[0], one)
|
||||||
|
q1 := big.NewInt(0).Sub(p.Primes[1], one)
|
||||||
|
exp1 := big.NewInt(0).Mod(p.D, p1)
|
||||||
|
exp2 := big.NewInt(0).Mod(p.D, q1)
|
||||||
|
coeff := big.NewInt(0).ModInverse(p.Primes[1], p.Primes[0])
|
||||||
|
|
||||||
|
exponent1 := toBase64(exp1.Bytes())
|
||||||
|
exponent2 := toBase64(exp2.Bytes())
|
||||||
|
coefficient := toBase64(coeff.Bytes())
|
||||||
|
|
||||||
|
return format +
|
||||||
|
"Algorithm: " + algorithm + "\n" +
|
||||||
|
"Modulus: " + modulus + "\n" +
|
||||||
|
"PublicExponent: " + publicExponent + "\n" +
|
||||||
|
"PrivateExponent: " + privateExponent + "\n" +
|
||||||
|
"Prime1: " + prime1 + "\n" +
|
||||||
|
"Prime2: " + prime2 + "\n" +
|
||||||
|
"Exponent1: " + exponent1 + "\n" +
|
||||||
|
"Exponent2: " + exponent2 + "\n" +
|
||||||
|
"Coefficient: " + coefficient + "\n"
|
||||||
|
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
var intlen int
|
||||||
|
switch r.Algorithm {
|
||||||
|
case ECDSAP256SHA256:
|
||||||
|
intlen = 32
|
||||||
|
case ECDSAP384SHA384:
|
||||||
|
intlen = 48
|
||||||
|
}
|
||||||
|
private := toBase64(intToBytes(p.D, intlen))
|
||||||
|
return format +
|
||||||
|
"Algorithm: " + algorithm + "\n" +
|
||||||
|
"PrivateKey: " + private + "\n"
|
||||||
|
|
||||||
|
case *dsa.PrivateKey:
|
||||||
|
T := divRoundUp(divRoundUp(p.PublicKey.Parameters.G.BitLen(), 8)-64, 8)
|
||||||
|
prime := toBase64(intToBytes(p.PublicKey.Parameters.P, 64+T*8))
|
||||||
|
subprime := toBase64(intToBytes(p.PublicKey.Parameters.Q, 20))
|
||||||
|
base := toBase64(intToBytes(p.PublicKey.Parameters.G, 64+T*8))
|
||||||
|
priv := toBase64(intToBytes(p.X, 20))
|
||||||
|
pub := toBase64(intToBytes(p.PublicKey.Y, 64+T*8))
|
||||||
|
return format +
|
||||||
|
"Algorithm: " + algorithm + "\n" +
|
||||||
|
"Prime(p): " + prime + "\n" +
|
||||||
|
"Subprime(q): " + subprime + "\n" +
|
||||||
|
"Base(g): " + base + "\n" +
|
||||||
|
"Private_value(x): " + priv + "\n" +
|
||||||
|
"Public_value(y): " + pub + "\n"
|
||||||
|
|
||||||
|
case ed25519.PrivateKey:
|
||||||
|
private := toBase64(p.Seed())
|
||||||
|
return format +
|
||||||
|
"Algorithm: " + algorithm + "\n" +
|
||||||
|
"PrivateKey: " + private + "\n"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
269
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/doc.go
generated
vendored
Normal file
269
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/doc.go
generated
vendored
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
/*
|
||||||
|
Package dns implements a full featured interface to the Domain Name System.
|
||||||
|
Both server- and client-side programming is supported. The package allows
|
||||||
|
complete control over what is sent out to the DNS. The API follows the
|
||||||
|
less-is-more principle, by presenting a small, clean interface.
|
||||||
|
|
||||||
|
It supports (asynchronous) querying/replying, incoming/outgoing zone transfers,
|
||||||
|
TSIG, EDNS0, dynamic updates, notifies and DNSSEC validation/signing.
|
||||||
|
|
||||||
|
Note that domain names MUST be fully qualified before sending them, unqualified
|
||||||
|
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."
|
||||||
|
|
||||||
|
Or directly from a string:
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
Or even:
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
Or when not certain if the domain name is fully qualified:
|
||||||
|
|
||||||
|
m.SetQuestion(dns.Fqdn("miek.nl"), dns.TypeMX)
|
||||||
|
|
||||||
|
The message m is now a message with the question section set to ask the MX
|
||||||
|
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}
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
Suppressing multiple outstanding queries (with the same question, type and
|
||||||
|
class) is as easy as setting:
|
||||||
|
|
||||||
|
c.SingleInflight = true
|
||||||
|
|
||||||
|
More advanced options are available using a net.Dialer and the corresponding API.
|
||||||
|
For example it is possible to set a timeout, or to specify a source IP address
|
||||||
|
and port to use for the connection:
|
||||||
|
|
||||||
|
c := new(dns.Client)
|
||||||
|
laddr := net.UDPAddr{
|
||||||
|
IP: net.ParseIP("[::1]"),
|
||||||
|
Port: 12345,
|
||||||
|
Zone: "",
|
||||||
|
}
|
||||||
|
c.Dialer := &net.Dialer{
|
||||||
|
Timeout: 200 * time.Millisecond,
|
||||||
|
LocalAddr: &laddr,
|
||||||
|
}
|
||||||
|
in, rtt, err := c.Exchange(m1, "8.8.8.8:53")
|
||||||
|
|
||||||
|
If these "advanced" features are not needed, a simple UDP query can be sent,
|
||||||
|
with:
|
||||||
|
|
||||||
|
in, err := dns.Exchange(m1, "127.0.0.1:53")
|
||||||
|
|
||||||
|
When this functions returns you will get dns message. A dns message consists
|
||||||
|
out of four sections.
|
||||||
|
The question section: in.Question, the answer section: in.Answer,
|
||||||
|
the authority section: in.Ns and the additional section: in.Extra.
|
||||||
|
|
||||||
|
Each of these sections (except the Question section) contain a []RR. Basic
|
||||||
|
use pattern for accessing the rdata of a TXT RR as the first RR in
|
||||||
|
the Answer section:
|
||||||
|
|
||||||
|
if t, ok := in.Answer[0].(*dns.TXT); ok {
|
||||||
|
// do something with t.Txt
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
For TXT character strings, tabs, carriage returns and line feeds will be
|
||||||
|
converted to \t, \r and \n respectively. Back slashes and quotations marks will
|
||||||
|
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 (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
|
||||||
|
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)
|
||||||
|
|
||||||
|
Signature generation, signature verification and key generation are all supported.
|
||||||
|
|
||||||
|
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
|
||||||
|
the Additional is not renamed. See RFC 2136 for the gory details.
|
||||||
|
|
||||||
|
You can set a rather complex set of rules for the existence of absence of
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
An TSIG or transaction signature adds a HMAC TSIG record to each message sent.
|
||||||
|
The supported algorithms include: HmacMD5, HmacSHA1, HmacSHA256 and HmacSHA512.
|
||||||
|
|
||||||
|
Basic use pattern when querying with a TSIG name "axfr." (note that these key names
|
||||||
|
must be fully qualified - as they are domain names) and the base64 secret
|
||||||
|
"so6ZGir4GPAqINNh9U5c3A==":
|
||||||
|
|
||||||
|
If an incoming message contains a TSIG record it MUST be the last record in
|
||||||
|
the additional section (RFC2845 3.2). This means that you should make the
|
||||||
|
call to SetTsig last, right before executing the query. If you make any
|
||||||
|
changes to the RRset after calling SetTsig() the signature will be incorrect.
|
||||||
|
|
||||||
|
c := new(dns.Client)
|
||||||
|
c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetQuestion("miek.nl.", dns.TypeMX)
|
||||||
|
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
|
||||||
|
...
|
||||||
|
// When sending the TSIG RR is calculated and filled in before sending
|
||||||
|
|
||||||
|
When requesting an zone transfer (almost all TSIG usage is when requesting zone
|
||||||
|
transfers), with TSIG, this is the basic use pattern. In this example we
|
||||||
|
request an AXFR for miek.nl. with TSIG key named "axfr." and secret
|
||||||
|
"so6ZGir4GPAqINNh9U5c3A==" and using the server 176.58.119.54:
|
||||||
|
|
||||||
|
t := new(dns.Transfer)
|
||||||
|
m := new(dns.Msg)
|
||||||
|
t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
||||||
|
m.SetAxfr("miek.nl.")
|
||||||
|
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
|
||||||
|
c, err := t.In(m, "176.58.119.54:53")
|
||||||
|
for r := range c { ... }
|
||||||
|
|
||||||
|
You can now read the records from the transfer as they come in. Each envelope
|
||||||
|
is checked with TSIG. If something is not correct an error is returned.
|
||||||
|
|
||||||
|
Basic use pattern validating and replying to a message that has TSIG set.
|
||||||
|
|
||||||
|
server := &dns.Server{Addr: ":53", Net: "udp"}
|
||||||
|
server.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
|
||||||
|
go server.ListenAndServe()
|
||||||
|
dns.HandleFunc(".", handleRequest)
|
||||||
|
|
||||||
|
func handleRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetReply(r)
|
||||||
|
if r.IsTsig() != nil {
|
||||||
|
if w.TsigStatus() == nil {
|
||||||
|
// *Msg r has an TSIG record and it was validated
|
||||||
|
m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
|
||||||
|
} else {
|
||||||
|
// *Msg r has an TSIG records and it was not valided
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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 is an extension mechanism for the DNS defined in RFC 2671 and updated by
|
||||||
|
RFC 6891. It defines an new RR type, the OPT RR, which is then completely
|
||||||
|
abused.
|
||||||
|
|
||||||
|
Basic use pattern for creating an (empty) OPT RR:
|
||||||
|
|
||||||
|
o := new(dns.OPT)
|
||||||
|
o.Hdr.Name = "." // MUST be the root zone, per definition.
|
||||||
|
o.Hdr.Rrtype = dns.TypeOPT
|
||||||
|
|
||||||
|
The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891) interfaces.
|
||||||
|
Currently only a few have been standardized: EDNS0_NSID (RFC 5001) and
|
||||||
|
EDNS0_SUBNET (draft-vandergaast-edns-client-subnet-02). Note that these options
|
||||||
|
may be combined in an OPT RR. Basic use pattern for a server to check if (and
|
||||||
|
which) options are set:
|
||||||
|
|
||||||
|
// o is a dns.OPT
|
||||||
|
for _, s := range o.Option {
|
||||||
|
switch e := s.(type) {
|
||||||
|
case *dns.EDNS0_NSID:
|
||||||
|
// do stuff with e.Nsid
|
||||||
|
case *dns.EDNS0_SUBNET:
|
||||||
|
// access e.Family, e.Address, etc.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
It works like TSIG, except that SIG(0) uses public key cryptography, instead of
|
||||||
|
the shared secret approach in TSIG. Supported algorithms: DSA, ECDSAP256SHA256,
|
||||||
|
ECDSAP384SHA384, RSASHA1, RSASHA256 and RSASHA512.
|
||||||
|
|
||||||
|
Signing subsequent messages in multi-message sessions is not implemented.
|
||||||
|
*/
|
||||||
|
package dns
|
||||||
25
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/duplicate.go
generated
vendored
Normal file
25
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/duplicate.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
//go:generate go run duplicate_generate.go
|
||||||
|
|
||||||
|
// IsDuplicate checks of r1 and r2 are duplicates of each other, excluding the TTL.
|
||||||
|
// So this means the header data is equal *and* the RDATA is the same. Return true
|
||||||
|
// is so, otherwise false.
|
||||||
|
// It's is a protocol violation to have identical RRs in a message.
|
||||||
|
func IsDuplicate(r1, r2 RR) bool {
|
||||||
|
if r1.Header().Class != r2.Header().Class {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.Header().Rrtype != r2.Header().Rrtype {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !isDulicateName(r1.Header().Name, r2.Header().Name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// ignore TTL
|
||||||
|
|
||||||
|
return isDuplicateRdata(r1, r2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDulicateName checks if the domain names s1 and s2 are equal.
|
||||||
|
func isDulicateName(s1, s2 string) bool { return equal(s1, s2) }
|
||||||
158
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/duplicate_generate.go
generated
vendored
Normal file
158
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/duplicate_generate.go
generated
vendored
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
//+build ignore
|
||||||
|
|
||||||
|
// types_generate.go is meant to run with go generate. It will use
|
||||||
|
// go/{importer,types} to track down all the RR struct types. Then for each type
|
||||||
|
// it will generate conversion tables (TypeToRR and TypeToString) and banal
|
||||||
|
// methods (len, Header, copy) based on the struct tags. The generated source is
|
||||||
|
// written to ztypes.go, and is meant to be checked into git.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"go/importer"
|
||||||
|
"go/types"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var packageHdr = `
|
||||||
|
// Code generated by "go run duplicate_generate.go"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
|
||||||
|
st, ok := t.Underlying().(*types.Struct)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
|
||||||
|
return st, false
|
||||||
|
}
|
||||||
|
if st.Field(0).Anonymous() {
|
||||||
|
st, _ := getTypeStruct(st.Field(0).Type(), scope)
|
||||||
|
return st, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Import and type-check the package
|
||||||
|
pkg, err := importer.Default().Import("github.com/miekg/dns")
|
||||||
|
fatalIfErr(err)
|
||||||
|
scope := pkg.Scope()
|
||||||
|
|
||||||
|
// Collect actual types (*X)
|
||||||
|
var namedTypes []string
|
||||||
|
for _, name := range scope.Names() {
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
if o == nil || !o.Exported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if st, _ := getTypeStruct(o.Type(), scope); st == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "PrivateRR" || name == "RFC3597" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if name == "OPT" || name == "ANY" || name == "IXFR" || name == "AXFR" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
namedTypes = append(namedTypes, o.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
b.WriteString(packageHdr)
|
||||||
|
|
||||||
|
// Generate the giant switch that calls the correct function for each type.
|
||||||
|
fmt.Fprint(b, "// isDuplicateRdata calls the rdata specific functions\n")
|
||||||
|
fmt.Fprint(b, "func isDuplicateRdata(r1, r2 RR) bool {\n")
|
||||||
|
fmt.Fprint(b, "switch r1.Header().Rrtype {\n")
|
||||||
|
|
||||||
|
for _, name := range namedTypes {
|
||||||
|
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
_, isEmbedded := getTypeStruct(o.Type(), scope)
|
||||||
|
if isEmbedded {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "case Type%s:\nreturn isDuplicate%s(r1.(*%s), r2.(*%s))\n", name, name, name, name)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "}\nreturn false\n}\n")
|
||||||
|
|
||||||
|
// Generate the duplicate check for each type.
|
||||||
|
fmt.Fprint(b, "// isDuplicate() functions\n\n")
|
||||||
|
for _, name := range namedTypes {
|
||||||
|
|
||||||
|
o := scope.Lookup(name)
|
||||||
|
st, isEmbedded := getTypeStruct(o.Type(), scope)
|
||||||
|
if isEmbedded {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "func isDuplicate%s(r1, r2 *%s) bool {\n", name, name)
|
||||||
|
for i := 1; i < st.NumFields(); i++ {
|
||||||
|
field := st.Field(i).Name()
|
||||||
|
o2 := func(s string) { fmt.Fprintf(b, s+"\n", field, field) }
|
||||||
|
o3 := func(s string) { fmt.Fprintf(b, s+"\n", field, field, field) }
|
||||||
|
|
||||||
|
// For some reason, a and aaaa don't pop up as *types.Slice here (mostly like because the are
|
||||||
|
// *indirectly* defined as a slice in the net package).
|
||||||
|
if _, ok := st.Field(i).Type().(*types.Slice); ok || st.Tag(i) == `dns:"a"` || st.Tag(i) == `dns:"aaaa"` {
|
||||||
|
o2("if len(r1.%s) != len(r2.%s) {\nreturn false\n}")
|
||||||
|
|
||||||
|
if st.Tag(i) == `dns:"cdomain-name"` || st.Tag(i) == `dns:"domain-name"` {
|
||||||
|
o3(`for i := 0; i < len(r1.%s); i++ {
|
||||||
|
if !isDulicateName(r1.%s[i], r2.%s[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
o3(`for i := 0; i < len(r1.%s); i++ {
|
||||||
|
if r1.%s[i] != r2.%s[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch st.Tag(i) {
|
||||||
|
case `dns:"-"`:
|
||||||
|
// ignored
|
||||||
|
case `dns:"cdomain-name"`, `dns:"domain-name"`:
|
||||||
|
o2("if !isDulicateName(r1.%s, r2.%s) {\nreturn false\n}")
|
||||||
|
default:
|
||||||
|
o2("if r1.%s != r2.%s {\nreturn false\n}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "return true\n}\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// gofmt
|
||||||
|
res, err := format.Source(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
b.WriteTo(os.Stderr)
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// write result
|
||||||
|
f, err := os.Create("zduplicate.go")
|
||||||
|
fatalIfErr(err)
|
||||||
|
defer f.Close()
|
||||||
|
f.Write(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatalIfErr(err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
623
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/edns.go
generated
vendored
Normal file
623
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/edns.go
generated
vendored
Normal file
@ -0,0 +1,623 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EDNS0 Option codes.
|
||||||
|
const (
|
||||||
|
EDNS0LLQ = 0x1 // long lived queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01
|
||||||
|
EDNS0UL = 0x2 // update lease draft: http://files.dns-sd.org/draft-sekar-dns-ul.txt
|
||||||
|
EDNS0NSID = 0x3 // nsid (See RFC 5001)
|
||||||
|
EDNS0DAU = 0x5 // DNSSEC Algorithm Understood
|
||||||
|
EDNS0DHU = 0x6 // DS Hash Understood
|
||||||
|
EDNS0N3U = 0x7 // NSEC3 Hash Understood
|
||||||
|
EDNS0SUBNET = 0x8 // client-subnet (See RFC 7871)
|
||||||
|
EDNS0EXPIRE = 0x9 // EDNS0 expire
|
||||||
|
EDNS0COOKIE = 0xa // EDNS0 Cookie
|
||||||
|
EDNS0TCPKEEPALIVE = 0xb // EDNS0 tcp keep alive (See RFC 7828)
|
||||||
|
EDNS0PADDING = 0xc // EDNS0 padding (See RFC 7830)
|
||||||
|
EDNS0LOCALSTART = 0xFDE9 // Beginning of range reserved for local/experimental use (See RFC 6891)
|
||||||
|
EDNS0LOCALEND = 0xFFFE // End of range reserved for local/experimental use (See RFC 6891)
|
||||||
|
_DO = 1 << 15 // DNSSEC OK
|
||||||
|
)
|
||||||
|
|
||||||
|
// OPT is the EDNS0 RR appended to messages to convey extra (meta) information.
|
||||||
|
// See RFC 6891.
|
||||||
|
type OPT struct {
|
||||||
|
Hdr RR_Header
|
||||||
|
Option []EDNS0 `dns:"opt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *OPT) String() string {
|
||||||
|
s := "\n;; OPT PSEUDOSECTION:\n; EDNS: version " + strconv.Itoa(int(rr.Version())) + "; "
|
||||||
|
if rr.Do() {
|
||||||
|
s += "flags: do; "
|
||||||
|
} else {
|
||||||
|
s += "flags: ; "
|
||||||
|
}
|
||||||
|
s += "udp: " + strconv.Itoa(int(rr.UDPSize()))
|
||||||
|
|
||||||
|
for _, o := range rr.Option {
|
||||||
|
switch o.(type) {
|
||||||
|
case *EDNS0_NSID:
|
||||||
|
s += "\n; NSID: " + o.String()
|
||||||
|
h, e := o.pack()
|
||||||
|
var r string
|
||||||
|
if e == nil {
|
||||||
|
for _, c := range h {
|
||||||
|
r += "(" + string(c) + ")"
|
||||||
|
}
|
||||||
|
s += " " + r
|
||||||
|
}
|
||||||
|
case *EDNS0_SUBNET:
|
||||||
|
s += "\n; SUBNET: " + o.String()
|
||||||
|
case *EDNS0_COOKIE:
|
||||||
|
s += "\n; COOKIE: " + o.String()
|
||||||
|
case *EDNS0_UL:
|
||||||
|
s += "\n; UPDATE LEASE: " + o.String()
|
||||||
|
case *EDNS0_LLQ:
|
||||||
|
s += "\n; LONG LIVED QUERIES: " + o.String()
|
||||||
|
case *EDNS0_DAU:
|
||||||
|
s += "\n; DNSSEC ALGORITHM UNDERSTOOD: " + o.String()
|
||||||
|
case *EDNS0_DHU:
|
||||||
|
s += "\n; DS HASH UNDERSTOOD: " + o.String()
|
||||||
|
case *EDNS0_N3U:
|
||||||
|
s += "\n; NSEC3 HASH UNDERSTOOD: " + o.String()
|
||||||
|
case *EDNS0_LOCAL:
|
||||||
|
s += "\n; LOCAL OPT: " + o.String()
|
||||||
|
case *EDNS0_PADDING:
|
||||||
|
s += "\n; PADDING: " + o.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *OPT) len() int {
|
||||||
|
l := rr.Hdr.len()
|
||||||
|
for i := 0; i < len(rr.Option); i++ {
|
||||||
|
l += 4 // Account for 2-byte option code and 2-byte option length.
|
||||||
|
lo, _ := rr.Option[i].pack()
|
||||||
|
l += len(lo)
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the old value -> delete SetVersion?
|
||||||
|
|
||||||
|
// Version returns the EDNS version used. Only zero is defined.
|
||||||
|
func (rr *OPT) Version() uint8 {
|
||||||
|
return uint8(rr.Hdr.Ttl & 0x00FF0000 >> 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVersion sets the version of EDNS. This is usually zero.
|
||||||
|
func (rr *OPT) SetVersion(v uint8) {
|
||||||
|
rr.Hdr.Ttl = rr.Hdr.Ttl&0xFF00FFFF | uint32(v)<<16
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtendedRcode returns the EDNS extended RCODE field (the upper 8 bits of the TTL).
|
||||||
|
func (rr *OPT) ExtendedRcode() int {
|
||||||
|
return int(rr.Hdr.Ttl&0xFF000000>>24) << 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExtendedRcode sets the EDNS extended RCODE field.
|
||||||
|
//
|
||||||
|
// If the RCODE is not an extended RCODE, will reset the extended RCODE field to 0.
|
||||||
|
func (rr *OPT) SetExtendedRcode(v uint16) {
|
||||||
|
rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | uint32(v>>4)<<24
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDPSize returns the UDP buffer size.
|
||||||
|
func (rr *OPT) UDPSize() uint16 {
|
||||||
|
return rr.Hdr.Class
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUDPSize sets the UDP buffer size.
|
||||||
|
func (rr *OPT) SetUDPSize(size uint16) {
|
||||||
|
rr.Hdr.Class = size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do returns the value of the DO (DNSSEC OK) bit.
|
||||||
|
func (rr *OPT) Do() bool {
|
||||||
|
return rr.Hdr.Ttl&_DO == _DO
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (rr *OPT) SetDo(do ...bool) {
|
||||||
|
if len(do) == 1 {
|
||||||
|
if do[0] {
|
||||||
|
rr.Hdr.Ttl |= _DO
|
||||||
|
} else {
|
||||||
|
rr.Hdr.Ttl &^= _DO
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rr.Hdr.Ttl |= _DO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to it.
|
||||||
|
type EDNS0 interface {
|
||||||
|
// Option returns the option code for the option.
|
||||||
|
Option() uint16
|
||||||
|
// pack returns the bytes of the option data.
|
||||||
|
pack() ([]byte, error)
|
||||||
|
// unpack sets the data as found in the buffer. Is also sets
|
||||||
|
// the length of the slice as the length of the option data.
|
||||||
|
unpack([]byte) error
|
||||||
|
// String returns the string representation of the option.
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDNS0_NSID option is used to retrieve a nameserver
|
||||||
|
// identifier. When sending a request Nsid must be set to the empty string
|
||||||
|
// The identifier is an opaque string encoded as hex.
|
||||||
|
// Basic use pattern for creating an nsid option:
|
||||||
|
//
|
||||||
|
// o := new(dns.OPT)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.TypeOPT
|
||||||
|
// e := new(dns.EDNS0_NSID)
|
||||||
|
// e.Code = dns.EDNS0NSID
|
||||||
|
// e.Nsid = "AA"
|
||||||
|
// o.Option = append(o.Option, e)
|
||||||
|
type EDNS0_NSID struct {
|
||||||
|
Code uint16 // Always EDNS0NSID
|
||||||
|
Nsid string // This string needs to be hex encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_NSID) pack() ([]byte, error) {
|
||||||
|
h, err := hex.DecodeString(e.Nsid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option implements the EDNS0 interface.
|
||||||
|
func (e *EDNS0_NSID) Option() uint16 { return EDNS0NSID } // Option returns the option code.
|
||||||
|
func (e *EDNS0_NSID) unpack(b []byte) error { e.Nsid = hex.EncodeToString(b); return nil }
|
||||||
|
func (e *EDNS0_NSID) String() string { return string(e.Nsid) }
|
||||||
|
|
||||||
|
// EDNS0_SUBNET is the subnet option that is used to give the remote nameserver
|
||||||
|
// an idea of where the client lives. See RFC 7871. It can then give back a different
|
||||||
|
// answer depending on the location or network topology.
|
||||||
|
// Basic use pattern for creating an subnet option:
|
||||||
|
//
|
||||||
|
// o := new(dns.OPT)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.TypeOPT
|
||||||
|
// e := new(dns.EDNS0_SUBNET)
|
||||||
|
// e.Code = dns.EDNS0SUBNET
|
||||||
|
// e.Family = 1 // 1 for IPv4 source address, 2 for IPv6
|
||||||
|
// e.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6
|
||||||
|
// e.SourceScope = 0
|
||||||
|
// e.Address = net.ParseIP("127.0.0.1").To4() // for IPv4
|
||||||
|
// // e.Address = net.ParseIP("2001:7b8:32a::2") // for IPV6
|
||||||
|
// o.Option = append(o.Option, e)
|
||||||
|
//
|
||||||
|
// This code will parse all the available bits when unpacking (up to optlen).
|
||||||
|
// When packing it will apply SourceNetmask. If you need more advanced logic,
|
||||||
|
// patches welcome and good luck.
|
||||||
|
type EDNS0_SUBNET struct {
|
||||||
|
Code uint16 // Always EDNS0SUBNET
|
||||||
|
Family uint16 // 1 for IP, 2 for IP6
|
||||||
|
SourceNetmask uint8
|
||||||
|
SourceScope uint8
|
||||||
|
Address net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option implements the EDNS0 interface.
|
||||||
|
func (e *EDNS0_SUBNET) Option() uint16 { return EDNS0SUBNET }
|
||||||
|
|
||||||
|
func (e *EDNS0_SUBNET) pack() ([]byte, error) {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint16(b[0:], e.Family)
|
||||||
|
b[2] = e.SourceNetmask
|
||||||
|
b[3] = e.SourceScope
|
||||||
|
switch e.Family {
|
||||||
|
case 0:
|
||||||
|
// "dig" sets AddressFamily to 0 if SourceNetmask is also 0
|
||||||
|
// We might don't need to complain either
|
||||||
|
if e.SourceNetmask != 0 {
|
||||||
|
return nil, errors.New("dns: bad address family")
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
if e.SourceNetmask > net.IPv4len*8 {
|
||||||
|
return nil, errors.New("dns: bad netmask")
|
||||||
|
}
|
||||||
|
if len(e.Address.To4()) != net.IPv4len {
|
||||||
|
return nil, errors.New("dns: bad address")
|
||||||
|
}
|
||||||
|
ip := e.Address.To4().Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv4len*8))
|
||||||
|
needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up
|
||||||
|
b = append(b, ip[:needLength]...)
|
||||||
|
case 2:
|
||||||
|
if e.SourceNetmask > net.IPv6len*8 {
|
||||||
|
return nil, errors.New("dns: bad netmask")
|
||||||
|
}
|
||||||
|
if len(e.Address) != net.IPv6len {
|
||||||
|
return nil, errors.New("dns: bad address")
|
||||||
|
}
|
||||||
|
ip := e.Address.Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv6len*8))
|
||||||
|
needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up
|
||||||
|
b = append(b, ip[:needLength]...)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("dns: bad address family")
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_SUBNET) unpack(b []byte) error {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return ErrBuf
|
||||||
|
}
|
||||||
|
e.Family = binary.BigEndian.Uint16(b)
|
||||||
|
e.SourceNetmask = b[2]
|
||||||
|
e.SourceScope = b[3]
|
||||||
|
switch e.Family {
|
||||||
|
case 0:
|
||||||
|
// "dig" sets AddressFamily to 0 if SourceNetmask is also 0
|
||||||
|
// It's okay to accept such a packet
|
||||||
|
if e.SourceNetmask != 0 {
|
||||||
|
return errors.New("dns: bad address family")
|
||||||
|
}
|
||||||
|
e.Address = net.IPv4(0, 0, 0, 0)
|
||||||
|
case 1:
|
||||||
|
if e.SourceNetmask > net.IPv4len*8 || e.SourceScope > net.IPv4len*8 {
|
||||||
|
return errors.New("dns: bad netmask")
|
||||||
|
}
|
||||||
|
addr := make(net.IP, net.IPv4len)
|
||||||
|
copy(addr, b[4:])
|
||||||
|
e.Address = addr.To16()
|
||||||
|
case 2:
|
||||||
|
if e.SourceNetmask > net.IPv6len*8 || e.SourceScope > net.IPv6len*8 {
|
||||||
|
return errors.New("dns: bad netmask")
|
||||||
|
}
|
||||||
|
addr := make(net.IP, net.IPv6len)
|
||||||
|
copy(addr, b[4:])
|
||||||
|
e.Address = addr
|
||||||
|
default:
|
||||||
|
return errors.New("dns: bad address family")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_SUBNET) String() (s string) {
|
||||||
|
if e.Address == nil {
|
||||||
|
s = "<nil>"
|
||||||
|
} else if e.Address.To4() != nil {
|
||||||
|
s = e.Address.String()
|
||||||
|
} else {
|
||||||
|
s = "[" + e.Address.String() + "]"
|
||||||
|
}
|
||||||
|
s += "/" + strconv.Itoa(int(e.SourceNetmask)) + "/" + strconv.Itoa(int(e.SourceScope))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The EDNS0_COOKIE option is used to add a DNS Cookie to a message.
|
||||||
|
//
|
||||||
|
// o := new(dns.OPT)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.TypeOPT
|
||||||
|
// e := new(dns.EDNS0_COOKIE)
|
||||||
|
// e.Code = dns.EDNS0COOKIE
|
||||||
|
// e.Cookie = "24a5ac.."
|
||||||
|
// o.Option = append(o.Option, e)
|
||||||
|
//
|
||||||
|
// The Cookie field consists out of a client cookie (RFC 7873 Section 4), that is
|
||||||
|
// always 8 bytes. It may then optionally be followed by the server cookie. The server
|
||||||
|
// cookie is of variable length, 8 to a maximum of 32 bytes. In other words:
|
||||||
|
//
|
||||||
|
// cCookie := o.Cookie[:16]
|
||||||
|
// sCookie := o.Cookie[16:]
|
||||||
|
//
|
||||||
|
// There is no guarantee that the Cookie string has a specific length.
|
||||||
|
type EDNS0_COOKIE struct {
|
||||||
|
Code uint16 // Always EDNS0COOKIE
|
||||||
|
Cookie string // Hex-encoded cookie data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_COOKIE) pack() ([]byte, error) {
|
||||||
|
h, err := hex.DecodeString(e.Cookie)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option implements the EDNS0 interface.
|
||||||
|
func (e *EDNS0_COOKIE) Option() uint16 { return EDNS0COOKIE }
|
||||||
|
func (e *EDNS0_COOKIE) unpack(b []byte) error { e.Cookie = hex.EncodeToString(b); return nil }
|
||||||
|
func (e *EDNS0_COOKIE) String() string { return e.Cookie }
|
||||||
|
|
||||||
|
// The EDNS0_UL (Update Lease) (draft RFC) option is used to tell the server to set
|
||||||
|
// an expiration on an update RR. This is helpful for clients that cannot clean
|
||||||
|
// up after themselves. This is a draft RFC and more information can be found at
|
||||||
|
// http://files.dns-sd.org/draft-sekar-dns-ul.txt
|
||||||
|
//
|
||||||
|
// o := new(dns.OPT)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.TypeOPT
|
||||||
|
// e := new(dns.EDNS0_UL)
|
||||||
|
// e.Code = dns.EDNS0UL
|
||||||
|
// e.Lease = 120 // in seconds
|
||||||
|
// o.Option = append(o.Option, e)
|
||||||
|
type EDNS0_UL struct {
|
||||||
|
Code uint16 // Always EDNS0UL
|
||||||
|
Lease uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option implements the EDNS0 interface.
|
||||||
|
func (e *EDNS0_UL) Option() uint16 { return EDNS0UL }
|
||||||
|
func (e *EDNS0_UL) String() string { return strconv.FormatUint(uint64(e.Lease), 10) }
|
||||||
|
|
||||||
|
// Copied: http://golang.org/src/pkg/net/dnsmsg.go
|
||||||
|
func (e *EDNS0_UL) pack() ([]byte, error) {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(b, e.Lease)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_UL) unpack(b []byte) error {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return ErrBuf
|
||||||
|
}
|
||||||
|
e.Lease = binary.BigEndian.Uint32(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDNS0_LLQ stands for Long Lived Queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01
|
||||||
|
// Implemented for completeness, as the EDNS0 type code is assigned.
|
||||||
|
type EDNS0_LLQ struct {
|
||||||
|
Code uint16 // Always EDNS0LLQ
|
||||||
|
Version uint16
|
||||||
|
Opcode uint16
|
||||||
|
Error uint16
|
||||||
|
Id uint64
|
||||||
|
LeaseLife uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option implements the EDNS0 interface.
|
||||||
|
func (e *EDNS0_LLQ) Option() uint16 { return EDNS0LLQ }
|
||||||
|
|
||||||
|
func (e *EDNS0_LLQ) pack() ([]byte, error) {
|
||||||
|
b := make([]byte, 18)
|
||||||
|
binary.BigEndian.PutUint16(b[0:], e.Version)
|
||||||
|
binary.BigEndian.PutUint16(b[2:], e.Opcode)
|
||||||
|
binary.BigEndian.PutUint16(b[4:], e.Error)
|
||||||
|
binary.BigEndian.PutUint64(b[6:], e.Id)
|
||||||
|
binary.BigEndian.PutUint32(b[14:], e.LeaseLife)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_LLQ) unpack(b []byte) error {
|
||||||
|
if len(b) < 18 {
|
||||||
|
return ErrBuf
|
||||||
|
}
|
||||||
|
e.Version = binary.BigEndian.Uint16(b[0:])
|
||||||
|
e.Opcode = binary.BigEndian.Uint16(b[2:])
|
||||||
|
e.Error = binary.BigEndian.Uint16(b[4:])
|
||||||
|
e.Id = binary.BigEndian.Uint64(b[6:])
|
||||||
|
e.LeaseLife = binary.BigEndian.Uint32(b[14:])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_LLQ) String() string {
|
||||||
|
s := strconv.FormatUint(uint64(e.Version), 10) + " " + strconv.FormatUint(uint64(e.Opcode), 10) +
|
||||||
|
" " + strconv.FormatUint(uint64(e.Error), 10) + " " + strconv.FormatUint(uint64(e.Id), 10) +
|
||||||
|
" " + strconv.FormatUint(uint64(e.LeaseLife), 10)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDNS0_DUA implements the EDNS0 "DNSSEC Algorithm Understood" option. See RFC 6975.
|
||||||
|
type EDNS0_DAU struct {
|
||||||
|
Code uint16 // Always EDNS0DAU
|
||||||
|
AlgCode []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) String() string {
|
||||||
|
s := ""
|
||||||
|
for i := 0; i < len(e.AlgCode); i++ {
|
||||||
|
if a, ok := AlgorithmToString[e.AlgCode[i]]; ok {
|
||||||
|
s += " " + a
|
||||||
|
} else {
|
||||||
|
s += " " + strconv.Itoa(int(e.AlgCode[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDNS0_DHU implements the EDNS0 "DS Hash Understood" option. See RFC 6975.
|
||||||
|
type EDNS0_DHU struct {
|
||||||
|
Code uint16 // Always EDNS0DHU
|
||||||
|
AlgCode []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) String() string {
|
||||||
|
s := ""
|
||||||
|
for i := 0; i < len(e.AlgCode); i++ {
|
||||||
|
if a, ok := HashToString[e.AlgCode[i]]; ok {
|
||||||
|
s += " " + a
|
||||||
|
} else {
|
||||||
|
s += " " + strconv.Itoa(int(e.AlgCode[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDNS0_N3U implements the EDNS0 "NSEC3 Hash Understood" option. See RFC 6975.
|
||||||
|
type EDNS0_N3U struct {
|
||||||
|
Code uint16 // Always EDNS0N3U
|
||||||
|
AlgCode []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) String() string {
|
||||||
|
// Re-use the hash map
|
||||||
|
s := ""
|
||||||
|
for i := 0; i < len(e.AlgCode); i++ {
|
||||||
|
if a, ok := HashToString[e.AlgCode[i]]; ok {
|
||||||
|
s += " " + a
|
||||||
|
} else {
|
||||||
|
s += " " + strconv.Itoa(int(e.AlgCode[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDNS0_EXPIRE implementes the EDNS0 option as described in RFC 7314.
|
||||||
|
type EDNS0_EXPIRE struct {
|
||||||
|
Code uint16 // Always EDNS0EXPIRE
|
||||||
|
Expire uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option implements the EDNS0 interface.
|
||||||
|
func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE }
|
||||||
|
func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) }
|
||||||
|
|
||||||
|
func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
b[0] = byte(e.Expire >> 24)
|
||||||
|
b[1] = byte(e.Expire >> 16)
|
||||||
|
b[2] = byte(e.Expire >> 8)
|
||||||
|
b[3] = byte(e.Expire)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_EXPIRE) unpack(b []byte) error {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return ErrBuf
|
||||||
|
}
|
||||||
|
e.Expire = binary.BigEndian.Uint32(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The EDNS0_LOCAL option is used for local/experimental purposes. The option
|
||||||
|
// code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND]
|
||||||
|
// (RFC6891), although any unassigned code can actually be used. The content of
|
||||||
|
// the option is made available in Data, unaltered.
|
||||||
|
// Basic use pattern for creating a local option:
|
||||||
|
//
|
||||||
|
// o := new(dns.OPT)
|
||||||
|
// o.Hdr.Name = "."
|
||||||
|
// o.Hdr.Rrtype = dns.TypeOPT
|
||||||
|
// e := new(dns.EDNS0_LOCAL)
|
||||||
|
// e.Code = dns.EDNS0LOCALSTART
|
||||||
|
// e.Data = []byte{72, 82, 74}
|
||||||
|
// o.Option = append(o.Option, e)
|
||||||
|
type EDNS0_LOCAL struct {
|
||||||
|
Code uint16
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDNS0_TCP_KEEPALIVE is an EDNS0 option that instructs the server to keep
|
||||||
|
// the TCP connection alive. See RFC 7828.
|
||||||
|
type EDNS0_TCP_KEEPALIVE struct {
|
||||||
|
Code uint16 // Always EDNSTCPKEEPALIVE
|
||||||
|
Length uint16 // the value 0 if the TIMEOUT is omitted, the value 2 if it is present;
|
||||||
|
Timeout uint16 // an idle timeout value for the TCP connection, specified in units of 100 milliseconds, encoded in network byte order.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option implements the EDNS0 interface.
|
||||||
|
func (e *EDNS0_TCP_KEEPALIVE) Option() uint16 { return EDNS0TCPKEEPALIVE }
|
||||||
|
|
||||||
|
func (e *EDNS0_TCP_KEEPALIVE) pack() ([]byte, error) {
|
||||||
|
if e.Timeout != 0 && e.Length != 2 {
|
||||||
|
return nil, errors.New("dns: timeout specified but length is not 2")
|
||||||
|
}
|
||||||
|
if e.Timeout == 0 && e.Length != 0 {
|
||||||
|
return nil, errors.New("dns: timeout not specified but length is not 0")
|
||||||
|
}
|
||||||
|
b := make([]byte, 4+e.Length)
|
||||||
|
binary.BigEndian.PutUint16(b[0:], e.Code)
|
||||||
|
binary.BigEndian.PutUint16(b[2:], e.Length)
|
||||||
|
if e.Length == 2 {
|
||||||
|
binary.BigEndian.PutUint16(b[4:], e.Timeout)
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_TCP_KEEPALIVE) unpack(b []byte) error {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return ErrBuf
|
||||||
|
}
|
||||||
|
e.Length = binary.BigEndian.Uint16(b[2:4])
|
||||||
|
if e.Length != 0 && e.Length != 2 {
|
||||||
|
return errors.New("dns: length mismatch, want 0/2 but got " + strconv.FormatUint(uint64(e.Length), 10))
|
||||||
|
}
|
||||||
|
if e.Length == 2 {
|
||||||
|
if len(b) < 6 {
|
||||||
|
return ErrBuf
|
||||||
|
}
|
||||||
|
e.Timeout = binary.BigEndian.Uint16(b[4:6])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EDNS0_TCP_KEEPALIVE) String() (s string) {
|
||||||
|
s = "use tcp keep-alive"
|
||||||
|
if e.Length == 0 {
|
||||||
|
s += ", timeout omitted"
|
||||||
|
} else {
|
||||||
|
s += fmt.Sprintf(", timeout %dms", e.Timeout*100)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDNS0_PADDING option is used to add padding to a request/response. The default
|
||||||
|
// value of padding SHOULD be 0x0 but other values MAY be used, for instance if
|
||||||
|
// compression is applied before encryption which may break signatures.
|
||||||
|
type EDNS0_PADDING struct {
|
||||||
|
Padding []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) String() string { return fmt.Sprintf("%0X", e.Padding) }
|
||||||
87
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/format.go
generated
vendored
Normal file
87
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/format.go
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NumField returns the number of rdata fields r has.
|
||||||
|
func NumField(r RR) int {
|
||||||
|
return reflect.ValueOf(r).Elem().NumField() - 1 // Remove RR_Header
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field returns the rdata field i as a string. Fields are indexed starting from 1.
|
||||||
|
// RR types that holds slice data, for instance the NSEC type bitmap will return a single
|
||||||
|
// string where the types are concatenated using a space.
|
||||||
|
// Accessing non existing fields will cause a panic.
|
||||||
|
func Field(r RR, i int) string {
|
||||||
|
if i == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
d := reflect.ValueOf(r).Elem().Field(i)
|
||||||
|
switch k := d.Kind(); k {
|
||||||
|
case reflect.String:
|
||||||
|
return d.String()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return strconv.FormatInt(d.Int(), 10)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
return strconv.FormatUint(d.Uint(), 10)
|
||||||
|
case reflect.Slice:
|
||||||
|
switch reflect.ValueOf(r).Elem().Type().Field(i).Tag {
|
||||||
|
case `dns:"a"`:
|
||||||
|
// TODO(miek): Hmm store this as 16 bytes
|
||||||
|
if d.Len() < net.IPv6len {
|
||||||
|
return net.IPv4(byte(d.Index(0).Uint()),
|
||||||
|
byte(d.Index(1).Uint()),
|
||||||
|
byte(d.Index(2).Uint()),
|
||||||
|
byte(d.Index(3).Uint())).String()
|
||||||
|
}
|
||||||
|
return net.IPv4(byte(d.Index(12).Uint()),
|
||||||
|
byte(d.Index(13).Uint()),
|
||||||
|
byte(d.Index(14).Uint()),
|
||||||
|
byte(d.Index(15).Uint())).String()
|
||||||
|
case `dns:"aaaa"`:
|
||||||
|
return net.IP{
|
||||||
|
byte(d.Index(0).Uint()),
|
||||||
|
byte(d.Index(1).Uint()),
|
||||||
|
byte(d.Index(2).Uint()),
|
||||||
|
byte(d.Index(3).Uint()),
|
||||||
|
byte(d.Index(4).Uint()),
|
||||||
|
byte(d.Index(5).Uint()),
|
||||||
|
byte(d.Index(6).Uint()),
|
||||||
|
byte(d.Index(7).Uint()),
|
||||||
|
byte(d.Index(8).Uint()),
|
||||||
|
byte(d.Index(9).Uint()),
|
||||||
|
byte(d.Index(10).Uint()),
|
||||||
|
byte(d.Index(11).Uint()),
|
||||||
|
byte(d.Index(12).Uint()),
|
||||||
|
byte(d.Index(13).Uint()),
|
||||||
|
byte(d.Index(14).Uint()),
|
||||||
|
byte(d.Index(15).Uint()),
|
||||||
|
}.String()
|
||||||
|
case `dns:"nsec"`:
|
||||||
|
if d.Len() == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
s := Type(d.Index(0).Uint()).String()
|
||||||
|
for i := 1; i < d.Len(); i++ {
|
||||||
|
s += " " + Type(d.Index(i).Uint()).String()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
default:
|
||||||
|
// if it does not have a tag its a string slice
|
||||||
|
fallthrough
|
||||||
|
case `dns:"txt"`:
|
||||||
|
if d.Len() == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
s := d.Index(0).String()
|
||||||
|
for i := 1; i < d.Len(); i++ {
|
||||||
|
s += " " + d.Index(i).String()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
23
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/fuzz.go
generated
vendored
Normal file
23
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/fuzz.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// +build fuzz
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
func Fuzz(data []byte) int {
|
||||||
|
msg := new(Msg)
|
||||||
|
|
||||||
|
if err := msg.Unpack(data); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if _, err := msg.Pack(); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzNewRR(data []byte) int {
|
||||||
|
if _, err := NewRR(string(data)); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
242
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/generate.go
generated
vendored
Normal file
242
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/generate.go
generated
vendored
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse the $GENERATE statement as used in BIND9 zones.
|
||||||
|
// See http://www.zytrax.com/books/dns/ch8/generate.html for instance.
|
||||||
|
// We are called after '$GENERATE '. After which we expect:
|
||||||
|
// * the range (12-24/2)
|
||||||
|
// * lhs (ownername)
|
||||||
|
// * [[ttl][class]]
|
||||||
|
// * type
|
||||||
|
// * rhs (rdata)
|
||||||
|
// But we are lazy here, only the range is parsed *all* occurrences
|
||||||
|
// of $ after that are interpreted.
|
||||||
|
func (zp *ZoneParser) generate(l lex) (RR, bool) {
|
||||||
|
token := l.token
|
||||||
|
step := 1
|
||||||
|
if i := strings.IndexByte(token, '/'); i >= 0 {
|
||||||
|
if i+1 == len(token) {
|
||||||
|
return zp.setParseError("bad step in $GENERATE range", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := strconv.Atoi(token[i+1:])
|
||||||
|
if err != nil || s <= 0 {
|
||||||
|
return zp.setParseError("bad step in $GENERATE range", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
step = s
|
||||||
|
token = token[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
sx := strings.SplitN(token, "-", 2)
|
||||||
|
if len(sx) != 2 {
|
||||||
|
return zp.setParseError("bad start-stop in $GENERATE range", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
start, err := strconv.Atoi(sx[0])
|
||||||
|
if err != nil {
|
||||||
|
return zp.setParseError("bad start in $GENERATE range", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
end, err := strconv.Atoi(sx[1])
|
||||||
|
if err != nil {
|
||||||
|
return zp.setParseError("bad stop in $GENERATE range", l)
|
||||||
|
}
|
||||||
|
if end < 0 || start < 0 || end < start {
|
||||||
|
return zp.setParseError("bad range in $GENERATE range", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
zp.c.Next() // _BLANK
|
||||||
|
|
||||||
|
// Create a complete new string, which we then parse again.
|
||||||
|
var s string
|
||||||
|
for l, ok := zp.c.Next(); ok; l, ok = zp.c.Next() {
|
||||||
|
if l.err {
|
||||||
|
return zp.setParseError("bad data in $GENERATE directive", l)
|
||||||
|
}
|
||||||
|
if l.value == zNewline {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
s += l.token
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &generateReader{
|
||||||
|
s: s,
|
||||||
|
|
||||||
|
cur: start,
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
step: step,
|
||||||
|
|
||||||
|
file: zp.file,
|
||||||
|
lex: &l,
|
||||||
|
}
|
||||||
|
zp.sub = NewZoneParser(r, zp.origin, zp.file)
|
||||||
|
zp.sub.includeDepth, zp.sub.includeAllowed = zp.includeDepth, zp.includeAllowed
|
||||||
|
zp.sub.SetDefaultTTL(defaultTtl)
|
||||||
|
return zp.subNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
type generateReader struct {
|
||||||
|
s string
|
||||||
|
si int
|
||||||
|
|
||||||
|
cur int
|
||||||
|
start int
|
||||||
|
end int
|
||||||
|
step int
|
||||||
|
|
||||||
|
mod bytes.Buffer
|
||||||
|
|
||||||
|
escape bool
|
||||||
|
|
||||||
|
eof bool
|
||||||
|
|
||||||
|
file string
|
||||||
|
lex *lex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *generateReader) parseError(msg string, end int) *ParseError {
|
||||||
|
r.eof = true // Make errors sticky.
|
||||||
|
|
||||||
|
l := *r.lex
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *generateReader) Read(p []byte) (int, error) {
|
||||||
|
// NewZLexer, through NewZoneParser, should use ReadByte and
|
||||||
|
// not end up here.
|
||||||
|
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *generateReader) ReadByte() (byte, error) {
|
||||||
|
if r.eof {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
if r.mod.Len() > 0 {
|
||||||
|
return r.mod.ReadByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.si >= len(r.s) {
|
||||||
|
r.si = 0
|
||||||
|
r.cur += r.step
|
||||||
|
|
||||||
|
r.eof = r.cur > r.end || r.cur < 0
|
||||||
|
return '\n', nil
|
||||||
|
}
|
||||||
|
|
||||||
|
si := r.si
|
||||||
|
r.si++
|
||||||
|
|
||||||
|
switch r.s[si] {
|
||||||
|
case '\\':
|
||||||
|
if r.escape {
|
||||||
|
r.escape = false
|
||||||
|
return '\\', nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r.escape = true
|
||||||
|
return r.ReadByte()
|
||||||
|
case '$':
|
||||||
|
if r.escape {
|
||||||
|
r.escape = false
|
||||||
|
return '$', nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mod := "%d"
|
||||||
|
|
||||||
|
if si >= len(r.s)-1 {
|
||||||
|
// End of the string
|
||||||
|
fmt.Fprintf(&r.mod, mod, r.cur)
|
||||||
|
return r.mod.ReadByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.s[si+1] == '$' {
|
||||||
|
r.si++
|
||||||
|
return '$', nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset int
|
||||||
|
|
||||||
|
// Search for { and }
|
||||||
|
if r.s[si+1] == '{' {
|
||||||
|
// Modifier block
|
||||||
|
sep := strings.Index(r.s[si+2:], "}")
|
||||||
|
if sep < 0 {
|
||||||
|
return 0, r.parseError("bad modifier in $GENERATE", len(r.s))
|
||||||
|
}
|
||||||
|
|
||||||
|
var errMsg string
|
||||||
|
mod, offset, errMsg = modToPrintf(r.s[si+2 : si+2+sep])
|
||||||
|
if errMsg != "" {
|
||||||
|
return 0, r.parseError(errMsg, si+3+sep)
|
||||||
|
}
|
||||||
|
if r.start+offset < 0 || r.end+offset > 1<<31-1 {
|
||||||
|
return 0, r.parseError("bad offset in $GENERATE", si+3+sep)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.si += 2 + sep // Jump to it
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(&r.mod, mod, r.cur+offset)
|
||||||
|
return r.mod.ReadByte()
|
||||||
|
default:
|
||||||
|
if r.escape { // Pretty useless here
|
||||||
|
r.escape = false
|
||||||
|
return r.ReadByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.s[si], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
|
||||||
|
func modToPrintf(s string) (string, int, 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:
|
||||||
|
return "", 0, "bad modifier in $GENERATE"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch base {
|
||||||
|
case "o", "d", "x", "X":
|
||||||
|
default:
|
||||||
|
return "", 0, "bad base in $GENERATE"
|
||||||
|
}
|
||||||
|
|
||||||
|
offset, err := strconv.Atoi(offStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, "bad offset in $GENERATE"
|
||||||
|
}
|
||||||
|
|
||||||
|
width, err := strconv.Atoi(widthStr)
|
||||||
|
if err != nil || width < 0 || width > 255 {
|
||||||
|
return "", 0, "bad width in $GENERATE"
|
||||||
|
}
|
||||||
|
|
||||||
|
if width == 0 {
|
||||||
|
return "%" + base, offset, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return "%0" + widthStr + base, offset, ""
|
||||||
|
}
|
||||||
191
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/labels.go
generated
vendored
Normal file
191
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/labels.go
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
// Holds a bunch of helper functions for dealing with labels.
|
||||||
|
|
||||||
|
// SplitDomainName splits a name string into it's labels.
|
||||||
|
// www.miek.nl. returns []string{"www", "miek", "nl"}
|
||||||
|
// .www.miek.nl. returns []string{"", "www", "miek", "nl"},
|
||||||
|
// The root label (.) returns nil. Note that using
|
||||||
|
// strings.Split(s) will work in most cases, but does not handle
|
||||||
|
// escaped dots (\.) for instance.
|
||||||
|
// s must be a syntactically valid domain name, see IsDomainName.
|
||||||
|
func SplitDomainName(s string) (labels []string) {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fqdnEnd := 0 // offset of the final '.' or the length of the name
|
||||||
|
idx := Split(s)
|
||||||
|
begin := 0
|
||||||
|
if s[len(s)-1] == '.' {
|
||||||
|
fqdnEnd = len(s) - 1
|
||||||
|
} else {
|
||||||
|
fqdnEnd = len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(idx) {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
// no-op
|
||||||
|
default:
|
||||||
|
end := 0
|
||||||
|
for i := 1; i < len(idx); i++ {
|
||||||
|
end = idx[i]
|
||||||
|
labels = append(labels, s[begin:end-1])
|
||||||
|
begin = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
labels = append(labels, s[begin:fqdnEnd])
|
||||||
|
return labels
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareDomainName compares the names s1 and s2 and
|
||||||
|
// returns how many labels they have in common starting from the *right*.
|
||||||
|
// The comparison stops at the first inequality. The names are downcased
|
||||||
|
// before the comparison.
|
||||||
|
//
|
||||||
|
// www.miek.nl. and miek.nl. have two labels in common: miek and nl
|
||||||
|
// www.miek.nl. and www.bla.nl. have one label in common: nl
|
||||||
|
//
|
||||||
|
// s1 and s2 must be syntactically valid domain names.
|
||||||
|
func CompareDomainName(s1, s2 string) (n int) {
|
||||||
|
// the first check: root label
|
||||||
|
if s1 == "." || s2 == "." {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
l1 := Split(s1)
|
||||||
|
l2 := Split(s2)
|
||||||
|
|
||||||
|
j1 := len(l1) - 1 // end
|
||||||
|
i1 := len(l1) - 2 // start
|
||||||
|
j2 := len(l2) - 1
|
||||||
|
i2 := len(l2) - 2
|
||||||
|
// the second check can be done here: last/only label
|
||||||
|
// before we fall through into the for-loop below
|
||||||
|
if equal(s1[l1[j1]:], s2[l2[j2]:]) {
|
||||||
|
n++
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if i1 < 0 || i2 < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if equal(s1[l1[i1]:l1[j1]], s2[l2[i2]:l2[j2]]) {
|
||||||
|
n++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
j1--
|
||||||
|
i1--
|
||||||
|
j2--
|
||||||
|
i2--
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountLabel counts the the number of labels in the string s.
|
||||||
|
// s must be a syntactically valid domain name.
|
||||||
|
func CountLabel(s string) (labels int) {
|
||||||
|
if s == "." {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
off := 0
|
||||||
|
end := false
|
||||||
|
for {
|
||||||
|
off, end = NextLabel(s, off)
|
||||||
|
labels++
|
||||||
|
if end {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split splits a name s into its label indexes.
|
||||||
|
// www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}.
|
||||||
|
// The root name (.) returns nil. Also see SplitDomainName.
|
||||||
|
// s must be a syntactically valid domain name.
|
||||||
|
func Split(s string) []int {
|
||||||
|
if s == "." {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
idx := make([]int, 1, 3)
|
||||||
|
off := 0
|
||||||
|
end := false
|
||||||
|
|
||||||
|
for {
|
||||||
|
off, end = NextLabel(s, off)
|
||||||
|
if end {
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
idx = append(idx, off)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextLabel returns the index of the start of the next label in the
|
||||||
|
// string s starting at offset.
|
||||||
|
// 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) {
|
||||||
|
quote := false
|
||||||
|
for i = offset; i < len(s)-1; i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '\\':
|
||||||
|
quote = !quote
|
||||||
|
default:
|
||||||
|
quote = false
|
||||||
|
case '.':
|
||||||
|
if quote {
|
||||||
|
quote = !quote
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return i + 1, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i + 1, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrevLabel returns the index of the label when starting from the right and
|
||||||
|
// jumping n labels to the left.
|
||||||
|
// The bool start is true when the start of the string has been overshot.
|
||||||
|
// Also see NextLabel.
|
||||||
|
func PrevLabel(s string, n int) (i int, start bool) {
|
||||||
|
if n == 0 {
|
||||||
|
return len(s), false
|
||||||
|
}
|
||||||
|
lab := Split(s)
|
||||||
|
if lab == nil {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if n > len(lab) {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
return lab[len(lab)-n], false
|
||||||
|
}
|
||||||
|
|
||||||
|
// equal compares a and b while ignoring case. It returns true when equal otherwise false.
|
||||||
|
func equal(a, b string) bool {
|
||||||
|
// might be lifted into API function.
|
||||||
|
la := len(a)
|
||||||
|
lb := len(b)
|
||||||
|
if la != lb {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := la - 1; i >= 0; i-- {
|
||||||
|
ai := a[i]
|
||||||
|
bi := b[i]
|
||||||
|
if ai >= 'A' && ai <= 'Z' {
|
||||||
|
ai |= 'a' - 'A'
|
||||||
|
}
|
||||||
|
if bi >= 'A' && bi <= 'Z' {
|
||||||
|
bi |= 'a' - 'A'
|
||||||
|
}
|
||||||
|
if ai != bi {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
44
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/listen_go111.go
generated
vendored
Normal file
44
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/listen_go111.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// +build go1.11
|
||||||
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const supportsReusePort = true
|
||||||
|
|
||||||
|
func reuseportControl(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_REUSEPORT, 1)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return opErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func listenTCP(network, addr string, reuseport bool) (net.Listener, error) {
|
||||||
|
var lc net.ListenConfig
|
||||||
|
if reuseport {
|
||||||
|
lc.Control = reuseportControl
|
||||||
|
}
|
||||||
|
|
||||||
|
return lc.Listen(context.Background(), network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) {
|
||||||
|
var lc net.ListenConfig
|
||||||
|
if reuseport {
|
||||||
|
lc.Control = reuseportControl
|
||||||
|
}
|
||||||
|
|
||||||
|
return lc.ListenPacket(context.Background(), network, addr)
|
||||||
|
}
|
||||||
23
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/listen_go_not111.go
generated
vendored
Normal file
23
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/listen_go_not111.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// +build !go1.11 !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
const supportsReusePort = false
|
||||||
|
|
||||||
|
func listenTCP(network, addr string, reuseport bool) (net.Listener, error) {
|
||||||
|
if reuseport {
|
||||||
|
// TODO(tmthrgd): return an error?
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.Listen(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) {
|
||||||
|
if reuseport {
|
||||||
|
// TODO(tmthrgd): return an error?
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.ListenPacket(network, addr)
|
||||||
|
}
|
||||||
1233
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/msg.go
generated
vendored
Normal file
1233
vendor/github.com/xenolf/lego/vendor/github.com/miekg/dns/msg.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user