mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-30 18:22:49 -04:00 
			
		
		
		
	Merge pull request #2072 from mholt/acmev2
tls: Use ACMEv2 and support automatic wildcard certificates
This commit is contained in:
		
						commit
						95514da91b
					
				| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user