mirror of
https://github.com/caddyserver/caddy.git
synced 2025-02-25 17:29:16 +01:00
Merge pull request #2072 from mholt/acmev2
tls: Use ACMEv2 and support automatic wildcard certificates
This commit is contained in:
commit
95514da91b
77 changed files with 6529 additions and 4248 deletions
|
@ -27,7 +27,7 @@ import (
|
||||||
|
|
||||||
"gopkg.in/natefinch/lumberjack.v2"
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acmev2"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
// plug in the HTTP server type
|
// plug in the HTTP server type
|
||||||
|
@ -42,7 +42,7 @@ func init() {
|
||||||
setVersion()
|
setVersion()
|
||||||
|
|
||||||
flag.BoolVar(&caddytls.Agreed, "agree", false, "Agree to the CA's Subscriber Agreement")
|
flag.BoolVar(&caddytls.Agreed, "agree", false, "Agree to the CA's Subscriber Agreement")
|
||||||
flag.StringVar(&caddytls.DefaultCAUrl, "ca", "https://acme-v01.api.letsencrypt.org/directory", "URL to certificate authority's ACME server directory")
|
flag.StringVar(&caddytls.DefaultCAUrl, "ca", "https://acme-v02.api.letsencrypt.org/directory", "URL to certificate authority's ACME server directory")
|
||||||
flag.BoolVar(&caddytls.DisableHTTPChallenge, "disable-http-challenge", caddytls.DisableHTTPChallenge, "Disable the ACME HTTP challenge")
|
flag.BoolVar(&caddytls.DisableHTTPChallenge, "disable-http-challenge", caddytls.DisableHTTPChallenge, "Disable the ACME HTTP challenge")
|
||||||
flag.BoolVar(&caddytls.DisableTLSSNIChallenge, "disable-tls-sni-challenge", caddytls.DisableTLSSNIChallenge, "Disable the ACME TLS-SNI challenge")
|
flag.BoolVar(&caddytls.DisableTLSSNIChallenge, "disable-tls-sni-challenge", caddytls.DisableTLSSNIChallenge, "Disable the ACME TLS-SNI challenge")
|
||||||
flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")")
|
flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")")
|
||||||
|
|
|
@ -100,8 +100,8 @@ func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error {
|
||||||
}
|
}
|
||||||
cfg.TLS.Enabled = true
|
cfg.TLS.Enabled = true
|
||||||
cfg.Addr.Scheme = "https"
|
cfg.Addr.Scheme = "https"
|
||||||
if loadCertificates && caddytls.HostQualifies(cfg.Addr.Host) {
|
if loadCertificates && caddytls.HostQualifies(cfg.TLS.Hostname) {
|
||||||
_, err := cfg.TLS.CacheManagedCertificate(cfg.Addr.Host)
|
_, err := cfg.TLS.CacheManagedCertificate(cfg.TLS.Hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -431,12 +431,26 @@ func (r *replacer) getSubstitution(key string) string {
|
||||||
return "UNKNOWN" // this should never happen, but guard in case
|
return "UNKNOWN" // this should never happen, but guard in case
|
||||||
}
|
}
|
||||||
return r.emptyValue
|
return r.emptyValue
|
||||||
|
default:
|
||||||
|
// {labelN}
|
||||||
|
if strings.HasPrefix(key, "{label") {
|
||||||
|
nStr := key[6 : len(key)-1] // get the integer N in "{labelN}"
|
||||||
|
n, err := strconv.Atoi(nStr)
|
||||||
|
if err != nil || n < 1 {
|
||||||
|
return r.emptyValue
|
||||||
|
}
|
||||||
|
labels := strings.Split(r.request.Host, ".")
|
||||||
|
if n > len(labels) {
|
||||||
|
return r.emptyValue
|
||||||
|
}
|
||||||
|
return labels[n-1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.emptyValue
|
return r.emptyValue
|
||||||
}
|
}
|
||||||
|
|
||||||
//convertToMilliseconds returns the number of milliseconds in the given duration
|
// convertToMilliseconds returns the number of milliseconds in the given duration
|
||||||
func convertToMilliseconds(d time.Duration) int64 {
|
func convertToMilliseconds(d time.Duration) int64 {
|
||||||
return d.Nanoseconds() / 1e6
|
return d.Nanoseconds() / 1e6
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ func TestReplace(t *testing.T) {
|
||||||
recordRequest := NewResponseRecorder(w)
|
recordRequest := NewResponseRecorder(w)
|
||||||
reader := strings.NewReader(`{"username": "dennis"}`)
|
reader := strings.NewReader(`{"username": "dennis"}`)
|
||||||
|
|
||||||
request, err := http.NewRequest("POST", "http://localhost/?foo=bar", reader)
|
request, err := http.NewRequest("POST", "http://localhost.local/?foo=bar", reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to make request: %v", err)
|
t.Fatalf("Failed to make request: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ func TestReplace(t *testing.T) {
|
||||||
expect string
|
expect string
|
||||||
}{
|
}{
|
||||||
{"This hostname is {hostname}", "This hostname is " + hostname},
|
{"This hostname is {hostname}", "This hostname is " + hostname},
|
||||||
{"This host is {host}.", "This host is localhost."},
|
{"This host is {host}.", "This host is localhost.local."},
|
||||||
{"This request method is {method}.", "This request method is POST."},
|
{"This request method is {method}.", "This request method is POST."},
|
||||||
{"The response status is {status}.", "The response status is 200."},
|
{"The response status is {status}.", "The response status is 200."},
|
||||||
{"{when}", "02/Jan/2006:15:04:05 +0000"},
|
{"{when}", "02/Jan/2006:15:04:05 +0000"},
|
||||||
|
@ -97,7 +97,7 @@ func TestReplace(t *testing.T) {
|
||||||
{"The CustomAdd header is {>CustomAdd}.", "The CustomAdd header is caddy."},
|
{"The CustomAdd header is {>CustomAdd}.", "The CustomAdd header is caddy."},
|
||||||
{"The Custom response header is {<Custom}.", "The Custom response header is CustomResponseHeader."},
|
{"The Custom response header is {<Custom}.", "The Custom response header is CustomResponseHeader."},
|
||||||
{"Bad {>Custom placeholder", "Bad {>Custom placeholder"},
|
{"Bad {>Custom placeholder", "Bad {>Custom placeholder"},
|
||||||
{"The request is {request}.", "The request is POST /?foo=bar HTTP/1.1\\r\\nHost: localhost\\r\\n" +
|
{"The request is {request}.", "The request is POST /?foo=bar HTTP/1.1\\r\\nHost: localhost.local\\r\\n" +
|
||||||
"Cookie: foo=bar; taste=delicious\\r\\nCustom: foobarbaz\\r\\nCustomadd: caddy\\r\\n" +
|
"Cookie: foo=bar; taste=delicious\\r\\nCustom: foobarbaz\\r\\nCustomadd: caddy\\r\\n" +
|
||||||
"Shorterval: 1\\r\\n\\r\\n."},
|
"Shorterval: 1\\r\\n\\r\\n."},
|
||||||
{"The cUsToM header is {>cUsToM}...", "The cUsToM header is foobarbaz..."},
|
{"The cUsToM header is {>cUsToM}...", "The cUsToM header is foobarbaz..."},
|
||||||
|
@ -112,6 +112,8 @@ func TestReplace(t *testing.T) {
|
||||||
{"Query string is {query}", "Query string is foo=bar"},
|
{"Query string is {query}", "Query string is foo=bar"},
|
||||||
{"Query string value for foo is {?foo}", "Query string value for foo is bar"},
|
{"Query string value for foo is {?foo}", "Query string value for foo is bar"},
|
||||||
{"Missing query string argument is {?missing}", "Missing query string argument is "},
|
{"Missing query string argument is {?missing}", "Missing query string argument is "},
|
||||||
|
{"{label1} {label2} {label3} {label4}", "localhost local - -"},
|
||||||
|
{"Label with missing number is {label} or {labelQQ}", "Label with missing number is - or -"},
|
||||||
{"\\{ 'hostname': '{hostname}' \\}", "{ 'hostname': '" + hostname + "' }"},
|
{"\\{ 'hostname': '{hostname}' \\}", "{ 'hostname': '" + hostname + "' }"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acmev2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// acmeMu ensures that only one ACME challenge occurs at a time.
|
// acmeMu ensures that only one ACME challenge occurs at a time.
|
||||||
|
@ -89,27 +89,22 @@ var newACMEClient = func(config *Config, allowPrompts bool) (*ACMEClient, error)
|
||||||
// If not registered, the user must register an account with the CA
|
// If not registered, the user must register an account with the CA
|
||||||
// and agree to terms
|
// and agree to terms
|
||||||
if leUser.Registration == nil {
|
if leUser.Registration == nil {
|
||||||
reg, err := client.Register()
|
if allowPrompts { // can't prompt a user who isn't there
|
||||||
|
termsURL := client.GetToSURL()
|
||||||
|
if !Agreed && termsURL != "" {
|
||||||
|
Agreed = askUserAgreement(client.GetToSURL())
|
||||||
|
}
|
||||||
|
if !Agreed && termsURL != "" {
|
||||||
|
return nil, errors.New("user must agree to CA terms (use -agree flag)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reg, err := client.Register(Agreed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("registration error: " + err.Error())
|
return nil, errors.New("registration error: " + err.Error())
|
||||||
}
|
}
|
||||||
leUser.Registration = reg
|
leUser.Registration = reg
|
||||||
|
|
||||||
if allowPrompts { // can't prompt a user who isn't there
|
|
||||||
if !Agreed && reg.TosURL == "" {
|
|
||||||
Agreed = promptUserAgreement(saURL, false) // TODO - latest URL
|
|
||||||
}
|
|
||||||
if !Agreed && reg.TosURL == "" {
|
|
||||||
return nil, errors.New("user must agree to terms")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = client.AgreeToTOS()
|
|
||||||
if err != nil {
|
|
||||||
saveUser(storage, leUser) // Might as well try, right?
|
|
||||||
return nil, errors.New("error agreeing to terms: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// save user to the file system
|
// save user to the file system
|
||||||
err = saveUser(storage, leUser)
|
err = saveUser(storage, leUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -136,38 +131,57 @@ var newACMEClient = func(config *Config, allowPrompts bool) (*ACMEClient, error)
|
||||||
useHTTPPort = DefaultHTTPAlternatePort
|
useHTTPPort = DefaultHTTPAlternatePort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: tls-sni challenge was removed in January 2018, but a variant of it might return
|
||||||
// See which port TLS-SNI challenges will be accomplished on
|
// See which port TLS-SNI challenges will be accomplished on
|
||||||
useTLSSNIPort := TLSSNIChallengePort
|
// useTLSSNIPort := TLSSNIChallengePort
|
||||||
if config.AltTLSSNIPort != "" {
|
// if config.AltTLSSNIPort != "" {
|
||||||
useTLSSNIPort = config.AltTLSSNIPort
|
// useTLSSNIPort = config.AltTLSSNIPort
|
||||||
}
|
// }
|
||||||
|
// err := c.acmeClient.SetTLSAddress(net.JoinHostPort(config.ListenHost, useTLSSNIPort))
|
||||||
// Always respect user's bind preferences by using config.ListenHost.
|
// if err != nil {
|
||||||
// NOTE(Sep'16): At time of writing, SetHTTPAddress() and SetTLSAddress()
|
// return nil, err
|
||||||
// must be called before SetChallengeProvider(), since they reset the
|
// }
|
||||||
// challenge provider back to the default one!
|
|
||||||
err := c.acmeClient.SetHTTPAddress(net.JoinHostPort(config.ListenHost, useHTTPPort))
|
// if using file storage, we can distribute the HTTP challenge across
|
||||||
if err != nil {
|
// all instances sharing the acme folder; either way, we must still set
|
||||||
return nil, err
|
// the address for the default HTTP provider server
|
||||||
}
|
var useDistributedHTTPSolver bool
|
||||||
err = c.acmeClient.SetTLSAddress(net.JoinHostPort(config.ListenHost, useTLSSNIPort))
|
if storage, err := c.config.StorageFor(c.config.CAUrl); err == nil {
|
||||||
if err != nil {
|
if _, ok := storage.(*FileStorage); ok {
|
||||||
return nil, err
|
useDistributedHTTPSolver = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if useDistributedHTTPSolver {
|
||||||
|
c.acmeClient.SetChallengeProvider(acme.HTTP01, distributedHTTPSolver{
|
||||||
|
// being careful to respect user's listener bind preferences
|
||||||
|
httpProviderServer: acme.NewHTTPProviderServer(config.ListenHost, useHTTPPort),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Always respect user's bind preferences by using config.ListenHost.
|
||||||
|
// NOTE(Sep'16): At time of writing, SetHTTPAddress() and SetTLSAddress()
|
||||||
|
// must be called before SetChallengeProvider() (see above), since they reset
|
||||||
|
// the challenge provider back to the default one! (still true in March 2018)
|
||||||
|
err := c.acmeClient.SetHTTPAddress(net.JoinHostPort(config.ListenHost, useHTTPPort))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: tls-sni challenge was removed in January 2018, but a variant of it might return
|
||||||
// See if TLS challenge needs to be handled by our own facilities
|
// See if TLS challenge needs to be handled by our own facilities
|
||||||
if caddy.HasListenerWithAddress(net.JoinHostPort(config.ListenHost, useTLSSNIPort)) {
|
// if caddy.HasListenerWithAddress(net.JoinHostPort(config.ListenHost, useTLSSNIPort)) {
|
||||||
c.acmeClient.SetChallengeProvider(acme.TLSSNI01, tlsSNISolver{certCache: config.certCache})
|
// c.acmeClient.SetChallengeProvider(acme.TLSSNI01, tlsSNISolver{certCache: config.certCache})
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Disable any challenges that should not be used
|
// Disable any challenges that should not be used
|
||||||
var disabledChallenges []acme.Challenge
|
var disabledChallenges []acme.Challenge
|
||||||
if DisableHTTPChallenge {
|
if DisableHTTPChallenge {
|
||||||
disabledChallenges = append(disabledChallenges, acme.HTTP01)
|
disabledChallenges = append(disabledChallenges, acme.HTTP01)
|
||||||
}
|
}
|
||||||
if DisableTLSSNIChallenge {
|
// TODO: tls-sni challenge was removed in January 2018, but a variant of it might return
|
||||||
disabledChallenges = append(disabledChallenges, acme.TLSSNI01)
|
// if DisableTLSSNIChallenge {
|
||||||
}
|
// disabledChallenges = append(disabledChallenges, acme.TLSSNI01)
|
||||||
|
// }
|
||||||
if len(disabledChallenges) > 0 {
|
if len(disabledChallenges) > 0 {
|
||||||
c.acmeClient.ExcludeChallenges(disabledChallenges)
|
c.acmeClient.ExcludeChallenges(disabledChallenges)
|
||||||
}
|
}
|
||||||
|
@ -188,7 +202,9 @@ var newACMEClient = func(config *Config, allowPrompts bool) (*ACMEClient, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the DNS challenge exclusively
|
// Use the DNS challenge exclusively
|
||||||
c.acmeClient.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
|
// TODO: tls-sni challenge was removed in January 2018, but a variant of it might return
|
||||||
|
// c.acmeClient.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
|
||||||
|
c.acmeClient.ExcludeChallenges([]acme.Challenge{acme.HTTP01})
|
||||||
c.acmeClient.SetChallengeProvider(acme.DNS01, prov)
|
c.acmeClient.SetChallengeProvider(acme.DNS01, prov)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +237,6 @@ func (c *ACMEClient) Obtain(name string) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
Attempts:
|
|
||||||
for attempts := 0; attempts < 2; attempts++ {
|
for attempts := 0; attempts < 2; attempts++ {
|
||||||
namesObtaining.Add([]string{name})
|
namesObtaining.Add([]string{name})
|
||||||
acmeMu.Lock()
|
acmeMu.Lock()
|
||||||
|
@ -230,31 +245,15 @@ Attempts:
|
||||||
namesObtaining.Remove([]string{name})
|
namesObtaining.Remove([]string{name})
|
||||||
if len(failures) > 0 {
|
if len(failures) > 0 {
|
||||||
// Error - try to fix it or report it to the user and abort
|
// Error - try to fix it or report it to the user and abort
|
||||||
var errMsg string // we'll combine all the failures into a single error message
|
|
||||||
var promptedForAgreement bool // only prompt user for agreement at most once
|
|
||||||
|
|
||||||
|
var errMsg string // combine all the failures into a single error message
|
||||||
for errDomain, obtainErr := range failures {
|
for errDomain, obtainErr := range failures {
|
||||||
if obtainErr == nil {
|
if obtainErr == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if tosErr, ok := obtainErr.(acme.TOSError); ok {
|
errMsg += fmt.Sprintf("[%s] failed to get certificate: %v\n", errDomain, obtainErr)
|
||||||
// Terms of Service agreement error; we can probably deal with this
|
|
||||||
if !Agreed && !promptedForAgreement && c.AllowPrompts {
|
|
||||||
Agreed = promptUserAgreement(tosErr.Detail, true) // TODO: Use latest URL
|
|
||||||
promptedForAgreement = true
|
|
||||||
}
|
|
||||||
if Agreed || !c.AllowPrompts {
|
|
||||||
err := c.acmeClient.AgreeToTOS()
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("error agreeing to updated terms: " + err.Error())
|
|
||||||
}
|
|
||||||
continue Attempts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user did not agree or it was any other kind of error, just append to the list of errors
|
|
||||||
errMsg += "[" + errDomain + "] failed to get certificate: " + obtainErr.Error() + "\n"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(errMsg)
|
return errors.New(errMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,19 +315,9 @@ func (c *ACMEClient) Renew(name string) error {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the legal terms were updated and need to be
|
// wait a little bit and try again
|
||||||
// agreed to again, we can handle that.
|
|
||||||
if _, ok := err.(acme.TOSError); ok {
|
|
||||||
err := c.acmeClient.AgreeToTOS()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// For any other kind of error, wait 10s and try again.
|
|
||||||
wait := 10 * time.Second
|
wait := 10 * time.Second
|
||||||
log.Printf("[ERROR] Renewing: %v; trying again in %s", err, wait)
|
log.Printf("[ERROR] Renewing [%v]: %v; trying again in %s", name, err, wait)
|
||||||
time.Sleep(wait)
|
time.Sleep(wait)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ import (
|
||||||
|
|
||||||
"github.com/codahale/aesnicheck"
|
"github.com/codahale/aesnicheck"
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acmev2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config describes how TLS should be configured and used.
|
// Config describes how TLS should be configured and used.
|
||||||
|
@ -190,10 +190,15 @@ func NewConfig(inst *caddy.Instance) *Config {
|
||||||
// it does not load them into memory. If allowPrompts is true,
|
// it does not load them into memory. If allowPrompts is true,
|
||||||
// the user may be shown a prompt.
|
// the user may be shown a prompt.
|
||||||
func (c *Config) ObtainCert(name string, allowPrompts bool) error {
|
func (c *Config) ObtainCert(name string, allowPrompts bool) error {
|
||||||
if !c.Managed || !HostQualifies(name) {
|
skip, err := c.preObtainOrRenewChecks(name, allowPrompts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if skip {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we expect this to be a new (non-existent) site
|
||||||
storage, err := c.StorageFor(c.CAUrl)
|
storage, err := c.StorageFor(c.CAUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -205,9 +210,6 @@ func (c *Config) ObtainCert(name string, allowPrompts bool) error {
|
||||||
if siteExists {
|
if siteExists {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if c.ACMEEmail == "" {
|
|
||||||
c.ACMEEmail = getEmail(storage, allowPrompts)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := newACMEClient(c, allowPrompts)
|
client, err := newACMEClient(c, allowPrompts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -219,6 +221,14 @@ func (c *Config) ObtainCert(name string, allowPrompts bool) error {
|
||||||
// RenewCert renews the certificate for name using c. It stows the
|
// RenewCert renews the certificate for name using c. It stows the
|
||||||
// renewed certificate and its assets in storage if successful.
|
// renewed certificate and its assets in storage if successful.
|
||||||
func (c *Config) RenewCert(name string, allowPrompts bool) error {
|
func (c *Config) RenewCert(name string, allowPrompts bool) error {
|
||||||
|
skip, err := c.preObtainOrRenewChecks(name, allowPrompts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if skip {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
client, err := newACMEClient(c, allowPrompts)
|
client, err := newACMEClient(c, allowPrompts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -226,6 +236,33 @@ func (c *Config) RenewCert(name string, allowPrompts bool) error {
|
||||||
return client.Renew(name)
|
return client.Renew(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 (c *Config) preObtainOrRenewChecks(name string, allowPrompts bool) (bool, error) {
|
||||||
|
if !c.Managed || !HostQualifies(name) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// wildcard certificates require DNS challenge (as of March 2018)
|
||||||
|
if strings.Contains(name, "*") && c.DNSProvider == "" {
|
||||||
|
return false, fmt.Errorf("wildcard domain name (%s) requires DNS challenge; use dns subdirective to configure it", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ACMEEmail == "" {
|
||||||
|
var err error
|
||||||
|
c.ACMEEmail, err = getEmail(c, allowPrompts)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
// StorageFor obtains a TLS Storage instance for the given CA URL which should
|
// StorageFor obtains a TLS Storage instance for the given CA URL which should
|
||||||
// be unique for every different ACME CA. If a StorageCreator is set on this
|
// be unique for every different ACME CA. If a StorageCreator is set on this
|
||||||
// Config, it will be used. Otherwise the default file storage implementation
|
// Config, it will be used. Otherwise the default file storage implementation
|
||||||
|
|
|
@ -42,7 +42,7 @@ import (
|
||||||
"golang.org/x/crypto/ocsp"
|
"golang.org/x/crypto/ocsp"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acmev2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// loadPrivateKey loads a PEM-encoded ECC/RSA private key from an array of bytes.
|
// loadPrivateKey loads a PEM-encoded ECC/RSA private key from an array of bytes.
|
||||||
|
@ -107,7 +107,8 @@ func stapleOCSP(cert *Certificate, pemBundle []byte) error {
|
||||||
// TODO: Use Storage interface instead of disk directly
|
// TODO: Use Storage interface instead of disk directly
|
||||||
var ocspFileNamePrefix string
|
var ocspFileNamePrefix string
|
||||||
if len(cert.Names) > 0 {
|
if len(cert.Names) > 0 {
|
||||||
ocspFileNamePrefix = cert.Names[0] + "-"
|
firstName := strings.Replace(cert.Names[0], "*", "wildcard_", -1)
|
||||||
|
ocspFileNamePrefix = firstName + "-"
|
||||||
}
|
}
|
||||||
ocspFileName := ocspFileNamePrefix + fastHash(pemBundle)
|
ocspFileName := ocspFileNamePrefix + fastHash(pemBundle)
|
||||||
ocspCachePath := filepath.Join(ocspFolder, ocspFileName)
|
ocspCachePath := filepath.Join(ocspFolder, ocspFileName)
|
||||||
|
|
|
@ -30,14 +30,14 @@ func init() {
|
||||||
RegisterStorageProvider("file", NewFileStorage)
|
RegisterStorageProvider("file", NewFileStorage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// storageBasePath is the root path in which all TLS/ACME assets are
|
|
||||||
// stored. Do not change this value during the lifetime of the program.
|
|
||||||
var storageBasePath = filepath.Join(caddy.AssetsPath(), "acme")
|
|
||||||
|
|
||||||
// NewFileStorage is a StorageConstructor function that creates a new
|
// NewFileStorage is a StorageConstructor function that creates a new
|
||||||
// Storage instance backed by the local disk. The resulting Storage
|
// Storage instance backed by the local disk. The resulting Storage
|
||||||
// instance is guaranteed to be non-nil if there is no error.
|
// instance is guaranteed to be non-nil if there is no error.
|
||||||
func NewFileStorage(caURL *url.URL) (Storage, error) {
|
func NewFileStorage(caURL *url.URL) (Storage, error) {
|
||||||
|
// storageBasePath is the root path in which all TLS/ACME assets are
|
||||||
|
// stored. Do not change this value during the lifetime of the program.
|
||||||
|
storageBasePath := filepath.Join(caddy.AssetsPath(), "acme")
|
||||||
|
|
||||||
storage := &FileStorage{Path: filepath.Join(storageBasePath, caURL.Host)}
|
storage := &FileStorage{Path: filepath.Join(storageBasePath, caURL.Host)}
|
||||||
storage.Locker = &fileStorageLock{caURL: caURL.Host, storage: storage}
|
storage.Locker = &fileStorageLock{caURL: caURL.Host, storage: storage}
|
||||||
return storage, nil
|
return storage, nil
|
||||||
|
@ -58,24 +58,29 @@ func (s *FileStorage) sites() string {
|
||||||
|
|
||||||
// site returns the path to the folder containing assets for domain.
|
// site returns the path to the folder containing assets for domain.
|
||||||
func (s *FileStorage) site(domain string) string {
|
func (s *FileStorage) site(domain string) string {
|
||||||
|
// Windows doesn't allow * in filenames, sigh...
|
||||||
|
domain = strings.Replace(domain, "*", "wildcard_", -1)
|
||||||
domain = strings.ToLower(domain)
|
domain = strings.ToLower(domain)
|
||||||
return filepath.Join(s.sites(), domain)
|
return filepath.Join(s.sites(), domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// siteCertFile returns the path to the certificate file for domain.
|
// siteCertFile returns the path to the certificate file for domain.
|
||||||
func (s *FileStorage) siteCertFile(domain string) string {
|
func (s *FileStorage) siteCertFile(domain string) string {
|
||||||
|
domain = strings.Replace(domain, "*", "wildcard_", -1)
|
||||||
domain = strings.ToLower(domain)
|
domain = strings.ToLower(domain)
|
||||||
return filepath.Join(s.site(domain), domain+".crt")
|
return filepath.Join(s.site(domain), domain+".crt")
|
||||||
}
|
}
|
||||||
|
|
||||||
// siteKeyFile returns the path to domain's private key file.
|
// siteKeyFile returns the path to domain's private key file.
|
||||||
func (s *FileStorage) siteKeyFile(domain string) string {
|
func (s *FileStorage) siteKeyFile(domain string) string {
|
||||||
|
domain = strings.Replace(domain, "*", "wildcard_", -1)
|
||||||
domain = strings.ToLower(domain)
|
domain = strings.ToLower(domain)
|
||||||
return filepath.Join(s.site(domain), domain+".key")
|
return filepath.Join(s.site(domain), domain+".key")
|
||||||
}
|
}
|
||||||
|
|
||||||
// siteMetaFile returns the path to the domain's asset metadata file.
|
// siteMetaFile returns the path to the domain's asset metadata file.
|
||||||
func (s *FileStorage) siteMetaFile(domain string) string {
|
func (s *FileStorage) siteMetaFile(domain string) string {
|
||||||
|
domain = strings.Replace(domain, "*", "wildcard_", -1)
|
||||||
domain = strings.ToLower(domain)
|
domain = strings.ToLower(domain)
|
||||||
return filepath.Join(s.site(domain), domain+".json")
|
return filepath.Join(s.site(domain), domain+".json")
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,16 @@ package caddytls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/xenolf/lego/acmev2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const challengeBasePath = "/.well-known/acme-challenge"
|
const challengeBasePath = "/.well-known/acme-challenge"
|
||||||
|
@ -38,6 +42,13 @@ func HTTPChallengeHandler(w http.ResponseWriter, r *http.Request, listenHost str
|
||||||
if DisableHTTPChallenge {
|
if DisableHTTPChallenge {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// see if another instance started the HTTP challenge for this name
|
||||||
|
if tryDistributedChallengeSolver(w, r) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, if we aren't getting the name, then ignore this challenge
|
||||||
if !namesObtaining.Has(r.Host) {
|
if !namesObtaining.Has(r.Host) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -70,3 +81,40 @@ func HTTPChallengeHandler(w http.ResponseWriter, r *http.Request, listenHost str
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tryDistributedChallengeSolver checks to see if this challenge
|
||||||
|
// request was initiated by another instance that shares file
|
||||||
|
// storage, and attempts to complete the challenge for it. It
|
||||||
|
// returns true if the challenge was handled; false otherwise.
|
||||||
|
func tryDistributedChallengeSolver(w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
filePath := distributedHTTPSolver{}.challengeTokensPath(r.Host)
|
||||||
|
f, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
log.Printf("[ERROR][%s] Opening distributed challenge token file: %v", r.Host, err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var chalInfo challengeInfo
|
||||||
|
err = json.NewDecoder(f).Decode(&chalInfo)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR][%s] Decoding challenge token file %s (corrupted?): %v", r.Host, filePath, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// this part borrowed from xenolf/lego's built-in HTTP-01 challenge solver (March 2018)
|
||||||
|
challengeReqPath := acme.HTTP01ChallengePath(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", chalInfo.Domain)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -207,8 +207,21 @@ func setupTLS(c *caddy.Controller) error {
|
||||||
}
|
}
|
||||||
case "must_staple":
|
case "must_staple":
|
||||||
config.MustStaple = true
|
config.MustStaple = true
|
||||||
|
case "wildcard":
|
||||||
|
if !HostQualifies(config.Hostname) {
|
||||||
|
return c.Errf("Hostname '%s' does not qualify for managed TLS, so cannot manage wildcard certificate for it", config.Hostname)
|
||||||
|
}
|
||||||
|
if strings.Contains(config.Hostname, "*") {
|
||||||
|
return c.Errf("Cannot convert domain name '%s' to a valid wildcard: already has a wildcard label", config.Hostname)
|
||||||
|
}
|
||||||
|
parts := strings.Split(config.Hostname, ".")
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return c.Errf("Cannot convert domain name '%s' to a valid wildcard: too few labels", config.Hostname)
|
||||||
|
}
|
||||||
|
parts[0] = "*"
|
||||||
|
config.Hostname = strings.Join(parts, ".")
|
||||||
default:
|
default:
|
||||||
return c.Errf("Unknown keyword '%s'", c.Val())
|
return c.Errf("Unknown subdirective '%s'", c.Val())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acmev2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
|
149
caddytls/tls.go
149
caddytls/tls.go
|
@ -30,26 +30,35 @@ package caddytls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acmev2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HostQualifies returns true if the hostname alone
|
// HostQualifies returns true if the hostname alone
|
||||||
// appears eligible for automatic HTTPS. For example,
|
// appears eligible for automatic HTTPS. For example:
|
||||||
// localhost, empty hostname, and IP addresses are
|
// localhost, empty hostname, and IP addresses are
|
||||||
// not eligible because we cannot obtain certificates
|
// not eligible because we cannot obtain certificates
|
||||||
// for those names.
|
// 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 {
|
func HostQualifies(hostname string) bool {
|
||||||
return hostname != "localhost" && // localhost is ineligible
|
return hostname != "localhost" && // localhost is ineligible
|
||||||
|
|
||||||
// hostname must not be empty
|
// hostname must not be empty
|
||||||
strings.TrimSpace(hostname) != "" &&
|
strings.TrimSpace(hostname) != "" &&
|
||||||
|
|
||||||
// must not contain wildcard (*) characters (until CA supports it)
|
// only one wildcard label allowed, and it must be left-most
|
||||||
!strings.Contains(hostname, "*") &&
|
(!strings.Contains(hostname, "*") ||
|
||||||
|
(strings.Count(hostname, "*") == 1 &&
|
||||||
|
strings.HasPrefix(hostname, "*."))) &&
|
||||||
|
|
||||||
// must not start or end with a dot
|
// must not start or end with a dot
|
||||||
!strings.HasPrefix(hostname, ".") &&
|
!strings.HasPrefix(hostname, ".") &&
|
||||||
|
@ -88,39 +97,125 @@ func Revoke(host string) error {
|
||||||
return client.Revoke(host)
|
return client.Revoke(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tlsSNISolver is a type that can solve TLS-SNI challenges using
|
// TODO: tls-sni challenge was removed in January 2018, but a variant of it might return
|
||||||
// an existing listener and our custom, in-memory certificate cache.
|
// // tlsSNISolver is a type that can solve TLS-SNI challenges using
|
||||||
type tlsSNISolver struct {
|
// // an existing listener and our custom, in-memory certificate cache.
|
||||||
certCache *certificateCache
|
// type tlsSNISolver struct {
|
||||||
|
// certCache *certificateCache
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Present adds the challenge certificate to the cache.
|
||||||
|
// func (s tlsSNISolver) Present(domain, token, keyAuth string) error {
|
||||||
|
// cert, acmeDomain, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// certHash := hashCertificateChain(cert.Certificate)
|
||||||
|
// s.certCache.Lock()
|
||||||
|
// s.certCache.cache[acmeDomain] = Certificate{
|
||||||
|
// Certificate: cert,
|
||||||
|
// Names: []string{acmeDomain},
|
||||||
|
// Hash: certHash, // perhaps not necesssary
|
||||||
|
// }
|
||||||
|
// s.certCache.Unlock()
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // CleanUp removes the challenge certificate from the cache.
|
||||||
|
// func (s tlsSNISolver) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
// _, acmeDomain, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// s.certCache.Lock()
|
||||||
|
// delete(s.certCache.cache, acmeDomain)
|
||||||
|
// s.certCache.Unlock()
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// distributedHTTPSolver allows the HTTP-01 challenge 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 this (the initiating) instance share
|
||||||
|
// the $CADDYPATH/acme folder with the instance that will complete
|
||||||
|
// the challenge. Mounting the folder locally should be sufficient.
|
||||||
|
//
|
||||||
|
// Obviously, the instance which completes the challenge must be
|
||||||
|
// serving on the HTTPChallengePort to receive and handle the request.
|
||||||
|
// The HTTP server which receives it must check if a file exists, e.g.:
|
||||||
|
// $CADDYPATH/acme/challenge_tokens/example.com.json, and if so,
|
||||||
|
// decode it and use it to serve up the correct response. Caddy's HTTP
|
||||||
|
// server does this by default.
|
||||||
|
//
|
||||||
|
// So as long as the folder is shared, this will just work. There are
|
||||||
|
// no other requirements. The instances may be on other machines or
|
||||||
|
// even other networks, as long as they share the folder as part of
|
||||||
|
// the local file system.
|
||||||
|
//
|
||||||
|
// This solver works by persisting the token and keyauth information
|
||||||
|
// to disk in the shared folder when the authorization is presented,
|
||||||
|
// and then deletes it when it is cleaned up.
|
||||||
|
type distributedHTTPSolver struct {
|
||||||
|
// The distributed HTTPS solver only works if an instance (either
|
||||||
|
// this one or another one) is already listening and serving on the
|
||||||
|
// HTTPChallengePort. If not -- for example: if this is the only
|
||||||
|
// instance, and it is just starting up and hasn't started serving
|
||||||
|
// yet -- then we still need a listener open with an HTTP server
|
||||||
|
// to handle the challenge request. Set this field to have the
|
||||||
|
// standard HTTPProviderServer open its listener for the duration
|
||||||
|
// of the challenge. Make sure to configure its listen address
|
||||||
|
// correctly.
|
||||||
|
httpProviderServer *acme.HTTPProviderServer
|
||||||
|
}
|
||||||
|
|
||||||
|
type challengeInfo struct {
|
||||||
|
Domain, Token, KeyAuth string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present adds the challenge certificate to the cache.
|
// Present adds the challenge certificate to the cache.
|
||||||
func (s tlsSNISolver) Present(domain, token, keyAuth string) error {
|
func (dhs distributedHTTPSolver) Present(domain, token, keyAuth string) error {
|
||||||
cert, acmeDomain, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
if dhs.httpProviderServer != nil {
|
||||||
|
err := dhs.httpProviderServer.Present(domain, token, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("presenting with standard HTTP provider server: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.MkdirAll(dhs.challengeTokensBasePath(), 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
certHash := hashCertificateChain(cert.Certificate)
|
|
||||||
s.certCache.Lock()
|
infoBytes, err := json.Marshal(challengeInfo{
|
||||||
s.certCache.cache[acmeDomain] = Certificate{
|
Domain: domain,
|
||||||
Certificate: cert,
|
Token: token,
|
||||||
Names: []string{acmeDomain},
|
KeyAuth: keyAuth,
|
||||||
Hash: certHash, // perhaps not necesssary
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
s.certCache.Unlock()
|
|
||||||
return nil
|
return ioutil.WriteFile(dhs.challengeTokensPath(domain), infoBytes, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp removes the challenge certificate from the cache.
|
// CleanUp removes the challenge certificate from the cache.
|
||||||
func (s tlsSNISolver) CleanUp(domain, token, keyAuth string) error {
|
func (dhs distributedHTTPSolver) CleanUp(domain, token, keyAuth string) error {
|
||||||
_, acmeDomain, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
if dhs.httpProviderServer != nil {
|
||||||
if err != nil {
|
err := dhs.httpProviderServer.CleanUp(domain, token, keyAuth)
|
||||||
return err
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Cleaning up standard HTTP provider server: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.certCache.Lock()
|
return os.Remove(dhs.challengeTokensPath(domain))
|
||||||
delete(s.certCache.cache, acmeDomain)
|
}
|
||||||
s.certCache.Unlock()
|
|
||||||
return nil
|
func (dhs distributedHTTPSolver) challengeTokensPath(domain string) string {
|
||||||
|
domainFile := strings.Replace(strings.ToLower(domain), "*", "wildcard_", -1)
|
||||||
|
return filepath.Join(dhs.challengeTokensBasePath(), domainFile+".json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dhs distributedHTTPSolver) challengeTokensBasePath() string {
|
||||||
|
return filepath.Join(caddy.AssetsPath(), "acme", "challenge_tokens")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigHolder is any type that has a Config; it presumably is
|
// ConfigHolder is any type that has a Config; it presumably is
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acmev2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHostQualifies(t *testing.T) {
|
func TestHostQualifies(t *testing.T) {
|
||||||
|
@ -37,7 +37,10 @@ func TestHostQualifies(t *testing.T) {
|
||||||
{"0.0.0.0", false},
|
{"0.0.0.0", false},
|
||||||
{"", false},
|
{"", false},
|
||||||
{" ", false},
|
{" ", false},
|
||||||
{"*.example.com", false},
|
{"*.example.com", true},
|
||||||
|
{"*.*.example.com", false},
|
||||||
|
{"sub.*.example.com", false},
|
||||||
|
{"*sub.example.com", false},
|
||||||
{".com", false},
|
{".com", false},
|
||||||
{"example.com.", false},
|
{"example.com.", false},
|
||||||
{"localhost", false},
|
{"localhost", false},
|
||||||
|
@ -77,7 +80,10 @@ func TestQualifiesForManagedTLS(t *testing.T) {
|
||||||
{holder{host: "localhost", cfg: new(Config)}, false},
|
{holder{host: "localhost", cfg: new(Config)}, false},
|
||||||
{holder{host: "123.44.3.21", cfg: new(Config)}, false},
|
{holder{host: "123.44.3.21", cfg: new(Config)}, false},
|
||||||
{holder{host: "example.com", cfg: new(Config)}, true},
|
{holder{host: "example.com", cfg: new(Config)}, true},
|
||||||
{holder{host: "*.example.com", cfg: new(Config)}, false},
|
{holder{host: "*.example.com", cfg: new(Config)}, true},
|
||||||
|
{holder{host: "*.*.example.com", cfg: new(Config)}, false},
|
||||||
|
{holder{host: "*sub.example.com", cfg: new(Config)}, false},
|
||||||
|
{holder{host: "sub.*.example.com", cfg: new(Config)}, false},
|
||||||
{holder{host: "example.com", cfg: &Config{Manual: true}}, false},
|
{holder{host: "example.com", cfg: &Config{Manual: true}}, false},
|
||||||
{holder{host: "example.com", cfg: &Config{ACMEEmail: "off"}}, false},
|
{holder{host: "example.com", cfg: &Config{ACMEEmail: "off"}}, false},
|
||||||
{holder{host: "example.com", cfg: &Config{ACMEEmail: "foo@bar.com"}}, true},
|
{holder{host: "example.com", cfg: &Config{ACMEEmail: "foo@bar.com"}}, true},
|
||||||
|
|
129
caddytls/user.go
129
caddytls/user.go
|
@ -27,7 +27,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acmev2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// User represents a Let's Encrypt user account.
|
// User represents a Let's Encrypt user account.
|
||||||
|
@ -67,43 +67,82 @@ func newUser(email string) (User, error) {
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getEmail does everything it can to obtain an email
|
// getEmail does everything it can to obtain an email address
|
||||||
// address from the user within the scope of storage
|
// from the user within the scope of memory and storage to use
|
||||||
// to use for ACME TLS. If it cannot get an email
|
// for ACME TLS. If it cannot get an email address, it returns
|
||||||
// address, it returns empty string. (It will warn the
|
// empty string. (If user is present, it will warn the user of
|
||||||
// user of the consequences of an empty email.) This
|
// the consequences of an empty email.) This function MAY prompt
|
||||||
// function MAY prompt the user for input. If userPresent
|
// the user for input. If userPresent is false, the operator
|
||||||
// is false, the operator will NOT be prompted and an
|
// will NOT be prompted and an empty email may be returned.
|
||||||
// empty email may be returned.
|
// If the user is prompted, a new User will be created and
|
||||||
func getEmail(storage Storage, userPresent bool) string {
|
// stored in storage according to the email address they
|
||||||
|
// provided (which might be blank).
|
||||||
|
func getEmail(cfg *Config, userPresent bool) (string, error) {
|
||||||
|
storage, err := cfg.StorageFor(cfg.CAUrl)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
// First try memory (command line flag or typed by user previously)
|
// First try memory (command line flag or typed by user previously)
|
||||||
leEmail := DefaultEmail
|
leEmail := DefaultEmail
|
||||||
|
|
||||||
|
// Then try to get most recent user email from storage
|
||||||
if leEmail == "" {
|
if leEmail == "" {
|
||||||
// Then try to get most recent user email
|
|
||||||
leEmail = storage.MostRecentUserEmail()
|
leEmail = storage.MostRecentUserEmail()
|
||||||
// Save for next time
|
DefaultEmail = leEmail // save for next time
|
||||||
DefaultEmail = leEmail
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Looks like there is no email address readily available,
|
||||||
|
// so we will have to ask the user if we can.
|
||||||
if leEmail == "" && userPresent {
|
if leEmail == "" && userPresent {
|
||||||
// Alas, we must bother the user and ask for an email address;
|
// evidently, no User data was present in storage;
|
||||||
// if they proceed they also agree to the SA.
|
// thus we must make a new User so that we can get
|
||||||
reader := bufio.NewReader(stdin)
|
// the Terms of Service URL via our ACME client, phew!
|
||||||
fmt.Println("\nYour sites will be served over HTTPS automatically using Let's Encrypt.")
|
user, err := newUser("")
|
||||||
fmt.Println("By continuing, you agree to the Let's Encrypt Subscriber Agreement at:")
|
|
||||||
fmt.Println(" " + saURL) // TODO: Show current SA link
|
|
||||||
fmt.Println("Please enter your email address so you can recover your account if needed.")
|
|
||||||
fmt.Println("You can leave it blank, but you'll lose the ability to recover your account.")
|
|
||||||
fmt.Print("Email address: ")
|
|
||||||
var err error
|
|
||||||
leEmail, err = reader.ReadString('\n')
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
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 := DefaultCAUrl
|
||||||
|
if cfg.CAUrl != "" {
|
||||||
|
caURL = cfg.CAUrl
|
||||||
|
}
|
||||||
|
tempClient, err := acme.NewClient(caURL, user, "")
|
||||||
|
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)
|
||||||
|
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)
|
leEmail = strings.TrimSpace(leEmail)
|
||||||
DefaultEmail = leEmail
|
DefaultEmail = leEmail
|
||||||
Agreed = true
|
Agreed = true
|
||||||
|
|
||||||
|
// save the new user to preserve this for next time
|
||||||
|
user.Email = leEmail
|
||||||
|
err = saveUser(storage, user)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return strings.ToLower(leEmail)
|
|
||||||
|
// lower-casing the email is important for consistency
|
||||||
|
return strings.ToLower(leEmail), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getUser loads the user with the given email from disk
|
// getUser loads the user with the given email from disk
|
||||||
|
@ -154,18 +193,21 @@ func saveUser(storage Storage, user User) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// promptUserAgreement prompts the user to agree to the agreement
|
// promptUserAgreement simply outputs the standard user
|
||||||
// at agreementURL via stdin. If the agreement has changed, then pass
|
// agreement prompt with the given agreement URL.
|
||||||
// true as the second argument. If this is the user's first time
|
// It outputs a newline after the message.
|
||||||
// agreeing, pass false. It returns whether the user agreed or not.
|
func promptUserAgreement(agreementURL string) {
|
||||||
func promptUserAgreement(agreementURL string, changed bool) bool {
|
const userAgreementPrompt = `Your sites will be served over HTTPS automatically using Let's Encrypt.
|
||||||
if changed {
|
By continuing, you agree to the Let's Encrypt Subscriber Agreement at:`
|
||||||
fmt.Printf("The Let's Encrypt Subscriber Agreement has changed:\n %s\n", agreementURL)
|
fmt.Printf("\n\n%s\n %s\n", userAgreementPrompt, agreementURL)
|
||||||
fmt.Print("Do you agree to the new terms? (y/n): ")
|
}
|
||||||
} else {
|
|
||||||
fmt.Printf("To continue, you must agree to the Let's Encrypt Subscriber Agreement:\n %s\n", agreementURL)
|
// askUserAgreement prompts the user to agree to the agreement
|
||||||
fmt.Print("Do you agree to the terms? (y/n): ")
|
// at the given agreement URL via stdin. It returns whether the
|
||||||
}
|
// user agreed or not.
|
||||||
|
func askUserAgreement(agreementURL string) bool {
|
||||||
|
promptUserAgreement(agreementURL)
|
||||||
|
fmt.Print("Do you agree to the terms? (y/n): ")
|
||||||
|
|
||||||
reader := bufio.NewReader(stdin)
|
reader := bufio.NewReader(stdin)
|
||||||
answer, err := reader.ReadString('\n')
|
answer, err := reader.ReadString('\n')
|
||||||
|
@ -177,14 +219,15 @@ func promptUserAgreement(agreementURL string, changed bool) bool {
|
||||||
return answer == "y" || answer == "yes"
|
return answer == "y" || answer == "yes"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
// stdin is used to read the user's input if prompted;
|
||||||
// this is changed by tests during tests.
|
// this is changed by tests during tests.
|
||||||
var stdin = io.ReadWriter(os.Stdin)
|
var stdin = io.ReadWriter(os.Stdin)
|
||||||
|
|
||||||
// The name of the folder for accounts where the email
|
// The name of the folder for accounts where the email
|
||||||
// address was not provided; default 'username' if you will.
|
// address was not provided; default 'username' if you will,
|
||||||
|
// but only for local/storage use, not with the CA.
|
||||||
const emptyEmail = "default"
|
const emptyEmail = "default"
|
||||||
|
|
||||||
// TODO: After Boulder implements the 'meta' field of the directory,
|
|
||||||
// we can get this link dynamically.
|
|
||||||
const saURL = "https://acme-v01.api.letsencrypt.org/terms"
|
|
||||||
|
|
|
@ -20,13 +20,14 @@ import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"io"
|
"io"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acmev2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUser(t *testing.T) {
|
func TestUser(t *testing.T) {
|
||||||
|
@ -135,7 +136,13 @@ func TestGetUserAlreadyExists(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetEmail(t *testing.T) {
|
func TestGetEmail(t *testing.T) {
|
||||||
storageBasePath = testStorage.Path // to contain calls that create a new Storage...
|
// ensure storage (via StorageFor) uses the local testdata folder that we delete later
|
||||||
|
origCaddypath := os.Getenv("CADDYPATH")
|
||||||
|
os.Setenv("CADDYPATH", "./testdata")
|
||||||
|
defer os.Setenv("CADDYPATH", origCaddypath)
|
||||||
|
|
||||||
|
agreementTestURL = "(none - testing)"
|
||||||
|
defer func() { agreementTestURL = "" }()
|
||||||
|
|
||||||
// let's not clutter up the output
|
// let's not clutter up the output
|
||||||
origStdout := os.Stdout
|
origStdout := os.Stdout
|
||||||
|
@ -146,7 +153,10 @@ func TestGetEmail(t *testing.T) {
|
||||||
DefaultEmail = "test2@foo.com"
|
DefaultEmail = "test2@foo.com"
|
||||||
|
|
||||||
// Test1: Use default email from flag (or user previously typing it)
|
// Test1: Use default email from flag (or user previously typing it)
|
||||||
actual := getEmail(testStorage, true)
|
actual, err := getEmail(testConfig, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("getEmail (1) error: %v", err)
|
||||||
|
}
|
||||||
if actual != DefaultEmail {
|
if actual != DefaultEmail {
|
||||||
t.Errorf("Did not get correct email from memory; expected '%s' but got '%s'", DefaultEmail, actual)
|
t.Errorf("Did not get correct email from memory; expected '%s' but got '%s'", DefaultEmail, actual)
|
||||||
}
|
}
|
||||||
|
@ -154,16 +164,19 @@ func TestGetEmail(t *testing.T) {
|
||||||
// Test2: Get input from user
|
// Test2: Get input from user
|
||||||
DefaultEmail = ""
|
DefaultEmail = ""
|
||||||
stdin = new(bytes.Buffer)
|
stdin = new(bytes.Buffer)
|
||||||
_, err := io.Copy(stdin, strings.NewReader("test3@foo.com\n"))
|
_, err = io.Copy(stdin, strings.NewReader("test3@foo.com\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not simulate user input, error: %v", err)
|
t.Fatalf("Could not simulate user input, error: %v", err)
|
||||||
}
|
}
|
||||||
actual = getEmail(testStorage, true)
|
actual, err = getEmail(testConfig, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("getEmail (2) error: %v", err)
|
||||||
|
}
|
||||||
if actual != "test3@foo.com" {
|
if actual != "test3@foo.com" {
|
||||||
t.Errorf("Did not get correct email from user input prompt; expected '%s' but got '%s'", "test3@foo.com", actual)
|
t.Errorf("Did not get correct email from user input prompt; expected '%s' but got '%s'", "test3@foo.com", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test3: Get most recent email from before
|
// Test3: Get most recent email from before (in storage)
|
||||||
DefaultEmail = ""
|
DefaultEmail = ""
|
||||||
for i, eml := range []string{
|
for i, eml := range []string{
|
||||||
"TEST4-3@foo.com", // test case insensitivity
|
"TEST4-3@foo.com", // test case insensitivity
|
||||||
|
@ -189,14 +202,20 @@ func TestGetEmail(t *testing.T) {
|
||||||
t.Fatalf("Could not change user folder mod time for '%s': %v", eml, err)
|
t.Fatalf("Could not change user folder mod time for '%s': %v", eml, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actual = getEmail(testStorage, true)
|
actual, err = getEmail(testConfig, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("getEmail (3) error: %v", err)
|
||||||
|
}
|
||||||
if actual != "test4-3@foo.com" {
|
if actual != "test4-3@foo.com" {
|
||||||
t.Errorf("Did not get correct email from storage; expected '%s' but got '%s'", "test4-3@foo.com", actual)
|
t.Errorf("Did not get correct email from storage; expected '%s' but got '%s'", "test4-3@foo.com", actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var testStorage = &FileStorage{Path: "./testdata"}
|
var (
|
||||||
|
testStorageBase = "./testdata" // ephemeral folder that gets deleted after tests finish
|
||||||
|
testCAHost = "localhost"
|
||||||
|
testConfig = &Config{CAUrl: "http://" + testCAHost + "/directory", StorageProvider: "file"}
|
||||||
|
testStorage = &FileStorage{Path: filepath.Join(testStorageBase, "acme", testCAHost)}
|
||||||
|
)
|
||||||
|
|
||||||
func (s *FileStorage) clean() error {
|
func (s *FileStorage) clean() error { return os.RemoveAll(testStorageBase) }
|
||||||
return os.RemoveAll(s.Path)
|
|
||||||
}
|
|
||||||
|
|
115
vendor/github.com/xenolf/lego/acme/messages.go
generated
vendored
115
vendor/github.com/xenolf/lego/acme/messages.go
generated
vendored
|
@ -1,115 +0,0 @@
|
||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type directory struct {
|
|
||||||
NewAuthzURL string `json:"new-authz"`
|
|
||||||
NewCertURL string `json:"new-cert"`
|
|
||||||
NewRegURL string `json:"new-reg"`
|
|
||||||
RevokeCertURL string `json:"revoke-cert"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type registrationMessage struct {
|
|
||||||
Resource string `json:"resource"`
|
|
||||||
Contact []string `json:"contact"`
|
|
||||||
Delete bool `json:"delete,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registration is returned by the ACME server after the registration
|
|
||||||
// The client implementation should save this registration somewhere.
|
|
||||||
type Registration struct {
|
|
||||||
Resource string `json:"resource,omitempty"`
|
|
||||||
ID int `json:"id"`
|
|
||||||
Key jose.JsonWebKey `json:"key"`
|
|
||||||
Contact []string `json:"contact"`
|
|
||||||
Agreement string `json:"agreement,omitempty"`
|
|
||||||
Authorizations string `json:"authorizations,omitempty"`
|
|
||||||
Certificates string `json:"certificates,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegistrationResource represents all important informations about a registration
|
|
||||||
// of which the client needs to keep track itself.
|
|
||||||
type RegistrationResource struct {
|
|
||||||
Body Registration `json:"body,omitempty"`
|
|
||||||
URI string `json:"uri,omitempty"`
|
|
||||||
NewAuthzURL string `json:"new_authzr_uri,omitempty"`
|
|
||||||
TosURL string `json:"terms_of_service,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type authorizationResource struct {
|
|
||||||
Body authorization
|
|
||||||
Domain string
|
|
||||||
NewCertURL string
|
|
||||||
AuthURL string
|
|
||||||
}
|
|
||||||
|
|
||||||
type authorization struct {
|
|
||||||
Resource string `json:"resource,omitempty"`
|
|
||||||
Identifier identifier `json:"identifier"`
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
Expires time.Time `json:"expires,omitempty"`
|
|
||||||
Challenges []challenge `json:"challenges,omitempty"`
|
|
||||||
Combinations [][]int `json:"combinations,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type identifier struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type validationRecord struct {
|
|
||||||
URI string `json:"url,omitempty"`
|
|
||||||
Hostname string `json:"hostname,omitempty"`
|
|
||||||
Port string `json:"port,omitempty"`
|
|
||||||
ResolvedAddresses []string `json:"addressesResolved,omitempty"`
|
|
||||||
UsedAddress string `json:"addressUsed,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type challenge struct {
|
|
||||||
Resource string `json:"resource,omitempty"`
|
|
||||||
Type Challenge `json:"type,omitempty"`
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
URI string `json:"uri,omitempty"`
|
|
||||||
Token string `json:"token,omitempty"`
|
|
||||||
KeyAuthorization string `json:"keyAuthorization,omitempty"`
|
|
||||||
TLS bool `json:"tls,omitempty"`
|
|
||||||
Iterations int `json:"n,omitempty"`
|
|
||||||
Error RemoteError `json:"error,omitempty"`
|
|
||||||
ValidationRecords []validationRecord `json:"validationRecord,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type csrMessage struct {
|
|
||||||
Resource string `json:"resource,omitempty"`
|
|
||||||
Csr string `json:"csr"`
|
|
||||||
Authorizations []string `json:"authorizations"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type revokeCertMessage struct {
|
|
||||||
Resource string `json:"resource"`
|
|
||||||
Certificate string `json:"certificate"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type deactivateAuthMessage struct {
|
|
||||||
Resource string `json:"resource,omitempty"`
|
|
||||||
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:"-"`
|
|
||||||
}
|
|
67
vendor/github.com/xenolf/lego/acme/tls_sni_challenge.go
generated
vendored
67
vendor/github.com/xenolf/lego/acme/tls_sni_challenge.go
generated
vendored
|
@ -1,67 +0,0 @@
|
||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tlsSNIChallenge struct {
|
|
||||||
jws *jws
|
|
||||||
validate validateFunc
|
|
||||||
provider ChallengeProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tlsSNIChallenge) Solve(chlng challenge, domain string) error {
|
|
||||||
// FIXME: https://github.com/ietf-wg-acme/acme/pull/22
|
|
||||||
// Currently we implement this challenge to track boulder, not the current spec!
|
|
||||||
|
|
||||||
logf("[INFO][%s] acme: Trying to solve TLS-SNI-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.Printf("[%s] error cleaning up: %v", domain, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return t.validate(t.jws, domain, chlng.URI, challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLSSNI01ChallengeCert returns a certificate and target domain for the `tls-sni-01` challenge
|
|
||||||
func TLSSNI01ChallengeCert(keyAuth string) (tls.Certificate, string, error) {
|
|
||||||
// generate a new RSA key for the certificates
|
|
||||||
tempPrivKey, err := generatePrivateKey(RSA2048)
|
|
||||||
if err != nil {
|
|
||||||
return tls.Certificate{}, "", err
|
|
||||||
}
|
|
||||||
rsaPrivKey := tempPrivKey.(*rsa.PrivateKey)
|
|
||||||
rsaPrivPEM := pemEncode(rsaPrivKey)
|
|
||||||
|
|
||||||
zBytes := sha256.Sum256([]byte(keyAuth))
|
|
||||||
z := hex.EncodeToString(zBytes[:sha256.Size])
|
|
||||||
domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:])
|
|
||||||
tempCertPEM, err := generatePemCert(rsaPrivKey, domain)
|
|
||||||
if err != nil {
|
|
||||||
return tls.Certificate{}, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
|
|
||||||
if err != nil {
|
|
||||||
return tls.Certificate{}, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return certificate, domain, nil
|
|
||||||
}
|
|
62
vendor/github.com/xenolf/lego/acme/tls_sni_challenge_server.go
generated
vendored
62
vendor/github.com/xenolf/lego/acme/tls_sni_challenge_server.go
generated
vendored
|
@ -1,62 +0,0 @@
|
||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TLSProviderServer implements ChallengeProvider for `TLS-SNI-01` challenge
|
|
||||||
// It may be instantiated without using the NewTLSProviderServer function if
|
|
||||||
// you want only to use the default values.
|
|
||||||
type TLSProviderServer struct {
|
|
||||||
iface string
|
|
||||||
port string
|
|
||||||
done chan bool
|
|
||||||
listener net.Listener
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTLSProviderServer creates a new TLSProviderServer on the selected interface and port.
|
|
||||||
// Setting iface and / or port to an empty string will make the server fall back to
|
|
||||||
// the "any" interface and port 443 respectively.
|
|
||||||
func NewTLSProviderServer(iface, port string) *TLSProviderServer {
|
|
||||||
return &TLSProviderServer{iface: iface, port: port}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Present makes the keyAuth available as a cert
|
|
||||||
func (s *TLSProviderServer) Present(domain, token, keyAuth string) error {
|
|
||||||
if s.port == "" {
|
|
||||||
s.port = "443"
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, _, err := TLSSNI01ChallengeCert(keyAuth)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConf := new(tls.Config)
|
|
||||||
tlsConf.Certificates = []tls.Certificate{cert}
|
|
||||||
|
|
||||||
s.listener, err = tls.Listen("tcp", net.JoinHostPort(s.iface, s.port), tlsConf)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Could not start HTTPS server for challenge -> %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.done = make(chan bool)
|
|
||||||
go func() {
|
|
||||||
http.Serve(s.listener, nil)
|
|
||||||
s.done <- true
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanUp closes the HTTP server.
|
|
||||||
func (s *TLSProviderServer) CleanUp(domain, token, keyAuth string) error {
|
|
||||||
if s.listener == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
s.listener.Close()
|
|
||||||
<-s.done
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -7,9 +7,6 @@ const (
|
||||||
// HTTP01 is the "http-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#http
|
// 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
|
// Note: HTTP01ChallengePath returns the URL path to fulfill this challenge
|
||||||
HTTP01 = Challenge("http-01")
|
HTTP01 = Challenge("http-01")
|
||||||
// TLSSNI01 is the "tls-sni-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#tls-with-server-name-indication-tls-sni
|
|
||||||
// Note: TLSSNI01ChallengeCert returns a certificate to fulfill this challenge
|
|
||||||
TLSSNI01 = Challenge("tls-sni-01")
|
|
||||||
// DNS01 is the "dns-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#dns
|
// 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
|
// Note: DNS01Record returns a DNS record which will fulfill this challenge
|
||||||
DNS01 = Challenge("dns-01")
|
DNS01 = Challenge("dns-01")
|
|
@ -5,13 +5,11 @@ import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -82,27 +80,26 @@ func NewClient(caDirURL string, user User, keyType KeyType) (*Client, error) {
|
||||||
return nil, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
|
return nil, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if dir.NewRegURL == "" {
|
if dir.NewAccountURL == "" {
|
||||||
return nil, errors.New("directory missing new registration URL")
|
return nil, errors.New("directory missing new registration URL")
|
||||||
}
|
}
|
||||||
if dir.NewAuthzURL == "" {
|
if dir.NewOrderURL == "" {
|
||||||
return nil, errors.New("directory missing new authz URL")
|
return nil, errors.New("directory missing new order URL")
|
||||||
}
|
}
|
||||||
if dir.NewCertURL == "" {
|
/*if dir.RevokeCertURL == "" {
|
||||||
return nil, errors.New("directory missing new certificate URL")
|
|
||||||
}
|
|
||||||
if dir.RevokeCertURL == "" {
|
|
||||||
return nil, errors.New("directory missing revoke certificate URL")
|
return nil, errors.New("directory missing revoke certificate URL")
|
||||||
}
|
}*/
|
||||||
|
|
||||||
jws := &jws{privKey: privKey, directoryURL: caDirURL}
|
jws := &jws{privKey: privKey, getNonceURL: dir.NewNonceURL}
|
||||||
|
if reg := user.GetRegistration(); reg != nil {
|
||||||
|
jws.kid = reg.URI
|
||||||
|
}
|
||||||
|
|
||||||
// REVIEW: best possibility?
|
// REVIEW: best possibility?
|
||||||
// Add all available solvers with the right index as per ACME
|
// Add all available solvers with the right index as per ACME
|
||||||
// spec to this map. Otherwise they won`t be found.
|
// spec to this map. Otherwise they won`t be found.
|
||||||
solvers := make(map[Challenge]solver)
|
solvers := make(map[Challenge]solver)
|
||||||
solvers[HTTP01] = &httpChallenge{jws: jws, validate: validate, provider: &HTTPProviderServer{}}
|
solvers[HTTP01] = &httpChallenge{jws: jws, validate: validate, provider: &HTTPProviderServer{}}
|
||||||
solvers[TLSSNI01] = &tlsSNIChallenge{jws: jws, validate: validate, provider: &TLSProviderServer{}}
|
|
||||||
|
|
||||||
return &Client{directory: dir, user: user, jws: jws, keyType: keyType, solvers: solvers}, nil
|
return &Client{directory: dir, user: user, jws: jws, keyType: keyType, solvers: solvers}, nil
|
||||||
}
|
}
|
||||||
|
@ -112,8 +109,6 @@ func (c *Client) SetChallengeProvider(challenge Challenge, p ChallengeProvider)
|
||||||
switch challenge {
|
switch challenge {
|
||||||
case HTTP01:
|
case HTTP01:
|
||||||
c.solvers[challenge] = &httpChallenge{jws: c.jws, validate: validate, provider: p}
|
c.solvers[challenge] = &httpChallenge{jws: c.jws, validate: validate, provider: p}
|
||||||
case TLSSNI01:
|
|
||||||
c.solvers[challenge] = &tlsSNIChallenge{jws: c.jws, validate: validate, provider: p}
|
|
||||||
case DNS01:
|
case DNS01:
|
||||||
c.solvers[challenge] = &dnsChallenge{jws: c.jws, validate: validate, provider: p}
|
c.solvers[challenge] = &dnsChallenge{jws: c.jws, validate: validate, provider: p}
|
||||||
default:
|
default:
|
||||||
|
@ -141,24 +136,6 @@ func (c *Client) SetHTTPAddress(iface string) error {
|
||||||
return nil
|
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-SNI provider previously set by calling
|
|
||||||
// c.SetChallengeProvider with the default TLS-SNI 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[TLSSNI01]; ok {
|
|
||||||
chlng.(*tlsSNIChallenge).provider = NewTLSProviderServer(host, port)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExcludeChallenges explicitly removes challenges from the pool for solving.
|
// ExcludeChallenges explicitly removes challenges from the pool for solving.
|
||||||
func (c *Client) ExcludeChallenges(challenges []Challenge) {
|
func (c *Client) ExcludeChallenges(challenges []Challenge) {
|
||||||
// Loop through all challenges and delete the requested one if found.
|
// Loop through all challenges and delete the requested one if found.
|
||||||
|
@ -167,61 +144,71 @@ func (c *Client) ExcludeChallenges(challenges []Challenge) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetToSURL returns the current ToS URL from the Directory
|
||||||
|
func (c *Client) GetToSURL() string {
|
||||||
|
return c.directory.Meta.TermsOfService
|
||||||
|
}
|
||||||
|
|
||||||
// Register the current account to the ACME server.
|
// Register the current account to the ACME server.
|
||||||
func (c *Client) Register() (*RegistrationResource, error) {
|
func (c *Client) Register(tosAgreed bool) (*RegistrationResource, error) {
|
||||||
if c == nil || c.user == nil {
|
if c == nil || c.user == nil {
|
||||||
return nil, errors.New("acme: cannot register a nil client or user")
|
return nil, errors.New("acme: cannot register a nil client or user")
|
||||||
}
|
}
|
||||||
logf("[INFO] acme: Registering account for %s", c.user.GetEmail())
|
logf("[INFO] acme: Registering account for %s", c.user.GetEmail())
|
||||||
|
|
||||||
regMsg := registrationMessage{
|
accMsg := accountMessage{}
|
||||||
Resource: "new-reg",
|
|
||||||
}
|
|
||||||
if c.user.GetEmail() != "" {
|
if c.user.GetEmail() != "" {
|
||||||
regMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
|
accMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
|
||||||
} else {
|
} else {
|
||||||
regMsg.Contact = []string{}
|
accMsg.Contact = []string{}
|
||||||
}
|
}
|
||||||
|
accMsg.TermsOfServiceAgreed = tosAgreed
|
||||||
|
|
||||||
var serverReg Registration
|
var serverReg accountMessage
|
||||||
var regURI string
|
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, accMsg, &serverReg)
|
||||||
hdr, err := postJSON(c.jws, c.directory.NewRegURL, regMsg, &serverReg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
remoteErr, ok := err.(RemoteError)
|
remoteErr, ok := err.(RemoteError)
|
||||||
if ok && remoteErr.StatusCode == 409 {
|
if ok && remoteErr.StatusCode == 409 {
|
||||||
regURI = hdr.Get("Location")
|
|
||||||
regMsg = registrationMessage{
|
|
||||||
Resource: "reg",
|
|
||||||
}
|
|
||||||
if hdr, err = postJSON(c.jws, regURI, regMsg, &serverReg); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reg := &RegistrationResource{Body: serverReg}
|
reg := &RegistrationResource{
|
||||||
|
URI: hdr.Get("Location"),
|
||||||
links := parseLinks(hdr["Link"])
|
Body: serverReg,
|
||||||
|
|
||||||
if regURI == "" {
|
|
||||||
regURI = hdr.Get("Location")
|
|
||||||
}
|
|
||||||
reg.URI = regURI
|
|
||||||
if links["terms-of-service"] != "" {
|
|
||||||
reg.TosURL = links["terms-of-service"]
|
|
||||||
}
|
|
||||||
|
|
||||||
if links["next"] != "" {
|
|
||||||
reg.NewAuthzURL = links["next"]
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("acme: The server did not return 'next' link to proceed")
|
|
||||||
}
|
}
|
||||||
|
c.jws.kid = reg.URI
|
||||||
|
|
||||||
return reg, nil
|
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) {
|
||||||
|
logf("[INFO] acme: Trying to resolve account by key")
|
||||||
|
|
||||||
|
acc := accountMessage{OnlyReturnExisting: true}
|
||||||
|
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, acc, &acc)
|
||||||
|
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
|
||||||
|
hdr, 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
|
// DeleteRegistration deletes the client's user registration from the ACME
|
||||||
// server.
|
// server.
|
||||||
func (c *Client) DeleteRegistration() error {
|
func (c *Client) DeleteRegistration() error {
|
||||||
|
@ -230,12 +217,11 @@ func (c *Client) DeleteRegistration() error {
|
||||||
}
|
}
|
||||||
logf("[INFO] acme: Deleting account for %s", c.user.GetEmail())
|
logf("[INFO] acme: Deleting account for %s", c.user.GetEmail())
|
||||||
|
|
||||||
regMsg := registrationMessage{
|
accMsg := accountMessage{
|
||||||
Resource: "reg",
|
Status: "deactivated",
|
||||||
Delete: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := postJSON(c.jws, c.user.GetRegistration().URI, regMsg, nil)
|
_, err := postJSON(c.jws, c.user.GetRegistration().URI, accMsg, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -255,46 +241,23 @@ func (c *Client) QueryRegistration() (*RegistrationResource, error) {
|
||||||
// Log the URL here instead of the email as the email may not be set
|
// Log the URL here instead of the email as the email may not be set
|
||||||
logf("[INFO] acme: Querying account for %s", c.user.GetRegistration().URI)
|
logf("[INFO] acme: Querying account for %s", c.user.GetRegistration().URI)
|
||||||
|
|
||||||
regMsg := registrationMessage{
|
accMsg := accountMessage{}
|
||||||
Resource: "reg",
|
|
||||||
}
|
|
||||||
|
|
||||||
var serverReg Registration
|
var serverReg accountMessage
|
||||||
hdr, err := postJSON(c.jws, c.user.GetRegistration().URI, regMsg, &serverReg)
|
_, err := postJSON(c.jws, c.user.GetRegistration().URI, accMsg, &serverReg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
reg := &RegistrationResource{Body: serverReg}
|
reg := &RegistrationResource{Body: serverReg}
|
||||||
|
|
||||||
links := parseLinks(hdr["Link"])
|
|
||||||
// Location: header is not returned so this needs to be populated off of
|
// Location: header is not returned so this needs to be populated off of
|
||||||
// existing URI
|
// existing URI
|
||||||
reg.URI = c.user.GetRegistration().URI
|
reg.URI = c.user.GetRegistration().URI
|
||||||
if links["terms-of-service"] != "" {
|
|
||||||
reg.TosURL = links["terms-of-service"]
|
|
||||||
}
|
|
||||||
|
|
||||||
if links["next"] != "" {
|
|
||||||
reg.NewAuthzURL = links["next"]
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("acme: No new-authz link in response to registration query")
|
|
||||||
}
|
|
||||||
|
|
||||||
return reg, nil
|
return reg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AgreeToTOS updates the Client registration and sends the agreement to
|
|
||||||
// the server.
|
|
||||||
func (c *Client) AgreeToTOS() error {
|
|
||||||
reg := c.user.GetRegistration()
|
|
||||||
|
|
||||||
reg.Body.Agreement = c.user.GetRegistration().TosURL
|
|
||||||
reg.Body.Resource = "reg"
|
|
||||||
_, err := postJSON(c.jws, c.user.GetRegistration().URI, c.user.GetRegistration().Body, nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ObtainCertificateForCSR tries to obtain a certificate matching the CSR passed into it.
|
// 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
|
// The domains are inferred from the CommonName and SubjectAltNames, if any. The private key
|
||||||
// for this CSR is not required.
|
// for this CSR is not required.
|
||||||
|
@ -327,17 +290,25 @@ DNSNames:
|
||||||
logf("[INFO][%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
|
logf("[INFO][%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
challenges, failures := c.getChallenges(domains)
|
order, err := c.createOrderForIdentifiers(domains)
|
||||||
|
if err != nil {
|
||||||
|
identErrors := make(map[string]error)
|
||||||
|
for _, auth := range order.Identifiers {
|
||||||
|
identErrors[auth.Value] = err
|
||||||
|
}
|
||||||
|
return CertificateResource{}, identErrors
|
||||||
|
}
|
||||||
|
authz, failures := c.getAuthzForOrder(order)
|
||||||
// If any challenge fails - return. Do not generate partial SAN certificates.
|
// If any challenge fails - return. Do not generate partial SAN certificates.
|
||||||
if len(failures) > 0 {
|
if len(failures) > 0 {
|
||||||
for _, auth := range challenges {
|
/*for _, auth := range authz {
|
||||||
c.disableAuthz(auth)
|
c.disableAuthz(auth)
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return CertificateResource{}, failures
|
return CertificateResource{}, failures
|
||||||
}
|
}
|
||||||
|
|
||||||
errs := c.solveChallenges(challenges)
|
errs := c.solveChallengeForAuthz(authz)
|
||||||
// If any challenge fails - return. Do not generate partial SAN certificates.
|
// If any challenge fails - return. Do not generate partial SAN certificates.
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return CertificateResource{}, errs
|
return CertificateResource{}, errs
|
||||||
|
@ -345,10 +316,10 @@ DNSNames:
|
||||||
|
|
||||||
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||||
|
|
||||||
cert, err := c.requestCertificateForCsr(challenges, bundle, csr.Raw, nil)
|
cert, err := c.requestCertificateForCsr(order, bundle, csr.Raw, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, chln := range challenges {
|
for _, chln := range authz {
|
||||||
failures[chln.Domain] = err
|
failures[chln.Identifier.Value] = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,17 +345,25 @@ func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto
|
||||||
logf("[INFO][%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
logf("[INFO][%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
challenges, failures := c.getChallenges(domains)
|
order, err := c.createOrderForIdentifiers(domains)
|
||||||
|
if err != nil {
|
||||||
|
identErrors := make(map[string]error)
|
||||||
|
for _, auth := range order.Identifiers {
|
||||||
|
identErrors[auth.Value] = err
|
||||||
|
}
|
||||||
|
return CertificateResource{}, identErrors
|
||||||
|
}
|
||||||
|
authz, failures := c.getAuthzForOrder(order)
|
||||||
// If any challenge fails - return. Do not generate partial SAN certificates.
|
// If any challenge fails - return. Do not generate partial SAN certificates.
|
||||||
if len(failures) > 0 {
|
if len(failures) > 0 {
|
||||||
for _, auth := range challenges {
|
/*for _, auth := range authz {
|
||||||
c.disableAuthz(auth)
|
c.disableAuthz(auth)
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return CertificateResource{}, failures
|
return CertificateResource{}, failures
|
||||||
}
|
}
|
||||||
|
|
||||||
errs := c.solveChallenges(challenges)
|
errs := c.solveChallengeForAuthz(authz)
|
||||||
// If any challenge fails - return. Do not generate partial SAN certificates.
|
// If any challenge fails - return. Do not generate partial SAN certificates.
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return CertificateResource{}, errs
|
return CertificateResource{}, errs
|
||||||
|
@ -392,10 +371,10 @@ func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto
|
||||||
|
|
||||||
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||||
|
|
||||||
cert, err := c.requestCertificate(challenges, bundle, privKey, mustStaple)
|
cert, err := c.requestCertificateForOrder(order, bundle, privKey, mustStaple)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, chln := range challenges {
|
for _, auth := range authz {
|
||||||
failures[chln.Domain] = err
|
failures[auth.Identifier.Value] = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,7 +395,7 @@ func (c *Client) RevokeCertificate(certificate []byte) error {
|
||||||
|
|
||||||
encodedCert := base64.URLEncoding.EncodeToString(x509Cert.Raw)
|
encodedCert := base64.URLEncoding.EncodeToString(x509Cert.Raw)
|
||||||
|
|
||||||
_, err = postJSON(c.jws, c.directory.RevokeCertURL, revokeCertMessage{Resource: "revoke-cert", Certificate: encodedCert}, nil)
|
_, err = postJSON(c.jws, c.directory.RevokeCertURL, revokeCertMessage{Certificate: encodedCert}, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,129 +463,123 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b
|
||||||
return newCert, failures[cert.Domain]
|
return newCert, failures[cert.Domain]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
orderMessage: response,
|
||||||
|
}
|
||||||
|
return orderRes, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Looks through the challenge combinations to find a solvable match.
|
// Looks through the challenge combinations to find a solvable match.
|
||||||
// Then solves the challenges in series and returns.
|
// Then solves the challenges in series and returns.
|
||||||
func (c *Client) solveChallenges(challenges []authorizationResource) map[string]error {
|
func (c *Client) solveChallengeForAuthz(authorizations []authorization) map[string]error {
|
||||||
// loop through the resources, basically through the domains.
|
// loop through the resources, basically through the domains.
|
||||||
failures := make(map[string]error)
|
failures := make(map[string]error)
|
||||||
for _, authz := range challenges {
|
for _, authz := range authorizations {
|
||||||
if authz.Body.Status == "valid" {
|
if authz.Status == "valid" {
|
||||||
// Boulder might recycle recent validated authz (see issue #267)
|
// Boulder might recycle recent validated authz (see issue #267)
|
||||||
logf("[INFO][%s] acme: Authorization already valid; skipping challenge", authz.Domain)
|
logf("[INFO][%s] acme: Authorization already valid; skipping challenge", authz.Identifier.Value)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// no solvers - no solving
|
// no solvers - no solving
|
||||||
if solvers := c.chooseSolvers(authz.Body, authz.Domain); solvers != nil {
|
if i, solver := c.chooseSolver(authz, authz.Identifier.Value); solver != nil {
|
||||||
for i, solver := range solvers {
|
err := solver.Solve(authz.Challenges[i], authz.Identifier.Value)
|
||||||
// TODO: do not immediately fail if one domain fails to validate.
|
if err != nil {
|
||||||
err := solver.Solve(authz.Body.Challenges[i], authz.Domain)
|
//c.disableAuthz(authz.Identifier)
|
||||||
if err != nil {
|
failures[authz.Identifier.Value] = err
|
||||||
c.disableAuthz(authz)
|
|
||||||
failures[authz.Domain] = err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.disableAuthz(authz)
|
//c.disableAuthz(authz)
|
||||||
failures[authz.Domain] = fmt.Errorf("[%s] acme: Could not determine solvers", authz.Domain)
|
failures[authz.Identifier.Value] = fmt.Errorf("[%s] acme: Could not determine solvers", authz.Identifier.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return failures
|
return failures
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks all combinations from the server and returns an array of
|
// Checks all challenges from the server in order and returns the first matching solver.
|
||||||
// solvers which should get executed in series.
|
func (c *Client) chooseSolver(auth authorization, domain string) (int, solver) {
|
||||||
func (c *Client) chooseSolvers(auth authorization, domain string) map[int]solver {
|
for i, challenge := range auth.Challenges {
|
||||||
for _, combination := range auth.Combinations {
|
if solver, ok := c.solvers[Challenge(challenge.Type)]; ok {
|
||||||
solvers := make(map[int]solver)
|
return i, solver
|
||||||
for _, idx := range combination {
|
|
||||||
if solver, ok := c.solvers[auth.Challenges[idx].Type]; ok {
|
|
||||||
solvers[idx] = solver
|
|
||||||
} else {
|
|
||||||
logf("[INFO][%s] acme: Could not find solver for: %s", domain, auth.Challenges[idx].Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we can solve the whole combination, return the solvers
|
|
||||||
if len(solvers) == len(combination) {
|
|
||||||
return solvers
|
|
||||||
}
|
}
|
||||||
|
logf("[INFO][%s] acme: Could not find solver for: %s", domain, challenge.Type)
|
||||||
}
|
}
|
||||||
return nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the challenges needed to proof our identifier to the ACME server.
|
// Get the challenges needed to proof our identifier to the ACME server.
|
||||||
func (c *Client) getChallenges(domains []string) ([]authorizationResource, map[string]error) {
|
func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, map[string]error) {
|
||||||
resc, errc := make(chan authorizationResource), make(chan domainError)
|
resc, errc := make(chan authorization), make(chan domainError)
|
||||||
|
|
||||||
delay := time.Second / overallRequestLimit
|
delay := time.Second / overallRequestLimit
|
||||||
|
|
||||||
for _, domain := range domains {
|
for _, authzURL := range order.Authorizations {
|
||||||
time.Sleep(delay)
|
time.Sleep(delay)
|
||||||
|
|
||||||
go func(domain string) {
|
go func(authzURL string) {
|
||||||
authMsg := authorization{Resource: "new-authz", Identifier: identifier{Type: "dns", Value: domain}}
|
|
||||||
var authz authorization
|
var authz authorization
|
||||||
hdr, err := postJSON(c.jws, c.user.GetRegistration().NewAuthzURL, authMsg, &authz)
|
_, err := getJSON(authzURL, &authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errc <- domainError{Domain: domain, Error: err}
|
errc <- domainError{Domain: authz.Identifier.Value, Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
links := parseLinks(hdr["Link"])
|
resc <- authz
|
||||||
if links["next"] == "" {
|
}(authzURL)
|
||||||
logf("[ERROR][%s] acme: Server did not provide next link to proceed", domain)
|
|
||||||
errc <- domainError{Domain: domain, Error: errors.New("Server did not provide next link to proceed")}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resc <- authorizationResource{Body: authz, NewCertURL: links["next"], AuthURL: hdr.Get("Location"), Domain: domain}
|
|
||||||
}(domain)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
responses := make(map[string]authorizationResource)
|
var responses []authorization
|
||||||
failures := make(map[string]error)
|
failures := make(map[string]error)
|
||||||
for i := 0; i < len(domains); i++ {
|
for i := 0; i < len(order.Authorizations); i++ {
|
||||||
select {
|
select {
|
||||||
case res := <-resc:
|
case res := <-resc:
|
||||||
responses[res.Domain] = res
|
responses = append(responses, res)
|
||||||
case err := <-errc:
|
case err := <-errc:
|
||||||
failures[err.Domain] = err.Error
|
failures[err.Domain] = err.Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
challenges := make([]authorizationResource, 0, len(responses))
|
logAuthz(order)
|
||||||
for _, domain := range domains {
|
|
||||||
if challenge, ok := responses[domain]; ok {
|
|
||||||
challenges = append(challenges, challenge)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logAuthz(challenges)
|
|
||||||
|
|
||||||
close(resc)
|
close(resc)
|
||||||
close(errc)
|
close(errc)
|
||||||
|
|
||||||
return challenges, failures
|
return responses, failures
|
||||||
}
|
}
|
||||||
|
|
||||||
func logAuthz(authz []authorizationResource) {
|
func logAuthz(order orderResource) {
|
||||||
for _, auth := range authz {
|
for i, auth := range order.Authorizations {
|
||||||
logf("[INFO][%s] AuthURL: %s", auth.Domain, auth.AuthURL)
|
logf("[INFO][%s] AuthURL: %s", order.Identifiers[i].Value, auth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanAuthz loops through the passed in slice and disables any auths which are not "valid"
|
// cleanAuthz loops through the passed in slice and disables any auths which are not "valid"
|
||||||
func (c *Client) disableAuthz(auth authorizationResource) error {
|
func (c *Client) disableAuthz(authURL string) error {
|
||||||
var disabledAuth authorization
|
var disabledAuth authorization
|
||||||
_, err := postJSON(c.jws, auth.AuthURL, deactivateAuthMessage{Resource: "authz", Status: "deactivated"}, &disabledAuth)
|
_, err := postJSON(c.jws, authURL, deactivateAuthMessage{Status: "deactivated"}, &disabledAuth)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) requestCertificate(authz []authorizationResource, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, error) {
|
func (c *Client) requestCertificateForOrder(order orderResource, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, error) {
|
||||||
if len(authz) == 0 {
|
|
||||||
return CertificateResource{}, errors.New("Passed no authorizations to requestCertificate!")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if privKey == nil {
|
if privKey == nil {
|
||||||
|
@ -617,50 +590,65 @@ func (c *Client) requestCertificate(authz []authorizationResource, bundle bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine certificate name(s) based on the authorization resources
|
// determine certificate name(s) based on the authorization resources
|
||||||
commonName := authz[0]
|
commonName := order.Identifiers[0].Value
|
||||||
var san []string
|
var san []string
|
||||||
for _, auth := range authz[1:] {
|
for _, auth := range order.Identifiers {
|
||||||
san = append(san, auth.Domain)
|
san = append(san, auth.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: should the CSR be customizable?
|
// TODO: should the CSR be customizable?
|
||||||
csr, err := generateCsr(privKey, commonName.Domain, san, mustStaple)
|
csr, err := generateCsr(privKey, commonName, san, mustStaple)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CertificateResource{}, err
|
return CertificateResource{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.requestCertificateForCsr(authz, bundle, csr, pemEncode(privKey))
|
return c.requestCertificateForCsr(order, bundle, csr, pemEncode(privKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) requestCertificateForCsr(authz []authorizationResource, bundle bool, csr []byte, privateKeyPem []byte) (CertificateResource, error) {
|
func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr []byte, privateKeyPem []byte) (CertificateResource, error) {
|
||||||
commonName := authz[0]
|
commonName := order.Identifiers[0].Value
|
||||||
|
|
||||||
var authURLs []string
|
var authURLs []string
|
||||||
for _, auth := range authz[1:] {
|
for _, auth := range order.Identifiers[1:] {
|
||||||
authURLs = append(authURLs, auth.AuthURL)
|
authURLs = append(authURLs, auth.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
csrString := base64.URLEncoding.EncodeToString(csr)
|
csrString := base64.RawURLEncoding.EncodeToString(csr)
|
||||||
jsonBytes, err := json.Marshal(csrMessage{Resource: "new-cert", Csr: csrString, Authorizations: authURLs})
|
var retOrder orderMessage
|
||||||
if err != nil {
|
_, error := postJSON(c.jws, order.Finalize, csrMessage{Csr: csrString}, &retOrder)
|
||||||
return CertificateResource{}, err
|
if error != nil {
|
||||||
|
return CertificateResource{}, error
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.jws.post(commonName.NewCertURL, jsonBytes)
|
if retOrder.Status == "invalid" {
|
||||||
if err != nil {
|
return CertificateResource{}, error
|
||||||
return CertificateResource{}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
certRes := CertificateResource{
|
certRes := CertificateResource{
|
||||||
Domain: commonName.Domain,
|
Domain: commonName,
|
||||||
CertURL: resp.Header.Get("Location"),
|
CertURL: retOrder.Certificate,
|
||||||
PrivateKey: privateKeyPem,
|
PrivateKey: privateKeyPem,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if retOrder.Status == "valid" {
|
||||||
|
// if the certificate is available right away, short cut!
|
||||||
|
ok, err := c.checkCertResponse(retOrder, &certRes, bundle)
|
||||||
|
if err != nil {
|
||||||
|
return CertificateResource{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return certRes, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
maxChecks := 1000
|
maxChecks := 1000
|
||||||
for i := 0; i < maxChecks; i++ {
|
for i := 0; i < maxChecks; i++ {
|
||||||
done, err := c.checkCertResponse(resp, &certRes, bundle)
|
_, err := getJSON(order.URL, &retOrder)
|
||||||
resp.Body.Close()
|
if err != nil {
|
||||||
|
return CertificateResource{}, err
|
||||||
|
}
|
||||||
|
done, err := c.checkCertResponse(retOrder, &certRes, bundle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CertificateResource{}, err
|
return CertificateResource{}, err
|
||||||
}
|
}
|
||||||
|
@ -670,43 +658,36 @@ func (c *Client) requestCertificateForCsr(authz []authorizationResource, bundle
|
||||||
if i == maxChecks-1 {
|
if i == maxChecks-1 {
|
||||||
return CertificateResource{}, fmt.Errorf("polled for certificate %d times; giving up", i)
|
return CertificateResource{}, fmt.Errorf("polled for certificate %d times; giving up", i)
|
||||||
}
|
}
|
||||||
resp, err = httpGet(certRes.CertURL)
|
|
||||||
if err != nil {
|
|
||||||
return CertificateResource{}, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return certRes, nil
|
return certRes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkCertResponse checks resp to see if a certificate is contained in the
|
// checkCertResponse checks to see if the certificate is ready and a link is contained in the
|
||||||
// response, and if so, loads it into certRes and returns true. If the cert
|
// response. if so, loads it into certRes and returns true. If the cert
|
||||||
// is not yet ready, it returns false. This function honors the waiting period
|
// is not yet ready, it returns false. The certRes input
|
||||||
// required by the Retry-After header of the response, if specified. This
|
|
||||||
// function may read from resp.Body but does NOT close it. The certRes input
|
|
||||||
// should already have the Domain (common name) field populated. If bundle is
|
// should already have the Domain (common name) field populated. If bundle is
|
||||||
// true, the certificate will be bundled with the issuer's cert.
|
// true, the certificate will be bundled with the issuer's cert.
|
||||||
func (c *Client) checkCertResponse(resp *http.Response, certRes *CertificateResource, bundle bool) (bool, error) {
|
func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResource, bundle bool) (bool, error) {
|
||||||
switch resp.StatusCode {
|
|
||||||
case 201, 202:
|
switch order.Status {
|
||||||
|
case "valid":
|
||||||
|
resp, err := httpGet(order.Certificate)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
cert, err := ioutil.ReadAll(limitReader(resp.Body, maxBodySize))
|
cert, err := ioutil.ReadAll(limitReader(resp.Body, maxBodySize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// The server returns a body with a length of zero if the
|
// The issuer certificate link is always supplied via an "up" link
|
||||||
// certificate was not ready at the time this request completed.
|
// in the response headers of a new certificate.
|
||||||
// Otherwise the body is the certificate.
|
links := parseLinks(resp.Header["Link"])
|
||||||
if len(cert) > 0 {
|
if link, ok := links["up"]; ok {
|
||||||
certRes.CertStableURL = resp.Header.Get("Content-Location")
|
issuerCert, err := c.getIssuerCertificate(link)
|
||||||
certRes.AccountRef = c.user.GetRegistration().URI
|
|
||||||
|
|
||||||
issuedCert := pemEncode(derCertificateBytes(cert))
|
|
||||||
|
|
||||||
// The issuer certificate link is always supplied via an "up" link
|
|
||||||
// in the response headers of a new certificate.
|
|
||||||
links := parseLinks(resp.Header["Link"])
|
|
||||||
issuerCert, err := c.getIssuerCertificate(links["up"])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
|
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
|
||||||
logf("[WARNING][%s] acme: Could not bundle issuer certificate: %v", certRes.Domain, err)
|
logf("[WARNING][%s] acme: Could not bundle issuer certificate: %v", certRes.Domain, err)
|
||||||
|
@ -716,31 +697,26 @@ func (c *Client) checkCertResponse(resp *http.Response, certRes *CertificateReso
|
||||||
// If bundle is true, we want to return a certificate bundle.
|
// If bundle is true, we want to return a certificate bundle.
|
||||||
// To do this, we append the issuer cert to the issued cert.
|
// To do this, we append the issuer cert to the issued cert.
|
||||||
if bundle {
|
if bundle {
|
||||||
issuedCert = append(issuedCert, issuerCert...)
|
cert = append(cert, issuerCert...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
certRes.IssuerCertificate = issuerCert
|
||||||
}
|
}
|
||||||
|
|
||||||
certRes.Certificate = issuedCert
|
|
||||||
certRes.IssuerCertificate = issuerCert
|
|
||||||
logf("[INFO][%s] Server responded with a certificate.", certRes.Domain)
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The certificate was granted but is not yet issued.
|
certRes.Certificate = cert
|
||||||
// Check retry-after and loop.
|
certRes.CertURL = order.Certificate
|
||||||
ra := resp.Header.Get("Retry-After")
|
certRes.CertStableURL = order.Certificate
|
||||||
retryAfter, err := strconv.Atoi(ra)
|
logf("[INFO][%s] Server responded with a certificate.", certRes.Domain)
|
||||||
if err != nil {
|
return true, nil
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logf("[INFO][%s] acme: Server responded with status 202; retrying after %ds", certRes.Domain, retryAfter)
|
|
||||||
time.Sleep(time.Duration(retryAfter) * time.Second)
|
|
||||||
|
|
||||||
|
case "processing":
|
||||||
return false, nil
|
return false, nil
|
||||||
default:
|
case "invalid":
|
||||||
return false, handleHTTPError(resp)
|
return false, errors.New("Order has invalid state: invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getIssuerCertificate requests the issuer certificate
|
// getIssuerCertificate requests the issuer certificate
|
||||||
|
@ -786,10 +762,10 @@ func parseLinks(links []string) map[string]string {
|
||||||
|
|
||||||
// validate makes the ACME server start validating a
|
// validate makes the ACME server start validating a
|
||||||
// challenge response, only returning once it is done.
|
// challenge response, only returning once it is done.
|
||||||
func validate(j *jws, domain, uri string, chlng challenge) error {
|
func validate(j *jws, domain, uri string, c challenge) error {
|
||||||
var challengeResponse challenge
|
var chlng challenge
|
||||||
|
|
||||||
hdr, err := postJSON(j, uri, chlng, &challengeResponse)
|
hdr, err := postJSON(j, uri, c, &chlng)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -797,27 +773,27 @@ func validate(j *jws, domain, uri string, chlng challenge) error {
|
||||||
// After the path is sent, the ACME server will access our server.
|
// After the path is sent, the ACME server will access our server.
|
||||||
// Repeatedly check the server for an updated status on our request.
|
// Repeatedly check the server for an updated status on our request.
|
||||||
for {
|
for {
|
||||||
switch challengeResponse.Status {
|
switch chlng.Status {
|
||||||
case "valid":
|
case "valid":
|
||||||
logf("[INFO][%s] The server validated our request", domain)
|
logf("[INFO][%s] The server validated our request", domain)
|
||||||
return nil
|
return nil
|
||||||
case "pending":
|
case "pending":
|
||||||
break
|
break
|
||||||
case "invalid":
|
case "invalid":
|
||||||
return handleChallengeError(challengeResponse)
|
return handleChallengeError(chlng)
|
||||||
default:
|
default:
|
||||||
return errors.New("The server returned an unexpected state.")
|
return errors.New("The server returned an unexpected state")
|
||||||
}
|
}
|
||||||
|
|
||||||
ra, err := strconv.Atoi(hdr.Get("Retry-After"))
|
ra, err := strconv.Atoi(hdr.Get("Retry-After"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The ACME server MUST return a Retry-After.
|
// The ACME server MUST return a Retry-After.
|
||||||
// If it doesn't, we'll just poll hard.
|
// If it doesn't, we'll just poll hard.
|
||||||
ra = 1
|
ra = 5
|
||||||
}
|
}
|
||||||
time.Sleep(time.Duration(ra) * time.Second)
|
time.Sleep(time.Duration(ra) * time.Second)
|
||||||
|
|
||||||
hdr, err = getJSON(uri, &challengeResponse)
|
hdr, err = getJSON(uri, &chlng)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
|
@ -17,12 +17,12 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
|
|
||||||
"golang.org/x/crypto/ocsp"
|
"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.
|
// KeyType represents the key algo as well as the key size or curve to use.
|
||||||
|
@ -136,9 +136,9 @@ func getKeyAuthorization(token string, key interface{}) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the Key Authorization for the challenge
|
// Generate the Key Authorization for the challenge
|
||||||
jwk := keyAsJWK(publicKey)
|
jwk := &jose.JSONWebKey{Key: publicKey}
|
||||||
if jwk == nil {
|
if jwk == nil {
|
||||||
return "", errors.New("Could not generate JWK from key.")
|
return "", errors.New("Could not generate JWK from key")
|
||||||
}
|
}
|
||||||
thumbBytes, err := jwk.Thumbprint(crypto.SHA256)
|
thumbBytes, err := jwk.Thumbprint(crypto.SHA256)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -146,11 +146,7 @@ func getKeyAuthorization(token string, key interface{}) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// unpad the base64URL
|
// unpad the base64URL
|
||||||
keyThumb := base64.URLEncoding.EncodeToString(thumbBytes)
|
keyThumb := base64.RawURLEncoding.EncodeToString(thumbBytes)
|
||||||
index := strings.Index(keyThumb, "=")
|
|
||||||
if index != -1 {
|
|
||||||
keyThumb = keyThumb[:index]
|
|
||||||
}
|
|
||||||
|
|
||||||
return token + "." + keyThumb, nil
|
return token + "." + keyThumb, nil
|
||||||
}
|
}
|
||||||
|
@ -177,7 +173,7 @@ func parsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(certificates) == 0 {
|
if len(certificates) == 0 {
|
||||||
return nil, errors.New("No certificates were found while parsing the bundle.")
|
return nil, errors.New("No certificates were found while parsing the bundle")
|
||||||
}
|
}
|
||||||
|
|
||||||
return certificates, nil
|
return certificates, nil
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"golang.org/x/net/publicsuffix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type preCheckDNSFunc func(fqdn, value string) (bool, error)
|
type preCheckDNSFunc func(fqdn, value string) (bool, error)
|
||||||
|
@ -30,6 +29,7 @@ var defaultNameservers = []string{
|
||||||
"google-public-dns-b.google.com:53",
|
"google-public-dns-b.google.com:53",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RecursiveNameservers are used to pre-check DNS propagations
|
||||||
var RecursiveNameservers = getNameservers(defaultResolvConf, defaultNameservers)
|
var RecursiveNameservers = getNameservers(defaultResolvConf, defaultNameservers)
|
||||||
|
|
||||||
// DNSTimeout is used to override the default DNS timeout of 10 seconds.
|
// DNSTimeout is used to override the default DNS timeout of 10 seconds.
|
||||||
|
@ -58,8 +58,7 @@ func getNameservers(path string, defaults []string) []string {
|
||||||
func DNS01Record(domain, keyAuth string) (fqdn string, value string, ttl int) {
|
func DNS01Record(domain, keyAuth string) (fqdn string, value string, ttl int) {
|
||||||
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
|
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
|
||||||
// base64URL encoding without padding
|
// base64URL encoding without padding
|
||||||
keyAuthSha := base64.URLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
|
value = base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
|
||||||
value = strings.TrimRight(keyAuthSha, "=")
|
|
||||||
ttl = 120
|
ttl = 120
|
||||||
fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
|
fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
|
||||||
return
|
return
|
||||||
|
@ -115,7 +114,7 @@ func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.validate(s.jws, domain, chlng.URI, challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
return s.validate(s.jws, domain, chlng.URL, challenge{Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
|
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
|
||||||
|
@ -194,7 +193,7 @@ func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (
|
||||||
|
|
||||||
if err == dns.ErrTruncated {
|
if err == dns.ErrTruncated {
|
||||||
tcp := &dns.Client{Net: "tcp", Timeout: DNSTimeout}
|
tcp := &dns.Client{Net: "tcp", Timeout: DNSTimeout}
|
||||||
// If the TCP request suceeds, the err will reset to nil
|
// If the TCP request succeeds, the err will reset to nil
|
||||||
in, _, err = tcp.Exchange(m, ns)
|
in, _, err = tcp.Exchange(m, ns)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,10 +241,6 @@ func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) {
|
||||||
labelIndexes := dns.Split(fqdn)
|
labelIndexes := dns.Split(fqdn)
|
||||||
for _, index := range labelIndexes {
|
for _, index := range labelIndexes {
|
||||||
domain := fqdn[index:]
|
domain := fqdn[index:]
|
||||||
// Give up if we have reached the TLD
|
|
||||||
if isTLD(domain) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
in, err := dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
in, err := dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -260,6 +255,13 @@ func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) {
|
||||||
|
|
||||||
// Check if we got a SOA RR in the answer section
|
// Check if we got a SOA RR in the answer section
|
||||||
if in.Rcode == dns.RcodeSuccess {
|
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 {
|
for _, ans := range in.Answer {
|
||||||
if soa, ok := ans.(*dns.SOA); ok {
|
if soa, ok := ans.(*dns.SOA); ok {
|
||||||
zone := soa.Hdr.Name
|
zone := soa.Hdr.Name
|
||||||
|
@ -273,10 +275,12 @@ func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) {
|
||||||
return "", fmt.Errorf("Could not find the start of authority")
|
return "", fmt.Errorf("Could not find the start of authority")
|
||||||
}
|
}
|
||||||
|
|
||||||
func isTLD(domain string) bool {
|
// dnsMsgContainsCNAME checks for a CNAME answer in msg
|
||||||
publicsuffix, _ := publicsuffix.PublicSuffix(UnFqdn(domain))
|
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
|
||||||
if publicsuffix == UnFqdn(domain) {
|
for _, ans := range msg.Answer {
|
||||||
return true
|
if _, ok := ans.(*dns.CNAME); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
|
@ -9,8 +9,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tosAgreementError = "Must agree to subscriber agreement before any further actions"
|
tosAgreementError = "Terms of service have changed"
|
||||||
invalidNonceError = "JWS has invalid anti-replay nonce"
|
invalidNonceError = "urn:ietf:params:acme:error:badNonce"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemoteError is the base type for all errors specific to the ACME protocol.
|
// RemoteError is the base type for all errors specific to the ACME protocol.
|
||||||
|
@ -42,27 +42,11 @@ type domainError struct {
|
||||||
Error error
|
Error error
|
||||||
}
|
}
|
||||||
|
|
||||||
type challengeError struct {
|
|
||||||
RemoteError
|
|
||||||
records []validationRecord
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c challengeError) Error() string {
|
|
||||||
|
|
||||||
var errStr string
|
|
||||||
for _, validation := range c.records {
|
|
||||||
errStr = errStr + fmt.Sprintf("\tValidation for %s:%s\n\tResolved to:\n\t\t%s\n\tUsed: %s\n\n",
|
|
||||||
validation.Hostname, validation.Port, strings.Join(validation.ResolvedAddresses, "\n\t\t"), validation.UsedAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s\nError Detail:\n%s", c.RemoteError.Error(), errStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleHTTPError(resp *http.Response) error {
|
func handleHTTPError(resp *http.Response) error {
|
||||||
var errorDetail RemoteError
|
var errorDetail RemoteError
|
||||||
|
|
||||||
contentType := resp.Header.Get("Content-Type")
|
contentType := resp.Header.Get("Content-Type")
|
||||||
if contentType == "application/json" || contentType == "application/problem+json" {
|
if contentType == "application/json" || strings.HasPrefix(contentType, "application/problem+json") {
|
||||||
err := json.NewDecoder(resp.Body).Decode(&errorDetail)
|
err := json.NewDecoder(resp.Body).Decode(&errorDetail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -82,7 +66,7 @@ func handleHTTPError(resp *http.Response) error {
|
||||||
return TOSError{errorDetail}
|
return TOSError{errorDetail}
|
||||||
}
|
}
|
||||||
|
|
||||||
if errorDetail.StatusCode == http.StatusBadRequest && strings.HasPrefix(errorDetail.Detail, invalidNonceError) {
|
if errorDetail.StatusCode == http.StatusBadRequest && errorDetail.Type == invalidNonceError {
|
||||||
return NonceError{errorDetail}
|
return NonceError{errorDetail}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,5 +74,5 @@ func handleHTTPError(resp *http.Response) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleChallengeError(chlng challenge) error {
|
func handleChallengeError(chlng challenge) error {
|
||||||
return challengeError{chlng.Error, chlng.ValidationRecords}
|
return chlng.Error
|
||||||
}
|
}
|
|
@ -18,6 +18,7 @@ var UserAgent string
|
||||||
// HTTPClient is an HTTP client with a reasonable timeout value.
|
// HTTPClient is an HTTP client with a reasonable timeout value.
|
||||||
var HTTPClient = http.Client{
|
var HTTPClient = http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
Dial: (&net.Dialer{
|
Dial: (&net.Dialer{
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
KeepAlive: 30 * time.Second,
|
KeepAlive: 30 * time.Second,
|
||||||
|
@ -101,7 +102,7 @@ func getJSON(uri string, respBody interface{}) (http.Header, error) {
|
||||||
func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, error) {
|
func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, error) {
|
||||||
jsonBytes, err := json.Marshal(reqBody)
|
jsonBytes, err := json.Marshal(reqBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("Failed to marshal network message...")
|
return nil, errors.New("Failed to marshal network message")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := j.post(uri, jsonBytes)
|
resp, err := j.post(uri, jsonBytes)
|
|
@ -37,5 +37,5 @@ func (s *httpChallenge) Solve(chlng challenge, domain string) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return s.validate(s.jws, domain, chlng.URI, challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
return s.validate(s.jws, domain, chlng.URL, challenge{Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
||||||
}
|
}
|
53
vendor/github.com/xenolf/lego/acme/jws.go → vendor/github.com/xenolf/lego/acmev2/jws.go
generated
vendored
53
vendor/github.com/xenolf/lego/acme/jws.go → vendor/github.com/xenolf/lego/acmev2/jws.go
generated
vendored
|
@ -10,37 +10,27 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v1"
|
"gopkg.in/square/go-jose.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type jws struct {
|
type jws struct {
|
||||||
directoryURL string
|
getNonceURL string
|
||||||
privKey crypto.PrivateKey
|
privKey crypto.PrivateKey
|
||||||
nonces nonceManager
|
kid string
|
||||||
}
|
nonces nonceManager
|
||||||
|
|
||||||
func keyAsJWK(key interface{}) *jose.JsonWebKey {
|
|
||||||
switch k := key.(type) {
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
return &jose.JsonWebKey{Key: k, Algorithm: "EC"}
|
|
||||||
case *rsa.PublicKey:
|
|
||||||
return &jose.JsonWebKey{Key: k, Algorithm: "RSA"}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Posts a JWS signed message to the specified URL.
|
// Posts a JWS signed message to the specified URL.
|
||||||
// It does NOT close the response body, so the caller must
|
// It does NOT close the response body, so the caller must
|
||||||
// do that if no error was returned.
|
// do that if no error was returned.
|
||||||
func (j *jws) post(url string, content []byte) (*http.Response, error) {
|
func (j *jws) post(url string, content []byte) (*http.Response, error) {
|
||||||
signedContent, err := j.signContent(content)
|
signedContent, err := j.signContent(url, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to sign content -> %s", err.Error())
|
return nil, fmt.Errorf("Failed to sign content -> %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := httpPost(url, "application/jose+json", bytes.NewBuffer([]byte(signedContent.FullSerialize())))
|
data := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
|
||||||
|
resp, err := httpPost(url, "application/jose+json", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to HTTP POST to %s -> %s", url, err.Error())
|
return nil, fmt.Errorf("Failed to HTTP POST to %s -> %s", url, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -53,7 +43,7 @@ func (j *jws) post(url string, content []byte) (*http.Response, error) {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
|
func (j *jws) signContent(url string, content []byte) (*jose.JSONWebSignature, error) {
|
||||||
|
|
||||||
var alg jose.SignatureAlgorithm
|
var alg jose.SignatureAlgorithm
|
||||||
switch k := j.privKey.(type) {
|
switch k := j.privKey.(type) {
|
||||||
|
@ -67,11 +57,28 @@ func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signer, err := jose.NewSigner(alg, j.privKey)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to create jose signer -> %s", err.Error())
|
return nil, fmt.Errorf("Failed to create jose signer -> %s", err.Error())
|
||||||
}
|
}
|
||||||
signer.SetNonceSource(j)
|
|
||||||
|
|
||||||
signed, err := signer.Sign(content)
|
signed, err := signer.Sign(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -85,7 +92,7 @@ func (j *jws) Nonce() (string, error) {
|
||||||
return nonce, nil
|
return nonce, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return getNonce(j.directoryURL)
|
return getNonce(j.getNonceURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
type nonceManager struct {
|
type nonceManager struct {
|
||||||
|
@ -124,7 +131,7 @@ func getNonce(url string) (string, error) {
|
||||||
func getNonceFromResponse(resp *http.Response) (string, error) {
|
func getNonceFromResponse(resp *http.Response) (string, error) {
|
||||||
nonce := resp.Header.Get("Replay-Nonce")
|
nonce := resp.Header.Get("Replay-Nonce")
|
||||||
if nonce == "" {
|
if nonce == "" {
|
||||||
return "", fmt.Errorf("Server did not respond with a proper nonce header.")
|
return "", fmt.Errorf("Server did not respond with a proper nonce header")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nonce, nil
|
return nonce, nil
|
103
vendor/github.com/xenolf/lego/acmev2/messages.go
generated
vendored
Normal file
103
vendor/github.com/xenolf/lego/acmev2/messages.go
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type orderResource struct {
|
||||||
|
URL string `json:"url,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 emptyObjectMessage struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
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:"-"`
|
||||||
|
}
|
1086
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
1086
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
File diff suppressed because it is too large
Load diff
819
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
819
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
|
@ -1,819 +0,0 @@
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package autocert provides automatic access to certificates from Let's Encrypt
|
|
||||||
// and any other ACME-based CA.
|
|
||||||
//
|
|
||||||
// This package is a work in progress and makes no API stability promises.
|
|
||||||
package autocert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
mathrand "math/rand"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/acme"
|
|
||||||
)
|
|
||||||
|
|
||||||
// createCertRetryAfter is how much time to wait before removing a failed state
|
|
||||||
// entry due to an unsuccessful createCert call.
|
|
||||||
// This is a variable instead of a const for testing.
|
|
||||||
// TODO: Consider making it configurable or an exp backoff?
|
|
||||||
var createCertRetryAfter = time.Minute
|
|
||||||
|
|
||||||
// pseudoRand is safe for concurrent use.
|
|
||||||
var pseudoRand *lockedMathRand
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
src := mathrand.NewSource(timeNow().UnixNano())
|
|
||||||
pseudoRand = &lockedMathRand{rnd: mathrand.New(src)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcceptTOS is a Manager.Prompt function that always returns true to
|
|
||||||
// indicate acceptance of the CA's Terms of Service during account
|
|
||||||
// registration.
|
|
||||||
func AcceptTOS(tosURL string) bool { return true }
|
|
||||||
|
|
||||||
// HostPolicy specifies which host names the Manager is allowed to respond to.
|
|
||||||
// It returns a non-nil error if the host should be rejected.
|
|
||||||
// The returned error is accessible via tls.Conn.Handshake and its callers.
|
|
||||||
// See Manager's HostPolicy field and GetCertificate method docs for more details.
|
|
||||||
type HostPolicy func(ctx context.Context, host string) error
|
|
||||||
|
|
||||||
// HostWhitelist returns a policy where only the specified host names are allowed.
|
|
||||||
// Only exact matches are currently supported. Subdomains, regexp or wildcard
|
|
||||||
// will not match.
|
|
||||||
func HostWhitelist(hosts ...string) HostPolicy {
|
|
||||||
whitelist := make(map[string]bool, len(hosts))
|
|
||||||
for _, h := range hosts {
|
|
||||||
whitelist[h] = true
|
|
||||||
}
|
|
||||||
return func(_ context.Context, host string) error {
|
|
||||||
if !whitelist[host] {
|
|
||||||
return errors.New("acme/autocert: host not configured")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// defaultHostPolicy is used when Manager.HostPolicy is not set.
|
|
||||||
func defaultHostPolicy(context.Context, string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manager is a stateful certificate manager built on top of acme.Client.
|
|
||||||
// It obtains and refreshes certificates automatically,
|
|
||||||
// as well as providing them to a TLS server via tls.Config.
|
|
||||||
//
|
|
||||||
// To preserve issued certificates and improve overall performance,
|
|
||||||
// use a cache implementation of Cache. For instance, DirCache.
|
|
||||||
type Manager struct {
|
|
||||||
// Prompt specifies a callback function to conditionally accept a CA's Terms of Service (TOS).
|
|
||||||
// The registration may require the caller to agree to the CA's TOS.
|
|
||||||
// If so, Manager calls Prompt with a TOS URL provided by the CA. Prompt should report
|
|
||||||
// whether the caller agrees to the terms.
|
|
||||||
//
|
|
||||||
// To always accept the terms, the callers can use AcceptTOS.
|
|
||||||
Prompt func(tosURL string) bool
|
|
||||||
|
|
||||||
// Cache optionally stores and retrieves previously-obtained certificates.
|
|
||||||
// If nil, certs will only be cached for the lifetime of the Manager.
|
|
||||||
//
|
|
||||||
// Manager passes the Cache certificates data encoded in PEM, with private/public
|
|
||||||
// parts combined in a single Cache.Put call, private key first.
|
|
||||||
Cache Cache
|
|
||||||
|
|
||||||
// HostPolicy controls which domains the Manager will attempt
|
|
||||||
// to retrieve new certificates for. It does not affect cached certs.
|
|
||||||
//
|
|
||||||
// If non-nil, HostPolicy is called before requesting a new cert.
|
|
||||||
// If nil, all hosts are currently allowed. This is not recommended,
|
|
||||||
// as it opens a potential attack where clients connect to a server
|
|
||||||
// by IP address and pretend to be asking for an incorrect host name.
|
|
||||||
// Manager will attempt to obtain a certificate for that host, incorrectly,
|
|
||||||
// eventually reaching the CA's rate limit for certificate requests
|
|
||||||
// and making it impossible to obtain actual certificates.
|
|
||||||
//
|
|
||||||
// See GetCertificate for more details.
|
|
||||||
HostPolicy HostPolicy
|
|
||||||
|
|
||||||
// RenewBefore optionally specifies how early certificates should
|
|
||||||
// be renewed before they expire.
|
|
||||||
//
|
|
||||||
// If zero, they're renewed 30 days before expiration.
|
|
||||||
RenewBefore time.Duration
|
|
||||||
|
|
||||||
// Client is used to perform low-level operations, such as account registration
|
|
||||||
// and requesting new certificates.
|
|
||||||
// If Client is nil, a zero-value acme.Client is used with acme.LetsEncryptURL
|
|
||||||
// directory endpoint and a newly-generated ECDSA P-256 key.
|
|
||||||
//
|
|
||||||
// Mutating the field after the first call of GetCertificate method will have no effect.
|
|
||||||
Client *acme.Client
|
|
||||||
|
|
||||||
// Email optionally specifies a contact email address.
|
|
||||||
// This is used by CAs, such as Let's Encrypt, to notify about problems
|
|
||||||
// with issued certificates.
|
|
||||||
//
|
|
||||||
// If the Client's account key is already registered, Email is not used.
|
|
||||||
Email string
|
|
||||||
|
|
||||||
// ForceRSA makes the Manager generate certificates with 2048-bit RSA keys.
|
|
||||||
//
|
|
||||||
// If false, a default is used. Currently the default
|
|
||||||
// is EC-based keys using the P-256 curve.
|
|
||||||
ForceRSA bool
|
|
||||||
|
|
||||||
clientMu sync.Mutex
|
|
||||||
client *acme.Client // initialized by acmeClient method
|
|
||||||
|
|
||||||
stateMu sync.Mutex
|
|
||||||
state map[string]*certState // keyed by domain name
|
|
||||||
|
|
||||||
// tokenCert is keyed by token domain name, which matches server name
|
|
||||||
// of ClientHello. Keys always have ".acme.invalid" suffix.
|
|
||||||
tokenCertMu sync.RWMutex
|
|
||||||
tokenCert map[string]*tls.Certificate
|
|
||||||
|
|
||||||
// renewal tracks the set of domains currently running renewal timers.
|
|
||||||
// It is keyed by domain name.
|
|
||||||
renewalMu sync.Mutex
|
|
||||||
renewal map[string]*domainRenewal
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCertificate implements the tls.Config.GetCertificate hook.
|
|
||||||
// It provides a TLS certificate for hello.ServerName host, including answering
|
|
||||||
// *.acme.invalid (TLS-SNI) challenges. All other fields of hello are ignored.
|
|
||||||
//
|
|
||||||
// If m.HostPolicy is non-nil, GetCertificate calls the policy before requesting
|
|
||||||
// a new cert. A non-nil error returned from m.HostPolicy halts TLS negotiation.
|
|
||||||
// The error is propagated back to the caller of GetCertificate and is user-visible.
|
|
||||||
// This does not affect cached certs. See HostPolicy field description for more details.
|
|
||||||
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
if m.Prompt == nil {
|
|
||||||
return nil, errors.New("acme/autocert: Manager.Prompt not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := hello.ServerName
|
|
||||||
if name == "" {
|
|
||||||
return nil, errors.New("acme/autocert: missing server name")
|
|
||||||
}
|
|
||||||
if !strings.Contains(strings.Trim(name, "."), ".") {
|
|
||||||
return nil, errors.New("acme/autocert: server name component count invalid")
|
|
||||||
}
|
|
||||||
if strings.ContainsAny(name, `/\`) {
|
|
||||||
return nil, errors.New("acme/autocert: server name contains invalid character")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// check whether this is a token cert requested for TLS-SNI challenge
|
|
||||||
if strings.HasSuffix(name, ".acme.invalid") {
|
|
||||||
m.tokenCertMu.RLock()
|
|
||||||
defer m.tokenCertMu.RUnlock()
|
|
||||||
if cert := m.tokenCert[name]; cert != nil {
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
if cert, err := m.cacheGet(ctx, name); err == nil {
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
// TODO: cache error results?
|
|
||||||
return nil, fmt.Errorf("acme/autocert: no token cert for %q", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// regular domain
|
|
||||||
name = strings.TrimSuffix(name, ".") // golang.org/issue/18114
|
|
||||||
cert, err := m.cert(ctx, name)
|
|
||||||
if err == nil {
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
if err != ErrCacheMiss {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// first-time
|
|
||||||
if err := m.hostPolicy()(ctx, name); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cert, err = m.createCert(ctx, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m.cachePut(ctx, name, cert)
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cert returns an existing certificate either from m.state or cache.
|
|
||||||
// If a certificate is found in cache but not in m.state, the latter will be filled
|
|
||||||
// with the cached value.
|
|
||||||
func (m *Manager) cert(ctx context.Context, name string) (*tls.Certificate, error) {
|
|
||||||
m.stateMu.Lock()
|
|
||||||
if s, ok := m.state[name]; ok {
|
|
||||||
m.stateMu.Unlock()
|
|
||||||
s.RLock()
|
|
||||||
defer s.RUnlock()
|
|
||||||
return s.tlscert()
|
|
||||||
}
|
|
||||||
defer m.stateMu.Unlock()
|
|
||||||
cert, err := m.cacheGet(ctx, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
signer, ok := cert.PrivateKey.(crypto.Signer)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("acme/autocert: private key cannot sign")
|
|
||||||
}
|
|
||||||
if m.state == nil {
|
|
||||||
m.state = make(map[string]*certState)
|
|
||||||
}
|
|
||||||
s := &certState{
|
|
||||||
key: signer,
|
|
||||||
cert: cert.Certificate,
|
|
||||||
leaf: cert.Leaf,
|
|
||||||
}
|
|
||||||
m.state[name] = s
|
|
||||||
go m.renew(name, s.key, s.leaf.NotAfter)
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cacheGet always returns a valid certificate, or an error otherwise.
|
|
||||||
// If a cached certficate exists but is not valid, ErrCacheMiss is returned.
|
|
||||||
func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate, error) {
|
|
||||||
if m.Cache == nil {
|
|
||||||
return nil, ErrCacheMiss
|
|
||||||
}
|
|
||||||
data, err := m.Cache.Get(ctx, domain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// private
|
|
||||||
priv, pub := pem.Decode(data)
|
|
||||||
if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
|
|
||||||
return nil, ErrCacheMiss
|
|
||||||
}
|
|
||||||
privKey, err := parsePrivateKey(priv.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// public
|
|
||||||
var pubDER [][]byte
|
|
||||||
for len(pub) > 0 {
|
|
||||||
var b *pem.Block
|
|
||||||
b, pub = pem.Decode(pub)
|
|
||||||
if b == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pubDER = append(pubDER, b.Bytes)
|
|
||||||
}
|
|
||||||
if len(pub) > 0 {
|
|
||||||
// Leftover content not consumed by pem.Decode. Corrupt. Ignore.
|
|
||||||
return nil, ErrCacheMiss
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify and create TLS cert
|
|
||||||
leaf, err := validCert(domain, pubDER, privKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ErrCacheMiss
|
|
||||||
}
|
|
||||||
tlscert := &tls.Certificate{
|
|
||||||
Certificate: pubDER,
|
|
||||||
PrivateKey: privKey,
|
|
||||||
Leaf: leaf,
|
|
||||||
}
|
|
||||||
return tlscert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) cachePut(ctx context.Context, domain string, tlscert *tls.Certificate) error {
|
|
||||||
if m.Cache == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// contains PEM-encoded data
|
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
// private
|
|
||||||
switch key := tlscert.PrivateKey.(type) {
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
if err := encodeECDSAKey(&buf, key); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
b := x509.MarshalPKCS1PrivateKey(key)
|
|
||||||
pb := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: b}
|
|
||||||
if err := pem.Encode(&buf, pb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("acme/autocert: unknown private key type")
|
|
||||||
}
|
|
||||||
|
|
||||||
// public
|
|
||||||
for _, b := range tlscert.Certificate {
|
|
||||||
pb := &pem.Block{Type: "CERTIFICATE", Bytes: b}
|
|
||||||
if err := pem.Encode(&buf, pb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.Cache.Put(ctx, domain, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
|
|
||||||
b, err := x509.MarshalECPrivateKey(key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pb := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
|
|
||||||
return pem.Encode(w, pb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// createCert starts the domain ownership verification and returns a certificate
|
|
||||||
// for that domain upon success.
|
|
||||||
//
|
|
||||||
// If the domain is already being verified, it waits for the existing verification to complete.
|
|
||||||
// Either way, createCert blocks for the duration of the whole process.
|
|
||||||
func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certificate, error) {
|
|
||||||
// TODO: maybe rewrite this whole piece using sync.Once
|
|
||||||
state, err := m.certState(domain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// state may exist if another goroutine is already working on it
|
|
||||||
// in which case just wait for it to finish
|
|
||||||
if !state.locked {
|
|
||||||
state.RLock()
|
|
||||||
defer state.RUnlock()
|
|
||||||
return state.tlscert()
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are the first; state is locked.
|
|
||||||
// Unblock the readers when domain ownership is verified
|
|
||||||
// and the we got the cert or the process failed.
|
|
||||||
defer state.Unlock()
|
|
||||||
state.locked = false
|
|
||||||
|
|
||||||
der, leaf, err := m.authorizedCert(ctx, state.key, domain)
|
|
||||||
if err != nil {
|
|
||||||
// Remove the failed state after some time,
|
|
||||||
// making the manager call createCert again on the following TLS hello.
|
|
||||||
time.AfterFunc(createCertRetryAfter, func() {
|
|
||||||
defer testDidRemoveState(domain)
|
|
||||||
m.stateMu.Lock()
|
|
||||||
defer m.stateMu.Unlock()
|
|
||||||
// Verify the state hasn't changed and it's still invalid
|
|
||||||
// before deleting.
|
|
||||||
s, ok := m.state[domain]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err := validCert(domain, s.cert, s.key); err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
delete(m.state, domain)
|
|
||||||
})
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
state.cert = der
|
|
||||||
state.leaf = leaf
|
|
||||||
go m.renew(domain, state.key, state.leaf.NotAfter)
|
|
||||||
return state.tlscert()
|
|
||||||
}
|
|
||||||
|
|
||||||
// certState returns a new or existing certState.
|
|
||||||
// If a new certState is returned, state.exist is false and the state is locked.
|
|
||||||
// The returned error is non-nil only in the case where a new state could not be created.
|
|
||||||
func (m *Manager) certState(domain string) (*certState, error) {
|
|
||||||
m.stateMu.Lock()
|
|
||||||
defer m.stateMu.Unlock()
|
|
||||||
if m.state == nil {
|
|
||||||
m.state = make(map[string]*certState)
|
|
||||||
}
|
|
||||||
// existing state
|
|
||||||
if state, ok := m.state[domain]; ok {
|
|
||||||
return state, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// new locked state
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
key crypto.Signer
|
|
||||||
)
|
|
||||||
if m.ForceRSA {
|
|
||||||
key, err = rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
} else {
|
|
||||||
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
state := &certState{
|
|
||||||
key: key,
|
|
||||||
locked: true,
|
|
||||||
}
|
|
||||||
state.Lock() // will be unlocked by m.certState caller
|
|
||||||
m.state[domain] = state
|
|
||||||
return state, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// authorizedCert starts domain ownership verification process and requests a new cert upon success.
|
|
||||||
// The key argument is the certificate private key.
|
|
||||||
func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain string) (der [][]byte, leaf *x509.Certificate, err error) {
|
|
||||||
if err := m.verify(ctx, domain); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
client, err := m.acmeClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
csr, err := certRequest(key, domain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
der, _, err = client.CreateCert(ctx, csr, 0, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
leaf, err = validCert(domain, der, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return der, leaf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify starts a new identifier (domain) authorization flow.
|
|
||||||
// It prepares a challenge response and then blocks until the authorization
|
|
||||||
// is marked as "completed" by the CA (either succeeded or failed).
|
|
||||||
//
|
|
||||||
// verify returns nil iff the verification was successful.
|
|
||||||
func (m *Manager) verify(ctx context.Context, domain string) error {
|
|
||||||
client, err := m.acmeClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// start domain authorization and get the challenge
|
|
||||||
authz, err := client.Authorize(ctx, domain)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// maybe don't need to at all
|
|
||||||
if authz.Status == acme.StatusValid {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// pick a challenge: prefer tls-sni-02 over tls-sni-01
|
|
||||||
// TODO: consider authz.Combinations
|
|
||||||
var chal *acme.Challenge
|
|
||||||
for _, c := range authz.Challenges {
|
|
||||||
if c.Type == "tls-sni-02" {
|
|
||||||
chal = c
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if c.Type == "tls-sni-01" {
|
|
||||||
chal = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if chal == nil {
|
|
||||||
return errors.New("acme/autocert: no supported challenge type found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a token cert for the challenge response
|
|
||||||
var (
|
|
||||||
cert tls.Certificate
|
|
||||||
name string
|
|
||||||
)
|
|
||||||
switch chal.Type {
|
|
||||||
case "tls-sni-01":
|
|
||||||
cert, name, err = client.TLSSNI01ChallengeCert(chal.Token)
|
|
||||||
case "tls-sni-02":
|
|
||||||
cert, name, err = client.TLSSNI02ChallengeCert(chal.Token)
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.putTokenCert(ctx, name, &cert)
|
|
||||||
defer func() {
|
|
||||||
// verification has ended at this point
|
|
||||||
// don't need token cert anymore
|
|
||||||
go m.deleteTokenCert(name)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// ready to fulfill the challenge
|
|
||||||
if _, err := client.Accept(ctx, chal); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// wait for the CA to validate
|
|
||||||
_, err = client.WaitAuthorization(ctx, authz.URI)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// putTokenCert stores the cert under the named key in both m.tokenCert map
|
|
||||||
// and m.Cache.
|
|
||||||
func (m *Manager) putTokenCert(ctx context.Context, name string, cert *tls.Certificate) {
|
|
||||||
m.tokenCertMu.Lock()
|
|
||||||
defer m.tokenCertMu.Unlock()
|
|
||||||
if m.tokenCert == nil {
|
|
||||||
m.tokenCert = make(map[string]*tls.Certificate)
|
|
||||||
}
|
|
||||||
m.tokenCert[name] = cert
|
|
||||||
m.cachePut(ctx, name, cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteTokenCert removes the token certificate for the specified domain name
|
|
||||||
// from both m.tokenCert map and m.Cache.
|
|
||||||
func (m *Manager) deleteTokenCert(name string) {
|
|
||||||
m.tokenCertMu.Lock()
|
|
||||||
defer m.tokenCertMu.Unlock()
|
|
||||||
delete(m.tokenCert, name)
|
|
||||||
if m.Cache != nil {
|
|
||||||
m.Cache.Delete(context.Background(), name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// renew starts a cert renewal timer loop, one per domain.
|
|
||||||
//
|
|
||||||
// The loop is scheduled in two cases:
|
|
||||||
// - a cert was fetched from cache for the first time (wasn't in m.state)
|
|
||||||
// - a new cert was created by m.createCert
|
|
||||||
//
|
|
||||||
// The key argument is a certificate private key.
|
|
||||||
// The exp argument is the cert expiration time (NotAfter).
|
|
||||||
func (m *Manager) renew(domain string, key crypto.Signer, exp time.Time) {
|
|
||||||
m.renewalMu.Lock()
|
|
||||||
defer m.renewalMu.Unlock()
|
|
||||||
if m.renewal[domain] != nil {
|
|
||||||
// another goroutine is already on it
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if m.renewal == nil {
|
|
||||||
m.renewal = make(map[string]*domainRenewal)
|
|
||||||
}
|
|
||||||
dr := &domainRenewal{m: m, domain: domain, key: key}
|
|
||||||
m.renewal[domain] = dr
|
|
||||||
dr.start(exp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stopRenew stops all currently running cert renewal timers.
|
|
||||||
// The timers are not restarted during the lifetime of the Manager.
|
|
||||||
func (m *Manager) stopRenew() {
|
|
||||||
m.renewalMu.Lock()
|
|
||||||
defer m.renewalMu.Unlock()
|
|
||||||
for name, dr := range m.renewal {
|
|
||||||
delete(m.renewal, name)
|
|
||||||
dr.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) {
|
|
||||||
const keyName = "acme_account.key"
|
|
||||||
|
|
||||||
genKey := func() (*ecdsa.PrivateKey, error) {
|
|
||||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Cache == nil {
|
|
||||||
return genKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := m.Cache.Get(ctx, keyName)
|
|
||||||
if err == ErrCacheMiss {
|
|
||||||
key, err := genKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := encodeECDSAKey(&buf, key); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := m.Cache.Put(ctx, keyName, buf.Bytes()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
priv, _ := pem.Decode(data)
|
|
||||||
if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
|
|
||||||
return nil, errors.New("acme/autocert: invalid account key found in cache")
|
|
||||||
}
|
|
||||||
return parsePrivateKey(priv.Bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) {
|
|
||||||
m.clientMu.Lock()
|
|
||||||
defer m.clientMu.Unlock()
|
|
||||||
if m.client != nil {
|
|
||||||
return m.client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
client := m.Client
|
|
||||||
if client == nil {
|
|
||||||
client = &acme.Client{DirectoryURL: acme.LetsEncryptURL}
|
|
||||||
}
|
|
||||||
if client.Key == nil {
|
|
||||||
var err error
|
|
||||||
client.Key, err = m.accountKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var contact []string
|
|
||||||
if m.Email != "" {
|
|
||||||
contact = []string{"mailto:" + m.Email}
|
|
||||||
}
|
|
||||||
a := &acme.Account{Contact: contact}
|
|
||||||
_, err := client.Register(ctx, a, m.Prompt)
|
|
||||||
if ae, ok := err.(*acme.Error); err == nil || ok && ae.StatusCode == http.StatusConflict {
|
|
||||||
// conflict indicates the key is already registered
|
|
||||||
m.client = client
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return m.client, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) hostPolicy() HostPolicy {
|
|
||||||
if m.HostPolicy != nil {
|
|
||||||
return m.HostPolicy
|
|
||||||
}
|
|
||||||
return defaultHostPolicy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) renewBefore() time.Duration {
|
|
||||||
if m.RenewBefore > renewJitter {
|
|
||||||
return m.RenewBefore
|
|
||||||
}
|
|
||||||
return 720 * time.Hour // 30 days
|
|
||||||
}
|
|
||||||
|
|
||||||
// certState is ready when its mutex is unlocked for reading.
|
|
||||||
type certState struct {
|
|
||||||
sync.RWMutex
|
|
||||||
locked bool // locked for read/write
|
|
||||||
key crypto.Signer // private key for cert
|
|
||||||
cert [][]byte // DER encoding
|
|
||||||
leaf *x509.Certificate // parsed cert[0]; always non-nil if cert != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// tlscert creates a tls.Certificate from s.key and s.cert.
|
|
||||||
// Callers should wrap it in s.RLock() and s.RUnlock().
|
|
||||||
func (s *certState) tlscert() (*tls.Certificate, error) {
|
|
||||||
if s.key == nil {
|
|
||||||
return nil, errors.New("acme/autocert: missing signer")
|
|
||||||
}
|
|
||||||
if len(s.cert) == 0 {
|
|
||||||
return nil, errors.New("acme/autocert: missing certificate")
|
|
||||||
}
|
|
||||||
return &tls.Certificate{
|
|
||||||
PrivateKey: s.key,
|
|
||||||
Certificate: s.cert,
|
|
||||||
Leaf: s.leaf,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// certRequest creates a certificate request for the given common name cn
|
|
||||||
// and optional SANs.
|
|
||||||
func certRequest(key crypto.Signer, cn string, san ...string) ([]byte, error) {
|
|
||||||
req := &x509.CertificateRequest{
|
|
||||||
Subject: pkix.Name{CommonName: cn},
|
|
||||||
DNSNames: san,
|
|
||||||
}
|
|
||||||
return x509.CreateCertificateRequest(rand.Reader, req, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
|
|
||||||
// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
|
|
||||||
// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
|
|
||||||
//
|
|
||||||
// Inspired by parsePrivateKey in crypto/tls/tls.go.
|
|
||||||
func parsePrivateKey(der []byte) (crypto.Signer, error) {
|
|
||||||
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
|
|
||||||
switch key := key.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
return key, nil
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
return key, nil
|
|
||||||
default:
|
|
||||||
return nil, errors.New("acme/autocert: unknown private key type in PKCS#8 wrapping")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if key, err := x509.ParseECPrivateKey(der); err == nil {
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("acme/autocert: failed to parse private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
// validCert parses a cert chain provided as der argument and verifies the leaf, der[0],
|
|
||||||
// corresponds to the private key, as well as the domain match and expiration dates.
|
|
||||||
// It doesn't do any revocation checking.
|
|
||||||
//
|
|
||||||
// The returned value is the verified leaf cert.
|
|
||||||
func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certificate, err error) {
|
|
||||||
// parse public part(s)
|
|
||||||
var n int
|
|
||||||
for _, b := range der {
|
|
||||||
n += len(b)
|
|
||||||
}
|
|
||||||
pub := make([]byte, n)
|
|
||||||
n = 0
|
|
||||||
for _, b := range der {
|
|
||||||
n += copy(pub[n:], b)
|
|
||||||
}
|
|
||||||
x509Cert, err := x509.ParseCertificates(pub)
|
|
||||||
if len(x509Cert) == 0 {
|
|
||||||
return nil, errors.New("acme/autocert: no public key found")
|
|
||||||
}
|
|
||||||
// verify the leaf is not expired and matches the domain name
|
|
||||||
leaf = x509Cert[0]
|
|
||||||
now := timeNow()
|
|
||||||
if now.Before(leaf.NotBefore) {
|
|
||||||
return nil, errors.New("acme/autocert: certificate is not valid yet")
|
|
||||||
}
|
|
||||||
if now.After(leaf.NotAfter) {
|
|
||||||
return nil, errors.New("acme/autocert: expired certificate")
|
|
||||||
}
|
|
||||||
if err := leaf.VerifyHostname(domain); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// ensure the leaf corresponds to the private key
|
|
||||||
switch pub := leaf.PublicKey.(type) {
|
|
||||||
case *rsa.PublicKey:
|
|
||||||
prv, ok := key.(*rsa.PrivateKey)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("acme/autocert: private key type does not match public key type")
|
|
||||||
}
|
|
||||||
if pub.N.Cmp(prv.N) != 0 {
|
|
||||||
return nil, errors.New("acme/autocert: private key does not match public key")
|
|
||||||
}
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
prv, ok := key.(*ecdsa.PrivateKey)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("acme/autocert: private key type does not match public key type")
|
|
||||||
}
|
|
||||||
if pub.X.Cmp(prv.X) != 0 || pub.Y.Cmp(prv.Y) != 0 {
|
|
||||||
return nil, errors.New("acme/autocert: private key does not match public key")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errors.New("acme/autocert: unknown public key algorithm")
|
|
||||||
}
|
|
||||||
return leaf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func retryAfter(v string) time.Duration {
|
|
||||||
if i, err := strconv.Atoi(v); err == nil {
|
|
||||||
return time.Duration(i) * time.Second
|
|
||||||
}
|
|
||||||
if t, err := http.ParseTime(v); err == nil {
|
|
||||||
return t.Sub(timeNow())
|
|
||||||
}
|
|
||||||
return time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
type lockedMathRand struct {
|
|
||||||
sync.Mutex
|
|
||||||
rnd *mathrand.Rand
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *lockedMathRand) int63n(max int64) int64 {
|
|
||||||
r.Lock()
|
|
||||||
n := r.rnd.Int63n(max)
|
|
||||||
r.Unlock()
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// For easier testing.
|
|
||||||
var (
|
|
||||||
timeNow = time.Now
|
|
||||||
|
|
||||||
// Called when a state is removed.
|
|
||||||
testDidRemoveState = func(domain string) {}
|
|
||||||
)
|
|
130
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
130
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
|
@ -1,130 +0,0 @@
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package autocert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrCacheMiss is returned when a certificate is not found in cache.
|
|
||||||
var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
|
|
||||||
|
|
||||||
// Cache is used by Manager to store and retrieve previously obtained certificates
|
|
||||||
// as opaque data.
|
|
||||||
//
|
|
||||||
// The key argument of the methods refers to a domain name but need not be an FQDN.
|
|
||||||
// Cache implementations should not rely on the key naming pattern.
|
|
||||||
type Cache interface {
|
|
||||||
// Get returns a certificate data for the specified key.
|
|
||||||
// If there's no such key, Get returns ErrCacheMiss.
|
|
||||||
Get(ctx context.Context, key string) ([]byte, error)
|
|
||||||
|
|
||||||
// Put stores the data in the cache under the specified key.
|
|
||||||
// Underlying implementations may use any data storage format,
|
|
||||||
// as long as the reverse operation, Get, results in the original data.
|
|
||||||
Put(ctx context.Context, key string, data []byte) error
|
|
||||||
|
|
||||||
// Delete removes a certificate data from the cache under the specified key.
|
|
||||||
// If there's no such key in the cache, Delete returns nil.
|
|
||||||
Delete(ctx context.Context, key string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// DirCache implements Cache using a directory on the local filesystem.
|
|
||||||
// If the directory does not exist, it will be created with 0700 permissions.
|
|
||||||
type DirCache string
|
|
||||||
|
|
||||||
// Get reads a certificate data from the specified file name.
|
|
||||||
func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
|
|
||||||
name = filepath.Join(string(d), name)
|
|
||||||
var (
|
|
||||||
data []byte
|
|
||||||
err error
|
|
||||||
done = make(chan struct{})
|
|
||||||
)
|
|
||||||
go func() {
|
|
||||||
data, err = ioutil.ReadFile(name)
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil, ErrCacheMiss
|
|
||||||
}
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put writes the certificate data to the specified file name.
|
|
||||||
// The file will be created with 0600 permissions.
|
|
||||||
func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
|
|
||||||
if err := os.MkdirAll(string(d), 0700); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
var err error
|
|
||||||
go func() {
|
|
||||||
defer close(done)
|
|
||||||
var tmp string
|
|
||||||
if tmp, err = d.writeTempFile(name, data); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
// Don't overwrite the file if the context was canceled.
|
|
||||||
default:
|
|
||||||
newName := filepath.Join(string(d), name)
|
|
||||||
err = os.Rename(tmp, newName)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes the specified file name.
|
|
||||||
func (d DirCache) Delete(ctx context.Context, name string) error {
|
|
||||||
name = filepath.Join(string(d), name)
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
done = make(chan struct{})
|
|
||||||
)
|
|
||||||
go func() {
|
|
||||||
err = os.Remove(name)
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeTempFile writes b to a temporary file, closes the file and returns its path.
|
|
||||||
func (d DirCache) writeTempFile(prefix string, b []byte) (string, error) {
|
|
||||||
// TempFile uses 0600 permissions
|
|
||||||
f, err := ioutil.TempFile(string(d), prefix)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if _, err := f.Write(b); err != nil {
|
|
||||||
f.Close()
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return f.Name(), f.Close()
|
|
||||||
}
|
|
160
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
160
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
|
@ -1,160 +0,0 @@
|
||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package autocert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewListener returns a net.Listener that listens on the standard TLS
|
|
||||||
// port (443) on all interfaces and returns *tls.Conn connections with
|
|
||||||
// LetsEncrypt certificates for the provided domain or domains.
|
|
||||||
//
|
|
||||||
// It enables one-line HTTPS servers:
|
|
||||||
//
|
|
||||||
// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
|
|
||||||
//
|
|
||||||
// NewListener is a convenience function for a common configuration.
|
|
||||||
// More complex or custom configurations can use the autocert.Manager
|
|
||||||
// type instead.
|
|
||||||
//
|
|
||||||
// Use of this function implies acceptance of the LetsEncrypt Terms of
|
|
||||||
// Service. If domains is not empty, the provided domains are passed
|
|
||||||
// to HostWhitelist. If domains is empty, the listener will do
|
|
||||||
// LetsEncrypt challenges for any requested domain, which is not
|
|
||||||
// recommended.
|
|
||||||
//
|
|
||||||
// Certificates are cached in a "golang-autocert" directory under an
|
|
||||||
// operating system-specific cache or temp directory. This may not
|
|
||||||
// be suitable for servers spanning multiple machines.
|
|
||||||
//
|
|
||||||
// The returned listener uses a *tls.Config that enables HTTP/2, and
|
|
||||||
// should only be used with servers that support HTTP/2.
|
|
||||||
//
|
|
||||||
// The returned Listener also enables TCP keep-alives on the accepted
|
|
||||||
// connections. The returned *tls.Conn are returned before their TLS
|
|
||||||
// handshake has completed.
|
|
||||||
func NewListener(domains ...string) net.Listener {
|
|
||||||
m := &Manager{
|
|
||||||
Prompt: AcceptTOS,
|
|
||||||
}
|
|
||||||
if len(domains) > 0 {
|
|
||||||
m.HostPolicy = HostWhitelist(domains...)
|
|
||||||
}
|
|
||||||
dir := cacheDir()
|
|
||||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
|
||||||
log.Printf("warning: autocert.NewListener not using a cache: %v", err)
|
|
||||||
} else {
|
|
||||||
m.Cache = DirCache(dir)
|
|
||||||
}
|
|
||||||
return m.Listener()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listener listens on the standard TLS port (443) on all interfaces
|
|
||||||
// and returns a net.Listener returning *tls.Conn connections.
|
|
||||||
//
|
|
||||||
// The returned listener uses a *tls.Config that enables HTTP/2, and
|
|
||||||
// should only be used with servers that support HTTP/2.
|
|
||||||
//
|
|
||||||
// The returned Listener also enables TCP keep-alives on the accepted
|
|
||||||
// connections. The returned *tls.Conn are returned before their TLS
|
|
||||||
// handshake has completed.
|
|
||||||
//
|
|
||||||
// Unlike NewListener, it is the caller's responsibility to initialize
|
|
||||||
// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
|
|
||||||
func (m *Manager) Listener() net.Listener {
|
|
||||||
ln := &listener{
|
|
||||||
m: m,
|
|
||||||
conf: &tls.Config{
|
|
||||||
GetCertificate: m.GetCertificate, // bonus: panic on nil m
|
|
||||||
NextProtos: []string{"h2", "http/1.1"}, // Enable HTTP/2
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
|
|
||||||
return ln
|
|
||||||
}
|
|
||||||
|
|
||||||
type listener struct {
|
|
||||||
m *Manager
|
|
||||||
conf *tls.Config
|
|
||||||
|
|
||||||
tcpListener net.Listener
|
|
||||||
tcpListenErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ln *listener) Accept() (net.Conn, error) {
|
|
||||||
if ln.tcpListenErr != nil {
|
|
||||||
return nil, ln.tcpListenErr
|
|
||||||
}
|
|
||||||
conn, err := ln.tcpListener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tcpConn := conn.(*net.TCPConn)
|
|
||||||
|
|
||||||
// Because Listener is a convenience function, help out with
|
|
||||||
// this too. This is not possible for the caller to set once
|
|
||||||
// we return a *tcp.Conn wrapping an inaccessible net.Conn.
|
|
||||||
// If callers don't want this, they can do things the manual
|
|
||||||
// way and tweak as needed. But this is what net/http does
|
|
||||||
// itself, so copy that. If net/http changes, we can change
|
|
||||||
// here too.
|
|
||||||
tcpConn.SetKeepAlive(true)
|
|
||||||
tcpConn.SetKeepAlivePeriod(3 * time.Minute)
|
|
||||||
|
|
||||||
return tls.Server(tcpConn, ln.conf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ln *listener) Addr() net.Addr {
|
|
||||||
if ln.tcpListener != nil {
|
|
||||||
return ln.tcpListener.Addr()
|
|
||||||
}
|
|
||||||
// net.Listen failed. Return something non-nil in case callers
|
|
||||||
// call Addr before Accept:
|
|
||||||
return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ln *listener) Close() error {
|
|
||||||
if ln.tcpListenErr != nil {
|
|
||||||
return ln.tcpListenErr
|
|
||||||
}
|
|
||||||
return ln.tcpListener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func homeDir() string {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
|
||||||
}
|
|
||||||
if h := os.Getenv("HOME"); h != "" {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
return "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
func cacheDir() string {
|
|
||||||
const base = "golang-autocert"
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "darwin":
|
|
||||||
return filepath.Join(homeDir(), "Library", "Caches", base)
|
|
||||||
case "windows":
|
|
||||||
for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} {
|
|
||||||
if v := os.Getenv(ev); v != "" {
|
|
||||||
return filepath.Join(v, base)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Worst case:
|
|
||||||
return filepath.Join(homeDir(), base)
|
|
||||||
}
|
|
||||||
if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
|
|
||||||
return filepath.Join(xdg, base)
|
|
||||||
}
|
|
||||||
return filepath.Join(homeDir(), ".cache", base)
|
|
||||||
}
|
|
124
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
124
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
|
@ -1,124 +0,0 @@
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package autocert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// renewJitter is the maximum deviation from Manager.RenewBefore.
|
|
||||||
const renewJitter = time.Hour
|
|
||||||
|
|
||||||
// domainRenewal tracks the state used by the periodic timers
|
|
||||||
// renewing a single domain's cert.
|
|
||||||
type domainRenewal struct {
|
|
||||||
m *Manager
|
|
||||||
domain string
|
|
||||||
key crypto.Signer
|
|
||||||
|
|
||||||
timerMu sync.Mutex
|
|
||||||
timer *time.Timer
|
|
||||||
}
|
|
||||||
|
|
||||||
// start starts a cert renewal timer at the time
|
|
||||||
// defined by the certificate expiration time exp.
|
|
||||||
//
|
|
||||||
// If the timer is already started, calling start is a noop.
|
|
||||||
func (dr *domainRenewal) start(exp time.Time) {
|
|
||||||
dr.timerMu.Lock()
|
|
||||||
defer dr.timerMu.Unlock()
|
|
||||||
if dr.timer != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dr.timer = time.AfterFunc(dr.next(exp), dr.renew)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop stops the cert renewal timer.
|
|
||||||
// If the timer is already stopped, calling stop is a noop.
|
|
||||||
func (dr *domainRenewal) stop() {
|
|
||||||
dr.timerMu.Lock()
|
|
||||||
defer dr.timerMu.Unlock()
|
|
||||||
if dr.timer == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dr.timer.Stop()
|
|
||||||
dr.timer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// renew is called periodically by a timer.
|
|
||||||
// The first renew call is kicked off by dr.start.
|
|
||||||
func (dr *domainRenewal) renew() {
|
|
||||||
dr.timerMu.Lock()
|
|
||||||
defer dr.timerMu.Unlock()
|
|
||||||
if dr.timer == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
// TODO: rotate dr.key at some point?
|
|
||||||
next, err := dr.do(ctx)
|
|
||||||
if err != nil {
|
|
||||||
next = renewJitter / 2
|
|
||||||
next += time.Duration(pseudoRand.int63n(int64(next)))
|
|
||||||
}
|
|
||||||
dr.timer = time.AfterFunc(next, dr.renew)
|
|
||||||
testDidRenewLoop(next, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// do is similar to Manager.createCert but it doesn't lock a Manager.state item.
|
|
||||||
// Instead, it requests a new certificate independently and, upon success,
|
|
||||||
// replaces dr.m.state item with a new one and updates cache for the given domain.
|
|
||||||
//
|
|
||||||
// It may return immediately if the expiration date of the currently cached cert
|
|
||||||
// is far enough in the future.
|
|
||||||
//
|
|
||||||
// The returned value is a time interval after which the renewal should occur again.
|
|
||||||
func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
|
||||||
// a race is likely unavoidable in a distributed environment
|
|
||||||
// but we try nonetheless
|
|
||||||
if tlscert, err := dr.m.cacheGet(ctx, dr.domain); err == nil {
|
|
||||||
next := dr.next(tlscert.Leaf.NotAfter)
|
|
||||||
if next > dr.m.renewBefore()+renewJitter {
|
|
||||||
return next, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.domain)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
state := &certState{
|
|
||||||
key: dr.key,
|
|
||||||
cert: der,
|
|
||||||
leaf: leaf,
|
|
||||||
}
|
|
||||||
tlscert, err := state.tlscert()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
dr.m.cachePut(ctx, dr.domain, tlscert)
|
|
||||||
dr.m.stateMu.Lock()
|
|
||||||
defer dr.m.stateMu.Unlock()
|
|
||||||
// m.state is guaranteed to be non-nil at this point
|
|
||||||
dr.m.state[dr.domain] = state
|
|
||||||
return dr.next(leaf.NotAfter), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dr *domainRenewal) next(expiry time.Time) time.Duration {
|
|
||||||
d := expiry.Sub(timeNow()) - dr.m.renewBefore()
|
|
||||||
// add a bit of randomness to renew deadline
|
|
||||||
n := pseudoRand.int63n(int64(renewJitter))
|
|
||||||
d -= time.Duration(n)
|
|
||||||
if d < 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
var testDidRenewLoop = func(next time.Duration, err error) {}
|
|
153
vendor/golang.org/x/crypto/acme/jws.go
generated
vendored
153
vendor/golang.org/x/crypto/acme/jws.go
generated
vendored
|
@ -1,153 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha256"
|
|
||||||
_ "crypto/sha512" // need for EC keys
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
// jwsEncodeJSON signs claimset using provided key and a nonce.
|
|
||||||
// The result is serialized in JSON format.
|
|
||||||
// See https://tools.ietf.org/html/rfc7515#section-7.
|
|
||||||
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, nonce string) ([]byte, error) {
|
|
||||||
jwk, err := jwkEncode(key.Public())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
alg, sha := jwsHasher(key)
|
|
||||||
if alg == "" || !sha.Available() {
|
|
||||||
return nil, ErrUnsupportedKey
|
|
||||||
}
|
|
||||||
phead := fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q}`, alg, jwk, nonce)
|
|
||||||
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
|
|
||||||
cs, err := json.Marshal(claimset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
payload := base64.RawURLEncoding.EncodeToString(cs)
|
|
||||||
hash := sha.New()
|
|
||||||
hash.Write([]byte(phead + "." + payload))
|
|
||||||
sig, err := jwsSign(key, sha, hash.Sum(nil))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
enc := struct {
|
|
||||||
Protected string `json:"protected"`
|
|
||||||
Payload string `json:"payload"`
|
|
||||||
Sig string `json:"signature"`
|
|
||||||
}{
|
|
||||||
Protected: phead,
|
|
||||||
Payload: payload,
|
|
||||||
Sig: base64.RawURLEncoding.EncodeToString(sig),
|
|
||||||
}
|
|
||||||
return json.Marshal(&enc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
|
|
||||||
// The result is also suitable for creating a JWK thumbprint.
|
|
||||||
// https://tools.ietf.org/html/rfc7517
|
|
||||||
func jwkEncode(pub crypto.PublicKey) (string, error) {
|
|
||||||
switch pub := pub.(type) {
|
|
||||||
case *rsa.PublicKey:
|
|
||||||
// https://tools.ietf.org/html/rfc7518#section-6.3.1
|
|
||||||
n := pub.N
|
|
||||||
e := big.NewInt(int64(pub.E))
|
|
||||||
// Field order is important.
|
|
||||||
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
|
||||||
return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
|
|
||||||
base64.RawURLEncoding.EncodeToString(e.Bytes()),
|
|
||||||
base64.RawURLEncoding.EncodeToString(n.Bytes()),
|
|
||||||
), nil
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
// https://tools.ietf.org/html/rfc7518#section-6.2.1
|
|
||||||
p := pub.Curve.Params()
|
|
||||||
n := p.BitSize / 8
|
|
||||||
if p.BitSize%8 != 0 {
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
x := pub.X.Bytes()
|
|
||||||
if n > len(x) {
|
|
||||||
x = append(make([]byte, n-len(x)), x...)
|
|
||||||
}
|
|
||||||
y := pub.Y.Bytes()
|
|
||||||
if n > len(y) {
|
|
||||||
y = append(make([]byte, n-len(y)), y...)
|
|
||||||
}
|
|
||||||
// Field order is important.
|
|
||||||
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
|
||||||
return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
|
|
||||||
p.Name,
|
|
||||||
base64.RawURLEncoding.EncodeToString(x),
|
|
||||||
base64.RawURLEncoding.EncodeToString(y),
|
|
||||||
), nil
|
|
||||||
}
|
|
||||||
return "", ErrUnsupportedKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// jwsSign signs the digest using the given key.
|
|
||||||
// It returns ErrUnsupportedKey if the key type is unknown.
|
|
||||||
// The hash is used only for RSA keys.
|
|
||||||
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
|
|
||||||
switch key := key.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
return key.Sign(rand.Reader, digest, hash)
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
r, s, err := ecdsa.Sign(rand.Reader, key, digest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rb, sb := r.Bytes(), s.Bytes()
|
|
||||||
size := key.Params().BitSize / 8
|
|
||||||
if size%8 > 0 {
|
|
||||||
size++
|
|
||||||
}
|
|
||||||
sig := make([]byte, size*2)
|
|
||||||
copy(sig[size-len(rb):], rb)
|
|
||||||
copy(sig[size*2-len(sb):], sb)
|
|
||||||
return sig, nil
|
|
||||||
}
|
|
||||||
return nil, ErrUnsupportedKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// jwsHasher indicates suitable JWS algorithm name and a hash function
|
|
||||||
// to use for signing a digest with the provided key.
|
|
||||||
// It returns ("", 0) if the key is not supported.
|
|
||||||
func jwsHasher(key crypto.Signer) (string, crypto.Hash) {
|
|
||||||
switch key := key.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
return "RS256", crypto.SHA256
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
switch key.Params().Name {
|
|
||||||
case "P-256":
|
|
||||||
return "ES256", crypto.SHA256
|
|
||||||
case "P-384":
|
|
||||||
return "ES384", crypto.SHA384
|
|
||||||
case "P-521":
|
|
||||||
return "ES512", crypto.SHA512
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// JWKThumbprint creates a JWK thumbprint out of pub
|
|
||||||
// as specified in https://tools.ietf.org/html/rfc7638.
|
|
||||||
func JWKThumbprint(pub crypto.PublicKey) (string, error) {
|
|
||||||
jwk, err := jwkEncode(pub)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
b := sha256.Sum256([]byte(jwk))
|
|
||||||
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
|
||||||
}
|
|
295
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
295
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
|
@ -1,295 +0,0 @@
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package acme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ACME server response statuses used to describe Authorization and Challenge states.
|
|
||||||
const (
|
|
||||||
StatusUnknown = "unknown"
|
|
||||||
StatusPending = "pending"
|
|
||||||
StatusProcessing = "processing"
|
|
||||||
StatusValid = "valid"
|
|
||||||
StatusInvalid = "invalid"
|
|
||||||
StatusRevoked = "revoked"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CRLReasonCode identifies the reason for a certificate revocation.
|
|
||||||
type CRLReasonCode int
|
|
||||||
|
|
||||||
// CRL reason codes as defined in RFC 5280.
|
|
||||||
const (
|
|
||||||
CRLReasonUnspecified CRLReasonCode = 0
|
|
||||||
CRLReasonKeyCompromise CRLReasonCode = 1
|
|
||||||
CRLReasonCACompromise CRLReasonCode = 2
|
|
||||||
CRLReasonAffiliationChanged CRLReasonCode = 3
|
|
||||||
CRLReasonSuperseded CRLReasonCode = 4
|
|
||||||
CRLReasonCessationOfOperation CRLReasonCode = 5
|
|
||||||
CRLReasonCertificateHold CRLReasonCode = 6
|
|
||||||
CRLReasonRemoveFromCRL CRLReasonCode = 8
|
|
||||||
CRLReasonPrivilegeWithdrawn CRLReasonCode = 9
|
|
||||||
CRLReasonAACompromise CRLReasonCode = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrUnsupportedKey is returned when an unsupported key type is encountered.
|
|
||||||
var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
|
|
||||||
|
|
||||||
// Error is an ACME error, defined in Problem Details for HTTP APIs doc
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem.
|
|
||||||
type Error struct {
|
|
||||||
// StatusCode is The HTTP status code generated by the origin server.
|
|
||||||
StatusCode int
|
|
||||||
// ProblemType is a URI reference that identifies the problem type,
|
|
||||||
// typically in a "urn:acme:error:xxx" form.
|
|
||||||
ProblemType string
|
|
||||||
// Detail is a human-readable explanation specific to this occurrence of the problem.
|
|
||||||
Detail string
|
|
||||||
// Header is the original server error response headers.
|
|
||||||
// It may be nil.
|
|
||||||
Header http.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
|
||||||
return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthorizationError indicates that an authorization for an identifier
|
|
||||||
// did not succeed.
|
|
||||||
// It contains all errors from Challenge items of the failed Authorization.
|
|
||||||
type AuthorizationError struct {
|
|
||||||
// URI uniquely identifies the failed Authorization.
|
|
||||||
URI string
|
|
||||||
|
|
||||||
// Identifier is an AuthzID.Value of the failed Authorization.
|
|
||||||
Identifier string
|
|
||||||
|
|
||||||
// Errors is a collection of non-nil error values of Challenge items
|
|
||||||
// of the failed Authorization.
|
|
||||||
Errors []error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AuthorizationError) Error() string {
|
|
||||||
e := make([]string, len(a.Errors))
|
|
||||||
for i, err := range a.Errors {
|
|
||||||
e[i] = err.Error()
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RateLimit reports whether err represents a rate limit error and
|
|
||||||
// any Retry-After duration returned by the server.
|
|
||||||
//
|
|
||||||
// See the following for more details on rate limiting:
|
|
||||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6
|
|
||||||
func RateLimit(err error) (time.Duration, bool) {
|
|
||||||
e, ok := err.(*Error)
|
|
||||||
if !ok {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
// Some CA implementations may return incorrect values.
|
|
||||||
// Use case-insensitive comparison.
|
|
||||||
if !strings.HasSuffix(strings.ToLower(e.ProblemType), ":ratelimited") {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
if e.Header == nil {
|
|
||||||
return 0, true
|
|
||||||
}
|
|
||||||
return retryAfter(e.Header.Get("Retry-After"), 0), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Account is a user account. It is associated with a private key.
|
|
||||||
type Account struct {
|
|
||||||
// URI is the account unique ID, which is also a URL used to retrieve
|
|
||||||
// account data from the CA.
|
|
||||||
URI string
|
|
||||||
|
|
||||||
// Contact is a slice of contact info used during registration.
|
|
||||||
Contact []string
|
|
||||||
|
|
||||||
// The terms user has agreed to.
|
|
||||||
// A value not matching CurrentTerms indicates that the user hasn't agreed
|
|
||||||
// to the actual Terms of Service of the CA.
|
|
||||||
AgreedTerms string
|
|
||||||
|
|
||||||
// Actual terms of a CA.
|
|
||||||
CurrentTerms string
|
|
||||||
|
|
||||||
// Authz is the authorization URL used to initiate a new authz flow.
|
|
||||||
Authz string
|
|
||||||
|
|
||||||
// Authorizations is a URI from which a list of authorizations
|
|
||||||
// granted to this account can be fetched via a GET request.
|
|
||||||
Authorizations string
|
|
||||||
|
|
||||||
// Certificates is a URI from which a list of certificates
|
|
||||||
// issued for this account can be fetched via a GET request.
|
|
||||||
Certificates string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Directory is ACME server discovery data.
|
|
||||||
type Directory struct {
|
|
||||||
// RegURL is an account endpoint URL, allowing for creating new
|
|
||||||
// and modifying existing accounts.
|
|
||||||
RegURL string
|
|
||||||
|
|
||||||
// AuthzURL is used to initiate Identifier Authorization flow.
|
|
||||||
AuthzURL string
|
|
||||||
|
|
||||||
// CertURL is a new certificate issuance endpoint URL.
|
|
||||||
CertURL string
|
|
||||||
|
|
||||||
// RevokeURL is used to initiate a certificate revocation flow.
|
|
||||||
RevokeURL string
|
|
||||||
|
|
||||||
// Term is a URI identifying the current terms of service.
|
|
||||||
Terms string
|
|
||||||
|
|
||||||
// Website is an HTTP or HTTPS URL locating a website
|
|
||||||
// providing more information about the ACME server.
|
|
||||||
Website string
|
|
||||||
|
|
||||||
// CAA consists of lowercase hostname elements, which the ACME server
|
|
||||||
// recognises as referring to itself for the purposes of CAA record validation
|
|
||||||
// as defined in RFC6844.
|
|
||||||
CAA []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Challenge encodes a returned CA challenge.
|
|
||||||
// Its Error field may be non-nil if the challenge is part of an Authorization
|
|
||||||
// with StatusInvalid.
|
|
||||||
type Challenge struct {
|
|
||||||
// Type is the challenge type, e.g. "http-01", "tls-sni-02", "dns-01".
|
|
||||||
Type string
|
|
||||||
|
|
||||||
// URI is where a challenge response can be posted to.
|
|
||||||
URI string
|
|
||||||
|
|
||||||
// Token is a random value that uniquely identifies the challenge.
|
|
||||||
Token string
|
|
||||||
|
|
||||||
// Status identifies the status of this challenge.
|
|
||||||
Status string
|
|
||||||
|
|
||||||
// Error indicates the reason for an authorization failure
|
|
||||||
// when this challenge was used.
|
|
||||||
// The type of a non-nil value is *Error.
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authorization encodes an authorization response.
|
|
||||||
type Authorization struct {
|
|
||||||
// URI uniquely identifies a authorization.
|
|
||||||
URI string
|
|
||||||
|
|
||||||
// Status identifies the status of an authorization.
|
|
||||||
Status string
|
|
||||||
|
|
||||||
// Identifier is what the account is authorized to represent.
|
|
||||||
Identifier AuthzID
|
|
||||||
|
|
||||||
// Challenges that the client needs to fulfill in order to prove possession
|
|
||||||
// of the identifier (for pending authorizations).
|
|
||||||
// For final authorizations, the challenges that were used.
|
|
||||||
Challenges []*Challenge
|
|
||||||
|
|
||||||
// A collection of sets of challenges, each of which would be sufficient
|
|
||||||
// to prove possession of the identifier.
|
|
||||||
// Clients must complete a set of challenges that covers at least one set.
|
|
||||||
// Challenges are identified by their indices in the challenges array.
|
|
||||||
// If this field is empty, the client needs to complete all challenges.
|
|
||||||
Combinations [][]int
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthzID is an identifier that an account is authorized to represent.
|
|
||||||
type AuthzID struct {
|
|
||||||
Type string // The type of identifier, e.g. "dns".
|
|
||||||
Value string // The identifier itself, e.g. "example.org".
|
|
||||||
}
|
|
||||||
|
|
||||||
// wireAuthz is ACME JSON representation of Authorization objects.
|
|
||||||
type wireAuthz struct {
|
|
||||||
Status string
|
|
||||||
Challenges []wireChallenge
|
|
||||||
Combinations [][]int
|
|
||||||
Identifier struct {
|
|
||||||
Type string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (z *wireAuthz) authorization(uri string) *Authorization {
|
|
||||||
a := &Authorization{
|
|
||||||
URI: uri,
|
|
||||||
Status: z.Status,
|
|
||||||
Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
|
|
||||||
Combinations: z.Combinations, // shallow copy
|
|
||||||
Challenges: make([]*Challenge, len(z.Challenges)),
|
|
||||||
}
|
|
||||||
for i, v := range z.Challenges {
|
|
||||||
a.Challenges[i] = v.challenge()
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func (z *wireAuthz) error(uri string) *AuthorizationError {
|
|
||||||
err := &AuthorizationError{
|
|
||||||
URI: uri,
|
|
||||||
Identifier: z.Identifier.Value,
|
|
||||||
}
|
|
||||||
for _, raw := range z.Challenges {
|
|
||||||
if raw.Error != nil {
|
|
||||||
err.Errors = append(err.Errors, raw.Error.error(nil))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// wireChallenge is ACME JSON challenge representation.
|
|
||||||
type wireChallenge struct {
|
|
||||||
URI string `json:"uri"`
|
|
||||||
Type string
|
|
||||||
Token string
|
|
||||||
Status string
|
|
||||||
Error *wireError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *wireChallenge) challenge() *Challenge {
|
|
||||||
v := &Challenge{
|
|
||||||
URI: c.URI,
|
|
||||||
Type: c.Type,
|
|
||||||
Token: c.Token,
|
|
||||||
Status: c.Status,
|
|
||||||
}
|
|
||||||
if v.Status == "" {
|
|
||||||
v.Status = StatusPending
|
|
||||||
}
|
|
||||||
if c.Error != nil {
|
|
||||||
v.Error = c.Error.error(nil)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// wireError is a subset of fields of the Problem Details object
|
|
||||||
// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
|
|
||||||
type wireError struct {
|
|
||||||
Status int
|
|
||||||
Type string
|
|
||||||
Detail string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *wireError) error(h http.Header) *Error {
|
|
||||||
return &Error{
|
|
||||||
StatusCode: e.Status,
|
|
||||||
ProblemType: e.Type,
|
|
||||||
Detail: e.Detail,
|
|
||||||
Header: h,
|
|
||||||
}
|
|
||||||
}
|
|
0
vendor/golang.org/x/crypto/acme/LICENSE → vendor/golang.org/x/crypto/ed25519/LICENSE
generated
vendored
0
vendor/golang.org/x/crypto/acme/LICENSE → vendor/golang.org/x/crypto/ed25519/LICENSE
generated
vendored
188
vendor/golang.org/x/crypto/ed25519/ed25519.go
generated
vendored
Normal file
188
vendor/golang.org/x/crypto/ed25519/ed25519.go
generated
vendored
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package ed25519 implements the Ed25519 signature algorithm. See
|
||||||
|
// https://ed25519.cr.yp.to/.
|
||||||
|
//
|
||||||
|
// These functions are also compatible with the “Ed25519” function defined in
|
||||||
|
// RFC 8032.
|
||||||
|
package ed25519
|
||||||
|
|
||||||
|
// This code is a port of the public domain, “ref10” implementation of ed25519
|
||||||
|
// from SUPERCOP.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
cryptorand "crypto/rand"
|
||||||
|
"crypto/sha512"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519/internal/edwards25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||||
|
PublicKeySize = 32
|
||||||
|
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||||
|
PrivateKeySize = 64
|
||||||
|
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||||
|
SignatureSize = 64
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicKey is the type of Ed25519 public keys.
|
||||||
|
type PublicKey []byte
|
||||||
|
|
||||||
|
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
|
||||||
|
type PrivateKey []byte
|
||||||
|
|
||||||
|
// Public returns the PublicKey corresponding to priv.
|
||||||
|
func (priv PrivateKey) Public() crypto.PublicKey {
|
||||||
|
publicKey := make([]byte, PublicKeySize)
|
||||||
|
copy(publicKey, priv[32:])
|
||||||
|
return PublicKey(publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the given message with priv.
|
||||||
|
// Ed25519 performs two passes over messages to be signed and therefore cannot
|
||||||
|
// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
|
||||||
|
// indicate the message hasn't been hashed. This can be achieved by passing
|
||||||
|
// crypto.Hash(0) as the value for opts.
|
||||||
|
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
|
if opts.HashFunc() != crypto.Hash(0) {
|
||||||
|
return nil, errors.New("ed25519: cannot sign hashed message")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sign(priv, message), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateKey generates a public/private key pair using entropy from rand.
|
||||||
|
// If rand is nil, crypto/rand.Reader will be used.
|
||||||
|
func GenerateKey(rand io.Reader) (publicKey PublicKey, privateKey PrivateKey, err error) {
|
||||||
|
if rand == nil {
|
||||||
|
rand = cryptorand.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey = make([]byte, PrivateKeySize)
|
||||||
|
publicKey = make([]byte, PublicKeySize)
|
||||||
|
_, err = io.ReadFull(rand, privateKey[:32])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := sha512.Sum512(privateKey[:32])
|
||||||
|
digest[0] &= 248
|
||||||
|
digest[31] &= 127
|
||||||
|
digest[31] |= 64
|
||||||
|
|
||||||
|
var A edwards25519.ExtendedGroupElement
|
||||||
|
var hBytes [32]byte
|
||||||
|
copy(hBytes[:], digest[:])
|
||||||
|
edwards25519.GeScalarMultBase(&A, &hBytes)
|
||||||
|
var publicKeyBytes [32]byte
|
||||||
|
A.ToBytes(&publicKeyBytes)
|
||||||
|
|
||||||
|
copy(privateKey[32:], publicKeyBytes[:])
|
||||||
|
copy(publicKey, publicKeyBytes[:])
|
||||||
|
|
||||||
|
return publicKey, privateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the message with privateKey and returns a signature. It will
|
||||||
|
// panic if len(privateKey) is not PrivateKeySize.
|
||||||
|
func Sign(privateKey PrivateKey, message []byte) []byte {
|
||||||
|
if l := len(privateKey); l != PrivateKeySize {
|
||||||
|
panic("ed25519: bad private key length: " + strconv.Itoa(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
h := sha512.New()
|
||||||
|
h.Write(privateKey[:32])
|
||||||
|
|
||||||
|
var digest1, messageDigest, hramDigest [64]byte
|
||||||
|
var expandedSecretKey [32]byte
|
||||||
|
h.Sum(digest1[:0])
|
||||||
|
copy(expandedSecretKey[:], digest1[:])
|
||||||
|
expandedSecretKey[0] &= 248
|
||||||
|
expandedSecretKey[31] &= 63
|
||||||
|
expandedSecretKey[31] |= 64
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
h.Write(digest1[32:])
|
||||||
|
h.Write(message)
|
||||||
|
h.Sum(messageDigest[:0])
|
||||||
|
|
||||||
|
var messageDigestReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
|
||||||
|
var R edwards25519.ExtendedGroupElement
|
||||||
|
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
|
||||||
|
|
||||||
|
var encodedR [32]byte
|
||||||
|
R.ToBytes(&encodedR)
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
h.Write(encodedR[:])
|
||||||
|
h.Write(privateKey[32:])
|
||||||
|
h.Write(message)
|
||||||
|
h.Sum(hramDigest[:0])
|
||||||
|
var hramDigestReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
|
||||||
|
|
||||||
|
var s [32]byte
|
||||||
|
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
|
||||||
|
|
||||||
|
signature := make([]byte, SignatureSize)
|
||||||
|
copy(signature[:], encodedR[:])
|
||||||
|
copy(signature[32:], s[:])
|
||||||
|
|
||||||
|
return signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify reports whether sig is a valid signature of message by publicKey. It
|
||||||
|
// will panic if len(publicKey) is not PublicKeySize.
|
||||||
|
func Verify(publicKey PublicKey, message, sig []byte) bool {
|
||||||
|
if l := len(publicKey); l != PublicKeySize {
|
||||||
|
panic("ed25519: bad public key length: " + strconv.Itoa(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sig) != SignatureSize || sig[63]&224 != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var A edwards25519.ExtendedGroupElement
|
||||||
|
var publicKeyBytes [32]byte
|
||||||
|
copy(publicKeyBytes[:], publicKey)
|
||||||
|
if !A.FromBytes(&publicKeyBytes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
edwards25519.FeNeg(&A.X, &A.X)
|
||||||
|
edwards25519.FeNeg(&A.T, &A.T)
|
||||||
|
|
||||||
|
h := sha512.New()
|
||||||
|
h.Write(sig[:32])
|
||||||
|
h.Write(publicKey[:])
|
||||||
|
h.Write(message)
|
||||||
|
var digest [64]byte
|
||||||
|
h.Sum(digest[:0])
|
||||||
|
|
||||||
|
var hReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&hReduced, &digest)
|
||||||
|
|
||||||
|
var R edwards25519.ProjectiveGroupElement
|
||||||
|
var s [32]byte
|
||||||
|
copy(s[:], sig[32:])
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc8032#section-5.1.7 requires that s be in
|
||||||
|
// the range [0, order) in order to prevent signature malleability.
|
||||||
|
if !edwards25519.ScMinimal(&s) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &s)
|
||||||
|
|
||||||
|
var checkR [32]byte
|
||||||
|
R.ToBytes(&checkR)
|
||||||
|
return bytes.Equal(sig[:32], checkR[:])
|
||||||
|
}
|
1422
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go
generated
vendored
Normal file
1422
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1793
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go
generated
vendored
Normal file
1793
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
224
vendor/gopkg.in/square/go-jose.v1/shared.go
generated
vendored
224
vendor/gopkg.in/square/go-jose.v1/shared.go
generated
vendored
|
@ -1,224 +0,0 @@
|
||||||
/*-
|
|
||||||
* Copyright 2014 Square Inc.
|
|
||||||
*
|
|
||||||
* 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 jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/elliptic"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KeyAlgorithm represents a key management algorithm.
|
|
||||||
type KeyAlgorithm string
|
|
||||||
|
|
||||||
// SignatureAlgorithm represents a signature (or MAC) algorithm.
|
|
||||||
type SignatureAlgorithm string
|
|
||||||
|
|
||||||
// ContentEncryption represents a content encryption algorithm.
|
|
||||||
type ContentEncryption string
|
|
||||||
|
|
||||||
// CompressionAlgorithm represents an algorithm used for plaintext compression.
|
|
||||||
type CompressionAlgorithm string
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrCryptoFailure represents an error in cryptographic primitive. This
|
|
||||||
// occurs when, for example, a message had an invalid authentication tag or
|
|
||||||
// could not be decrypted.
|
|
||||||
ErrCryptoFailure = errors.New("square/go-jose: error in cryptographic primitive")
|
|
||||||
|
|
||||||
// ErrUnsupportedAlgorithm indicates that a selected algorithm is not
|
|
||||||
// supported. This occurs when trying to instantiate an encrypter for an
|
|
||||||
// algorithm that is not yet implemented.
|
|
||||||
ErrUnsupportedAlgorithm = errors.New("square/go-jose: unknown/unsupported algorithm")
|
|
||||||
|
|
||||||
// ErrUnsupportedKeyType indicates that the given key type/format is not
|
|
||||||
// supported. This occurs when trying to instantiate an encrypter and passing
|
|
||||||
// it a key of an unrecognized type or with unsupported parameters, such as
|
|
||||||
// an RSA private key with more than two primes.
|
|
||||||
ErrUnsupportedKeyType = errors.New("square/go-jose: unsupported key type/format")
|
|
||||||
|
|
||||||
// ErrNotSupported serialization of object is not supported. This occurs when
|
|
||||||
// trying to compact-serialize an object which can't be represented in
|
|
||||||
// compact form.
|
|
||||||
ErrNotSupported = errors.New("square/go-jose: compact serialization not supported for object")
|
|
||||||
|
|
||||||
// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
|
|
||||||
// nonce header parameter was included in an unprotected header object.
|
|
||||||
ErrUnprotectedNonce = errors.New("square/go-jose: Nonce parameter included in unprotected header")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Key management algorithms
|
|
||||||
const (
|
|
||||||
RSA1_5 = KeyAlgorithm("RSA1_5") // RSA-PKCS1v1.5
|
|
||||||
RSA_OAEP = KeyAlgorithm("RSA-OAEP") // RSA-OAEP-SHA1
|
|
||||||
RSA_OAEP_256 = KeyAlgorithm("RSA-OAEP-256") // RSA-OAEP-SHA256
|
|
||||||
A128KW = KeyAlgorithm("A128KW") // AES key wrap (128)
|
|
||||||
A192KW = KeyAlgorithm("A192KW") // AES key wrap (192)
|
|
||||||
A256KW = KeyAlgorithm("A256KW") // AES key wrap (256)
|
|
||||||
DIRECT = KeyAlgorithm("dir") // Direct encryption
|
|
||||||
ECDH_ES = KeyAlgorithm("ECDH-ES") // ECDH-ES
|
|
||||||
ECDH_ES_A128KW = KeyAlgorithm("ECDH-ES+A128KW") // ECDH-ES + AES key wrap (128)
|
|
||||||
ECDH_ES_A192KW = KeyAlgorithm("ECDH-ES+A192KW") // ECDH-ES + AES key wrap (192)
|
|
||||||
ECDH_ES_A256KW = KeyAlgorithm("ECDH-ES+A256KW") // ECDH-ES + AES key wrap (256)
|
|
||||||
A128GCMKW = KeyAlgorithm("A128GCMKW") // AES-GCM key wrap (128)
|
|
||||||
A192GCMKW = KeyAlgorithm("A192GCMKW") // AES-GCM key wrap (192)
|
|
||||||
A256GCMKW = KeyAlgorithm("A256GCMKW") // AES-GCM key wrap (256)
|
|
||||||
PBES2_HS256_A128KW = KeyAlgorithm("PBES2-HS256+A128KW") // PBES2 + HMAC-SHA256 + AES key wrap (128)
|
|
||||||
PBES2_HS384_A192KW = KeyAlgorithm("PBES2-HS384+A192KW") // PBES2 + HMAC-SHA384 + AES key wrap (192)
|
|
||||||
PBES2_HS512_A256KW = KeyAlgorithm("PBES2-HS512+A256KW") // PBES2 + HMAC-SHA512 + AES key wrap (256)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Signature algorithms
|
|
||||||
const (
|
|
||||||
HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256
|
|
||||||
HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384
|
|
||||||
HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512
|
|
||||||
RS256 = SignatureAlgorithm("RS256") // RSASSA-PKCS-v1.5 using SHA-256
|
|
||||||
RS384 = SignatureAlgorithm("RS384") // RSASSA-PKCS-v1.5 using SHA-384
|
|
||||||
RS512 = SignatureAlgorithm("RS512") // RSASSA-PKCS-v1.5 using SHA-512
|
|
||||||
ES256 = SignatureAlgorithm("ES256") // ECDSA using P-256 and SHA-256
|
|
||||||
ES384 = SignatureAlgorithm("ES384") // ECDSA using P-384 and SHA-384
|
|
||||||
ES512 = SignatureAlgorithm("ES512") // ECDSA using P-521 and SHA-512
|
|
||||||
PS256 = SignatureAlgorithm("PS256") // RSASSA-PSS using SHA256 and MGF1-SHA256
|
|
||||||
PS384 = SignatureAlgorithm("PS384") // RSASSA-PSS using SHA384 and MGF1-SHA384
|
|
||||||
PS512 = SignatureAlgorithm("PS512") // RSASSA-PSS using SHA512 and MGF1-SHA512
|
|
||||||
)
|
|
||||||
|
|
||||||
// Content encryption algorithms
|
|
||||||
const (
|
|
||||||
A128CBC_HS256 = ContentEncryption("A128CBC-HS256") // AES-CBC + HMAC-SHA256 (128)
|
|
||||||
A192CBC_HS384 = ContentEncryption("A192CBC-HS384") // AES-CBC + HMAC-SHA384 (192)
|
|
||||||
A256CBC_HS512 = ContentEncryption("A256CBC-HS512") // AES-CBC + HMAC-SHA512 (256)
|
|
||||||
A128GCM = ContentEncryption("A128GCM") // AES-GCM (128)
|
|
||||||
A192GCM = ContentEncryption("A192GCM") // AES-GCM (192)
|
|
||||||
A256GCM = ContentEncryption("A256GCM") // AES-GCM (256)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Compression algorithms
|
|
||||||
const (
|
|
||||||
NONE = CompressionAlgorithm("") // No compression
|
|
||||||
DEFLATE = CompressionAlgorithm("DEF") // DEFLATE (RFC 1951)
|
|
||||||
)
|
|
||||||
|
|
||||||
// rawHeader represents the JOSE header for JWE/JWS objects (used for parsing).
|
|
||||||
type rawHeader struct {
|
|
||||||
Alg string `json:"alg,omitempty"`
|
|
||||||
Enc ContentEncryption `json:"enc,omitempty"`
|
|
||||||
Zip CompressionAlgorithm `json:"zip,omitempty"`
|
|
||||||
Crit []string `json:"crit,omitempty"`
|
|
||||||
Apu *byteBuffer `json:"apu,omitempty"`
|
|
||||||
Apv *byteBuffer `json:"apv,omitempty"`
|
|
||||||
Epk *JsonWebKey `json:"epk,omitempty"`
|
|
||||||
Iv *byteBuffer `json:"iv,omitempty"`
|
|
||||||
Tag *byteBuffer `json:"tag,omitempty"`
|
|
||||||
Jwk *JsonWebKey `json:"jwk,omitempty"`
|
|
||||||
Kid string `json:"kid,omitempty"`
|
|
||||||
Nonce string `json:"nonce,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoseHeader represents the read-only JOSE header for JWE/JWS objects.
|
|
||||||
type JoseHeader struct {
|
|
||||||
KeyID string
|
|
||||||
JsonWebKey *JsonWebKey
|
|
||||||
Algorithm string
|
|
||||||
Nonce string
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanitized produces a cleaned-up header object from the raw JSON.
|
|
||||||
func (parsed rawHeader) sanitized() JoseHeader {
|
|
||||||
return JoseHeader{
|
|
||||||
KeyID: parsed.Kid,
|
|
||||||
JsonWebKey: parsed.Jwk,
|
|
||||||
Algorithm: parsed.Alg,
|
|
||||||
Nonce: parsed.Nonce,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge headers from src into dst, giving precedence to headers from l.
|
|
||||||
func (dst *rawHeader) merge(src *rawHeader) {
|
|
||||||
if src == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if dst.Alg == "" {
|
|
||||||
dst.Alg = src.Alg
|
|
||||||
}
|
|
||||||
if dst.Enc == "" {
|
|
||||||
dst.Enc = src.Enc
|
|
||||||
}
|
|
||||||
if dst.Zip == "" {
|
|
||||||
dst.Zip = src.Zip
|
|
||||||
}
|
|
||||||
if dst.Crit == nil {
|
|
||||||
dst.Crit = src.Crit
|
|
||||||
}
|
|
||||||
if dst.Crit == nil {
|
|
||||||
dst.Crit = src.Crit
|
|
||||||
}
|
|
||||||
if dst.Apu == nil {
|
|
||||||
dst.Apu = src.Apu
|
|
||||||
}
|
|
||||||
if dst.Apv == nil {
|
|
||||||
dst.Apv = src.Apv
|
|
||||||
}
|
|
||||||
if dst.Epk == nil {
|
|
||||||
dst.Epk = src.Epk
|
|
||||||
}
|
|
||||||
if dst.Iv == nil {
|
|
||||||
dst.Iv = src.Iv
|
|
||||||
}
|
|
||||||
if dst.Tag == nil {
|
|
||||||
dst.Tag = src.Tag
|
|
||||||
}
|
|
||||||
if dst.Kid == "" {
|
|
||||||
dst.Kid = src.Kid
|
|
||||||
}
|
|
||||||
if dst.Jwk == nil {
|
|
||||||
dst.Jwk = src.Jwk
|
|
||||||
}
|
|
||||||
if dst.Nonce == "" {
|
|
||||||
dst.Nonce = src.Nonce
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get JOSE name of curve
|
|
||||||
func curveName(crv elliptic.Curve) (string, error) {
|
|
||||||
switch crv {
|
|
||||||
case elliptic.P256():
|
|
||||||
return "P-256", nil
|
|
||||||
case elliptic.P384():
|
|
||||||
return "P-384", nil
|
|
||||||
case elliptic.P521():
|
|
||||||
return "P-521", nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("square/go-jose: unsupported/unknown elliptic curve")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get size of curve in bytes
|
|
||||||
func curveSize(crv elliptic.Curve) int {
|
|
||||||
bits := crv.Params().BitSize
|
|
||||||
|
|
||||||
div := bits / 8
|
|
||||||
mod := bits % 8
|
|
||||||
|
|
||||||
if mod == 0 {
|
|
||||||
return div
|
|
||||||
}
|
|
||||||
|
|
||||||
return div + 1
|
|
||||||
}
|
|
258
vendor/gopkg.in/square/go-jose.v1/signing.go
generated
vendored
258
vendor/gopkg.in/square/go-jose.v1/signing.go
generated
vendored
|
@ -1,258 +0,0 @@
|
||||||
/*-
|
|
||||||
* Copyright 2014 Square Inc.
|
|
||||||
*
|
|
||||||
* 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 jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rsa"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NonceSource represents a source of random nonces to go into JWS objects
|
|
||||||
type NonceSource interface {
|
|
||||||
Nonce() (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signer represents a signer which takes a payload and produces a signed JWS object.
|
|
||||||
type Signer interface {
|
|
||||||
Sign(payload []byte) (*JsonWebSignature, error)
|
|
||||||
SetNonceSource(source NonceSource)
|
|
||||||
SetEmbedJwk(embed bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiSigner represents a signer which supports multiple recipients.
|
|
||||||
type MultiSigner interface {
|
|
||||||
Sign(payload []byte) (*JsonWebSignature, error)
|
|
||||||
SetNonceSource(source NonceSource)
|
|
||||||
SetEmbedJwk(embed bool)
|
|
||||||
AddRecipient(alg SignatureAlgorithm, signingKey interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type payloadSigner interface {
|
|
||||||
signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type payloadVerifier interface {
|
|
||||||
verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type genericSigner struct {
|
|
||||||
recipients []recipientSigInfo
|
|
||||||
nonceSource NonceSource
|
|
||||||
embedJwk bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type recipientSigInfo struct {
|
|
||||||
sigAlg SignatureAlgorithm
|
|
||||||
keyID string
|
|
||||||
publicKey *JsonWebKey
|
|
||||||
signer payloadSigner
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSigner creates an appropriate signer based on the key type
|
|
||||||
func NewSigner(alg SignatureAlgorithm, signingKey interface{}) (Signer, error) {
|
|
||||||
// NewMultiSigner never fails (currently)
|
|
||||||
signer := NewMultiSigner()
|
|
||||||
|
|
||||||
err := signer.AddRecipient(alg, signingKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return signer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMultiSigner creates a signer for multiple recipients
|
|
||||||
func NewMultiSigner() MultiSigner {
|
|
||||||
return &genericSigner{
|
|
||||||
recipients: []recipientSigInfo{},
|
|
||||||
embedJwk: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// newVerifier creates a verifier based on the key type
|
|
||||||
func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
|
|
||||||
switch verificationKey := verificationKey.(type) {
|
|
||||||
case *rsa.PublicKey:
|
|
||||||
return &rsaEncrypterVerifier{
|
|
||||||
publicKey: verificationKey,
|
|
||||||
}, nil
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
return &ecEncrypterVerifier{
|
|
||||||
publicKey: verificationKey,
|
|
||||||
}, nil
|
|
||||||
case []byte:
|
|
||||||
return &symmetricMac{
|
|
||||||
key: verificationKey,
|
|
||||||
}, nil
|
|
||||||
case *JsonWebKey:
|
|
||||||
return newVerifier(verificationKey.Key)
|
|
||||||
default:
|
|
||||||
return nil, ErrUnsupportedKeyType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *genericSigner) AddRecipient(alg SignatureAlgorithm, signingKey interface{}) error {
|
|
||||||
recipient, err := makeJWSRecipient(alg, signingKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.recipients = append(ctx.recipients, recipient)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeJWSRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipientSigInfo, error) {
|
|
||||||
switch signingKey := signingKey.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
return newRSASigner(alg, signingKey)
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
return newECDSASigner(alg, signingKey)
|
|
||||||
case []byte:
|
|
||||||
return newSymmetricSigner(alg, signingKey)
|
|
||||||
case *JsonWebKey:
|
|
||||||
recipient, err := makeJWSRecipient(alg, signingKey.Key)
|
|
||||||
if err != nil {
|
|
||||||
return recipientSigInfo{}, err
|
|
||||||
}
|
|
||||||
recipient.keyID = signingKey.KeyID
|
|
||||||
return recipient, nil
|
|
||||||
default:
|
|
||||||
return recipientSigInfo{}, ErrUnsupportedKeyType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *genericSigner) Sign(payload []byte) (*JsonWebSignature, error) {
|
|
||||||
obj := &JsonWebSignature{}
|
|
||||||
obj.payload = payload
|
|
||||||
obj.Signatures = make([]Signature, len(ctx.recipients))
|
|
||||||
|
|
||||||
for i, recipient := range ctx.recipients {
|
|
||||||
protected := &rawHeader{
|
|
||||||
Alg: string(recipient.sigAlg),
|
|
||||||
}
|
|
||||||
|
|
||||||
if recipient.publicKey != nil && ctx.embedJwk {
|
|
||||||
protected.Jwk = recipient.publicKey
|
|
||||||
}
|
|
||||||
if recipient.keyID != "" {
|
|
||||||
protected.Kid = recipient.keyID
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.nonceSource != nil {
|
|
||||||
nonce, err := ctx.nonceSource.Nonce()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("square/go-jose: Error generating nonce: %v", err)
|
|
||||||
}
|
|
||||||
protected.Nonce = nonce
|
|
||||||
}
|
|
||||||
|
|
||||||
serializedProtected := mustSerializeJSON(protected)
|
|
||||||
|
|
||||||
input := []byte(fmt.Sprintf("%s.%s",
|
|
||||||
base64URLEncode(serializedProtected),
|
|
||||||
base64URLEncode(payload)))
|
|
||||||
|
|
||||||
signatureInfo, err := recipient.signer.signPayload(input, recipient.sigAlg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
signatureInfo.protected = protected
|
|
||||||
obj.Signatures[i] = signatureInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNonceSource provides or updates a nonce pool to the first recipients.
|
|
||||||
// After this method is called, the signer will consume one nonce per
|
|
||||||
// signature, returning an error it is unable to get a nonce.
|
|
||||||
func (ctx *genericSigner) SetNonceSource(source NonceSource) {
|
|
||||||
ctx.nonceSource = source
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEmbedJwk specifies if the signing key should be embedded in the protected
|
|
||||||
// header, if any. It defaults to 'true', though that may change in the future.
|
|
||||||
// Note that the use of embedded JWKs in the signature header can be dangerous,
|
|
||||||
// as you cannot assume that the key received in a payload is trusted.
|
|
||||||
func (ctx *genericSigner) SetEmbedJwk(embed bool) {
|
|
||||||
ctx.embedJwk = embed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify validates the signature on the object and returns the payload.
|
|
||||||
// This function does not support multi-signature, if you desire multi-sig
|
|
||||||
// verification use VerifyMulti instead.
|
|
||||||
//
|
|
||||||
// Be careful when verifying signatures based on embedded JWKs inside the
|
|
||||||
// payload header. You cannot assume that the key received in a payload is
|
|
||||||
// trusted.
|
|
||||||
func (obj JsonWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
|
|
||||||
verifier, err := newVerifier(verificationKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(obj.Signatures) > 1 {
|
|
||||||
return nil, errors.New("square/go-jose: too many signatures in payload; expecting only one")
|
|
||||||
}
|
|
||||||
|
|
||||||
signature := obj.Signatures[0]
|
|
||||||
headers := signature.mergedHeaders()
|
|
||||||
if len(headers.Crit) > 0 {
|
|
||||||
// Unsupported crit header
|
|
||||||
return nil, ErrCryptoFailure
|
|
||||||
}
|
|
||||||
|
|
||||||
input := obj.computeAuthData(&signature)
|
|
||||||
alg := SignatureAlgorithm(headers.Alg)
|
|
||||||
err = verifier.verifyPayload(input, signature.Signature, alg)
|
|
||||||
if err == nil {
|
|
||||||
return obj.payload, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrCryptoFailure
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyMulti validates (one of the multiple) signatures on the object and
|
|
||||||
// returns the index of the signature that was verified, along with the signature
|
|
||||||
// object and the payload. We return the signature and index to guarantee that
|
|
||||||
// callers are getting the verified value.
|
|
||||||
func (obj JsonWebSignature) VerifyMulti(verificationKey interface{}) (int, Signature, []byte, error) {
|
|
||||||
verifier, err := newVerifier(verificationKey)
|
|
||||||
if err != nil {
|
|
||||||
return -1, Signature{}, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, signature := range obj.Signatures {
|
|
||||||
headers := signature.mergedHeaders()
|
|
||||||
if len(headers.Crit) > 0 {
|
|
||||||
// Unsupported crit header
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
input := obj.computeAuthData(&signature)
|
|
||||||
alg := SignatureAlgorithm(headers.Alg)
|
|
||||||
err := verifier.verifyPayload(input, signature.Signature, alg)
|
|
||||||
if err == nil {
|
|
||||||
return i, signature, obj.payload, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1, Signature{}, nil, ErrCryptoFailure
|
|
||||||
}
|
|
|
@ -28,7 +28,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v1/cipher"
|
"golang.org/x/crypto/ed25519"
|
||||||
|
"gopkg.in/square/go-jose.v2/cipher"
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A generic RSA-based encrypter/verifier
|
// A generic RSA-based encrypter/verifier
|
||||||
|
@ -46,6 +48,10 @@ type ecEncrypterVerifier struct {
|
||||||
publicKey *ecdsa.PublicKey
|
publicKey *ecdsa.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type edEncrypterVerifier struct {
|
||||||
|
publicKey ed25519.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
// A key generator for ECDH-ES
|
// A key generator for ECDH-ES
|
||||||
type ecKeyGenerator struct {
|
type ecKeyGenerator struct {
|
||||||
size int
|
size int
|
||||||
|
@ -58,6 +64,10 @@ type ecDecrypterSigner struct {
|
||||||
privateKey *ecdsa.PrivateKey
|
privateKey *ecdsa.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type edDecrypterSigner struct {
|
||||||
|
privateKey ed25519.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
// newRSARecipient creates recipientKeyInfo based on the given key.
|
// newRSARecipient creates recipientKeyInfo based on the given key.
|
||||||
func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKeyInfo, error) {
|
func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKeyInfo, error) {
|
||||||
// Verify that key management algorithm is supported by this encrypter
|
// Verify that key management algorithm is supported by this encrypter
|
||||||
|
@ -94,7 +104,7 @@ func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipi
|
||||||
|
|
||||||
return recipientSigInfo{
|
return recipientSigInfo{
|
||||||
sigAlg: sigAlg,
|
sigAlg: sigAlg,
|
||||||
publicKey: &JsonWebKey{
|
publicKey: &JSONWebKey{
|
||||||
Key: &privateKey.PublicKey,
|
Key: &privateKey.PublicKey,
|
||||||
},
|
},
|
||||||
signer: &rsaDecrypterSigner{
|
signer: &rsaDecrypterSigner{
|
||||||
|
@ -103,6 +113,25 @@ func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipi
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newEd25519Signer(sigAlg SignatureAlgorithm, privateKey ed25519.PrivateKey) (recipientSigInfo, error) {
|
||||||
|
if sigAlg != EdDSA {
|
||||||
|
return recipientSigInfo{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
if privateKey == nil {
|
||||||
|
return recipientSigInfo{}, errors.New("invalid private key")
|
||||||
|
}
|
||||||
|
return recipientSigInfo{
|
||||||
|
sigAlg: sigAlg,
|
||||||
|
publicKey: &JSONWebKey{
|
||||||
|
Key: privateKey.Public(),
|
||||||
|
},
|
||||||
|
signer: &edDecrypterSigner{
|
||||||
|
privateKey: privateKey,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// newECDHRecipient creates recipientKeyInfo based on the given key.
|
// newECDHRecipient creates recipientKeyInfo based on the given key.
|
||||||
func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipientKeyInfo, error) {
|
func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipientKeyInfo, error) {
|
||||||
// Verify that key management algorithm is supported by this encrypter
|
// Verify that key management algorithm is supported by this encrypter
|
||||||
|
@ -139,7 +168,7 @@ func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (re
|
||||||
|
|
||||||
return recipientSigInfo{
|
return recipientSigInfo{
|
||||||
sigAlg: sigAlg,
|
sigAlg: sigAlg,
|
||||||
publicKey: &JsonWebKey{
|
publicKey: &JSONWebKey{
|
||||||
Key: &privateKey.PublicKey,
|
Key: &privateKey.PublicKey,
|
||||||
},
|
},
|
||||||
signer: &ecDecrypterSigner{
|
signer: &ecDecrypterSigner{
|
||||||
|
@ -178,7 +207,7 @@ func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, e
|
||||||
|
|
||||||
// Decrypt the given payload and return the content encryption key.
|
// Decrypt the given payload and return the content encryption key.
|
||||||
func (ctx rsaDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
func (ctx rsaDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
||||||
return ctx.decrypt(recipient.encryptedKey, KeyAlgorithm(headers.Alg), generator)
|
return ctx.decrypt(recipient.encryptedKey, headers.getAlgorithm(), generator)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt the given payload. Based on the key encryption algorithm,
|
// Decrypt the given payload. Based on the key encryption algorithm,
|
||||||
|
@ -366,10 +395,15 @@ func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
|
||||||
|
|
||||||
out := josecipher.DeriveECDHES(ctx.algID, []byte{}, []byte{}, priv, ctx.publicKey, ctx.size)
|
out := josecipher.DeriveECDHES(ctx.algID, []byte{}, []byte{}, priv, ctx.publicKey, ctx.size)
|
||||||
|
|
||||||
|
b, err := json.Marshal(&JSONWebKey{
|
||||||
|
Key: &priv.PublicKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
headers := rawHeader{
|
headers := rawHeader{
|
||||||
Epk: &JsonWebKey{
|
headerEPK: makeRawMessage(b),
|
||||||
Key: &priv.PublicKey,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return out, headers, nil
|
return out, headers, nil
|
||||||
|
@ -377,11 +411,15 @@ func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
|
||||||
|
|
||||||
// Decrypt the given payload and return the content encryption key.
|
// Decrypt the given payload and return the content encryption key.
|
||||||
func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
||||||
if headers.Epk == nil {
|
epk, err := headers.getEPK()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("square/go-jose: invalid epk header")
|
||||||
|
}
|
||||||
|
if epk == nil {
|
||||||
return nil, errors.New("square/go-jose: missing epk header")
|
return nil, errors.New("square/go-jose: missing epk header")
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKey, ok := headers.Epk.Key.(*ecdsa.PublicKey)
|
publicKey, ok := epk.Key.(*ecdsa.PublicKey)
|
||||||
if publicKey == nil || !ok {
|
if publicKey == nil || !ok {
|
||||||
return nil, errors.New("square/go-jose: invalid epk header")
|
return nil, errors.New("square/go-jose: invalid epk header")
|
||||||
}
|
}
|
||||||
|
@ -390,19 +428,26 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI
|
||||||
return nil, errors.New("square/go-jose: invalid public key in epk header")
|
return nil, errors.New("square/go-jose: invalid public key in epk header")
|
||||||
}
|
}
|
||||||
|
|
||||||
apuData := headers.Apu.bytes()
|
apuData, err := headers.getAPU()
|
||||||
apvData := headers.Apv.bytes()
|
if err != nil {
|
||||||
|
return nil, errors.New("square/go-jose: invalid apu header")
|
||||||
|
}
|
||||||
|
apvData, err := headers.getAPV()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("square/go-jose: invalid apv header")
|
||||||
|
}
|
||||||
|
|
||||||
deriveKey := func(algID string, size int) []byte {
|
deriveKey := func(algID string, size int) []byte {
|
||||||
return josecipher.DeriveECDHES(algID, apuData, apvData, ctx.privateKey, publicKey, size)
|
return josecipher.DeriveECDHES(algID, apuData.bytes(), apvData.bytes(), ctx.privateKey, publicKey, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
var keySize int
|
var keySize int
|
||||||
|
|
||||||
switch KeyAlgorithm(headers.Alg) {
|
algorithm := headers.getAlgorithm()
|
||||||
|
switch algorithm {
|
||||||
case ECDH_ES:
|
case ECDH_ES:
|
||||||
// ECDH-ES uses direct key agreement, no key unwrapping necessary.
|
// ECDH-ES uses direct key agreement, no key unwrapping necessary.
|
||||||
return deriveKey(string(headers.Enc), generator.keySize()), nil
|
return deriveKey(string(headers.getEncryption()), generator.keySize()), nil
|
||||||
case ECDH_ES_A128KW:
|
case ECDH_ES_A128KW:
|
||||||
keySize = 16
|
keySize = 16
|
||||||
case ECDH_ES_A192KW:
|
case ECDH_ES_A192KW:
|
||||||
|
@ -413,7 +458,7 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI
|
||||||
return nil, ErrUnsupportedAlgorithm
|
return nil, ErrUnsupportedAlgorithm
|
||||||
}
|
}
|
||||||
|
|
||||||
key := deriveKey(headers.Alg, keySize)
|
key := deriveKey(string(algorithm), keySize)
|
||||||
block, err := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -421,6 +466,32 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI
|
||||||
|
|
||||||
return josecipher.KeyUnwrap(block, recipient.encryptedKey)
|
return josecipher.KeyUnwrap(block, recipient.encryptedKey)
|
||||||
}
|
}
|
||||||
|
func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||||
|
if alg != EdDSA {
|
||||||
|
return Signature{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := ctx.privateKey.Sign(randReader, payload, crypto.Hash(0))
|
||||||
|
if err != nil {
|
||||||
|
return Signature{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Signature{
|
||||||
|
Signature: sig,
|
||||||
|
protected: &rawHeader{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx edEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
|
||||||
|
if alg != EdDSA {
|
||||||
|
return ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
ok := ed25519.Verify(ctx.publicKey, payload, signature)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("square/go-jose: ed25519 signature failed to verify")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Sign the given payload
|
// Sign the given payload
|
||||||
func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||||
|
@ -457,7 +528,7 @@ func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm)
|
||||||
|
|
||||||
keyBytes := curveBits / 8
|
keyBytes := curveBits / 8
|
||||||
if curveBits%8 > 0 {
|
if curveBits%8 > 0 {
|
||||||
keyBytes += 1
|
keyBytes++
|
||||||
}
|
}
|
||||||
|
|
||||||
// We serialize the outpus (r and s) into big-endian byte arrays and pad
|
// We serialize the outpus (r and s) into big-endian byte arrays and pad
|
|
@ -28,7 +28,7 @@ import (
|
||||||
// size may be at most 1<<16 bytes (64 KiB).
|
// size may be at most 1<<16 bytes (64 KiB).
|
||||||
func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte {
|
func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte {
|
||||||
if size > 1<<16 {
|
if size > 1<<16 {
|
||||||
panic("ECDH-ES output size too large, must be less than 1<<16")
|
panic("ECDH-ES output size too large, must be less than or equal to 1<<16")
|
||||||
}
|
}
|
||||||
|
|
||||||
// algId, partyUInfo, partyVInfo inputs must be prefixed with the length
|
// algId, partyUInfo, partyVInfo inputs must be prefixed with the length
|
240
vendor/gopkg.in/square/go-jose.v1/crypter.go → vendor/gopkg.in/square/go-jose.v2/crypter.go
generated
vendored
240
vendor/gopkg.in/square/go-jose.v1/crypter.go → vendor/gopkg.in/square/go-jose.v2/crypter.go
generated
vendored
|
@ -22,21 +22,15 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Encrypter represents an encrypter which produces an encrypted JWE object.
|
// Encrypter represents an encrypter which produces an encrypted JWE object.
|
||||||
type Encrypter interface {
|
type Encrypter interface {
|
||||||
Encrypt(plaintext []byte) (*JsonWebEncryption, error)
|
Encrypt(plaintext []byte) (*JSONWebEncryption, error)
|
||||||
EncryptWithAuthData(plaintext []byte, aad []byte) (*JsonWebEncryption, error)
|
EncryptWithAuthData(plaintext []byte, aad []byte) (*JSONWebEncryption, error)
|
||||||
SetCompression(alg CompressionAlgorithm)
|
Options() EncrypterOptions
|
||||||
}
|
|
||||||
|
|
||||||
// MultiEncrypter represents an encrypter which supports multiple recipients.
|
|
||||||
type MultiEncrypter interface {
|
|
||||||
Encrypt(plaintext []byte) (*JsonWebEncryption, error)
|
|
||||||
EncryptWithAuthData(plaintext []byte, aad []byte) (*JsonWebEncryption, error)
|
|
||||||
SetCompression(alg CompressionAlgorithm)
|
|
||||||
AddRecipient(alg KeyAlgorithm, encryptionKey interface{}) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A generic content cipher
|
// A generic content cipher
|
||||||
|
@ -69,6 +63,7 @@ type genericEncrypter struct {
|
||||||
cipher contentCipher
|
cipher contentCipher
|
||||||
recipients []recipientKeyInfo
|
recipients []recipientKeyInfo
|
||||||
keyGenerator keyGenerator
|
keyGenerator keyGenerator
|
||||||
|
extraHeaders map[HeaderKey]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type recipientKeyInfo struct {
|
type recipientKeyInfo struct {
|
||||||
|
@ -77,18 +72,54 @@ type recipientKeyInfo struct {
|
||||||
keyEncrypter keyEncrypter
|
keyEncrypter keyEncrypter
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCompression sets a compression algorithm to be applied before encryption.
|
// EncrypterOptions represents options that can be set on new encrypters.
|
||||||
func (ctx *genericEncrypter) SetCompression(compressionAlg CompressionAlgorithm) {
|
type EncrypterOptions struct {
|
||||||
ctx.compressionAlg = compressionAlg
|
Compression CompressionAlgorithm
|
||||||
|
|
||||||
|
// Optional map of additional keys to be inserted into the protected header
|
||||||
|
// of a JWS object. Some specifications which make use of JWS like to insert
|
||||||
|
// additional values here. All values must be JSON-serializable.
|
||||||
|
ExtraHeaders map[HeaderKey]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
|
||||||
|
// if necessary. It returns itself and so can be used in a fluent style.
|
||||||
|
func (eo *EncrypterOptions) WithHeader(k HeaderKey, v interface{}) *EncrypterOptions {
|
||||||
|
if eo.ExtraHeaders == nil {
|
||||||
|
eo.ExtraHeaders = map[HeaderKey]interface{}{}
|
||||||
|
}
|
||||||
|
eo.ExtraHeaders[k] = v
|
||||||
|
return eo
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContentType adds a content type ("cty") header and returns the updated
|
||||||
|
// EncrypterOptions.
|
||||||
|
func (eo *EncrypterOptions) WithContentType(contentType ContentType) *EncrypterOptions {
|
||||||
|
return eo.WithHeader(HeaderContentType, contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithType adds a type ("typ") header and returns the updated EncrypterOptions.
|
||||||
|
func (eo *EncrypterOptions) WithType(typ ContentType) *EncrypterOptions {
|
||||||
|
return eo.WithHeader(HeaderType, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recipient represents an algorithm/key to encrypt messages to.
|
||||||
|
type Recipient struct {
|
||||||
|
Algorithm KeyAlgorithm
|
||||||
|
Key interface{}
|
||||||
|
KeyID string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEncrypter creates an appropriate encrypter based on the key type
|
// NewEncrypter creates an appropriate encrypter based on the key type
|
||||||
func NewEncrypter(alg KeyAlgorithm, enc ContentEncryption, encryptionKey interface{}) (Encrypter, error) {
|
func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions) (Encrypter, error) {
|
||||||
encrypter := &genericEncrypter{
|
encrypter := &genericEncrypter{
|
||||||
contentAlg: enc,
|
contentAlg: enc,
|
||||||
compressionAlg: NONE,
|
recipients: []recipientKeyInfo{},
|
||||||
recipients: []recipientKeyInfo{},
|
cipher: getContentCipher(enc),
|
||||||
cipher: getContentCipher(enc),
|
}
|
||||||
|
if opts != nil {
|
||||||
|
encrypter.compressionAlg = opts.Compression
|
||||||
|
encrypter.extraHeaders = opts.ExtraHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
if encrypter.cipher == nil {
|
if encrypter.cipher == nil {
|
||||||
|
@ -97,15 +128,16 @@ func NewEncrypter(alg KeyAlgorithm, enc ContentEncryption, encryptionKey interfa
|
||||||
|
|
||||||
var keyID string
|
var keyID string
|
||||||
var rawKey interface{}
|
var rawKey interface{}
|
||||||
switch encryptionKey := encryptionKey.(type) {
|
switch encryptionKey := rcpt.Key.(type) {
|
||||||
case *JsonWebKey:
|
case JSONWebKey:
|
||||||
keyID = encryptionKey.KeyID
|
keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key
|
||||||
rawKey = encryptionKey.Key
|
case *JSONWebKey:
|
||||||
|
keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key
|
||||||
default:
|
default:
|
||||||
rawKey = encryptionKey
|
rawKey = encryptionKey
|
||||||
}
|
}
|
||||||
|
|
||||||
switch alg {
|
switch rcpt.Algorithm {
|
||||||
case DIRECT:
|
case DIRECT:
|
||||||
// Direct encryption mode must be treated differently
|
// Direct encryption mode must be treated differently
|
||||||
if reflect.TypeOf(rawKey) != reflect.TypeOf([]byte{}) {
|
if reflect.TypeOf(rawKey) != reflect.TypeOf([]byte{}) {
|
||||||
|
@ -114,11 +146,12 @@ func NewEncrypter(alg KeyAlgorithm, enc ContentEncryption, encryptionKey interfa
|
||||||
encrypter.keyGenerator = staticKeyGenerator{
|
encrypter.keyGenerator = staticKeyGenerator{
|
||||||
key: rawKey.([]byte),
|
key: rawKey.([]byte),
|
||||||
}
|
}
|
||||||
recipient, _ := newSymmetricRecipient(alg, rawKey.([]byte))
|
recipientInfo, _ := newSymmetricRecipient(rcpt.Algorithm, rawKey.([]byte))
|
||||||
if keyID != "" {
|
recipientInfo.keyID = keyID
|
||||||
recipient.keyID = keyID
|
if rcpt.KeyID != "" {
|
||||||
|
recipientInfo.keyID = rcpt.KeyID
|
||||||
}
|
}
|
||||||
encrypter.recipients = []recipientKeyInfo{recipient}
|
encrypter.recipients = []recipientKeyInfo{recipientInfo}
|
||||||
return encrypter, nil
|
return encrypter, nil
|
||||||
case ECDH_ES:
|
case ECDH_ES:
|
||||||
// ECDH-ES (w/o key wrapping) is similar to DIRECT mode
|
// ECDH-ES (w/o key wrapping) is similar to DIRECT mode
|
||||||
|
@ -131,55 +164,72 @@ func NewEncrypter(alg KeyAlgorithm, enc ContentEncryption, encryptionKey interfa
|
||||||
algID: string(enc),
|
algID: string(enc),
|
||||||
publicKey: rawKey.(*ecdsa.PublicKey),
|
publicKey: rawKey.(*ecdsa.PublicKey),
|
||||||
}
|
}
|
||||||
recipient, _ := newECDHRecipient(alg, rawKey.(*ecdsa.PublicKey))
|
recipientInfo, _ := newECDHRecipient(rcpt.Algorithm, rawKey.(*ecdsa.PublicKey))
|
||||||
if keyID != "" {
|
recipientInfo.keyID = keyID
|
||||||
recipient.keyID = keyID
|
if rcpt.KeyID != "" {
|
||||||
|
recipientInfo.keyID = rcpt.KeyID
|
||||||
}
|
}
|
||||||
encrypter.recipients = []recipientKeyInfo{recipient}
|
encrypter.recipients = []recipientKeyInfo{recipientInfo}
|
||||||
return encrypter, nil
|
return encrypter, nil
|
||||||
default:
|
default:
|
||||||
// Can just add a standard recipient
|
// Can just add a standard recipient
|
||||||
encrypter.keyGenerator = randomKeyGenerator{
|
encrypter.keyGenerator = randomKeyGenerator{
|
||||||
size: encrypter.cipher.keySize(),
|
size: encrypter.cipher.keySize(),
|
||||||
}
|
}
|
||||||
err := encrypter.AddRecipient(alg, encryptionKey)
|
err := encrypter.addRecipient(rcpt)
|
||||||
return encrypter, err
|
return encrypter, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMultiEncrypter creates a multi-encrypter based on the given parameters
|
// NewMultiEncrypter creates a multi-encrypter based on the given parameters
|
||||||
func NewMultiEncrypter(enc ContentEncryption) (MultiEncrypter, error) {
|
func NewMultiEncrypter(enc ContentEncryption, rcpts []Recipient, opts *EncrypterOptions) (Encrypter, error) {
|
||||||
cipher := getContentCipher(enc)
|
cipher := getContentCipher(enc)
|
||||||
|
|
||||||
if cipher == nil {
|
if cipher == nil {
|
||||||
return nil, ErrUnsupportedAlgorithm
|
return nil, ErrUnsupportedAlgorithm
|
||||||
}
|
}
|
||||||
|
if rcpts == nil || len(rcpts) == 0 {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: recipients is nil or empty")
|
||||||
|
}
|
||||||
|
|
||||||
encrypter := &genericEncrypter{
|
encrypter := &genericEncrypter{
|
||||||
contentAlg: enc,
|
contentAlg: enc,
|
||||||
compressionAlg: NONE,
|
recipients: []recipientKeyInfo{},
|
||||||
recipients: []recipientKeyInfo{},
|
cipher: cipher,
|
||||||
cipher: cipher,
|
|
||||||
keyGenerator: randomKeyGenerator{
|
keyGenerator: randomKeyGenerator{
|
||||||
size: cipher.keySize(),
|
size: cipher.keySize(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts != nil {
|
||||||
|
encrypter.compressionAlg = opts.Compression
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, recipient := range rcpts {
|
||||||
|
err := encrypter.addRecipient(recipient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return encrypter, nil
|
return encrypter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *genericEncrypter) AddRecipient(alg KeyAlgorithm, encryptionKey interface{}) (err error) {
|
func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) {
|
||||||
var recipient recipientKeyInfo
|
var recipientInfo recipientKeyInfo
|
||||||
|
|
||||||
switch alg {
|
switch recipient.Algorithm {
|
||||||
case DIRECT, ECDH_ES:
|
case DIRECT, ECDH_ES:
|
||||||
return fmt.Errorf("square/go-jose: key algorithm '%s' not supported in multi-recipient mode", alg)
|
return fmt.Errorf("square/go-jose: key algorithm '%s' not supported in multi-recipient mode", recipient.Algorithm)
|
||||||
}
|
}
|
||||||
|
|
||||||
recipient, err = makeJWERecipient(alg, encryptionKey)
|
recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key)
|
||||||
|
if recipient.KeyID != "" {
|
||||||
|
recipientInfo.keyID = recipient.KeyID
|
||||||
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ctx.recipients = append(ctx.recipients, recipient)
|
ctx.recipients = append(ctx.recipients, recipientInfo)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -192,11 +242,9 @@ func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKey
|
||||||
return newECDHRecipient(alg, encryptionKey)
|
return newECDHRecipient(alg, encryptionKey)
|
||||||
case []byte:
|
case []byte:
|
||||||
return newSymmetricRecipient(alg, encryptionKey)
|
return newSymmetricRecipient(alg, encryptionKey)
|
||||||
case *JsonWebKey:
|
case *JSONWebKey:
|
||||||
recipient, err := makeJWERecipient(alg, encryptionKey.Key)
|
recipient, err := makeJWERecipient(alg, encryptionKey.Key)
|
||||||
if err == nil && encryptionKey.KeyID != "" {
|
recipient.keyID = encryptionKey.KeyID
|
||||||
recipient.keyID = encryptionKey.KeyID
|
|
||||||
}
|
|
||||||
return recipient, err
|
return recipient, err
|
||||||
default:
|
default:
|
||||||
return recipientKeyInfo{}, ErrUnsupportedKeyType
|
return recipientKeyInfo{}, ErrUnsupportedKeyType
|
||||||
|
@ -218,7 +266,9 @@ func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) {
|
||||||
return &symmetricKeyCipher{
|
return &symmetricKeyCipher{
|
||||||
key: decryptionKey,
|
key: decryptionKey,
|
||||||
}, nil
|
}, nil
|
||||||
case *JsonWebKey:
|
case JSONWebKey:
|
||||||
|
return newDecrypter(decryptionKey.Key)
|
||||||
|
case *JSONWebKey:
|
||||||
return newDecrypter(decryptionKey.Key)
|
return newDecrypter(decryptionKey.Key)
|
||||||
default:
|
default:
|
||||||
return nil, ErrUnsupportedKeyType
|
return nil, ErrUnsupportedKeyType
|
||||||
|
@ -226,18 +276,21 @@ func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementation of encrypt method producing a JWE object.
|
// Implementation of encrypt method producing a JWE object.
|
||||||
func (ctx *genericEncrypter) Encrypt(plaintext []byte) (*JsonWebEncryption, error) {
|
func (ctx *genericEncrypter) Encrypt(plaintext []byte) (*JSONWebEncryption, error) {
|
||||||
return ctx.EncryptWithAuthData(plaintext, nil)
|
return ctx.EncryptWithAuthData(plaintext, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementation of encrypt method producing a JWE object.
|
// Implementation of encrypt method producing a JWE object.
|
||||||
func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JsonWebEncryption, error) {
|
func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWebEncryption, error) {
|
||||||
obj := &JsonWebEncryption{}
|
obj := &JSONWebEncryption{}
|
||||||
obj.aad = aad
|
obj.aad = aad
|
||||||
|
|
||||||
obj.protected = &rawHeader{
|
obj.protected = &rawHeader{}
|
||||||
Enc: ctx.contentAlg,
|
err := obj.protected.set(headerEncryption, ctx.contentAlg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.recipients = make([]recipientInfo, len(ctx.recipients))
|
obj.recipients = make([]recipientInfo, len(ctx.recipients))
|
||||||
|
|
||||||
if len(ctx.recipients) == 0 {
|
if len(ctx.recipients) == 0 {
|
||||||
|
@ -257,9 +310,16 @@ func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JsonWe
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
recipient.header.Alg = string(info.keyAlg)
|
err = recipient.header.set(headerAlgorithm, info.keyAlg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if info.keyID != "" {
|
if info.keyID != "" {
|
||||||
recipient.header.Kid = info.keyID
|
err = recipient.header.set(headerKeyID, info.keyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
obj.recipients[i] = recipient
|
obj.recipients[i] = recipient
|
||||||
}
|
}
|
||||||
|
@ -277,7 +337,18 @@ func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JsonWe
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.protected.Zip = ctx.compressionAlg
|
err = obj.protected.set(headerCompression, ctx.compressionAlg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range ctx.extraHeaders {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
(*obj.protected)[k] = makeRawMessage(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
authData := obj.computeAuthData()
|
authData := obj.computeAuthData()
|
||||||
|
@ -293,17 +364,29 @@ func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JsonWe
|
||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *genericEncrypter) Options() EncrypterOptions {
|
||||||
|
return EncrypterOptions{
|
||||||
|
Compression: ctx.compressionAlg,
|
||||||
|
ExtraHeaders: ctx.extraHeaders,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Decrypt and validate the object and return the plaintext. Note that this
|
// Decrypt and validate the object and return the plaintext. Note that this
|
||||||
// function does not support multi-recipient, if you desire multi-recipient
|
// function does not support multi-recipient, if you desire multi-recipient
|
||||||
// decryption use DecryptMulti instead.
|
// decryption use DecryptMulti instead.
|
||||||
func (obj JsonWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
|
func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
|
||||||
headers := obj.mergedHeaders(nil)
|
headers := obj.mergedHeaders(nil)
|
||||||
|
|
||||||
if len(obj.recipients) > 1 {
|
if len(obj.recipients) > 1 {
|
||||||
return nil, errors.New("square/go-jose: too many recipients in payload; expecting only one")
|
return nil, errors.New("square/go-jose: too many recipients in payload; expecting only one")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(headers.Crit) > 0 {
|
critical, err := headers.getCritical()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid crit header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(critical) > 0 {
|
||||||
return nil, fmt.Errorf("square/go-jose: unsupported crit header")
|
return nil, fmt.Errorf("square/go-jose: unsupported crit header")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,9 +395,9 @@ func (obj JsonWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cipher := getContentCipher(headers.Enc)
|
cipher := getContentCipher(headers.getEncryption())
|
||||||
if cipher == nil {
|
if cipher == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(headers.Enc))
|
return nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(headers.getEncryption()))
|
||||||
}
|
}
|
||||||
|
|
||||||
generator := randomKeyGenerator{
|
generator := randomKeyGenerator{
|
||||||
|
@ -344,8 +427,8 @@ func (obj JsonWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The "zip" header parameter may only be present in the protected header.
|
// The "zip" header parameter may only be present in the protected header.
|
||||||
if obj.protected.Zip != "" {
|
if comp := obj.protected.getCompression(); comp != "" {
|
||||||
plaintext, err = decompress(obj.protected.Zip, plaintext)
|
plaintext, err = decompress(comp, plaintext)
|
||||||
}
|
}
|
||||||
|
|
||||||
return plaintext, err
|
return plaintext, err
|
||||||
|
@ -355,21 +438,27 @@ func (obj JsonWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
|
||||||
// with support for multiple recipients. It returns the index of the recipient
|
// with support for multiple recipients. It returns the index of the recipient
|
||||||
// for which the decryption was successful, the merged headers for that recipient,
|
// for which the decryption was successful, the merged headers for that recipient,
|
||||||
// and the plaintext.
|
// and the plaintext.
|
||||||
func (obj JsonWebEncryption) DecryptMulti(decryptionKey interface{}) (int, JoseHeader, []byte, error) {
|
func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
|
||||||
globalHeaders := obj.mergedHeaders(nil)
|
globalHeaders := obj.mergedHeaders(nil)
|
||||||
|
|
||||||
if len(globalHeaders.Crit) > 0 {
|
critical, err := globalHeaders.getCritical()
|
||||||
return -1, JoseHeader{}, nil, fmt.Errorf("square/go-jose: unsupported crit header")
|
if err != nil {
|
||||||
|
return -1, Header{}, nil, fmt.Errorf("square/go-jose: invalid crit header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(critical) > 0 {
|
||||||
|
return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported crit header")
|
||||||
}
|
}
|
||||||
|
|
||||||
decrypter, err := newDecrypter(decryptionKey)
|
decrypter, err := newDecrypter(decryptionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, JoseHeader{}, nil, err
|
return -1, Header{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cipher := getContentCipher(globalHeaders.Enc)
|
encryption := globalHeaders.getEncryption()
|
||||||
|
cipher := getContentCipher(encryption)
|
||||||
if cipher == nil {
|
if cipher == nil {
|
||||||
return -1, JoseHeader{}, nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(globalHeaders.Enc))
|
return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(encryption))
|
||||||
}
|
}
|
||||||
|
|
||||||
generator := randomKeyGenerator{
|
generator := randomKeyGenerator{
|
||||||
|
@ -404,13 +493,18 @@ func (obj JsonWebEncryption) DecryptMulti(decryptionKey interface{}) (int, JoseH
|
||||||
}
|
}
|
||||||
|
|
||||||
if plaintext == nil || err != nil {
|
if plaintext == nil || err != nil {
|
||||||
return -1, JoseHeader{}, nil, ErrCryptoFailure
|
return -1, Header{}, nil, ErrCryptoFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
// The "zip" header parameter may only be present in the protected header.
|
// The "zip" header parameter may only be present in the protected header.
|
||||||
if obj.protected.Zip != "" {
|
if comp := obj.protected.getCompression(); comp != "" {
|
||||||
plaintext, err = decompress(obj.protected.Zip, plaintext)
|
plaintext, err = decompress(comp, plaintext)
|
||||||
}
|
}
|
||||||
|
|
||||||
return index, headers.sanitized(), plaintext, err
|
sanitized, err := headers.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return -1, Header{}, nil, fmt.Errorf("square/go-jose: failed to sanitize header: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return index, sanitized, plaintext, err
|
||||||
}
|
}
|
11
vendor/gopkg.in/square/go-jose.v1/doc.go → vendor/gopkg.in/square/go-jose.v2/doc.go
generated
vendored
11
vendor/gopkg.in/square/go-jose.v1/doc.go → vendor/gopkg.in/square/go-jose.v2/doc.go
generated
vendored
|
@ -17,10 +17,11 @@
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Package jose aims to provide an implementation of the Javascript Object Signing
|
Package jose aims to provide an implementation of the Javascript Object Signing
|
||||||
and Encryption set of standards. For the moment, it mainly focuses on
|
and Encryption set of standards. It implements encryption and signing based on
|
||||||
encryption and signing based on the JSON Web Encryption and JSON Web Signature
|
the JSON Web Encryption and JSON Web Signature standards, with optional JSON
|
||||||
standards. The library supports both the compact and full serialization
|
Web Token support available in a sub-package. The library supports both the
|
||||||
formats, and has optional support for multiple recipients.
|
compact and full serialization formats, and has optional support for multiple
|
||||||
|
recipients.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package jose // import "gopkg.in/square/go-jose.v1"
|
package jose
|
|
@ -21,29 +21,14 @@ import (
|
||||||
"compress/flate"
|
"compress/flate"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v1/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var stripWhitespaceRegex = regexp.MustCompile("\\s")
|
var stripWhitespaceRegex = regexp.MustCompile("\\s")
|
||||||
|
|
||||||
// Url-safe base64 encode that strips padding
|
|
||||||
func base64URLEncode(data []byte) string {
|
|
||||||
var result = base64.URLEncoding.EncodeToString(data)
|
|
||||||
return strings.TrimRight(result, "=")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Url-safe base64 decoder that adds padding
|
|
||||||
func base64URLDecode(data string) ([]byte, error) {
|
|
||||||
var missing = (4 - len(data)%4) % 4
|
|
||||||
data += strings.Repeat("=", missing)
|
|
||||||
return base64.URLEncoding.DecodeString(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to serialize known-good objects.
|
// Helper function to serialize known-good objects.
|
||||||
// Precondition: value is not a nil pointer.
|
// Precondition: value is not a nil pointer.
|
||||||
func mustSerializeJSON(value interface{}) []byte {
|
func mustSerializeJSON(value interface{}) []byte {
|
||||||
|
@ -162,7 +147,7 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
decoded, err := base64URLDecode(encoded)
|
decoded, err := base64.RawURLEncoding.DecodeString(encoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -173,7 +158,7 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *byteBuffer) base64() string {
|
func (b *byteBuffer) base64() string {
|
||||||
return base64URLEncode(b.data)
|
return base64.RawURLEncoding.EncodeToString(b.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *byteBuffer) bytes() []byte {
|
func (b *byteBuffer) bytes() []byte {
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"gopkg.in/alecthomas/kingpin.v2"
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
"gopkg.in/square/go-jose.v1"
|
"gopkg.in/square/go-jose.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -50,7 +50,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app.Version("v1")
|
app.Version("v2")
|
||||||
|
|
||||||
command := kingpin.MustParse(app.Parse(os.Args[1:]))
|
command := kingpin.MustParse(app.Parse(os.Args[1:]))
|
||||||
|
|
||||||
|
@ -63,13 +63,13 @@ func main() {
|
||||||
|
|
||||||
switch command {
|
switch command {
|
||||||
case "encrypt":
|
case "encrypt":
|
||||||
pub, err := jose.LoadPublicKey(keyBytes)
|
pub, err := LoadPublicKey(keyBytes)
|
||||||
exitOnError(err, "unable to read public key")
|
exitOnError(err, "unable to read public key")
|
||||||
|
|
||||||
alg := jose.KeyAlgorithm(*algFlag)
|
alg := jose.KeyAlgorithm(*algFlag)
|
||||||
enc := jose.ContentEncryption(*encFlag)
|
enc := jose.ContentEncryption(*encFlag)
|
||||||
|
|
||||||
crypter, err := jose.NewEncrypter(alg, enc, pub)
|
crypter, err := jose.NewEncrypter(enc, jose.Recipient{Algorithm: alg, Key: pub}, nil)
|
||||||
exitOnError(err, "unable to instantiate encrypter")
|
exitOnError(err, "unable to instantiate encrypter")
|
||||||
|
|
||||||
obj, err := crypter.Encrypt(readInput(*inFile))
|
obj, err := crypter.Encrypt(readInput(*inFile))
|
||||||
|
@ -85,7 +85,7 @@ func main() {
|
||||||
|
|
||||||
writeOutput(*outFile, []byte(msg))
|
writeOutput(*outFile, []byte(msg))
|
||||||
case "decrypt":
|
case "decrypt":
|
||||||
priv, err := jose.LoadPrivateKey(keyBytes)
|
priv, err := LoadPrivateKey(keyBytes)
|
||||||
exitOnError(err, "unable to read private key")
|
exitOnError(err, "unable to read private key")
|
||||||
|
|
||||||
obj, err := jose.ParseEncrypted(string(readInput(*inFile)))
|
obj, err := jose.ParseEncrypted(string(readInput(*inFile)))
|
||||||
|
@ -96,11 +96,11 @@ func main() {
|
||||||
|
|
||||||
writeOutput(*outFile, plaintext)
|
writeOutput(*outFile, plaintext)
|
||||||
case "sign":
|
case "sign":
|
||||||
signingKey, err := jose.LoadPrivateKey(keyBytes)
|
signingKey, err := LoadPrivateKey(keyBytes)
|
||||||
exitOnError(err, "unable to read private key")
|
exitOnError(err, "unable to read private key")
|
||||||
|
|
||||||
alg := jose.SignatureAlgorithm(*sigAlgFlag)
|
alg := jose.SignatureAlgorithm(*sigAlgFlag)
|
||||||
signer, err := jose.NewSigner(alg, signingKey)
|
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: alg, Key: signingKey}, nil)
|
||||||
exitOnError(err, "unable to make signer")
|
exitOnError(err, "unable to make signer")
|
||||||
|
|
||||||
obj, err := signer.Sign(readInput(*inFile))
|
obj, err := signer.Sign(readInput(*inFile))
|
||||||
|
@ -116,8 +116,8 @@ func main() {
|
||||||
|
|
||||||
writeOutput(*outFile, []byte(msg))
|
writeOutput(*outFile, []byte(msg))
|
||||||
case "verify":
|
case "verify":
|
||||||
verificationKey, err := jose.LoadPublicKey(keyBytes)
|
verificationKey, err := LoadPublicKey(keyBytes)
|
||||||
exitOnError(err, "unable to read private key")
|
exitOnError(err, "unable to read public key")
|
||||||
|
|
||||||
obj, err := jose.ParseSigned(string(readInput(*inFile)))
|
obj, err := jose.ParseSigned(string(readInput(*inFile)))
|
||||||
exitOnError(err, "unable to parse message")
|
exitOnError(err, "unable to parse message")
|
||||||
|
@ -133,13 +133,13 @@ func main() {
|
||||||
var err error
|
var err error
|
||||||
switch *formatFlag {
|
switch *formatFlag {
|
||||||
case "", "JWE":
|
case "", "JWE":
|
||||||
var jwe *jose.JsonWebEncryption
|
var jwe *jose.JSONWebEncryption
|
||||||
jwe, err = jose.ParseEncrypted(input)
|
jwe, err = jose.ParseEncrypted(input)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
serialized = jwe.FullSerialize()
|
serialized = jwe.FullSerialize()
|
||||||
}
|
}
|
||||||
case "JWS":
|
case "JWS":
|
||||||
var jws *jose.JsonWebSignature
|
var jws *jose.JSONWebSignature
|
||||||
jws, err = jose.ParseSigned(input)
|
jws, err = jose.ParseSigned(input)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
serialized = jws.FullSerialize()
|
serialized = jws.FullSerialize()
|
|
@ -14,15 +14,32 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package jose
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadPublicKey loads a public key from PEM/DER-encoded data.
|
func LoadJSONWebKey(json []byte, pub bool) (*jose.JSONWebKey, error) {
|
||||||
|
var jwk jose.JSONWebKey
|
||||||
|
err := jwk.UnmarshalJSON(json)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !jwk.Valid() {
|
||||||
|
return nil, errors.New("invalid JWK key")
|
||||||
|
}
|
||||||
|
if jwk.IsPublic() != pub {
|
||||||
|
return nil, errors.New("priv/pub JWK key mismatch")
|
||||||
|
}
|
||||||
|
return &jwk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadPublicKey loads a public key from PEM/DER/JWK-encoded data.
|
||||||
func LoadPublicKey(data []byte) (interface{}, error) {
|
func LoadPublicKey(data []byte) (interface{}, error) {
|
||||||
input := data
|
input := data
|
||||||
|
|
||||||
|
@ -42,10 +59,15 @@ func LoadPublicKey(data []byte) (interface{}, error) {
|
||||||
return cert.PublicKey, nil
|
return cert.PublicKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("square/go-jose: parse error, got '%s' and '%s'", err0, err1)
|
jwk, err2 := LoadJSONWebKey(data, true)
|
||||||
|
if err2 == nil {
|
||||||
|
return jwk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("square/go-jose: parse error, got '%s', '%s' and '%s'", err0, err1, err2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadPrivateKey loads a private key from PEM/DER-encoded data.
|
// LoadPrivateKey loads a private key from PEM/DER/JWK-encoded data.
|
||||||
func LoadPrivateKey(data []byte) (interface{}, error) {
|
func LoadPrivateKey(data []byte) (interface{}, error) {
|
||||||
input := data
|
input := data
|
||||||
|
|
||||||
|
@ -70,5 +92,10 @@ func LoadPrivateKey(data []byte) (interface{}, error) {
|
||||||
return priv, nil
|
return priv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("square/go-jose: parse error, got '%s', '%s' and '%s'", err0, err1, err2)
|
jwk, err3 := LoadJSONWebKey(input, false)
|
||||||
|
if err3 == nil {
|
||||||
|
return jwk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("square/go-jose: parse error, got '%s', '%s', '%s' and '%s'", err0, err1, err2, err3)
|
||||||
}
|
}
|
95
vendor/gopkg.in/square/go-jose.v1/jwe.go → vendor/gopkg.in/square/go-jose.v2/jwe.go
generated
vendored
95
vendor/gopkg.in/square/go-jose.v1/jwe.go → vendor/gopkg.in/square/go-jose.v2/jwe.go
generated
vendored
|
@ -17,14 +17,14 @@
|
||||||
package jose
|
package jose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v1/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// rawJsonWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
|
// rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
|
||||||
type rawJsonWebEncryption struct {
|
type rawJSONWebEncryption struct {
|
||||||
Protected *byteBuffer `json:"protected,omitempty"`
|
Protected *byteBuffer `json:"protected,omitempty"`
|
||||||
Unprotected *rawHeader `json:"unprotected,omitempty"`
|
Unprotected *rawHeader `json:"unprotected,omitempty"`
|
||||||
Header *rawHeader `json:"header,omitempty"`
|
Header *rawHeader `json:"header,omitempty"`
|
||||||
|
@ -42,13 +42,13 @@ type rawRecipientInfo struct {
|
||||||
EncryptedKey string `json:"encrypted_key,omitempty"`
|
EncryptedKey string `json:"encrypted_key,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JsonWebEncryption represents an encrypted JWE object after parsing.
|
// JSONWebEncryption represents an encrypted JWE object after parsing.
|
||||||
type JsonWebEncryption struct {
|
type JSONWebEncryption struct {
|
||||||
Header JoseHeader
|
Header Header
|
||||||
protected, unprotected *rawHeader
|
protected, unprotected *rawHeader
|
||||||
recipients []recipientInfo
|
recipients []recipientInfo
|
||||||
aad, iv, ciphertext, tag []byte
|
aad, iv, ciphertext, tag []byte
|
||||||
original *rawJsonWebEncryption
|
original *rawJSONWebEncryption
|
||||||
}
|
}
|
||||||
|
|
||||||
// recipientInfo represents a raw JWE Per-Recipient header JSON object after parsing.
|
// recipientInfo represents a raw JWE Per-Recipient header JSON object after parsing.
|
||||||
|
@ -58,7 +58,7 @@ type recipientInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthData retrieves the (optional) authenticated data attached to the object.
|
// GetAuthData retrieves the (optional) authenticated data attached to the object.
|
||||||
func (obj JsonWebEncryption) GetAuthData() []byte {
|
func (obj JSONWebEncryption) GetAuthData() []byte {
|
||||||
if obj.aad != nil {
|
if obj.aad != nil {
|
||||||
out := make([]byte, len(obj.aad))
|
out := make([]byte, len(obj.aad))
|
||||||
copy(out, obj.aad)
|
copy(out, obj.aad)
|
||||||
|
@ -69,7 +69,7 @@ func (obj JsonWebEncryption) GetAuthData() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the merged header values
|
// Get the merged header values
|
||||||
func (obj JsonWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
|
func (obj JSONWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
|
||||||
out := rawHeader{}
|
out := rawHeader{}
|
||||||
out.merge(obj.protected)
|
out.merge(obj.protected)
|
||||||
out.merge(obj.unprotected)
|
out.merge(obj.unprotected)
|
||||||
|
@ -82,26 +82,26 @@ func (obj JsonWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the additional authenticated data from a JWE object.
|
// Get the additional authenticated data from a JWE object.
|
||||||
func (obj JsonWebEncryption) computeAuthData() []byte {
|
func (obj JSONWebEncryption) computeAuthData() []byte {
|
||||||
var protected string
|
var protected string
|
||||||
|
|
||||||
if obj.original != nil {
|
if obj.original != nil {
|
||||||
protected = obj.original.Protected.base64()
|
protected = obj.original.Protected.base64()
|
||||||
} else {
|
} else {
|
||||||
protected = base64URLEncode(mustSerializeJSON((obj.protected)))
|
protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected)))
|
||||||
}
|
}
|
||||||
|
|
||||||
output := []byte(protected)
|
output := []byte(protected)
|
||||||
if obj.aad != nil {
|
if obj.aad != nil {
|
||||||
output = append(output, '.')
|
output = append(output, '.')
|
||||||
output = append(output, []byte(base64URLEncode(obj.aad))...)
|
output = append(output, []byte(base64.RawURLEncoding.EncodeToString(obj.aad))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseEncrypted parses an encrypted message in compact or full serialization format.
|
// ParseEncrypted parses an encrypted message in compact or full serialization format.
|
||||||
func ParseEncrypted(input string) (*JsonWebEncryption, error) {
|
func ParseEncrypted(input string) (*JSONWebEncryption, error) {
|
||||||
input = stripWhitespace(input)
|
input = stripWhitespace(input)
|
||||||
if strings.HasPrefix(input, "{") {
|
if strings.HasPrefix(input, "{") {
|
||||||
return parseEncryptedFull(input)
|
return parseEncryptedFull(input)
|
||||||
|
@ -111,8 +111,8 @@ func ParseEncrypted(input string) (*JsonWebEncryption, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseEncryptedFull parses a message in compact format.
|
// parseEncryptedFull parses a message in compact format.
|
||||||
func parseEncryptedFull(input string) (*JsonWebEncryption, error) {
|
func parseEncryptedFull(input string) (*JSONWebEncryption, error) {
|
||||||
var parsed rawJsonWebEncryption
|
var parsed rawJSONWebEncryption
|
||||||
err := json.Unmarshal([]byte(input), &parsed)
|
err := json.Unmarshal([]byte(input), &parsed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -122,16 +122,22 @@ func parseEncryptedFull(input string) (*JsonWebEncryption, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanitized produces a cleaned-up JWE object from the raw JSON.
|
// sanitized produces a cleaned-up JWE object from the raw JSON.
|
||||||
func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) {
|
func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
|
||||||
obj := &JsonWebEncryption{
|
obj := &JSONWebEncryption{
|
||||||
original: parsed,
|
original: parsed,
|
||||||
unprotected: parsed.Unprotected,
|
unprotected: parsed.Unprotected,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that there is not a nonce in the unprotected headers
|
// Check that there is not a nonce in the unprotected headers
|
||||||
if (parsed.Unprotected != nil && parsed.Unprotected.Nonce != "") ||
|
if parsed.Unprotected != nil {
|
||||||
(parsed.Header != nil && parsed.Header.Nonce != "") {
|
if nonce := parsed.Unprotected.getNonce(); nonce != "" {
|
||||||
return nil, ErrUnprotectedNonce
|
return nil, ErrUnprotectedNonce
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if parsed.Header != nil {
|
||||||
|
if nonce := parsed.Header.getNonce(); nonce != "" {
|
||||||
|
return nil, ErrUnprotectedNonce
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
|
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
|
||||||
|
@ -143,11 +149,16 @@ func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) {
|
||||||
|
|
||||||
// Note: this must be called _after_ we parse the protected header,
|
// Note: this must be called _after_ we parse the protected header,
|
||||||
// otherwise fields from the protected header will not get picked up.
|
// otherwise fields from the protected header will not get picked up.
|
||||||
obj.Header = obj.mergedHeaders(nil).sanitized()
|
var err error
|
||||||
|
mergedHeaders := obj.mergedHeaders(nil)
|
||||||
|
obj.Header, err = mergedHeaders.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: cannot sanitize merged headers: %v (%v)", err, mergedHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
if len(parsed.Recipients) == 0 {
|
if len(parsed.Recipients) == 0 {
|
||||||
obj.recipients = []recipientInfo{
|
obj.recipients = []recipientInfo{
|
||||||
recipientInfo{
|
{
|
||||||
header: parsed.Header,
|
header: parsed.Header,
|
||||||
encryptedKey: parsed.EncryptedKey.bytes(),
|
encryptedKey: parsed.EncryptedKey.bytes(),
|
||||||
},
|
},
|
||||||
|
@ -155,13 +166,13 @@ func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) {
|
||||||
} else {
|
} else {
|
||||||
obj.recipients = make([]recipientInfo, len(parsed.Recipients))
|
obj.recipients = make([]recipientInfo, len(parsed.Recipients))
|
||||||
for r := range parsed.Recipients {
|
for r := range parsed.Recipients {
|
||||||
encryptedKey, err := base64URLDecode(parsed.Recipients[r].EncryptedKey)
|
encryptedKey, err := base64.RawURLEncoding.DecodeString(parsed.Recipients[r].EncryptedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that there is not a nonce in the unprotected header
|
// Check that there is not a nonce in the unprotected header
|
||||||
if parsed.Recipients[r].Header != nil && parsed.Recipients[r].Header.Nonce != "" {
|
if parsed.Recipients[r].Header != nil && parsed.Recipients[r].Header.getNonce() != "" {
|
||||||
return nil, ErrUnprotectedNonce
|
return nil, ErrUnprotectedNonce
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +183,7 @@ func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) {
|
||||||
|
|
||||||
for _, recipient := range obj.recipients {
|
for _, recipient := range obj.recipients {
|
||||||
headers := obj.mergedHeaders(&recipient)
|
headers := obj.mergedHeaders(&recipient)
|
||||||
if headers.Alg == "" || headers.Enc == "" {
|
if headers.getAlgorithm() == "" || headers.getEncryption() == "" {
|
||||||
return nil, fmt.Errorf("square/go-jose: message is missing alg/enc headers")
|
return nil, fmt.Errorf("square/go-jose: message is missing alg/enc headers")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,38 +197,38 @@ func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseEncryptedCompact parses a message in compact format.
|
// parseEncryptedCompact parses a message in compact format.
|
||||||
func parseEncryptedCompact(input string) (*JsonWebEncryption, error) {
|
func parseEncryptedCompact(input string) (*JSONWebEncryption, error) {
|
||||||
parts := strings.Split(input, ".")
|
parts := strings.Split(input, ".")
|
||||||
if len(parts) != 5 {
|
if len(parts) != 5 {
|
||||||
return nil, fmt.Errorf("square/go-jose: compact JWE format must have five parts")
|
return nil, fmt.Errorf("square/go-jose: compact JWE format must have five parts")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawProtected, err := base64URLDecode(parts[0])
|
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedKey, err := base64URLDecode(parts[1])
|
encryptedKey, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
iv, err := base64URLDecode(parts[2])
|
iv, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ciphertext, err := base64URLDecode(parts[3])
|
ciphertext, err := base64.RawURLEncoding.DecodeString(parts[3])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tag, err := base64URLDecode(parts[4])
|
tag, err := base64.RawURLEncoding.DecodeString(parts[4])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
raw := &rawJsonWebEncryption{
|
raw := &rawJSONWebEncryption{
|
||||||
Protected: newBuffer(rawProtected),
|
Protected: newBuffer(rawProtected),
|
||||||
EncryptedKey: newBuffer(encryptedKey),
|
EncryptedKey: newBuffer(encryptedKey),
|
||||||
Iv: newBuffer(iv),
|
Iv: newBuffer(iv),
|
||||||
|
@ -229,7 +240,7 @@ func parseEncryptedCompact(input string) (*JsonWebEncryption, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompactSerialize serializes an object using the compact serialization format.
|
// CompactSerialize serializes an object using the compact serialization format.
|
||||||
func (obj JsonWebEncryption) CompactSerialize() (string, error) {
|
func (obj JSONWebEncryption) CompactSerialize() (string, error) {
|
||||||
if len(obj.recipients) != 1 || obj.unprotected != nil ||
|
if len(obj.recipients) != 1 || obj.unprotected != nil ||
|
||||||
obj.protected == nil || obj.recipients[0].header != nil {
|
obj.protected == nil || obj.recipients[0].header != nil {
|
||||||
return "", ErrNotSupported
|
return "", ErrNotSupported
|
||||||
|
@ -239,16 +250,16 @@ func (obj JsonWebEncryption) CompactSerialize() (string, error) {
|
||||||
|
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"%s.%s.%s.%s.%s",
|
"%s.%s.%s.%s.%s",
|
||||||
base64URLEncode(serializedProtected),
|
base64.RawURLEncoding.EncodeToString(serializedProtected),
|
||||||
base64URLEncode(obj.recipients[0].encryptedKey),
|
base64.RawURLEncoding.EncodeToString(obj.recipients[0].encryptedKey),
|
||||||
base64URLEncode(obj.iv),
|
base64.RawURLEncoding.EncodeToString(obj.iv),
|
||||||
base64URLEncode(obj.ciphertext),
|
base64.RawURLEncoding.EncodeToString(obj.ciphertext),
|
||||||
base64URLEncode(obj.tag)), nil
|
base64.RawURLEncoding.EncodeToString(obj.tag)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FullSerialize serializes an object using the full JSON serialization format.
|
// FullSerialize serializes an object using the full JSON serialization format.
|
||||||
func (obj JsonWebEncryption) FullSerialize() string {
|
func (obj JSONWebEncryption) FullSerialize() string {
|
||||||
raw := rawJsonWebEncryption{
|
raw := rawJSONWebEncryption{
|
||||||
Unprotected: obj.unprotected,
|
Unprotected: obj.unprotected,
|
||||||
Iv: newBuffer(obj.iv),
|
Iv: newBuffer(obj.iv),
|
||||||
Ciphertext: newBuffer(obj.ciphertext),
|
Ciphertext: newBuffer(obj.ciphertext),
|
||||||
|
@ -262,7 +273,7 @@ func (obj JsonWebEncryption) FullSerialize() string {
|
||||||
for _, recipient := range obj.recipients {
|
for _, recipient := range obj.recipients {
|
||||||
info := rawRecipientInfo{
|
info := rawRecipientInfo{
|
||||||
Header: recipient.header,
|
Header: recipient.header,
|
||||||
EncryptedKey: base64URLEncode(recipient.encryptedKey),
|
EncryptedKey: base64.RawURLEncoding.EncodeToString(recipient.encryptedKey),
|
||||||
}
|
}
|
||||||
raw.Recipients = append(raw.Recipients, info)
|
raw.Recipients = append(raw.Recipients, info)
|
||||||
}
|
}
|
200
vendor/gopkg.in/square/go-jose.v2/jwk-keygen/main.go
generated
vendored
Normal file
200
vendor/gopkg.in/square/go-jose.v2/jwk-keygen/main.go
generated
vendored
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2017 Square Inc.
|
||||||
|
*
|
||||||
|
* 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base32"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
app = kingpin.New("jwk-keygen", "A command-line utility to generate public/pirvate keypairs in JWK format.")
|
||||||
|
|
||||||
|
use = app.Flag("use", "Desrired key use").Required().Enum("enc", "sig")
|
||||||
|
alg = app.Flag("alg", "Generate key to be used for ALG").Required().Enum(
|
||||||
|
// `sig`
|
||||||
|
string(jose.ES256), string(jose.ES384), string(jose.ES512), string(jose.EdDSA),
|
||||||
|
string(jose.RS256), string(jose.RS384), string(jose.RS512), string(jose.PS256), string(jose.PS384), string(jose.PS512),
|
||||||
|
// `enc`
|
||||||
|
string(jose.RSA1_5), string(jose.RSA_OAEP), string(jose.RSA_OAEP_256),
|
||||||
|
string(jose.ECDH_ES), string(jose.ECDH_ES_A128KW), string(jose.ECDH_ES_A192KW), string(jose.ECDH_ES_A256KW),
|
||||||
|
)
|
||||||
|
bits = app.Flag("bits", "Key size in bits").Int()
|
||||||
|
kid = app.Flag("kid", "Key ID").String()
|
||||||
|
kidRand = app.Flag("kid-rand", "Generate random Key ID").Bool()
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeygenSig generates keypair for corresponding SignatureAlgorithm.
|
||||||
|
func KeygenSig(alg jose.SignatureAlgorithm, bits int) (crypto.PublicKey, crypto.PrivateKey, error) {
|
||||||
|
switch alg {
|
||||||
|
case jose.ES256, jose.ES384, jose.ES512, jose.EdDSA:
|
||||||
|
keylen := map[jose.SignatureAlgorithm]int{
|
||||||
|
jose.ES256: 256,
|
||||||
|
jose.ES384: 384,
|
||||||
|
jose.ES512: 521, // sic!
|
||||||
|
jose.EdDSA: 256,
|
||||||
|
}
|
||||||
|
if bits != 0 && bits != keylen[alg] {
|
||||||
|
return nil, nil, errors.New("this `alg` does not support arbitrary key length")
|
||||||
|
}
|
||||||
|
case jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512:
|
||||||
|
if bits == 0 {
|
||||||
|
bits = 2048
|
||||||
|
}
|
||||||
|
if bits < 2048 {
|
||||||
|
return nil, nil, errors.New("too short key for RSA `alg`, 2048+ is required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch alg {
|
||||||
|
case jose.ES256:
|
||||||
|
// The cryptographic operations are implemented using constant-time algorithms.
|
||||||
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
return key.Public(), key, err
|
||||||
|
case jose.ES384:
|
||||||
|
// NB: The cryptographic operations do not use constant-time algorithms.
|
||||||
|
key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||||
|
return key.Public(), key, err
|
||||||
|
case jose.ES512:
|
||||||
|
// NB: The cryptographic operations do not use constant-time algorithms.
|
||||||
|
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||||
|
return key.Public(), key, err
|
||||||
|
case jose.EdDSA:
|
||||||
|
pub, key, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
return pub, key, err
|
||||||
|
case jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512:
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, bits)
|
||||||
|
return key.Public(), key, err
|
||||||
|
default:
|
||||||
|
return nil, nil, errors.New("unknown `alg` for `use` = `sig`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeygenEnc generates keypair for corresponding KeyAlgorithm.
|
||||||
|
func KeygenEnc(alg jose.KeyAlgorithm, bits int) (crypto.PublicKey, crypto.PrivateKey, error) {
|
||||||
|
switch alg {
|
||||||
|
case jose.RSA1_5, jose.RSA_OAEP, jose.RSA_OAEP_256:
|
||||||
|
if bits == 0 {
|
||||||
|
bits = 2048
|
||||||
|
}
|
||||||
|
if bits < 2048 {
|
||||||
|
return nil, nil, errors.New("too short key for RSA `alg`, 2048+ is required")
|
||||||
|
}
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, bits)
|
||||||
|
return key.Public(), key, err
|
||||||
|
case jose.ECDH_ES, jose.ECDH_ES_A128KW, jose.ECDH_ES_A192KW, jose.ECDH_ES_A256KW:
|
||||||
|
var crv elliptic.Curve
|
||||||
|
switch bits {
|
||||||
|
case 0, 256:
|
||||||
|
crv = elliptic.P256()
|
||||||
|
case 384:
|
||||||
|
crv = elliptic.P384()
|
||||||
|
case 521:
|
||||||
|
crv = elliptic.P521()
|
||||||
|
default:
|
||||||
|
return nil, nil, errors.New("unknown elliptic curve bit length, use one of 256, 384, 521")
|
||||||
|
}
|
||||||
|
key, err := ecdsa.GenerateKey(crv, rand.Reader)
|
||||||
|
return key.Public(), key, err
|
||||||
|
default:
|
||||||
|
return nil, nil, errors.New("unknown `alg` for `use` = `enc`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app.Version("v2")
|
||||||
|
kingpin.MustParse(app.Parse(os.Args[1:]))
|
||||||
|
|
||||||
|
if *kidRand {
|
||||||
|
if *kid == "" {
|
||||||
|
b := make([]byte, 5)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
app.FatalIfError(err, "can't Read() crypto/rand")
|
||||||
|
*kid = base32.StdEncoding.EncodeToString(b)
|
||||||
|
} else {
|
||||||
|
app.FatalUsage("can't combine --kid and --kid-rand")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var privKey crypto.PublicKey
|
||||||
|
var pubKey crypto.PrivateKey
|
||||||
|
var err error
|
||||||
|
switch *use {
|
||||||
|
case "sig":
|
||||||
|
pubKey, privKey, err = KeygenSig(jose.SignatureAlgorithm(*alg), *bits)
|
||||||
|
case "enc":
|
||||||
|
pubKey, privKey, err = KeygenEnc(jose.KeyAlgorithm(*alg), *bits)
|
||||||
|
}
|
||||||
|
app.FatalIfError(err, "unable to generate key")
|
||||||
|
|
||||||
|
priv := jose.JSONWebKey{Key: privKey, KeyID: *kid, Algorithm: *alg, Use: *use}
|
||||||
|
pub := jose.JSONWebKey{Key: pubKey, KeyID: *kid, Algorithm: *alg, Use: *use}
|
||||||
|
|
||||||
|
if priv.IsPublic() || !pub.IsPublic() || !priv.Valid() || !pub.Valid() {
|
||||||
|
app.Fatalf("invalid keys were generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
privJS, err := priv.MarshalJSON()
|
||||||
|
app.FatalIfError(err, "can't Marshal private key to JSON")
|
||||||
|
pubJS, err := pub.MarshalJSON()
|
||||||
|
app.FatalIfError(err, "can't Marshal public key to JSON")
|
||||||
|
|
||||||
|
if *kid == "" {
|
||||||
|
fmt.Printf("==> jwk_%s.pub <==\n", *alg)
|
||||||
|
fmt.Println(string(pubJS))
|
||||||
|
fmt.Printf("==> jwk_%s <==\n", *alg)
|
||||||
|
fmt.Println(string(privJS))
|
||||||
|
} else {
|
||||||
|
// JWK Thumbprint (RFC7638) is not used for key id because of
|
||||||
|
// lack of canonical representation.
|
||||||
|
fname := fmt.Sprintf("jwk_%s_%s_%s", *use, *alg, *kid)
|
||||||
|
err = writeNewFile(fname+".pub", pubJS, 0444)
|
||||||
|
app.FatalIfError(err, "can't write public key to file %s.pub", fname)
|
||||||
|
fmt.Printf("Written public key to %s.pub\n", fname)
|
||||||
|
err = writeNewFile(fname, privJS, 0400)
|
||||||
|
app.FatalIfError(err, "cant' write private key to file %s", fname)
|
||||||
|
fmt.Printf("Written private key to %s\n", fname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeNewFile is shameless copy-paste from ioutil.WriteFile with a bit
|
||||||
|
// different flags for OpenFile.
|
||||||
|
func writeNewFile(filename string, data []byte, perm os.FileMode) error {
|
||||||
|
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n, err := f.Write(data)
|
||||||
|
if err == nil && n < len(data) {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
}
|
||||||
|
if err1 := f.Close(); err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
165
vendor/gopkg.in/square/go-jose.v1/jwk.go → vendor/gopkg.in/square/go-jose.v2/jwk.go
generated
vendored
165
vendor/gopkg.in/square/go-jose.v1/jwk.go → vendor/gopkg.in/square/go-jose.v2/jwk.go
generated
vendored
|
@ -29,11 +29,13 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v1/json"
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// rawJsonWebKey represents a public or private key in JWK format, used for parsing/serializing.
|
// rawJSONWebKey represents a public or private key in JWK format, used for parsing/serializing.
|
||||||
type rawJsonWebKey struct {
|
type rawJSONWebKey struct {
|
||||||
Use string `json:"use,omitempty"`
|
Use string `json:"use,omitempty"`
|
||||||
Kty string `json:"kty,omitempty"`
|
Kty string `json:"kty,omitempty"`
|
||||||
Kid string `json:"kid,omitempty"`
|
Kid string `json:"kid,omitempty"`
|
||||||
|
@ -58,8 +60,8 @@ type rawJsonWebKey struct {
|
||||||
X5c []string `json:"x5c,omitempty"`
|
X5c []string `json:"x5c,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JsonWebKey represents a public or private key in JWK format.
|
// JSONWebKey represents a public or private key in JWK format.
|
||||||
type JsonWebKey struct {
|
type JSONWebKey struct {
|
||||||
Key interface{}
|
Key interface{}
|
||||||
Certificates []*x509.Certificate
|
Certificates []*x509.Certificate
|
||||||
KeyID string
|
KeyID string
|
||||||
|
@ -68,15 +70,19 @@ type JsonWebKey struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON serializes the given key to its JSON representation.
|
// MarshalJSON serializes the given key to its JSON representation.
|
||||||
func (k JsonWebKey) MarshalJSON() ([]byte, error) {
|
func (k JSONWebKey) MarshalJSON() ([]byte, error) {
|
||||||
var raw *rawJsonWebKey
|
var raw *rawJSONWebKey
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch key := k.Key.(type) {
|
switch key := k.Key.(type) {
|
||||||
|
case ed25519.PublicKey:
|
||||||
|
raw = fromEdPublicKey(key)
|
||||||
case *ecdsa.PublicKey:
|
case *ecdsa.PublicKey:
|
||||||
raw, err = fromEcPublicKey(key)
|
raw, err = fromEcPublicKey(key)
|
||||||
case *rsa.PublicKey:
|
case *rsa.PublicKey:
|
||||||
raw = fromRsaPublicKey(key)
|
raw = fromRsaPublicKey(key)
|
||||||
|
case ed25519.PrivateKey:
|
||||||
|
raw, err = fromEdPrivateKey(key)
|
||||||
case *ecdsa.PrivateKey:
|
case *ecdsa.PrivateKey:
|
||||||
raw, err = fromEcPrivateKey(key)
|
raw, err = fromEcPrivateKey(key)
|
||||||
case *rsa.PrivateKey:
|
case *rsa.PrivateKey:
|
||||||
|
@ -103,8 +109,8 @@ func (k JsonWebKey) MarshalJSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON reads a key from its JSON representation.
|
// UnmarshalJSON reads a key from its JSON representation.
|
||||||
func (k *JsonWebKey) UnmarshalJSON(data []byte) (err error) {
|
func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
||||||
var raw rawJsonWebKey
|
var raw rawJSONWebKey
|
||||||
err = json.Unmarshal(data, &raw)
|
err = json.Unmarshal(data, &raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -126,12 +132,22 @@ func (k *JsonWebKey) UnmarshalJSON(data []byte) (err error) {
|
||||||
}
|
}
|
||||||
case "oct":
|
case "oct":
|
||||||
key, err = raw.symmetricKey()
|
key, err = raw.symmetricKey()
|
||||||
|
case "OKP":
|
||||||
|
if raw.Crv == "Ed25519" && raw.X != nil {
|
||||||
|
if raw.D != nil {
|
||||||
|
key, err = raw.edPrivateKey()
|
||||||
|
} else {
|
||||||
|
key, err = raw.edPublicKey()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("square/go-jose: unknown curve %s'", raw.Crv)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("square/go-jose: unknown json web key type '%s'", raw.Kty)
|
err = fmt.Errorf("square/go-jose: unknown json web key type '%s'", raw.Kty)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
*k = JsonWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use}
|
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use}
|
||||||
}
|
}
|
||||||
|
|
||||||
k.Certificates = make([]*x509.Certificate, len(raw.X5c))
|
k.Certificates = make([]*x509.Certificate, len(raw.X5c))
|
||||||
|
@ -149,17 +165,17 @@ func (k *JsonWebKey) UnmarshalJSON(data []byte) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// JsonWebKeySet represents a JWK Set object.
|
// JSONWebKeySet represents a JWK Set object.
|
||||||
type JsonWebKeySet struct {
|
type JSONWebKeySet struct {
|
||||||
Keys []JsonWebKey `json:"keys"`
|
Keys []JSONWebKey `json:"keys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key convenience method returns keys by key ID. Specification states
|
// Key convenience method returns keys by key ID. Specification states
|
||||||
// that a JWK Set "SHOULD" use distinct key IDs, but allows for some
|
// that a JWK Set "SHOULD" use distinct key IDs, but allows for some
|
||||||
// cases where they are not distinct. Hence method returns a slice
|
// cases where they are not distinct. Hence method returns a slice
|
||||||
// of JsonWebKeys.
|
// of JSONWebKeys.
|
||||||
func (s *JsonWebKeySet) Key(kid string) []JsonWebKey {
|
func (s *JSONWebKeySet) Key(kid string) []JSONWebKey {
|
||||||
var keys []JsonWebKey
|
var keys []JSONWebKey
|
||||||
for _, key := range s.Keys {
|
for _, key := range s.Keys {
|
||||||
if key.KeyID == kid {
|
if key.KeyID == kid {
|
||||||
keys = append(keys, key)
|
keys = append(keys, key)
|
||||||
|
@ -171,6 +187,7 @@ func (s *JsonWebKeySet) Key(kid string) []JsonWebKey {
|
||||||
|
|
||||||
const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
|
const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
|
||||||
const ecThumbprintTemplate = `{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`
|
const ecThumbprintTemplate = `{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`
|
||||||
|
const edThumbprintTemplate = `{"crv":"%s","kty":"OKP",x":"%s"}`
|
||||||
|
|
||||||
func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
|
func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
|
||||||
coordLength := curveSize(curve)
|
coordLength := curveSize(curve)
|
||||||
|
@ -190,12 +207,20 @@ func rsaThumbprintInput(n *big.Int, e int) (string, error) {
|
||||||
newBuffer(n.Bytes()).base64()), nil
|
newBuffer(n.Bytes()).base64()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func edThumbprintInput(ed ed25519.PublicKey) (string, error) {
|
||||||
|
crv := "Ed25519"
|
||||||
|
return fmt.Sprintf(edThumbprintTemplate, crv,
|
||||||
|
newFixedSizeBuffer(ed, 32).base64()), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Thumbprint computes the JWK Thumbprint of a key using the
|
// Thumbprint computes the JWK Thumbprint of a key using the
|
||||||
// indicated hash algorithm.
|
// indicated hash algorithm.
|
||||||
func (k *JsonWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
|
func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
|
||||||
var input string
|
var input string
|
||||||
var err error
|
var err error
|
||||||
switch key := k.Key.(type) {
|
switch key := k.Key.(type) {
|
||||||
|
case ed25519.PublicKey:
|
||||||
|
input, err = edThumbprintInput(key)
|
||||||
case *ecdsa.PublicKey:
|
case *ecdsa.PublicKey:
|
||||||
input, err = ecThumbprintInput(key.Curve, key.X, key.Y)
|
input, err = ecThumbprintInput(key.Curve, key.X, key.Y)
|
||||||
case *ecdsa.PrivateKey:
|
case *ecdsa.PrivateKey:
|
||||||
|
@ -204,6 +229,8 @@ func (k *JsonWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
|
||||||
input, err = rsaThumbprintInput(key.N, key.E)
|
input, err = rsaThumbprintInput(key.N, key.E)
|
||||||
case *rsa.PrivateKey:
|
case *rsa.PrivateKey:
|
||||||
input, err = rsaThumbprintInput(key.N, key.E)
|
input, err = rsaThumbprintInput(key.N, key.E)
|
||||||
|
case ed25519.PrivateKey:
|
||||||
|
input, err = edThumbprintInput(ed25519.PublicKey(key[0:32]))
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("square/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
return nil, fmt.Errorf("square/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
||||||
}
|
}
|
||||||
|
@ -218,17 +245,36 @@ func (k *JsonWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPublic returns true if the JWK represents a public key (not symmetric, not private).
|
// IsPublic returns true if the JWK represents a public key (not symmetric, not private).
|
||||||
func (k *JsonWebKey) IsPublic() bool {
|
func (k *JSONWebKey) IsPublic() bool {
|
||||||
switch k.Key.(type) {
|
switch k.Key.(type) {
|
||||||
case *ecdsa.PublicKey, *rsa.PublicKey:
|
case *ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Public creates JSONWebKey with corresponding publik key if JWK represents asymmetric private key.
|
||||||
|
func (k *JSONWebKey) Public() JSONWebKey {
|
||||||
|
if k.IsPublic() {
|
||||||
|
return *k
|
||||||
|
}
|
||||||
|
ret := *k
|
||||||
|
switch key := k.Key.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
ret.Key = key.Public()
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
ret.Key = key.Public()
|
||||||
|
case ed25519.PrivateKey:
|
||||||
|
ret.Key = key.Public()
|
||||||
|
default:
|
||||||
|
return JSONWebKey{} // returning invalid key
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// Valid checks that the key contains the expected parameters.
|
// Valid checks that the key contains the expected parameters.
|
||||||
func (k *JsonWebKey) Valid() bool {
|
func (k *JSONWebKey) Valid() bool {
|
||||||
if k.Key == nil {
|
if k.Key == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -249,13 +295,21 @@ func (k *JsonWebKey) Valid() bool {
|
||||||
if key.N == nil || key.E == 0 || key.D == nil || len(key.Primes) < 2 {
|
if key.N == nil || key.E == 0 || key.D == nil || len(key.Primes) < 2 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case ed25519.PublicKey:
|
||||||
|
if len(key) != 32 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case ed25519.PrivateKey:
|
||||||
|
if len(key) != 64 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key rawJsonWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
|
func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
|
||||||
if key.N == nil || key.E == nil {
|
if key.N == nil || key.E == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid RSA key, missing n/e values")
|
return nil, fmt.Errorf("square/go-jose: invalid RSA key, missing n/e values")
|
||||||
}
|
}
|
||||||
|
@ -266,15 +320,23 @@ func (key rawJsonWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromRsaPublicKey(pub *rsa.PublicKey) *rawJsonWebKey {
|
func fromEdPublicKey(pub ed25519.PublicKey) *rawJSONWebKey {
|
||||||
return &rawJsonWebKey{
|
return &rawJSONWebKey{
|
||||||
|
Kty: "OKP",
|
||||||
|
Crv: "Ed25519",
|
||||||
|
X: newBuffer(pub),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromRsaPublicKey(pub *rsa.PublicKey) *rawJSONWebKey {
|
||||||
|
return &rawJSONWebKey{
|
||||||
Kty: "RSA",
|
Kty: "RSA",
|
||||||
N: newBuffer(pub.N.Bytes()),
|
N: newBuffer(pub.N.Bytes()),
|
||||||
E: newBufferFromInt(uint64(pub.E)),
|
E: newBufferFromInt(uint64(pub.E)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key rawJsonWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
|
func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
|
||||||
var curve elliptic.Curve
|
var curve elliptic.Curve
|
||||||
switch key.Crv {
|
switch key.Crv {
|
||||||
case "P-256":
|
case "P-256":
|
||||||
|
@ -305,7 +367,7 @@ func (key rawJsonWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJsonWebKey, error) {
|
func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJSONWebKey, error) {
|
||||||
if pub == nil || pub.X == nil || pub.Y == nil {
|
if pub == nil || pub.X == nil || pub.Y == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid EC key (nil, or X/Y missing)")
|
return nil, fmt.Errorf("square/go-jose: invalid EC key (nil, or X/Y missing)")
|
||||||
}
|
}
|
||||||
|
@ -324,7 +386,7 @@ func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJsonWebKey, error) {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid EC key (X/Y too large)")
|
return nil, fmt.Errorf("square/go-jose: invalid EC key (X/Y too large)")
|
||||||
}
|
}
|
||||||
|
|
||||||
key := &rawJsonWebKey{
|
key := &rawJSONWebKey{
|
||||||
Kty: "EC",
|
Kty: "EC",
|
||||||
Crv: name,
|
Crv: name,
|
||||||
X: newFixedSizeBuffer(xBytes, size),
|
X: newFixedSizeBuffer(xBytes, size),
|
||||||
|
@ -334,7 +396,37 @@ func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJsonWebKey, error) {
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key rawJsonWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
|
func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) {
|
||||||
|
var missing []string
|
||||||
|
switch {
|
||||||
|
case key.D == nil:
|
||||||
|
missing = append(missing, "D")
|
||||||
|
case key.X == nil:
|
||||||
|
missing = append(missing, "X")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missing) > 0 {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid Ed25519 private key, missing %s value(s)", strings.Join(missing, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey := make([]byte, ed25519.PrivateKeySize)
|
||||||
|
copy(privateKey[0:32], key.X.bytes())
|
||||||
|
copy(privateKey[32:], key.D.bytes())
|
||||||
|
rv := ed25519.PrivateKey(privateKey)
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key rawJSONWebKey) edPublicKey() (ed25519.PublicKey, error) {
|
||||||
|
if key.X == nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid Ed key, missing x value")
|
||||||
|
}
|
||||||
|
publicKey := make([]byte, ed25519.PublicKeySize)
|
||||||
|
copy(publicKey[0:32], key.X.bytes())
|
||||||
|
rv := ed25519.PublicKey(publicKey)
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
|
||||||
var missing []string
|
var missing []string
|
||||||
switch {
|
switch {
|
||||||
case key.N == nil:
|
case key.N == nil:
|
||||||
|
@ -379,7 +471,14 @@ func (key rawJsonWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
|
||||||
return rv, err
|
return rv, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromRsaPrivateKey(rsa *rsa.PrivateKey) (*rawJsonWebKey, error) {
|
func fromEdPrivateKey(ed ed25519.PrivateKey) (*rawJSONWebKey, error) {
|
||||||
|
raw := fromEdPublicKey(ed25519.PublicKey(ed[0:32]))
|
||||||
|
|
||||||
|
raw.D = newBuffer(ed[32:])
|
||||||
|
return raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromRsaPrivateKey(rsa *rsa.PrivateKey) (*rawJSONWebKey, error) {
|
||||||
if len(rsa.Primes) != 2 {
|
if len(rsa.Primes) != 2 {
|
||||||
return nil, ErrUnsupportedKeyType
|
return nil, ErrUnsupportedKeyType
|
||||||
}
|
}
|
||||||
|
@ -393,7 +492,7 @@ func fromRsaPrivateKey(rsa *rsa.PrivateKey) (*rawJsonWebKey, error) {
|
||||||
return raw, nil
|
return raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key rawJsonWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
|
func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
|
||||||
var curve elliptic.Curve
|
var curve elliptic.Curve
|
||||||
switch key.Crv {
|
switch key.Crv {
|
||||||
case "P-256":
|
case "P-256":
|
||||||
|
@ -427,7 +526,7 @@ func (key rawJsonWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromEcPrivateKey(ec *ecdsa.PrivateKey) (*rawJsonWebKey, error) {
|
func fromEcPrivateKey(ec *ecdsa.PrivateKey) (*rawJSONWebKey, error) {
|
||||||
raw, err := fromEcPublicKey(&ec.PublicKey)
|
raw, err := fromEcPublicKey(&ec.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -442,14 +541,14 @@ func fromEcPrivateKey(ec *ecdsa.PrivateKey) (*rawJsonWebKey, error) {
|
||||||
return raw, nil
|
return raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromSymmetricKey(key []byte) (*rawJsonWebKey, error) {
|
func fromSymmetricKey(key []byte) (*rawJSONWebKey, error) {
|
||||||
return &rawJsonWebKey{
|
return &rawJSONWebKey{
|
||||||
Kty: "oct",
|
Kty: "oct",
|
||||||
K: newBuffer(key),
|
K: newBuffer(key),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key rawJsonWebKey) symmetricKey() ([]byte, error) {
|
func (key rawJSONWebKey) symmetricKey() ([]byte, error) {
|
||||||
if key.K == nil {
|
if key.K == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: invalid OCT (symmetric) key, missing k value")
|
return nil, fmt.Errorf("square/go-jose: invalid OCT (symmetric) key, missing k value")
|
||||||
}
|
}
|
113
vendor/gopkg.in/square/go-jose.v1/jws.go → vendor/gopkg.in/square/go-jose.v2/jws.go
generated
vendored
113
vendor/gopkg.in/square/go-jose.v1/jws.go → vendor/gopkg.in/square/go-jose.v2/jws.go
generated
vendored
|
@ -17,15 +17,16 @@
|
||||||
package jose
|
package jose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v1/json"
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// rawJsonWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
|
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
|
||||||
type rawJsonWebSignature struct {
|
type rawJSONWebSignature struct {
|
||||||
Payload *byteBuffer `json:"payload,omitempty"`
|
Payload *byteBuffer `json:"payload,omitempty"`
|
||||||
Signatures []rawSignatureInfo `json:"signatures,omitempty"`
|
Signatures []rawSignatureInfo `json:"signatures,omitempty"`
|
||||||
Protected *byteBuffer `json:"protected,omitempty"`
|
Protected *byteBuffer `json:"protected,omitempty"`
|
||||||
|
@ -40,8 +41,8 @@ type rawSignatureInfo struct {
|
||||||
Signature *byteBuffer `json:"signature,omitempty"`
|
Signature *byteBuffer `json:"signature,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JsonWebSignature represents a signed JWS object after parsing.
|
// JSONWebSignature represents a signed JWS object after parsing.
|
||||||
type JsonWebSignature struct {
|
type JSONWebSignature struct {
|
||||||
payload []byte
|
payload []byte
|
||||||
// Signatures attached to this object (may be more than one for multi-sig).
|
// Signatures attached to this object (may be more than one for multi-sig).
|
||||||
// Be careful about accessing these directly, prefer to use Verify() or
|
// Be careful about accessing these directly, prefer to use Verify() or
|
||||||
|
@ -51,8 +52,19 @@ type JsonWebSignature struct {
|
||||||
|
|
||||||
// Signature represents a single signature over the JWS payload and protected header.
|
// Signature represents a single signature over the JWS payload and protected header.
|
||||||
type Signature struct {
|
type Signature struct {
|
||||||
// Header fields, such as the signature algorithm
|
// Merged header fields. Contains both protected and unprotected header
|
||||||
Header JoseHeader
|
// values. Prefer using Protected and Unprotected fields instead of this.
|
||||||
|
// Values in this header may or may not have been signed and in general
|
||||||
|
// should not be trusted.
|
||||||
|
Header Header
|
||||||
|
|
||||||
|
// Protected header. Values in this header were signed and
|
||||||
|
// will be verified as part of the signature verification process.
|
||||||
|
Protected Header
|
||||||
|
|
||||||
|
// Unprotected header. Values in this header were not signed
|
||||||
|
// and in general should not be trusted.
|
||||||
|
Unprotected Header
|
||||||
|
|
||||||
// The actual signature value
|
// The actual signature value
|
||||||
Signature []byte
|
Signature []byte
|
||||||
|
@ -63,7 +75,7 @@ type Signature struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSigned parses a signed message in compact or full serialization format.
|
// ParseSigned parses a signed message in compact or full serialization format.
|
||||||
func ParseSigned(input string) (*JsonWebSignature, error) {
|
func ParseSigned(input string) (*JSONWebSignature, error) {
|
||||||
input = stripWhitespace(input)
|
input = stripWhitespace(input)
|
||||||
if strings.HasPrefix(input, "{") {
|
if strings.HasPrefix(input, "{") {
|
||||||
return parseSignedFull(input)
|
return parseSignedFull(input)
|
||||||
|
@ -81,25 +93,25 @@ func (sig Signature) mergedHeaders() rawHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute data to be signed
|
// Compute data to be signed
|
||||||
func (obj JsonWebSignature) computeAuthData(signature *Signature) []byte {
|
func (obj JSONWebSignature) computeAuthData(signature *Signature) []byte {
|
||||||
var serializedProtected string
|
var serializedProtected string
|
||||||
|
|
||||||
if signature.original != nil && signature.original.Protected != nil {
|
if signature.original != nil && signature.original.Protected != nil {
|
||||||
serializedProtected = signature.original.Protected.base64()
|
serializedProtected = signature.original.Protected.base64()
|
||||||
} else if signature.protected != nil {
|
} else if signature.protected != nil {
|
||||||
serializedProtected = base64URLEncode(mustSerializeJSON(signature.protected))
|
serializedProtected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON(signature.protected))
|
||||||
} else {
|
} else {
|
||||||
serializedProtected = ""
|
serializedProtected = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return []byte(fmt.Sprintf("%s.%s",
|
return []byte(fmt.Sprintf("%s.%s",
|
||||||
serializedProtected,
|
serializedProtected,
|
||||||
base64URLEncode(obj.payload)))
|
base64.RawURLEncoding.EncodeToString(obj.payload)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSignedFull parses a message in full format.
|
// parseSignedFull parses a message in full format.
|
||||||
func parseSignedFull(input string) (*JsonWebSignature, error) {
|
func parseSignedFull(input string) (*JSONWebSignature, error) {
|
||||||
var parsed rawJsonWebSignature
|
var parsed rawJSONWebSignature
|
||||||
err := json.Unmarshal([]byte(input), &parsed)
|
err := json.Unmarshal([]byte(input), &parsed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -109,12 +121,12 @@ func parseSignedFull(input string) (*JsonWebSignature, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanitized produces a cleaned-up JWS object from the raw JSON.
|
// sanitized produces a cleaned-up JWS object from the raw JSON.
|
||||||
func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) {
|
func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
|
||||||
if parsed.Payload == nil {
|
if parsed.Payload == nil {
|
||||||
return nil, fmt.Errorf("square/go-jose: missing payload in JWS message")
|
return nil, fmt.Errorf("square/go-jose: missing payload in JWS message")
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := &JsonWebSignature{
|
obj := &JSONWebSignature{
|
||||||
payload: parsed.Payload.bytes(),
|
payload: parsed.Payload.bytes(),
|
||||||
Signatures: make([]Signature, len(parsed.Signatures)),
|
Signatures: make([]Signature, len(parsed.Signatures)),
|
||||||
}
|
}
|
||||||
|
@ -131,7 +143,7 @@ func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that there is not a nonce in the unprotected header
|
// Check that there is not a nonce in the unprotected header
|
||||||
if parsed.Header != nil && parsed.Header.Nonce != "" {
|
if parsed.Header != nil && parsed.Header.getNonce() != "" {
|
||||||
return nil, ErrUnprotectedNonce
|
return nil, ErrUnprotectedNonce
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,10 +164,28 @@ func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) {
|
||||||
Signature: parsed.Signature,
|
Signature: parsed.Signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
signature.Header = signature.mergedHeaders().sanitized()
|
var err error
|
||||||
|
signature.Header, err = signature.mergedHeaders().sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if signature.header != nil {
|
||||||
|
signature.Unprotected, err = signature.header.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if signature.protected != nil {
|
||||||
|
signature.Protected, err = signature.protected.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
||||||
jwk := signature.Header.JsonWebKey
|
jwk := signature.Header.JSONWebKey
|
||||||
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
||||||
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
|
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
|
||||||
}
|
}
|
||||||
|
@ -173,15 +203,34 @@ func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that there is not a nonce in the unprotected header
|
// Check that there is not a nonce in the unprotected header
|
||||||
if sig.Header != nil && sig.Header.Nonce != "" {
|
if sig.Header != nil && sig.Header.getNonce() != "" {
|
||||||
return nil, ErrUnprotectedNonce
|
return nil, ErrUnprotectedNonce
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.Signatures[i].Header = obj.Signatures[i].mergedHeaders().sanitized()
|
var err error
|
||||||
|
obj.Signatures[i].Header, err = obj.Signatures[i].mergedHeaders().sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Signatures[i].header != nil {
|
||||||
|
obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Signatures[i].protected != nil {
|
||||||
|
obj.Signatures[i].Protected, err = obj.Signatures[i].protected.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
obj.Signatures[i].Signature = sig.Signature.bytes()
|
obj.Signatures[i].Signature = sig.Signature.bytes()
|
||||||
|
|
||||||
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
||||||
jwk := obj.Signatures[i].Header.JsonWebKey
|
jwk := obj.Signatures[i].Header.JSONWebKey
|
||||||
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
||||||
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
|
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
|
||||||
}
|
}
|
||||||
|
@ -197,28 +246,28 @@ func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSignedCompact parses a message in compact format.
|
// parseSignedCompact parses a message in compact format.
|
||||||
func parseSignedCompact(input string) (*JsonWebSignature, error) {
|
func parseSignedCompact(input string) (*JSONWebSignature, error) {
|
||||||
parts := strings.Split(input, ".")
|
parts := strings.Split(input, ".")
|
||||||
if len(parts) != 3 {
|
if len(parts) != 3 {
|
||||||
return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts")
|
return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawProtected, err := base64URLDecode(parts[0])
|
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
payload, err := base64URLDecode(parts[1])
|
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
signature, err := base64URLDecode(parts[2])
|
signature, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
raw := &rawJsonWebSignature{
|
raw := &rawJSONWebSignature{
|
||||||
Payload: newBuffer(payload),
|
Payload: newBuffer(payload),
|
||||||
Protected: newBuffer(rawProtected),
|
Protected: newBuffer(rawProtected),
|
||||||
Signature: newBuffer(signature),
|
Signature: newBuffer(signature),
|
||||||
|
@ -227,7 +276,7 @@ func parseSignedCompact(input string) (*JsonWebSignature, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompactSerialize serializes an object using the compact serialization format.
|
// CompactSerialize serializes an object using the compact serialization format.
|
||||||
func (obj JsonWebSignature) CompactSerialize() (string, error) {
|
func (obj JSONWebSignature) CompactSerialize() (string, error) {
|
||||||
if len(obj.Signatures) != 1 || obj.Signatures[0].header != nil || obj.Signatures[0].protected == nil {
|
if len(obj.Signatures) != 1 || obj.Signatures[0].header != nil || obj.Signatures[0].protected == nil {
|
||||||
return "", ErrNotSupported
|
return "", ErrNotSupported
|
||||||
}
|
}
|
||||||
|
@ -236,14 +285,14 @@ func (obj JsonWebSignature) CompactSerialize() (string, error) {
|
||||||
|
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"%s.%s.%s",
|
"%s.%s.%s",
|
||||||
base64URLEncode(serializedProtected),
|
base64.RawURLEncoding.EncodeToString(serializedProtected),
|
||||||
base64URLEncode(obj.payload),
|
base64.RawURLEncoding.EncodeToString(obj.payload),
|
||||||
base64URLEncode(obj.Signatures[0].Signature)), nil
|
base64.RawURLEncoding.EncodeToString(obj.Signatures[0].Signature)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FullSerialize serializes an object using the full JSON serialization format.
|
// FullSerialize serializes an object using the full JSON serialization format.
|
||||||
func (obj JsonWebSignature) FullSerialize() string {
|
func (obj JSONWebSignature) FullSerialize() string {
|
||||||
raw := rawJsonWebSignature{
|
raw := rawJSONWebSignature{
|
||||||
Payload: newBuffer(obj.payload),
|
Payload: newBuffer(obj.payload),
|
||||||
}
|
}
|
||||||
|
|
334
vendor/gopkg.in/square/go-jose.v2/jwt/builder.go
generated
vendored
Normal file
334
vendor/gopkg.in/square/go-jose.v2/jwt/builder.go
generated
vendored
Normal file
|
@ -0,0 +1,334 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2016 Zbigniew Mandziejewicz
|
||||||
|
* Copyright 2016 Square, Inc.
|
||||||
|
*
|
||||||
|
* 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 jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Builder is a utility for making JSON Web Tokens. Calls can be chained, and
|
||||||
|
// errors are accumulated until the final call to CompactSerialize/FullSerialize.
|
||||||
|
type Builder interface {
|
||||||
|
// Claims encodes claims into JWE/JWS form. Multiple calls will merge claims
|
||||||
|
// into single JSON object. If you are passing private claims, make sure to set
|
||||||
|
// struct field tags to specify the name for the JSON key to be used when
|
||||||
|
// serializing.
|
||||||
|
Claims(i interface{}) Builder
|
||||||
|
// Token builds a JSONWebToken from provided data.
|
||||||
|
Token() (*JSONWebToken, error)
|
||||||
|
// FullSerialize serializes a token using the full serialization format.
|
||||||
|
FullSerialize() (string, error)
|
||||||
|
// CompactSerialize serializes a token using the compact serialization format.
|
||||||
|
CompactSerialize() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NestedBuilder is a utility for making Signed-Then-Encrypted JSON Web Tokens.
|
||||||
|
// Calls can be chained, and errors are accumulated until final call to
|
||||||
|
// CompactSerialize/FullSerialize.
|
||||||
|
type NestedBuilder interface {
|
||||||
|
// Claims encodes claims into JWE/JWS form. Multiple calls will merge claims
|
||||||
|
// into single JSON object. If you are passing private claims, make sure to set
|
||||||
|
// struct field tags to specify the name for the JSON key to be used when
|
||||||
|
// serializing.
|
||||||
|
Claims(i interface{}) NestedBuilder
|
||||||
|
// Token builds a NestedJSONWebToken from provided data.
|
||||||
|
Token() (*NestedJSONWebToken, error)
|
||||||
|
// FullSerialize serializes a token using the full serialization format.
|
||||||
|
FullSerialize() (string, error)
|
||||||
|
// CompactSerialize serializes a token using the compact serialization format.
|
||||||
|
CompactSerialize() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type builder struct {
|
||||||
|
payload map[string]interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type signedBuilder struct {
|
||||||
|
builder
|
||||||
|
sig jose.Signer
|
||||||
|
}
|
||||||
|
|
||||||
|
type encryptedBuilder struct {
|
||||||
|
builder
|
||||||
|
enc jose.Encrypter
|
||||||
|
}
|
||||||
|
|
||||||
|
type nestedBuilder struct {
|
||||||
|
builder
|
||||||
|
sig jose.Signer
|
||||||
|
enc jose.Encrypter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signed creates builder for signed tokens.
|
||||||
|
func Signed(sig jose.Signer) Builder {
|
||||||
|
return &signedBuilder{
|
||||||
|
sig: sig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypted creates builder for encrypted tokens.
|
||||||
|
func Encrypted(enc jose.Encrypter) Builder {
|
||||||
|
return &encryptedBuilder{
|
||||||
|
enc: enc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedAndEncrypted creates builder for signed-then-encrypted tokens.
|
||||||
|
// ErrInvalidContentType will be returned if encrypter doesn't have JWT content type.
|
||||||
|
func SignedAndEncrypted(sig jose.Signer, enc jose.Encrypter) NestedBuilder {
|
||||||
|
if contentType, _ := enc.Options().ExtraHeaders[jose.HeaderContentType].(jose.ContentType); contentType != "JWT" {
|
||||||
|
return &nestedBuilder{
|
||||||
|
builder: builder{
|
||||||
|
err: ErrInvalidContentType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &nestedBuilder{
|
||||||
|
sig: sig,
|
||||||
|
enc: enc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b builder) claims(i interface{}) builder {
|
||||||
|
if b.err != nil {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
m, ok := i.(map[string]interface{})
|
||||||
|
switch {
|
||||||
|
case ok:
|
||||||
|
return b.merge(m)
|
||||||
|
case reflect.Indirect(reflect.ValueOf(i)).Kind() == reflect.Struct:
|
||||||
|
m, err := normalize(i)
|
||||||
|
if err != nil {
|
||||||
|
return builder{
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.merge(m)
|
||||||
|
default:
|
||||||
|
return builder{
|
||||||
|
err: ErrInvalidClaims,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalize(i interface{}) (map[string]interface{}, error) {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
|
||||||
|
raw, err := json.Marshal(i)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d := json.NewDecoder(bytes.NewReader(raw))
|
||||||
|
d.UseNumber()
|
||||||
|
|
||||||
|
if err := d.Decode(&m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *builder) merge(m map[string]interface{}) builder {
|
||||||
|
p := make(map[string]interface{})
|
||||||
|
for k, v := range b.payload {
|
||||||
|
p[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range m {
|
||||||
|
p[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder{
|
||||||
|
payload: p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *builder) token(p func(interface{}) ([]byte, error), h []jose.Header) (*JSONWebToken, error) {
|
||||||
|
return &JSONWebToken{
|
||||||
|
payload: p,
|
||||||
|
Headers: h,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *signedBuilder) Claims(i interface{}) Builder {
|
||||||
|
return &signedBuilder{
|
||||||
|
builder: b.builder.claims(i),
|
||||||
|
sig: b.sig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *signedBuilder) Token() (*JSONWebToken, error) {
|
||||||
|
sig, err := b.sign()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h := make([]jose.Header, len(sig.Signatures))
|
||||||
|
for i, v := range sig.Signatures {
|
||||||
|
h[i] = v.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.builder.token(sig.Verify, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *signedBuilder) CompactSerialize() (string, error) {
|
||||||
|
sig, err := b.sign()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig.CompactSerialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *signedBuilder) FullSerialize() (string, error) {
|
||||||
|
sig, err := b.sign()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig.FullSerialize(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *signedBuilder) sign() (*jose.JSONWebSignature, error) {
|
||||||
|
if b.err != nil {
|
||||||
|
return nil, b.err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := json.Marshal(b.payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.sig.Sign(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *encryptedBuilder) Claims(i interface{}) Builder {
|
||||||
|
return &encryptedBuilder{
|
||||||
|
builder: b.builder.claims(i),
|
||||||
|
enc: b.enc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *encryptedBuilder) CompactSerialize() (string, error) {
|
||||||
|
enc, err := b.encrypt()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return enc.CompactSerialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *encryptedBuilder) FullSerialize() (string, error) {
|
||||||
|
enc, err := b.encrypt()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return enc.FullSerialize(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *encryptedBuilder) Token() (*JSONWebToken, error) {
|
||||||
|
enc, err := b.encrypt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.builder.token(enc.Decrypt, []jose.Header{enc.Header})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *encryptedBuilder) encrypt() (*jose.JSONWebEncryption, error) {
|
||||||
|
if b.err != nil {
|
||||||
|
return nil, b.err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := json.Marshal(b.payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.enc.Encrypt(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *nestedBuilder) Claims(i interface{}) NestedBuilder {
|
||||||
|
return &nestedBuilder{
|
||||||
|
builder: b.builder.claims(i),
|
||||||
|
sig: b.sig,
|
||||||
|
enc: b.enc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *nestedBuilder) Token() (*NestedJSONWebToken, error) {
|
||||||
|
enc, err := b.signAndEncrypt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &NestedJSONWebToken{
|
||||||
|
enc: enc,
|
||||||
|
Headers: []jose.Header{enc.Header},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *nestedBuilder) CompactSerialize() (string, error) {
|
||||||
|
enc, err := b.signAndEncrypt()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return enc.CompactSerialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *nestedBuilder) FullSerialize() (string, error) {
|
||||||
|
enc, err := b.signAndEncrypt()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return enc.FullSerialize(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *nestedBuilder) signAndEncrypt() (*jose.JSONWebEncryption, error) {
|
||||||
|
if b.err != nil {
|
||||||
|
return nil, b.err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := json.Marshal(b.payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := b.sig.Sign(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p2, err := sig.CompactSerialize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.enc.Encrypt([]byte(p2))
|
||||||
|
}
|
115
vendor/gopkg.in/square/go-jose.v2/jwt/claims.go
generated
vendored
Normal file
115
vendor/gopkg.in/square/go-jose.v2/jwt/claims.go
generated
vendored
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2016 Zbigniew Mandziejewicz
|
||||||
|
* Copyright 2016 Square, Inc.
|
||||||
|
*
|
||||||
|
* 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 jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Claims represents public claim values (as specified in RFC 7519).
|
||||||
|
type Claims struct {
|
||||||
|
Issuer string `json:"iss,omitempty"`
|
||||||
|
Subject string `json:"sub,omitempty"`
|
||||||
|
Audience Audience `json:"aud,omitempty"`
|
||||||
|
Expiry NumericDate `json:"exp,omitempty"`
|
||||||
|
NotBefore NumericDate `json:"nbf,omitempty"`
|
||||||
|
IssuedAt NumericDate `json:"iat,omitempty"`
|
||||||
|
ID string `json:"jti,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumericDate represents date and time as the number of seconds since the
|
||||||
|
// epoch, including leap seconds. Non-integer values can be represented
|
||||||
|
// in the serialized format, but we round to the nearest second.
|
||||||
|
type NumericDate int64
|
||||||
|
|
||||||
|
// NewNumericDate constructs NumericDate from time.Time value.
|
||||||
|
func NewNumericDate(t time.Time) NumericDate {
|
||||||
|
if t.IsZero() {
|
||||||
|
return NumericDate(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// While RFC 7519 technically states that NumericDate values may be
|
||||||
|
// non-integer values, we don't bother serializing timestamps in
|
||||||
|
// claims with sub-second accurancy and just round to the nearest
|
||||||
|
// second instead. Not convined sub-second accuracy is useful here.
|
||||||
|
return NumericDate(t.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON serializes the given NumericDate into its JSON representation.
|
||||||
|
func (n NumericDate) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(strconv.FormatInt(int64(n), 10)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON reads a date from its JSON representation.
|
||||||
|
func (n *NumericDate) UnmarshalJSON(b []byte) error {
|
||||||
|
s := string(b)
|
||||||
|
|
||||||
|
f, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return ErrUnmarshalNumericDate
|
||||||
|
}
|
||||||
|
|
||||||
|
*n = NumericDate(f)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns time.Time representation of NumericDate.
|
||||||
|
func (n NumericDate) Time() time.Time {
|
||||||
|
return time.Unix(int64(n), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audience represents the recipents that the token is intended for.
|
||||||
|
type Audience []string
|
||||||
|
|
||||||
|
// UnmarshalJSON reads an audience from its JSON representation.
|
||||||
|
func (s *Audience) UnmarshalJSON(b []byte) error {
|
||||||
|
var v interface{}
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
*s = []string{v}
|
||||||
|
case []interface{}:
|
||||||
|
a := make([]string, len(v))
|
||||||
|
for i, e := range v {
|
||||||
|
s, ok := e.(string)
|
||||||
|
if !ok {
|
||||||
|
return ErrUnmarshalAudience
|
||||||
|
}
|
||||||
|
a[i] = s
|
||||||
|
}
|
||||||
|
*s = a
|
||||||
|
default:
|
||||||
|
return ErrUnmarshalAudience
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Audience) Contains(v string) bool {
|
||||||
|
for _, a := range s {
|
||||||
|
if a == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
22
vendor/gopkg.in/square/go-jose.v2/jwt/doc.go
generated
vendored
Normal file
22
vendor/gopkg.in/square/go-jose.v2/jwt/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2017 Square Inc.
|
||||||
|
*
|
||||||
|
* 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 jwt provides an implementation of the JSON Web Token standard.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package jwt
|
50
vendor/gopkg.in/square/go-jose.v2/jwt/errors.go
generated
vendored
Normal file
50
vendor/gopkg.in/square/go-jose.v2/jwt/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2016 Zbigniew Mandziejewicz
|
||||||
|
* Copyright 2016 Square, Inc.
|
||||||
|
*
|
||||||
|
* 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 jwt
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// ErrUnmarshalAudience indicates that aud claim could not be unmarshalled.
|
||||||
|
var ErrUnmarshalAudience = errors.New("square/go-jose/jwt: expected string or array value to unmarshal to Audience")
|
||||||
|
|
||||||
|
// ErrUnmarshalNumericDate indicates that JWT NumericDate could not be unmarshalled.
|
||||||
|
var ErrUnmarshalNumericDate = errors.New("square/go-jose/jwt: expected number value to unmarshal NumericDate")
|
||||||
|
|
||||||
|
// ErrInvalidClaims indicates that given claims have invalid type.
|
||||||
|
var ErrInvalidClaims = errors.New("square/go-jose/jwt: expected claims to be value convertible into JSON object")
|
||||||
|
|
||||||
|
// ErrInvalidIssuer indicates invalid iss claim.
|
||||||
|
var ErrInvalidIssuer = errors.New("square/go-jose/jwt: validation failed, invalid issuer claim (iss)")
|
||||||
|
|
||||||
|
// ErrInvalidSubject indicates invalid sub claim.
|
||||||
|
var ErrInvalidSubject = errors.New("square/go-jose/jwt: validation failed, invalid subject claim (sub)")
|
||||||
|
|
||||||
|
// ErrInvalidAudience indicated invalid aud claim.
|
||||||
|
var ErrInvalidAudience = errors.New("square/go-jose/jwt: validation failed, invalid audience claim (aud)")
|
||||||
|
|
||||||
|
// ErrInvalidID indicates invalid jti claim.
|
||||||
|
var ErrInvalidID = errors.New("square/go-jose/jwt: validation failed, invalid ID claim (jti)")
|
||||||
|
|
||||||
|
// ErrNotValidYet indicates that token is used before time indicated in nbf claim.
|
||||||
|
var ErrNotValidYet = errors.New("square/go-jose/jwt: validation failed, token not valid yet (nbf)")
|
||||||
|
|
||||||
|
// ErrExpired indicates that token is used after expiry time indicated in exp claim.
|
||||||
|
var ErrExpired = errors.New("square/go-jose/jwt: validation failed, token is expired (exp)")
|
||||||
|
|
||||||
|
// ErrInvalidContentType indicated that token requires JWT cty header.
|
||||||
|
var ErrInvalidContentType = errors.New("square/go-jose/jwt: expected content type to be JWT (cty header)")
|
113
vendor/gopkg.in/square/go-jose.v2/jwt/jwt.go
generated
vendored
Normal file
113
vendor/gopkg.in/square/go-jose.v2/jwt/jwt.go
generated
vendored
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2016 Zbigniew Mandziejewicz
|
||||||
|
* Copyright 2016 Square, Inc.
|
||||||
|
*
|
||||||
|
* 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 jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSONWebToken represents a JSON Web Token (as specified in RFC7519).
|
||||||
|
type JSONWebToken struct {
|
||||||
|
payload func(k interface{}) ([]byte, error)
|
||||||
|
Headers []jose.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
type NestedJSONWebToken struct {
|
||||||
|
enc *jose.JSONWebEncryption
|
||||||
|
Headers []jose.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claims deserializes a JSONWebToken into dest using the provided key.
|
||||||
|
func (t *JSONWebToken) Claims(key interface{}, dest ...interface{}) error {
|
||||||
|
b, err := t.payload(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range dest {
|
||||||
|
if err := json.Unmarshal(b, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *NestedJSONWebToken) Decrypt(decryptionKey interface{}) (*JSONWebToken, error) {
|
||||||
|
b, err := t.enc.Decrypt(decryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := ParseSigned(string(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSigned parses token from JWS form.
|
||||||
|
func ParseSigned(s string) (*JSONWebToken, error) {
|
||||||
|
sig, err := jose.ParseSigned(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
headers := make([]jose.Header, len(sig.Signatures))
|
||||||
|
for i, signature := range sig.Signatures {
|
||||||
|
headers[i] = signature.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
return &JSONWebToken{
|
||||||
|
payload: sig.Verify,
|
||||||
|
Headers: headers,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEncrypted parses token from JWE form.
|
||||||
|
func ParseEncrypted(s string) (*JSONWebToken, error) {
|
||||||
|
enc, err := jose.ParseEncrypted(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &JSONWebToken{
|
||||||
|
payload: enc.Decrypt,
|
||||||
|
Headers: []jose.Header{enc.Header},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSignedAndEncrypted parses signed-then-encrypted token from JWE form.
|
||||||
|
func ParseSignedAndEncrypted(s string) (*NestedJSONWebToken, error) {
|
||||||
|
enc, err := jose.ParseEncrypted(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType, _ := enc.Header.ExtraHeaders[jose.HeaderContentType].(string)
|
||||||
|
if strings.ToUpper(contentType) != "JWT" {
|
||||||
|
return nil, ErrInvalidContentType
|
||||||
|
}
|
||||||
|
|
||||||
|
return &NestedJSONWebToken{
|
||||||
|
enc: enc,
|
||||||
|
Headers: []jose.Header{enc.Header},
|
||||||
|
}, nil
|
||||||
|
}
|
89
vendor/gopkg.in/square/go-jose.v2/jwt/validation.go
generated
vendored
Normal file
89
vendor/gopkg.in/square/go-jose.v2/jwt/validation.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2016 Zbigniew Mandziejewicz
|
||||||
|
* Copyright 2016 Square, Inc.
|
||||||
|
*
|
||||||
|
* 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 jwt
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultLeeway defines the default leeway for matching NotBefore/Expiry claims.
|
||||||
|
DefaultLeeway = 1.0 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
// Expected defines values used for protected claims validation.
|
||||||
|
// If field has zero value then validation is skipped.
|
||||||
|
type Expected struct {
|
||||||
|
// Issuer matches the "iss" claim exactly.
|
||||||
|
Issuer string
|
||||||
|
// Subject matches the "sub" claim exactly.
|
||||||
|
Subject string
|
||||||
|
// Audience matches the values in "aud" claim, regardless of their order.
|
||||||
|
Audience Audience
|
||||||
|
// ID matches the "jti" claim exactly.
|
||||||
|
ID string
|
||||||
|
// Time matches the "exp" and "ebf" claims with leeway.
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTime copies expectations with new time.
|
||||||
|
func (e Expected) WithTime(t time.Time) Expected {
|
||||||
|
e.Time = t
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks claims in a token against expected values.
|
||||||
|
// A default leeway value of one minute is used to compare time values.
|
||||||
|
func (c Claims) Validate(e Expected) error {
|
||||||
|
return c.ValidateWithLeeway(e, DefaultLeeway)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateWithLeeway checks claims in a token against expected values. A
|
||||||
|
// custom leeway may be specified for comparing time values. You may pass a
|
||||||
|
// zero value to check time values with no leeway, but you should not that
|
||||||
|
// numeric date values are rounded to the nearest second and sub-second
|
||||||
|
// precision is not supported.
|
||||||
|
func (c Claims) ValidateWithLeeway(e Expected, leeway time.Duration) error {
|
||||||
|
if e.Issuer != "" && e.Issuer != c.Issuer {
|
||||||
|
return ErrInvalidIssuer
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Subject != "" && e.Subject != c.Subject {
|
||||||
|
return ErrInvalidSubject
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.ID != "" && e.ID != c.ID {
|
||||||
|
return ErrInvalidID
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(e.Audience) != 0 {
|
||||||
|
for _, v := range e.Audience {
|
||||||
|
if !c.Audience.Contains(v) {
|
||||||
|
return ErrInvalidAudience
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !e.Time.IsZero() && e.Time.Add(leeway).Before(c.NotBefore.Time()) {
|
||||||
|
return ErrNotValidYet
|
||||||
|
}
|
||||||
|
|
||||||
|
if !e.Time.IsZero() && e.Time.Add(-leeway).After(c.Expiry.Time()) {
|
||||||
|
return ErrExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
417
vendor/gopkg.in/square/go-jose.v2/shared.go
generated
vendored
Normal file
417
vendor/gopkg.in/square/go-jose.v2/shared.go
generated
vendored
Normal file
|
@ -0,0 +1,417 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* 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 jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/elliptic"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyAlgorithm represents a key management algorithm.
|
||||||
|
type KeyAlgorithm string
|
||||||
|
|
||||||
|
// SignatureAlgorithm represents a signature (or MAC) algorithm.
|
||||||
|
type SignatureAlgorithm string
|
||||||
|
|
||||||
|
// ContentEncryption represents a content encryption algorithm.
|
||||||
|
type ContentEncryption string
|
||||||
|
|
||||||
|
// CompressionAlgorithm represents an algorithm used for plaintext compression.
|
||||||
|
type CompressionAlgorithm string
|
||||||
|
|
||||||
|
// ContentType represents type of the contained data.
|
||||||
|
type ContentType string
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCryptoFailure represents an error in cryptographic primitive. This
|
||||||
|
// occurs when, for example, a message had an invalid authentication tag or
|
||||||
|
// could not be decrypted.
|
||||||
|
ErrCryptoFailure = errors.New("square/go-jose: error in cryptographic primitive")
|
||||||
|
|
||||||
|
// ErrUnsupportedAlgorithm indicates that a selected algorithm is not
|
||||||
|
// supported. This occurs when trying to instantiate an encrypter for an
|
||||||
|
// algorithm that is not yet implemented.
|
||||||
|
ErrUnsupportedAlgorithm = errors.New("square/go-jose: unknown/unsupported algorithm")
|
||||||
|
|
||||||
|
// ErrUnsupportedKeyType indicates that the given key type/format is not
|
||||||
|
// supported. This occurs when trying to instantiate an encrypter and passing
|
||||||
|
// it a key of an unrecognized type or with unsupported parameters, such as
|
||||||
|
// an RSA private key with more than two primes.
|
||||||
|
ErrUnsupportedKeyType = errors.New("square/go-jose: unsupported key type/format")
|
||||||
|
|
||||||
|
// ErrNotSupported serialization of object is not supported. This occurs when
|
||||||
|
// trying to compact-serialize an object which can't be represented in
|
||||||
|
// compact form.
|
||||||
|
ErrNotSupported = errors.New("square/go-jose: compact serialization not supported for object")
|
||||||
|
|
||||||
|
// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
|
||||||
|
// nonce header parameter was included in an unprotected header object.
|
||||||
|
ErrUnprotectedNonce = errors.New("square/go-jose: Nonce parameter included in unprotected header")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key management algorithms
|
||||||
|
const (
|
||||||
|
ED25519 = KeyAlgorithm("ED25519")
|
||||||
|
RSA1_5 = KeyAlgorithm("RSA1_5") // RSA-PKCS1v1.5
|
||||||
|
RSA_OAEP = KeyAlgorithm("RSA-OAEP") // RSA-OAEP-SHA1
|
||||||
|
RSA_OAEP_256 = KeyAlgorithm("RSA-OAEP-256") // RSA-OAEP-SHA256
|
||||||
|
A128KW = KeyAlgorithm("A128KW") // AES key wrap (128)
|
||||||
|
A192KW = KeyAlgorithm("A192KW") // AES key wrap (192)
|
||||||
|
A256KW = KeyAlgorithm("A256KW") // AES key wrap (256)
|
||||||
|
DIRECT = KeyAlgorithm("dir") // Direct encryption
|
||||||
|
ECDH_ES = KeyAlgorithm("ECDH-ES") // ECDH-ES
|
||||||
|
ECDH_ES_A128KW = KeyAlgorithm("ECDH-ES+A128KW") // ECDH-ES + AES key wrap (128)
|
||||||
|
ECDH_ES_A192KW = KeyAlgorithm("ECDH-ES+A192KW") // ECDH-ES + AES key wrap (192)
|
||||||
|
ECDH_ES_A256KW = KeyAlgorithm("ECDH-ES+A256KW") // ECDH-ES + AES key wrap (256)
|
||||||
|
A128GCMKW = KeyAlgorithm("A128GCMKW") // AES-GCM key wrap (128)
|
||||||
|
A192GCMKW = KeyAlgorithm("A192GCMKW") // AES-GCM key wrap (192)
|
||||||
|
A256GCMKW = KeyAlgorithm("A256GCMKW") // AES-GCM key wrap (256)
|
||||||
|
PBES2_HS256_A128KW = KeyAlgorithm("PBES2-HS256+A128KW") // PBES2 + HMAC-SHA256 + AES key wrap (128)
|
||||||
|
PBES2_HS384_A192KW = KeyAlgorithm("PBES2-HS384+A192KW") // PBES2 + HMAC-SHA384 + AES key wrap (192)
|
||||||
|
PBES2_HS512_A256KW = KeyAlgorithm("PBES2-HS512+A256KW") // PBES2 + HMAC-SHA512 + AES key wrap (256)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Signature algorithms
|
||||||
|
const (
|
||||||
|
EdDSA = SignatureAlgorithm("EdDSA")
|
||||||
|
HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256
|
||||||
|
HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384
|
||||||
|
HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512
|
||||||
|
RS256 = SignatureAlgorithm("RS256") // RSASSA-PKCS-v1.5 using SHA-256
|
||||||
|
RS384 = SignatureAlgorithm("RS384") // RSASSA-PKCS-v1.5 using SHA-384
|
||||||
|
RS512 = SignatureAlgorithm("RS512") // RSASSA-PKCS-v1.5 using SHA-512
|
||||||
|
ES256 = SignatureAlgorithm("ES256") // ECDSA using P-256 and SHA-256
|
||||||
|
ES384 = SignatureAlgorithm("ES384") // ECDSA using P-384 and SHA-384
|
||||||
|
ES512 = SignatureAlgorithm("ES512") // ECDSA using P-521 and SHA-512
|
||||||
|
PS256 = SignatureAlgorithm("PS256") // RSASSA-PSS using SHA256 and MGF1-SHA256
|
||||||
|
PS384 = SignatureAlgorithm("PS384") // RSASSA-PSS using SHA384 and MGF1-SHA384
|
||||||
|
PS512 = SignatureAlgorithm("PS512") // RSASSA-PSS using SHA512 and MGF1-SHA512
|
||||||
|
)
|
||||||
|
|
||||||
|
// Content encryption algorithms
|
||||||
|
const (
|
||||||
|
A128CBC_HS256 = ContentEncryption("A128CBC-HS256") // AES-CBC + HMAC-SHA256 (128)
|
||||||
|
A192CBC_HS384 = ContentEncryption("A192CBC-HS384") // AES-CBC + HMAC-SHA384 (192)
|
||||||
|
A256CBC_HS512 = ContentEncryption("A256CBC-HS512") // AES-CBC + HMAC-SHA512 (256)
|
||||||
|
A128GCM = ContentEncryption("A128GCM") // AES-GCM (128)
|
||||||
|
A192GCM = ContentEncryption("A192GCM") // AES-GCM (192)
|
||||||
|
A256GCM = ContentEncryption("A256GCM") // AES-GCM (256)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compression algorithms
|
||||||
|
const (
|
||||||
|
NONE = CompressionAlgorithm("") // No compression
|
||||||
|
DEFLATE = CompressionAlgorithm("DEF") // DEFLATE (RFC 1951)
|
||||||
|
)
|
||||||
|
|
||||||
|
// A key in the protected header of a JWS object. Use of the Header...
|
||||||
|
// constants is preferred to enhance type safety.
|
||||||
|
type HeaderKey string
|
||||||
|
|
||||||
|
const (
|
||||||
|
HeaderType HeaderKey = "typ" // string
|
||||||
|
HeaderContentType = "cty" // string
|
||||||
|
|
||||||
|
// These are set by go-jose and shouldn't need to be set by consumers of the
|
||||||
|
// library.
|
||||||
|
headerAlgorithm = "alg" // string
|
||||||
|
headerEncryption = "enc" // ContentEncryption
|
||||||
|
headerCompression = "zip" // CompressionAlgorithm
|
||||||
|
headerCritical = "crit" // []string
|
||||||
|
|
||||||
|
headerAPU = "apu" // *byteBuffer
|
||||||
|
headerAPV = "apv" // *byteBuffer
|
||||||
|
headerEPK = "epk" // *JSONWebKey
|
||||||
|
headerIV = "iv" // *byteBuffer
|
||||||
|
headerTag = "tag" // *byteBuffer
|
||||||
|
|
||||||
|
headerJWK = "jwk" // *JSONWebKey
|
||||||
|
headerKeyID = "kid" // string
|
||||||
|
headerNonce = "nonce" // string
|
||||||
|
)
|
||||||
|
|
||||||
|
// rawHeader represents the JOSE header for JWE/JWS objects (used for parsing).
|
||||||
|
//
|
||||||
|
// The decoding of the constituent items is deferred because we want to marshal
|
||||||
|
// some members into particular structs rather than generic maps, but at the
|
||||||
|
// same time we need to receive any extra fields unhandled by this library to
|
||||||
|
// pass through to consuming code in case it wants to examine them.
|
||||||
|
type rawHeader map[HeaderKey]*json.RawMessage
|
||||||
|
|
||||||
|
// Header represents the read-only JOSE header for JWE/JWS objects.
|
||||||
|
type Header struct {
|
||||||
|
KeyID string
|
||||||
|
JSONWebKey *JSONWebKey
|
||||||
|
Algorithm string
|
||||||
|
Nonce string
|
||||||
|
|
||||||
|
// Any headers not recognised above get unmarshaled from JSON in a generic
|
||||||
|
// manner and placed in this map.
|
||||||
|
ExtraHeaders map[HeaderKey]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parsed rawHeader) set(k HeaderKey, v interface{}) error {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed[k] = makeRawMessage(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getString gets a string from the raw JSON, defaulting to "".
|
||||||
|
func (parsed rawHeader) getString(k HeaderKey) string {
|
||||||
|
v, ok := parsed[k]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
err := json.Unmarshal(*v, &s)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// getByteBuffer gets a byte buffer from the raw JSON. Returns (nil, nil) if
|
||||||
|
// not specified.
|
||||||
|
func (parsed rawHeader) getByteBuffer(k HeaderKey) (*byteBuffer, error) {
|
||||||
|
v := parsed[k]
|
||||||
|
if v == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var bb *byteBuffer
|
||||||
|
err := json.Unmarshal(*v, &bb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAlgorithm extracts parsed "alg" from the raw JSON as a KeyAlgorithm.
|
||||||
|
func (parsed rawHeader) getAlgorithm() KeyAlgorithm {
|
||||||
|
return KeyAlgorithm(parsed.getString(headerAlgorithm))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSignatureAlgorithm extracts parsed "alg" from the raw JSON as a SignatureAlgorithm.
|
||||||
|
func (parsed rawHeader) getSignatureAlgorithm() SignatureAlgorithm {
|
||||||
|
return SignatureAlgorithm(parsed.getString(headerAlgorithm))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEncryption extracts parsed "enc" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getEncryption() ContentEncryption {
|
||||||
|
return ContentEncryption(parsed.getString(headerEncryption))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCompression extracts parsed "zip" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getCompression() CompressionAlgorithm {
|
||||||
|
return CompressionAlgorithm(parsed.getString(headerCompression))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parsed rawHeader) getNonce() string {
|
||||||
|
return parsed.getString(headerNonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEPK extracts parsed "epk" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getEPK() (*JSONWebKey, error) {
|
||||||
|
v := parsed[headerEPK]
|
||||||
|
if v == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var epk *JSONWebKey
|
||||||
|
err := json.Unmarshal(*v, &epk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return epk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAPU extracts parsed "apu" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getAPU() (*byteBuffer, error) {
|
||||||
|
return parsed.getByteBuffer(headerAPU)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAPV extracts parsed "apv" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getAPV() (*byteBuffer, error) {
|
||||||
|
return parsed.getByteBuffer(headerAPV)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getIV extracts parsed "iv" frpom the raw JSON.
|
||||||
|
func (parsed rawHeader) getIV() (*byteBuffer, error) {
|
||||||
|
return parsed.getByteBuffer(headerIV)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTag extracts parsed "tag" frpom the raw JSON.
|
||||||
|
func (parsed rawHeader) getTag() (*byteBuffer, error) {
|
||||||
|
return parsed.getByteBuffer(headerTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getJWK extracts parsed "jwk" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getJWK() (*JSONWebKey, error) {
|
||||||
|
v := parsed[headerJWK]
|
||||||
|
if v == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var jwk *JSONWebKey
|
||||||
|
err := json.Unmarshal(*v, &jwk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return jwk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCritical extracts parsed "crit" from the raw JSON. If omitted, it
|
||||||
|
// returns an empty slice.
|
||||||
|
func (parsed rawHeader) getCritical() ([]string, error) {
|
||||||
|
v := parsed[headerCritical]
|
||||||
|
if v == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var q []string
|
||||||
|
err := json.Unmarshal(*v, &q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitized produces a cleaned-up header object from the raw JSON.
|
||||||
|
func (parsed rawHeader) sanitized() (h Header, err error) {
|
||||||
|
for k, v := range parsed {
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch k {
|
||||||
|
case headerJWK:
|
||||||
|
var jwk *JSONWebKey
|
||||||
|
err = json.Unmarshal(*v, &jwk)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to unmarshal JWK: %v: %#v", err, string(*v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.JSONWebKey = jwk
|
||||||
|
case headerKeyID:
|
||||||
|
var s string
|
||||||
|
err = json.Unmarshal(*v, &s)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to unmarshal key ID: %v: %#v", err, string(*v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.KeyID = s
|
||||||
|
case headerAlgorithm:
|
||||||
|
var s string
|
||||||
|
err = json.Unmarshal(*v, &s)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to unmarshal algorithm: %v: %#v", err, string(*v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.Algorithm = s
|
||||||
|
case headerNonce:
|
||||||
|
var s string
|
||||||
|
err = json.Unmarshal(*v, &s)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to unmarshal nonce: %v: %#v", err, string(*v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.Nonce = s
|
||||||
|
default:
|
||||||
|
if h.ExtraHeaders == nil {
|
||||||
|
h.ExtraHeaders = map[HeaderKey]interface{}{}
|
||||||
|
}
|
||||||
|
var v2 interface{}
|
||||||
|
err = json.Unmarshal(*v, &v2)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to unmarshal value: %v: %#v", err, string(*v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.ExtraHeaders[k] = v2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dst rawHeader) isSet(k HeaderKey) bool {
|
||||||
|
dvr := dst[k]
|
||||||
|
if dvr == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var dv interface{}
|
||||||
|
err := json.Unmarshal(*dvr, &dv)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if dvStr, ok := dv.(string); ok {
|
||||||
|
return dvStr != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge headers from src into dst, giving precedence to headers from l.
|
||||||
|
func (dst rawHeader) merge(src *rawHeader) {
|
||||||
|
if src == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range *src {
|
||||||
|
if dst.isSet(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dst[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get JOSE name of curve
|
||||||
|
func curveName(crv elliptic.Curve) (string, error) {
|
||||||
|
switch crv {
|
||||||
|
case elliptic.P256():
|
||||||
|
return "P-256", nil
|
||||||
|
case elliptic.P384():
|
||||||
|
return "P-384", nil
|
||||||
|
case elliptic.P521():
|
||||||
|
return "P-521", nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("square/go-jose: unsupported/unknown elliptic curve")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get size of curve in bytes
|
||||||
|
func curveSize(crv elliptic.Curve) int {
|
||||||
|
bits := crv.Params().BitSize
|
||||||
|
|
||||||
|
div := bits / 8
|
||||||
|
mod := bits % 8
|
||||||
|
|
||||||
|
if mod == 0 {
|
||||||
|
return div
|
||||||
|
}
|
||||||
|
|
||||||
|
return div + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRawMessage(b []byte) *json.RawMessage {
|
||||||
|
rm := json.RawMessage(b)
|
||||||
|
return &rm
|
||||||
|
}
|
343
vendor/gopkg.in/square/go-jose.v2/signing.go
generated
vendored
Normal file
343
vendor/gopkg.in/square/go-jose.v2/signing.go
generated
vendored
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* 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 jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NonceSource represents a source of random nonces to go into JWS objects
|
||||||
|
type NonceSource interface {
|
||||||
|
Nonce() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signer represents a signer which takes a payload and produces a signed JWS object.
|
||||||
|
type Signer interface {
|
||||||
|
Sign(payload []byte) (*JSONWebSignature, error)
|
||||||
|
Options() SignerOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigningKey represents an algorithm/key used to sign a message.
|
||||||
|
type SigningKey struct {
|
||||||
|
Algorithm SignatureAlgorithm
|
||||||
|
Key interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignerOptions represents options that can be set when creating signers.
|
||||||
|
type SignerOptions struct {
|
||||||
|
NonceSource NonceSource
|
||||||
|
EmbedJWK bool
|
||||||
|
|
||||||
|
// Optional map of additional keys to be inserted into the protected header
|
||||||
|
// of a JWS object. Some specifications which make use of JWS like to insert
|
||||||
|
// additional values here. All values must be JSON-serializable.
|
||||||
|
ExtraHeaders map[HeaderKey]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
|
||||||
|
// if necessary. It returns itself and so can be used in a fluent style.
|
||||||
|
func (so *SignerOptions) WithHeader(k HeaderKey, v interface{}) *SignerOptions {
|
||||||
|
if so.ExtraHeaders == nil {
|
||||||
|
so.ExtraHeaders = map[HeaderKey]interface{}{}
|
||||||
|
}
|
||||||
|
so.ExtraHeaders[k] = v
|
||||||
|
return so
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContentType adds a content type ("cty") header and returns the updated
|
||||||
|
// SignerOptions.
|
||||||
|
func (so *SignerOptions) WithContentType(contentType ContentType) *SignerOptions {
|
||||||
|
return so.WithHeader(HeaderContentType, contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithType adds a type ("typ") header and returns the updated SignerOptions.
|
||||||
|
func (so *SignerOptions) WithType(typ ContentType) *SignerOptions {
|
||||||
|
return so.WithHeader(HeaderType, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
type payloadSigner interface {
|
||||||
|
signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type payloadVerifier interface {
|
||||||
|
verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type genericSigner struct {
|
||||||
|
recipients []recipientSigInfo
|
||||||
|
nonceSource NonceSource
|
||||||
|
embedJWK bool
|
||||||
|
extraHeaders map[HeaderKey]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type recipientSigInfo struct {
|
||||||
|
sigAlg SignatureAlgorithm
|
||||||
|
publicKey *JSONWebKey
|
||||||
|
signer payloadSigner
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSigner creates an appropriate signer based on the key type
|
||||||
|
func NewSigner(sig SigningKey, opts *SignerOptions) (Signer, error) {
|
||||||
|
return NewMultiSigner([]SigningKey{sig}, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMultiSigner creates a signer for multiple recipients
|
||||||
|
func NewMultiSigner(sigs []SigningKey, opts *SignerOptions) (Signer, error) {
|
||||||
|
signer := &genericSigner{recipients: []recipientSigInfo{}}
|
||||||
|
|
||||||
|
if opts != nil {
|
||||||
|
signer.nonceSource = opts.NonceSource
|
||||||
|
signer.embedJWK = opts.EmbedJWK
|
||||||
|
signer.extraHeaders = opts.ExtraHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sig := range sigs {
|
||||||
|
err := signer.addRecipient(sig.Algorithm, sig.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return signer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newVerifier creates a verifier based on the key type
|
||||||
|
func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
|
||||||
|
switch verificationKey := verificationKey.(type) {
|
||||||
|
case ed25519.PublicKey:
|
||||||
|
return &edEncrypterVerifier{
|
||||||
|
publicKey: verificationKey,
|
||||||
|
}, nil
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
return &rsaEncrypterVerifier{
|
||||||
|
publicKey: verificationKey,
|
||||||
|
}, nil
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
return &ecEncrypterVerifier{
|
||||||
|
publicKey: verificationKey,
|
||||||
|
}, nil
|
||||||
|
case []byte:
|
||||||
|
return &symmetricMac{
|
||||||
|
key: verificationKey,
|
||||||
|
}, nil
|
||||||
|
case JSONWebKey:
|
||||||
|
return newVerifier(verificationKey.Key)
|
||||||
|
case *JSONWebKey:
|
||||||
|
return newVerifier(verificationKey.Key)
|
||||||
|
default:
|
||||||
|
return nil, ErrUnsupportedKeyType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *genericSigner) addRecipient(alg SignatureAlgorithm, signingKey interface{}) error {
|
||||||
|
recipient, err := makeJWSRecipient(alg, signingKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.recipients = append(ctx.recipients, recipient)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeJWSRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipientSigInfo, error) {
|
||||||
|
switch signingKey := signingKey.(type) {
|
||||||
|
case ed25519.PrivateKey:
|
||||||
|
return newEd25519Signer(alg, signingKey)
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return newRSASigner(alg, signingKey)
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
return newECDSASigner(alg, signingKey)
|
||||||
|
case []byte:
|
||||||
|
return newSymmetricSigner(alg, signingKey)
|
||||||
|
case JSONWebKey:
|
||||||
|
return newJWKSigner(alg, signingKey)
|
||||||
|
case *JSONWebKey:
|
||||||
|
return newJWKSigner(alg, *signingKey)
|
||||||
|
default:
|
||||||
|
return recipientSigInfo{}, ErrUnsupportedKeyType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigInfo, error) {
|
||||||
|
recipient, err := makeJWSRecipient(alg, signingKey.Key)
|
||||||
|
if err != nil {
|
||||||
|
return recipientSigInfo{}, err
|
||||||
|
}
|
||||||
|
if recipient.publicKey != nil {
|
||||||
|
// recipient.publicKey is a JWK synthesized for embedding when recipientSigInfo
|
||||||
|
// was created for the inner key (such as a RSA or ECDSA public key). It contains
|
||||||
|
// the pub key for embedding, but doesn't have extra params like key id.
|
||||||
|
publicKey := signingKey
|
||||||
|
publicKey.Key = recipient.publicKey.Key
|
||||||
|
recipient.publicKey = &publicKey
|
||||||
|
|
||||||
|
// This should be impossible, but let's check anyway.
|
||||||
|
if !recipient.publicKey.IsPublic() {
|
||||||
|
return recipientSigInfo{}, errors.New("square/go-jose: public key was unexpectedly not public")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return recipient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
|
||||||
|
obj := &JSONWebSignature{}
|
||||||
|
obj.payload = payload
|
||||||
|
obj.Signatures = make([]Signature, len(ctx.recipients))
|
||||||
|
|
||||||
|
for i, recipient := range ctx.recipients {
|
||||||
|
protected := map[HeaderKey]interface{}{
|
||||||
|
headerAlgorithm: string(recipient.sigAlg),
|
||||||
|
}
|
||||||
|
|
||||||
|
if recipient.publicKey != nil {
|
||||||
|
// We want to embed the JWK or set the kid header, but not both. Having a protected
|
||||||
|
// header that contains an embedded JWK while also simultaneously containing the kid
|
||||||
|
// header is confusing, and at least in ACME the two are considered to be mutually
|
||||||
|
// exclusive. The fact that both can exist at the same time is a somewhat unfortunate
|
||||||
|
// result of the JOSE spec. We've decided that this library will only include one or
|
||||||
|
// the other to avoid this confusion.
|
||||||
|
//
|
||||||
|
// See https://github.com/square/go-jose/issues/157 for more context.
|
||||||
|
if ctx.embedJWK {
|
||||||
|
protected[headerJWK] = recipient.publicKey
|
||||||
|
} else {
|
||||||
|
protected[headerKeyID] = recipient.publicKey.KeyID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.nonceSource != nil {
|
||||||
|
nonce, err := ctx.nonceSource.Nonce()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
protected[headerNonce] = nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range ctx.extraHeaders {
|
||||||
|
protected[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedProtected := mustSerializeJSON(protected)
|
||||||
|
|
||||||
|
input := []byte(fmt.Sprintf("%s.%s",
|
||||||
|
base64.RawURLEncoding.EncodeToString(serializedProtected),
|
||||||
|
base64.RawURLEncoding.EncodeToString(payload)))
|
||||||
|
|
||||||
|
signatureInfo, err := recipient.signer.signPayload(input, recipient.sigAlg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signatureInfo.protected = &rawHeader{}
|
||||||
|
for k, v := range protected {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: Error marshalling item %#v: %v", k, err)
|
||||||
|
}
|
||||||
|
(*signatureInfo.protected)[k] = makeRawMessage(b)
|
||||||
|
}
|
||||||
|
obj.Signatures[i] = signatureInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *genericSigner) Options() SignerOptions {
|
||||||
|
return SignerOptions{
|
||||||
|
NonceSource: ctx.nonceSource,
|
||||||
|
EmbedJWK: ctx.embedJWK,
|
||||||
|
ExtraHeaders: ctx.extraHeaders,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify validates the signature on the object and returns the payload.
|
||||||
|
// This function does not support multi-signature, if you desire multi-sig
|
||||||
|
// verification use VerifyMulti instead.
|
||||||
|
//
|
||||||
|
// Be careful when verifying signatures based on embedded JWKs inside the
|
||||||
|
// payload header. You cannot assume that the key received in a payload is
|
||||||
|
// trusted.
|
||||||
|
func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
|
||||||
|
verifier, err := newVerifier(verificationKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(obj.Signatures) > 1 {
|
||||||
|
return nil, errors.New("square/go-jose: too many signatures in payload; expecting only one")
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := obj.Signatures[0]
|
||||||
|
headers := signature.mergedHeaders()
|
||||||
|
critical, err := headers.getCritical()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(critical) > 0 {
|
||||||
|
// Unsupported crit header
|
||||||
|
return nil, ErrCryptoFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
input := obj.computeAuthData(&signature)
|
||||||
|
alg := headers.getSignatureAlgorithm()
|
||||||
|
err = verifier.verifyPayload(input, signature.Signature, alg)
|
||||||
|
if err == nil {
|
||||||
|
return obj.payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrCryptoFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyMulti validates (one of the multiple) signatures on the object and
|
||||||
|
// returns the index of the signature that was verified, along with the signature
|
||||||
|
// object and the payload. We return the signature and index to guarantee that
|
||||||
|
// callers are getting the verified value.
|
||||||
|
func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signature, []byte, error) {
|
||||||
|
verifier, err := newVerifier(verificationKey)
|
||||||
|
if err != nil {
|
||||||
|
return -1, Signature{}, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, signature := range obj.Signatures {
|
||||||
|
headers := signature.mergedHeaders()
|
||||||
|
critical, err := headers.getCritical()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(critical) > 0 {
|
||||||
|
// Unsupported crit header
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
input := obj.computeAuthData(&signature)
|
||||||
|
alg := headers.getSignatureAlgorithm()
|
||||||
|
err = verifier.verifyPayload(input, signature.Signature, alg)
|
||||||
|
if err == nil {
|
||||||
|
return i, signature, obj.payload, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, Signature{}, nil, ErrCryptoFailure
|
||||||
|
}
|
|
@ -25,10 +25,11 @@ import (
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v1/cipher"
|
"gopkg.in/square/go-jose.v2/cipher"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Random reader (stubbed out in tests)
|
// Random reader (stubbed out in tests)
|
||||||
|
@ -229,11 +230,12 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
|
||||||
return recipientInfo{}, err
|
return recipientInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header := &rawHeader{}
|
||||||
|
header.set(headerIV, newBuffer(parts.iv))
|
||||||
|
header.set(headerTag, newBuffer(parts.tag))
|
||||||
|
|
||||||
return recipientInfo{
|
return recipientInfo{
|
||||||
header: &rawHeader{
|
header: header,
|
||||||
Iv: newBuffer(parts.iv),
|
|
||||||
Tag: newBuffer(parts.tag),
|
|
||||||
},
|
|
||||||
encryptedKey: parts.ciphertext,
|
encryptedKey: parts.ciphertext,
|
||||||
}, nil
|
}, nil
|
||||||
case A128KW, A192KW, A256KW:
|
case A128KW, A192KW, A256KW:
|
||||||
|
@ -258,7 +260,7 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
|
||||||
|
|
||||||
// Decrypt the content encryption key.
|
// Decrypt the content encryption key.
|
||||||
func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
||||||
switch KeyAlgorithm(headers.Alg) {
|
switch headers.getAlgorithm() {
|
||||||
case DIRECT:
|
case DIRECT:
|
||||||
cek := make([]byte, len(ctx.key))
|
cek := make([]byte, len(ctx.key))
|
||||||
copy(cek, ctx.key)
|
copy(cek, ctx.key)
|
||||||
|
@ -266,10 +268,19 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
|
||||||
case A128GCMKW, A192GCMKW, A256GCMKW:
|
case A128GCMKW, A192GCMKW, A256GCMKW:
|
||||||
aead := newAESGCM(len(ctx.key))
|
aead := newAESGCM(len(ctx.key))
|
||||||
|
|
||||||
|
iv, err := headers.getIV()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid IV: %v", err)
|
||||||
|
}
|
||||||
|
tag, err := headers.getTag()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid tag: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
parts := &aeadParts{
|
parts := &aeadParts{
|
||||||
iv: headers.Iv.bytes(),
|
iv: iv.bytes(),
|
||||||
ciphertext: recipient.encryptedKey,
|
ciphertext: recipient.encryptedKey,
|
||||||
tag: headers.Tag.bytes(),
|
tag: tag.bytes(),
|
||||||
}
|
}
|
||||||
|
|
||||||
cek, err := aead.decrypt(ctx.key, []byte{}, parts)
|
cek, err := aead.decrypt(ctx.key, []byte{}, parts)
|
32
vendor/manifest
vendored
32
vendor/manifest
vendored
|
@ -190,12 +190,12 @@
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"importpath": "github.com/xenolf/lego/acme",
|
"importpath": "github.com/xenolf/lego/acmev2",
|
||||||
"repository": "https://github.com/xenolf/lego",
|
"repository": "https://github.com/xenolf/lego",
|
||||||
"vcs": "git",
|
"vcs": "git",
|
||||||
"revision": "4dde48a9b9916926a8dd4f69639c8dba40930355",
|
"revision": "805eec97569ff533e1b75b16eac0bdd94e67bdd6",
|
||||||
"branch": "master",
|
"branch": "acmev2",
|
||||||
"path": "/acme",
|
"path": "/acmev2",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -207,15 +207,6 @@
|
||||||
"path": "/syncutil/singleflight",
|
"path": "/syncutil/singleflight",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"importpath": "golang.org/x/crypto/acme",
|
|
||||||
"repository": "https://go.googlesource.com/crypto",
|
|
||||||
"vcs": "git",
|
|
||||||
"revision": "2faea1465de239e4babd8f5905cc25b781712442",
|
|
||||||
"branch": "master",
|
|
||||||
"path": "acme",
|
|
||||||
"notests": true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"importpath": "golang.org/x/crypto/curve25519",
|
"importpath": "golang.org/x/crypto/curve25519",
|
||||||
"repository": "https://go.googlesource.com/crypto",
|
"repository": "https://go.googlesource.com/crypto",
|
||||||
|
@ -225,6 +216,15 @@
|
||||||
"path": "curve25519",
|
"path": "curve25519",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"importpath": "golang.org/x/crypto/ed25519",
|
||||||
|
"repository": "https://go.googlesource.com/crypto",
|
||||||
|
"vcs": "git",
|
||||||
|
"revision": "c4a91bd4f524f10d064139674cf55852e055ad01",
|
||||||
|
"branch": "master",
|
||||||
|
"path": "/ed25519",
|
||||||
|
"notests": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"importpath": "golang.org/x/crypto/hkdf",
|
"importpath": "golang.org/x/crypto/hkdf",
|
||||||
"repository": "https://go.googlesource.com/crypto",
|
"repository": "https://go.googlesource.com/crypto",
|
||||||
|
@ -447,10 +447,10 @@
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"importpath": "gopkg.in/square/go-jose.v1",
|
"importpath": "gopkg.in/square/go-jose.v2",
|
||||||
"repository": "https://gopkg.in/square/go-jose.v1",
|
"repository": "https://gopkg.in/square/go-jose.v2",
|
||||||
"vcs": "git",
|
"vcs": "git",
|
||||||
"revision": "aa2e30fdd1fe9dd3394119af66451ae790d50e0d",
|
"revision": "6ee92191fea850cdcab9a18867abf5f521cdbadb",
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue