mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 09:32:25 -05:00 
			
		
		
		
	[feature] Try HTTP signature validation with and without query params for incoming requests (#2591)
* [feature] Verify signatures both with + without query params * Bump to tagged version
This commit is contained in:
		
					parent
					
						
							
								c675d47a8c
							
						
					
				
			
			
				commit
				
					
						b614d33c40
					
				
			
		
					 18 changed files with 1799 additions and 22 deletions
				
			
		|  | @ -263,7 +263,6 @@ The following open source libraries, frameworks, and tools are used by GoToSocia | ||||||
|   - [gin-contrib/gzip](https://github.com/gin-contrib/gzip); Gin gzip middleware. [MIT License](https://spdx.org/licenses/MIT.html). |   - [gin-contrib/gzip](https://github.com/gin-contrib/gzip); Gin gzip middleware. [MIT License](https://spdx.org/licenses/MIT.html). | ||||||
|   - [gin-contrib/sessions](https://github.com/gin-contrib/sessions); Gin sessions middleware. [MIT License](https://spdx.org/licenses/MIT.html). |   - [gin-contrib/sessions](https://github.com/gin-contrib/sessions); Gin sessions middleware. [MIT License](https://spdx.org/licenses/MIT.html). | ||||||
|   - [gin-gonic/gin](https://github.com/gin-gonic/gin); speedy router engine. [MIT License](https://spdx.org/licenses/MIT.html). |   - [gin-gonic/gin](https://github.com/gin-gonic/gin); speedy router engine. [MIT License](https://spdx.org/licenses/MIT.html). | ||||||
| - [go-fed/httpsig](https://github.com/go-fed/httpsig); secure HTTP signature library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). |  | ||||||
| - [google/uuid](https://github.com/google/uuid); UUID generation. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | - [google/uuid](https://github.com/google/uuid); UUID generation. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | ||||||
| - [google/wuffs](https://github.com/google/wuffs); png-stripping code. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html). | - [google/wuffs](https://github.com/google/wuffs); png-stripping code. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html). | ||||||
| - Go-Playground: | - Go-Playground: | ||||||
|  | @ -306,6 +305,7 @@ The following open source libraries, frameworks, and tools are used by GoToSocia | ||||||
| - superseriousbusiness: | - superseriousbusiness: | ||||||
|   - [superseriousbusiness/activity](https://github.com/superseriousbusiness/activity) forked from [go-fed/activity](https://github.com/go-fed/activity); Golang ActivityPub/ActivityStreams library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). |   - [superseriousbusiness/activity](https://github.com/superseriousbusiness/activity) forked from [go-fed/activity](https://github.com/go-fed/activity); Golang ActivityPub/ActivityStreams library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | ||||||
|   - [superseriousbusiness/exif-terminator](https://codeberg.org/superseriousbusiness/exif-terminator); EXIF data removal. [GNU AGPL v3 LICENSE](https://spdx.org/licenses/AGPL-3.0-or-later.html). |   - [superseriousbusiness/exif-terminator](https://codeberg.org/superseriousbusiness/exif-terminator); EXIF data removal. [GNU AGPL v3 LICENSE](https://spdx.org/licenses/AGPL-3.0-or-later.html). | ||||||
|  |   - [superseriousbusiness/httpsig](https://github.com/superseriousbusiness/httpsig) forked from [go-fed/httpsig](https://github.com/go-fed/httpsig); secure HTTP signature library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | ||||||
|   - [superseriousbusiness/oauth2](https://github.com/superseriousbusiness/oauth2) forked from [go-oauth2/oauth2](https://github.com/go-oauth2/oauth2); OAuth server framework and token handling. [MIT License](https://spdx.org/licenses/MIT.html). |   - [superseriousbusiness/oauth2](https://github.com/superseriousbusiness/oauth2) forked from [go-oauth2/oauth2](https://github.com/go-oauth2/oauth2); OAuth server framework and token handling. [MIT License](https://spdx.org/licenses/MIT.html). | ||||||
| - [tdewolff/minify](https://github.com/tdewolff/minify); HTML minification for Markdown-submitted posts. [MIT License](https://spdx.org/licenses/MIT.html). | - [tdewolff/minify](https://github.com/tdewolff/minify); HTML minification for Markdown-submitted posts. [MIT License](https://spdx.org/licenses/MIT.html). | ||||||
| - [uber-go/automaxprocs](https://github.com/uber-go/automaxprocs); GOMAXPROCS automation. [MIT License](https://spdx.org/licenses/MIT.html). | - [uber-go/automaxprocs](https://github.com/uber-go/automaxprocs); GOMAXPROCS automation. [MIT License](https://spdx.org/licenses/MIT.html). | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ GoToSocial will also sign all outgoing `GET` and `POST` requests that it makes t | ||||||
| 
 | 
 | ||||||
| This behavior is the equivalent of Mastodon's [AUTHORIZED_FETCH / "secure mode"](https://docs.joinmastodon.org/admin/config/#authorized_fetch). | This behavior is the equivalent of Mastodon's [AUTHORIZED_FETCH / "secure mode"](https://docs.joinmastodon.org/admin/config/#authorized_fetch). | ||||||
| 
 | 
 | ||||||
| GoToSocial uses the [go-fed/httpsig](https://github.com/go-fed/httpsig) library for signing outgoing requests, and for parsing and validating the signatures of incoming requests. This library strictly follows the [Cavage http signature RFC](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12), which is the same RFC used by other implementations like Mastodon, Pixelfed, Akkoma/Pleroma, etc. (This RFC has since been superceded by the [httpbis http signature RFC](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures), but this is not yet widely implemented.) | GoToSocial uses the [superseriousbusiness/httpsig](https://github.com/superseriousbusiness/httpsign) library (forked from go-fed) for signing outgoing requests, and for parsing and validating the signatures of incoming requests. This library strictly follows the [Cavage http signature RFC](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12), which is the same RFC used by other implementations like Mastodon, Pixelfed, Akkoma/Pleroma, etc. (This RFC has since been superceded by the [httpbis http signature RFC](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures), but this is not yet widely implemented.) | ||||||
| 
 | 
 | ||||||
| ### Incoming Requests | ### Incoming Requests | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -31,7 +31,6 @@ require ( | ||||||
| 	github.com/gin-contrib/gzip v0.0.6 | 	github.com/gin-contrib/gzip v0.0.6 | ||||||
| 	github.com/gin-contrib/sessions v0.0.5 | 	github.com/gin-contrib/sessions v0.0.5 | ||||||
| 	github.com/gin-gonic/gin v1.9.1 | 	github.com/gin-gonic/gin v1.9.1 | ||||||
| 	github.com/go-fed/httpsig v1.1.0 |  | ||||||
| 	github.com/go-playground/form/v4 v4.2.1 | 	github.com/go-playground/form/v4 v4.2.1 | ||||||
| 	github.com/google/uuid v1.5.0 | 	github.com/google/uuid v1.5.0 | ||||||
| 	github.com/gorilla/feeds v1.1.2 | 	github.com/gorilla/feeds v1.1.2 | ||||||
|  | @ -48,6 +47,7 @@ require ( | ||||||
| 	github.com/spf13/viper v1.18.2 | 	github.com/spf13/viper v1.18.2 | ||||||
| 	github.com/stretchr/testify v1.8.4 | 	github.com/stretchr/testify v1.8.4 | ||||||
| 	github.com/superseriousbusiness/activity v1.4.0-gts | 	github.com/superseriousbusiness/activity v1.4.0-gts | ||||||
|  | 	github.com/superseriousbusiness/httpsig v1.2.0-SSB | ||||||
| 	github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 | 	github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 | ||||||
| 	github.com/tdewolff/minify/v2 v2.20.14 | 	github.com/tdewolff/minify/v2 v2.20.14 | ||||||
| 	github.com/technologize/otel-go-contrib v1.1.0 | 	github.com/technologize/otel-go-contrib v1.1.0 | ||||||
|  | @ -106,6 +106,7 @@ require ( | ||||||
| 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect | 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect | ||||||
| 	github.com/gin-contrib/sse v0.1.0 // indirect | 	github.com/gin-contrib/sse v0.1.0 // indirect | ||||||
| 	github.com/go-errors/errors v1.4.1 // indirect | 	github.com/go-errors/errors v1.4.1 // indirect | ||||||
|  | 	github.com/go-fed/httpsig v1.1.0 // indirect | ||||||
| 	github.com/go-jose/go-jose/v3 v3.0.1 // indirect | 	github.com/go-jose/go-jose/v3 v3.0.1 // indirect | ||||||
| 	github.com/go-logr/logr v1.4.1 // indirect | 	github.com/go-logr/logr v1.4.1 // indirect | ||||||
| 	github.com/go-logr/stdr v1.2.2 // indirect | 	github.com/go-logr/stdr v1.2.2 // indirect | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								go.sum
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
										
									
									
									
								
							|  | @ -489,6 +489,8 @@ github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430 | ||||||
| github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe/go.mod h1:gH4P6gN1V+wmIw5o97KGaa1RgXB/tVpC2UNzijhg3E4= | github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe/go.mod h1:gH4P6gN1V+wmIw5o97KGaa1RgXB/tVpC2UNzijhg3E4= | ||||||
| github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB h1:8psprYSK1KdOSH7yQ4PbJq0YYaGQY+gzdW/B0ExDb/8= | github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB h1:8psprYSK1KdOSH7yQ4PbJq0YYaGQY+gzdW/B0ExDb/8= | ||||||
| github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB/go.mod h1:ymKGfy9kg4dIdraeZRAdobMS/flzLk3VcRPLpEWOAXg= | github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB/go.mod h1:ymKGfy9kg4dIdraeZRAdobMS/flzLk3VcRPLpEWOAXg= | ||||||
|  | github.com/superseriousbusiness/httpsig v1.2.0-SSB h1:BinBGKbf2LSuVT5+MuH0XynHN9f0XVshx2CTDtkaWj0= | ||||||
|  | github.com/superseriousbusiness/httpsig v1.2.0-SSB/go.mod h1:+rxfATjFaDoDIVaJOTSP0gj6UrbicaYPEptvCLC9F28= | ||||||
| github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 h1:nTIhuP157oOFcscuoK1kCme1xTeGIzztSw70lX9NrDQ= | github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 h1:nTIhuP157oOFcscuoK1kCme1xTeGIzztSw70lX9NrDQ= | ||||||
| github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo= | github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo= | ||||||
| github.com/tdewolff/minify/v2 v2.20.14 h1:sktSuVixRwk0ryQjqvKBu/uYS+MWmkwEFMEWtFZ+TdE= | github.com/tdewolff/minify/v2 v2.20.14 h1:sktSuVixRwk0ryQjqvKBu/uYS+MWmkwEFMEWtFZ+TdE= | ||||||
|  |  | ||||||
|  | @ -28,7 +28,6 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"codeberg.org/gruf/go-kv" | 	"codeberg.org/gruf/go-kv" | ||||||
| 	"github.com/go-fed/httpsig" |  | ||||||
| 	"github.com/superseriousbusiness/activity/streams" | 	"github.com/superseriousbusiness/activity/streams" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
|  | @ -37,6 +36,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/log" | 	"github.com/superseriousbusiness/gotosocial/internal/log" | ||||||
|  | 	"github.com/superseriousbusiness/httpsig" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
|  | @ -509,25 +509,63 @@ var signingAlgorithms = []httpsig.Algorithm{ | ||||||
| 	httpsig.ED25519,    // Try ED25519 as a long shot. | 	httpsig.ED25519,    // Try ED25519 as a long shot. | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // verifyAuth verifies auth using generated verifier, according to pubkey and our supported signing algorithms. | // Cheeky type to wrap a signing option with a | ||||||
| func verifyAuth(l *log.Entry, verifier httpsig.Verifier, pubKey *rsa.PublicKey) bool { | // description of that option for logging purposes. | ||||||
|  | type signingOption struct { | ||||||
|  | 	desc   string                  // Description of this options set. | ||||||
|  | 	sigOpt httpsig.SignatureOption // The options themselves. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var signingOptions = []signingOption{ | ||||||
|  | 	{ | ||||||
|  | 		// Prefer include query params. | ||||||
|  | 		desc: "include query params", | ||||||
|  | 		sigOpt: httpsig.SignatureOption{ | ||||||
|  | 			ExcludeQueryStringFromPathPseudoHeader: false, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		// Fall back to exclude query params. | ||||||
|  | 		desc: "exclude query params", | ||||||
|  | 		sigOpt: httpsig.SignatureOption{ | ||||||
|  | 			ExcludeQueryStringFromPathPseudoHeader: true, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // verifyAuth verifies auth using generated verifier, | ||||||
|  | // according to pubkey, our supported signing algorithms, | ||||||
|  | // and signature options. The loops in the function are | ||||||
|  | // arranged in such a way that the most common combos are | ||||||
|  | // tried first, so that we can hopefully succeed quickly | ||||||
|  | // without wasting too many CPU cycles. | ||||||
|  | func verifyAuth( | ||||||
|  | 	l *log.Entry, | ||||||
|  | 	verifier httpsig.VerifierWithOptions, | ||||||
|  | 	pubKey *rsa.PublicKey, | ||||||
|  | ) bool { | ||||||
| 	if pubKey == nil { | 	if pubKey == nil { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Loop through all supported algorithms. | 	// Loop through supported algorithms. | ||||||
| 	for _, algo := range signingAlgorithms { | 	for _, algo := range signingAlgorithms { | ||||||
| 
 | 
 | ||||||
| 		// Verify according to pubkey and algo. | 		// Loop through signing options. | ||||||
| 		err := verifier.Verify(pubKey, algo) | 		for _, opt := range signingOptions { | ||||||
|  | 
 | ||||||
|  | 			// Try to verify according to this pubkey, | ||||||
|  | 			// algo, and signing options combination. | ||||||
|  | 			err := verifier.VerifyWithOptions(pubKey, algo, opt.sigOpt) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 			l.Tracef("authentication NOT PASSED with %s: %v", algo, err) | 				l.Tracef("authentication NOT PASSED with %s (%s): %v", algo, opt.desc, err) | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 		l.Tracef("authenticated PASSED with %s", algo) | 			l.Tracef("authenticated PASSED with %s (%s)", algo, opt.desc) | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -27,12 +27,12 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/go-fed/httpsig" |  | ||||||
| 	"github.com/stretchr/testify/suite" | 	"github.com/stretchr/testify/suite" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/testrig" | 	"github.com/superseriousbusiness/gotosocial/testrig" | ||||||
|  | 	"github.com/superseriousbusiness/httpsig" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type FederatingProtocolTestSuite struct { | type FederatingProtocolTestSuite struct { | ||||||
|  |  | ||||||
|  | @ -21,8 +21,8 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 
 | 
 | ||||||
| 	"github.com/go-fed/httpsig" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/httpsig" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // package private context key type. | // package private context key type. | ||||||
|  | @ -129,8 +129,8 @@ func SetOtherIRIs(ctx context.Context, iris []*url.URL) context.Context { | ||||||
| 
 | 
 | ||||||
| // HTTPSignatureVerifier returns an http signature verifier for the current ActivityPub | // HTTPSignatureVerifier returns an http signature verifier for the current ActivityPub | ||||||
| // request chain. This verifier can be called to authenticate the current request. | // request chain. This verifier can be called to authenticate the current request. | ||||||
| func HTTPSignatureVerifier(ctx context.Context) httpsig.Verifier { | func HTTPSignatureVerifier(ctx context.Context) httpsig.VerifierWithOptions { | ||||||
| 	verifier, _ := ctx.Value(httpSigVerifierKey).(httpsig.Verifier) | 	verifier, _ := ctx.Value(httpSigVerifierKey).(httpsig.VerifierWithOptions) | ||||||
| 	return verifier | 	return verifier | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/log" | 	"github.com/superseriousbusiness/gotosocial/internal/log" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| 	"github.com/go-fed/httpsig" | 	"github.com/superseriousbusiness/httpsig" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ | ||||||
| package transport | package transport | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/go-fed/httpsig" | 	"github.com/superseriousbusiness/httpsig" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
|  |  | ||||||
|  | @ -27,10 +27,10 @@ import ( | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/go-fed/httpsig" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/httpclient" | 	"github.com/superseriousbusiness/gotosocial/internal/httpclient" | ||||||
|  | 	"github.com/superseriousbusiness/httpsig" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Transport implements the pub.Transport interface with some additional functionality for fetching remote media. | // Transport implements the pub.Transport interface with some additional functionality for fetching remote media. | ||||||
|  |  | ||||||
							
								
								
									
										29
									
								
								vendor/github.com/superseriousbusiness/httpsig/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								vendor/github.com/superseriousbusiness/httpsig/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | BSD 3-Clause License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2018, go-fed | ||||||
|  | All rights reserved. | ||||||
|  | 
 | ||||||
|  | Redistribution and use in source and binary forms, with or without | ||||||
|  | modification, are permitted provided that the following conditions are met: | ||||||
|  | 
 | ||||||
|  | * Redistributions of source code must retain the above copyright notice, this | ||||||
|  |   list of conditions and the following disclaimer. | ||||||
|  | 
 | ||||||
|  | * Redistributions in binary form must reproduce the above copyright notice, | ||||||
|  |   this list of conditions and the following disclaimer in the documentation | ||||||
|  |   and/or other materials provided with the distribution. | ||||||
|  | 
 | ||||||
|  | * Neither the name of the copyright holder nor the names of its | ||||||
|  |   contributors may be used to endorse or promote products derived from | ||||||
|  |   this software without specific prior written permission. | ||||||
|  | 
 | ||||||
|  | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||||
|  | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||||
|  | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||||
|  | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||||
|  | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||||
|  | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||||
|  | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||||
|  | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||||
|  | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||||
|  | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
							
								
								
									
										101
									
								
								vendor/github.com/superseriousbusiness/httpsig/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								vendor/github.com/superseriousbusiness/httpsig/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,101 @@ | ||||||
|  | # httpsig | ||||||
|  | 
 | ||||||
|  | **THIS IS A FORK OF https://github.com/go-fed/httpsig, WHICH WAS NO LONGER MAINTAINED. THANK YOU TO [cjslep](https://github.com/cjslep) FOR ALL YOUR HARD WORK!** | ||||||
|  | 
 | ||||||
|  | > HTTP Signatures made simple | ||||||
|  | 
 | ||||||
|  | `go get github.com/superseriousbusiness/httpsig` | ||||||
|  | 
 | ||||||
|  | Implementation of [HTTP Signatures](https://tools.ietf.org/html/draft-cavage-http-signatures). | ||||||
|  | 
 | ||||||
|  | Supports many different combinations of MAC, HMAC signing of hash, or RSA | ||||||
|  | signing of hash schemes. Its goals are: | ||||||
|  | 
 | ||||||
|  | * Have a very simple interface for signing and validating | ||||||
|  | * Support a variety of signing algorithms and combinations | ||||||
|  | * Support setting either headers (`Authorization` or `Signature`) | ||||||
|  | * Remaining flexible with headers included in the signing string | ||||||
|  | * Support both HTTP requests and responses | ||||||
|  | * Explicitly not support known-cryptographically weak algorithms | ||||||
|  | * Support automatic signing and validating Digest headers | ||||||
|  | 
 | ||||||
|  | ## How to use | ||||||
|  | 
 | ||||||
|  | `import "github.com/superseriousbusiness/httpsig"` | ||||||
|  | 
 | ||||||
|  | ### Signing | ||||||
|  | 
 | ||||||
|  | Signing a request or response requires creating a new `Signer` and using it: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | func sign(privateKey crypto.PrivateKey, pubKeyId string, r *http.Request) error { | ||||||
|  | 	prefs := []httpsig.Algorithm{httpsig.RSA_SHA512, httpsig.RSA_SHA256} | ||||||
|  | 	digestAlgorithm := DigestSha256 | ||||||
|  | 	// The "Date" and "Digest" headers must already be set on r, as well as r.URL. | ||||||
|  | 	headersToSign := []string{httpsig.RequestTarget, "date", "digest"} | ||||||
|  | 	signer, chosenAlgo, err := httpsig.NewSigner(prefs, digestAlgorithm, headersToSign, httpsig.Signature) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	// To sign the digest, we need to give the signer a copy of the body... | ||||||
|  | 	// ...but it is optional, no digest will be signed if given "nil" | ||||||
|  | 	body := ... | ||||||
|  | 	// If r were a http.ResponseWriter, call SignResponse instead. | ||||||
|  | 	return signer.SignRequest(privateKey, pubKeyId, r, body) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | `Signer`s are not safe for concurrent use by goroutines, so be sure to guard | ||||||
|  | access: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | type server struct { | ||||||
|  | 	signer httpsig.Signer | ||||||
|  | 	mu *sync.Mutex | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *server) handlerFunc(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	privateKey := ... | ||||||
|  | 	pubKeyId := ... | ||||||
|  | 	// Set headers and such on w | ||||||
|  | 	s.mu.Lock() | ||||||
|  | 	defer s.mu.Unlock() | ||||||
|  | 	// To sign the digest, we need to give the signer a copy of the response body... | ||||||
|  | 	// ...but it is optional, no digest will be signed if given "nil" | ||||||
|  | 	body := ... | ||||||
|  | 	err := s.signer.SignResponse(privateKey, pubKeyId, w, body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		... | ||||||
|  | 	} | ||||||
|  | 	... | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The `pubKeyId` will be used at verification time. | ||||||
|  | 
 | ||||||
|  | ### Verifying | ||||||
|  | 
 | ||||||
|  | Verifying requires an application to use the `pubKeyId` to both retrieve the key | ||||||
|  | needed for verification as well as determine the algorithm to use. Use a | ||||||
|  | `Verifier`: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | func verify(r *http.Request) error { | ||||||
|  | 	verifier, err := httpsig.NewVerifier(r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	pubKeyId := verifier.KeyId() | ||||||
|  | 	var algo httpsig.Algorithm = ... | ||||||
|  | 	var pubKey crypto.PublicKey = ... | ||||||
|  | 	// The verifier will verify the Digest in addition to the HTTP signature | ||||||
|  | 	return verifier.Verify(pubKey, algo) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | `Verifier`s are not safe for concurrent use by goroutines, but since they are | ||||||
|  | constructed on a per-request or per-response basis it should not be a common | ||||||
|  | restriction. | ||||||
|  | 
 | ||||||
|  | [License-Image]: https://img.shields.io/github/license/go-fed/httpsig?color=blue | ||||||
|  | [License-Url]: https://opensource.org/licenses/BSD-3-Clause | ||||||
							
								
								
									
										532
									
								
								vendor/github.com/superseriousbusiness/httpsig/algorithms.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										532
									
								
								vendor/github.com/superseriousbusiness/httpsig/algorithms.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,532 @@ | ||||||
|  | package httpsig | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto" | ||||||
|  | 	"crypto/ecdsa" | ||||||
|  | 	"crypto/hmac" | ||||||
|  | 	"crypto/rsa" | ||||||
|  | 	"crypto/sha1" | ||||||
|  | 	"crypto/sha256" | ||||||
|  | 	"crypto/sha512" | ||||||
|  | 	"crypto/subtle" // Use should trigger great care | ||||||
|  | 	"encoding/asn1" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"hash" | ||||||
|  | 	"io" | ||||||
|  | 	"math/big" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/crypto/blake2b" | ||||||
|  | 	"golang.org/x/crypto/blake2s" | ||||||
|  | 	"golang.org/x/crypto/ed25519" | ||||||
|  | 	"golang.org/x/crypto/ripemd160" | ||||||
|  | 	"golang.org/x/crypto/sha3" | ||||||
|  | 	"golang.org/x/crypto/ssh" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	hmacPrefix        = "hmac" | ||||||
|  | 	rsaPrefix         = "rsa" | ||||||
|  | 	sshPrefix         = "ssh" | ||||||
|  | 	ecdsaPrefix       = "ecdsa" | ||||||
|  | 	ed25519Prefix     = "ed25519" | ||||||
|  | 	md4String         = "md4" | ||||||
|  | 	md5String         = "md5" | ||||||
|  | 	sha1String        = "sha1" | ||||||
|  | 	sha224String      = "sha224" | ||||||
|  | 	sha256String      = "sha256" | ||||||
|  | 	sha384String      = "sha384" | ||||||
|  | 	sha512String      = "sha512" | ||||||
|  | 	md5sha1String     = "md5sha1" | ||||||
|  | 	ripemd160String   = "ripemd160" | ||||||
|  | 	sha3_224String    = "sha3-224" | ||||||
|  | 	sha3_256String    = "sha3-256" | ||||||
|  | 	sha3_384String    = "sha3-384" | ||||||
|  | 	sha3_512String    = "sha3-512" | ||||||
|  | 	sha512_224String  = "sha512-224" | ||||||
|  | 	sha512_256String  = "sha512-256" | ||||||
|  | 	blake2s_256String = "blake2s-256" | ||||||
|  | 	blake2b_256String = "blake2b-256" | ||||||
|  | 	blake2b_384String = "blake2b-384" | ||||||
|  | 	blake2b_512String = "blake2b-512" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var blake2Algorithms = map[crypto.Hash]bool{ | ||||||
|  | 	crypto.BLAKE2s_256: true, | ||||||
|  | 	crypto.BLAKE2b_256: true, | ||||||
|  | 	crypto.BLAKE2b_384: true, | ||||||
|  | 	crypto.BLAKE2b_512: true, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var hashToDef = map[crypto.Hash]struct { | ||||||
|  | 	name string | ||||||
|  | 	new  func(key []byte) (hash.Hash, error) // Only MACers will accept a key | ||||||
|  | }{ | ||||||
|  | 	// Which standard names these? | ||||||
|  | 	// The spec lists the following as a canonical reference, which is dead: | ||||||
|  | 	// http://www.iana.org/assignments/signature-algorithms | ||||||
|  | 	// | ||||||
|  | 	// Note that the forbidden hashes have an invalid 'new' function. | ||||||
|  | 	crypto.MD4: {md4String, func(key []byte) (hash.Hash, error) { return nil, nil }}, | ||||||
|  | 	crypto.MD5: {md5String, func(key []byte) (hash.Hash, error) { return nil, nil }}, | ||||||
|  | 	// Temporarily enable SHA1 because of issue https://github.com/golang/go/issues/37278 | ||||||
|  | 	crypto.SHA1:        {sha1String, func(key []byte) (hash.Hash, error) { return sha1.New(), nil }}, | ||||||
|  | 	crypto.SHA224:      {sha224String, func(key []byte) (hash.Hash, error) { return sha256.New224(), nil }}, | ||||||
|  | 	crypto.SHA256:      {sha256String, func(key []byte) (hash.Hash, error) { return sha256.New(), nil }}, | ||||||
|  | 	crypto.SHA384:      {sha384String, func(key []byte) (hash.Hash, error) { return sha512.New384(), nil }}, | ||||||
|  | 	crypto.SHA512:      {sha512String, func(key []byte) (hash.Hash, error) { return sha512.New(), nil }}, | ||||||
|  | 	crypto.MD5SHA1:     {md5sha1String, func(key []byte) (hash.Hash, error) { return nil, nil }}, | ||||||
|  | 	crypto.RIPEMD160:   {ripemd160String, func(key []byte) (hash.Hash, error) { return ripemd160.New(), nil }}, | ||||||
|  | 	crypto.SHA3_224:    {sha3_224String, func(key []byte) (hash.Hash, error) { return sha3.New224(), nil }}, | ||||||
|  | 	crypto.SHA3_256:    {sha3_256String, func(key []byte) (hash.Hash, error) { return sha3.New256(), nil }}, | ||||||
|  | 	crypto.SHA3_384:    {sha3_384String, func(key []byte) (hash.Hash, error) { return sha3.New384(), nil }}, | ||||||
|  | 	crypto.SHA3_512:    {sha3_512String, func(key []byte) (hash.Hash, error) { return sha3.New512(), nil }}, | ||||||
|  | 	crypto.SHA512_224:  {sha512_224String, func(key []byte) (hash.Hash, error) { return sha512.New512_224(), nil }}, | ||||||
|  | 	crypto.SHA512_256:  {sha512_256String, func(key []byte) (hash.Hash, error) { return sha512.New512_256(), nil }}, | ||||||
|  | 	crypto.BLAKE2s_256: {blake2s_256String, func(key []byte) (hash.Hash, error) { return blake2s.New256(key) }}, | ||||||
|  | 	crypto.BLAKE2b_256: {blake2b_256String, func(key []byte) (hash.Hash, error) { return blake2b.New256(key) }}, | ||||||
|  | 	crypto.BLAKE2b_384: {blake2b_384String, func(key []byte) (hash.Hash, error) { return blake2b.New384(key) }}, | ||||||
|  | 	crypto.BLAKE2b_512: {blake2b_512String, func(key []byte) (hash.Hash, error) { return blake2b.New512(key) }}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var stringToHash map[string]crypto.Hash | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	defaultAlgorithm        = RSA_SHA256 | ||||||
|  | 	defaultAlgorithmHashing = sha256String | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	stringToHash = make(map[string]crypto.Hash, len(hashToDef)) | ||||||
|  | 	for k, v := range hashToDef { | ||||||
|  | 		stringToHash[v.name] = k | ||||||
|  | 	} | ||||||
|  | 	// This should guarantee that at runtime the defaultAlgorithm will not | ||||||
|  | 	// result in errors when fetching a macer or signer (see algorithms.go) | ||||||
|  | 	if ok, err := isAvailable(string(defaultAlgorithmHashing)); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} else if !ok { | ||||||
|  | 		panic(fmt.Sprintf("the default httpsig algorithm is unavailable: %q", defaultAlgorithm)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isForbiddenHash(h crypto.Hash) bool { | ||||||
|  | 	switch h { | ||||||
|  | 	// Not actually cryptographically secure | ||||||
|  | 	case crypto.MD4: | ||||||
|  | 		fallthrough | ||||||
|  | 	case crypto.MD5: | ||||||
|  | 		fallthrough | ||||||
|  | 	case crypto.MD5SHA1: // shorthand for crypto/tls, not actually implemented | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	// Still cryptographically secure | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // signer is an internally public type. | ||||||
|  | type signer interface { | ||||||
|  | 	Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) | ||||||
|  | 	Verify(pub crypto.PublicKey, toHash, signature []byte) error | ||||||
|  | 	String() string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // macer is an internally public type. | ||||||
|  | type macer interface { | ||||||
|  | 	Sign(sig, key []byte) ([]byte, error) | ||||||
|  | 	Equal(sig, actualMAC, key []byte) (bool, error) | ||||||
|  | 	String() string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ macer = &hmacAlgorithm{} | ||||||
|  | 
 | ||||||
|  | type hmacAlgorithm struct { | ||||||
|  | 	fn   func(key []byte) (hash.Hash, error) | ||||||
|  | 	kind crypto.Hash | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (h *hmacAlgorithm) Sign(sig, key []byte) ([]byte, error) { | ||||||
|  | 	hs, err := h.fn(key) | ||||||
|  | 	if err = setSig(hs, sig); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return hs.Sum(nil), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (h *hmacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) { | ||||||
|  | 	hs, err := h.fn(key) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	defer hs.Reset() | ||||||
|  | 	err = setSig(hs, sig) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	expected := hs.Sum(nil) | ||||||
|  | 	return hmac.Equal(actualMAC, expected), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (h *hmacAlgorithm) String() string { | ||||||
|  | 	return fmt.Sprintf("%s-%s", hmacPrefix, hashToDef[h.kind].name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ signer = &rsaAlgorithm{} | ||||||
|  | 
 | ||||||
|  | type rsaAlgorithm struct { | ||||||
|  | 	hash.Hash | ||||||
|  | 	kind      crypto.Hash | ||||||
|  | 	sshSigner ssh.Signer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *rsaAlgorithm) setSig(b []byte) error { | ||||||
|  | 	n, err := r.Write(b) | ||||||
|  | 	if err != nil { | ||||||
|  | 		r.Reset() | ||||||
|  | 		return err | ||||||
|  | 	} else if n != len(b) { | ||||||
|  | 		r.Reset() | ||||||
|  | 		return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *rsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { | ||||||
|  | 	if r.sshSigner != nil { | ||||||
|  | 		sshsig, err := r.sshSigner.Sign(rand, sig) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return sshsig.Blob, nil | ||||||
|  | 	} | ||||||
|  | 	defer r.Reset() | ||||||
|  | 
 | ||||||
|  | 	if err := r.setSig(sig); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	rsaK, ok := p.(*rsa.PrivateKey) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, errors.New("crypto.PrivateKey is not *rsa.PrivateKey") | ||||||
|  | 	} | ||||||
|  | 	return rsa.SignPKCS1v15(rand, rsaK, r.kind, r.Sum(nil)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *rsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { | ||||||
|  | 	defer r.Reset() | ||||||
|  | 	rsaK, ok := pub.(*rsa.PublicKey) | ||||||
|  | 	if !ok { | ||||||
|  | 		return errors.New("crypto.PublicKey is not *rsa.PublicKey") | ||||||
|  | 	} | ||||||
|  | 	if err := r.setSig(toHash); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return rsa.VerifyPKCS1v15(rsaK, r.kind, r.Sum(nil), signature) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *rsaAlgorithm) String() string { | ||||||
|  | 	return fmt.Sprintf("%s-%s", rsaPrefix, hashToDef[r.kind].name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ signer = &ed25519Algorithm{} | ||||||
|  | 
 | ||||||
|  | type ed25519Algorithm struct { | ||||||
|  | 	sshSigner ssh.Signer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *ed25519Algorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { | ||||||
|  | 	if r.sshSigner != nil { | ||||||
|  | 		sshsig, err := r.sshSigner.Sign(rand, sig) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return sshsig.Blob, nil | ||||||
|  | 	} | ||||||
|  | 	ed25519K, ok := p.(ed25519.PrivateKey) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, errors.New("crypto.PrivateKey is not ed25519.PrivateKey") | ||||||
|  | 	} | ||||||
|  | 	return ed25519.Sign(ed25519K, sig), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *ed25519Algorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { | ||||||
|  | 	ed25519K, ok := pub.(ed25519.PublicKey) | ||||||
|  | 	if !ok { | ||||||
|  | 		return errors.New("crypto.PublicKey is not ed25519.PublicKey") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if ed25519.Verify(ed25519K, toHash, signature) { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return errors.New("ed25519 verify failed") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *ed25519Algorithm) String() string { | ||||||
|  | 	return fmt.Sprintf("%s", ed25519Prefix) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ signer = &ecdsaAlgorithm{} | ||||||
|  | 
 | ||||||
|  | type ecdsaAlgorithm struct { | ||||||
|  | 	hash.Hash | ||||||
|  | 	kind crypto.Hash | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *ecdsaAlgorithm) setSig(b []byte) error { | ||||||
|  | 	n, err := r.Write(b) | ||||||
|  | 	if err != nil { | ||||||
|  | 		r.Reset() | ||||||
|  | 		return err | ||||||
|  | 	} else if n != len(b) { | ||||||
|  | 		r.Reset() | ||||||
|  | 		return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ECDSASignature struct { | ||||||
|  | 	R, S *big.Int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *ecdsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { | ||||||
|  | 	defer r.Reset() | ||||||
|  | 	if err := r.setSig(sig); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	ecdsaK, ok := p.(*ecdsa.PrivateKey) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, errors.New("crypto.PrivateKey is not *ecdsa.PrivateKey") | ||||||
|  | 	} | ||||||
|  | 	R, S, err := ecdsa.Sign(rand, ecdsaK, r.Sum(nil)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	signature := ECDSASignature{R: R, S: S} | ||||||
|  | 	bytes, err := asn1.Marshal(signature) | ||||||
|  | 
 | ||||||
|  | 	return bytes, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *ecdsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { | ||||||
|  | 	defer r.Reset() | ||||||
|  | 	ecdsaK, ok := pub.(*ecdsa.PublicKey) | ||||||
|  | 	if !ok { | ||||||
|  | 		return errors.New("crypto.PublicKey is not *ecdsa.PublicKey") | ||||||
|  | 	} | ||||||
|  | 	if err := r.setSig(toHash); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sig := new(ECDSASignature) | ||||||
|  | 	_, err := asn1.Unmarshal(signature, sig) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if ecdsa.Verify(ecdsaK, r.Sum(nil), sig.R, sig.S) { | ||||||
|  | 		return nil | ||||||
|  | 	} else { | ||||||
|  | 		return errors.New("Invalid signature") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *ecdsaAlgorithm) String() string { | ||||||
|  | 	return fmt.Sprintf("%s-%s", ecdsaPrefix, hashToDef[r.kind].name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ macer = &blakeMacAlgorithm{} | ||||||
|  | 
 | ||||||
|  | type blakeMacAlgorithm struct { | ||||||
|  | 	fn   func(key []byte) (hash.Hash, error) | ||||||
|  | 	kind crypto.Hash | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *blakeMacAlgorithm) Sign(sig, key []byte) ([]byte, error) { | ||||||
|  | 	hs, err := r.fn(key) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if err = setSig(hs, sig); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return hs.Sum(nil), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *blakeMacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) { | ||||||
|  | 	hs, err := r.fn(key) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	defer hs.Reset() | ||||||
|  | 	err = setSig(hs, sig) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	expected := hs.Sum(nil) | ||||||
|  | 	return subtle.ConstantTimeCompare(actualMAC, expected) == 1, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *blakeMacAlgorithm) String() string { | ||||||
|  | 	return fmt.Sprintf("%s", hashToDef[r.kind].name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func setSig(a hash.Hash, b []byte) error { | ||||||
|  | 	n, err := a.Write(b) | ||||||
|  | 	if err != nil { | ||||||
|  | 		a.Reset() | ||||||
|  | 		return err | ||||||
|  | 	} else if n != len(b) { | ||||||
|  | 		a.Reset() | ||||||
|  | 		return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsSupportedHttpSigAlgorithm returns true if the string is supported by this | ||||||
|  | // library, is not a hash known to be weak, and is supported by the hardware. | ||||||
|  | func IsSupportedHttpSigAlgorithm(algo string) bool { | ||||||
|  | 	a, err := isAvailable(algo) | ||||||
|  | 	return a && err == nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // isAvailable is an internally public function | ||||||
|  | func isAvailable(algo string) (bool, error) { | ||||||
|  | 	c, ok := stringToHash[algo] | ||||||
|  | 	if !ok { | ||||||
|  | 		return false, fmt.Errorf("no match for %q", algo) | ||||||
|  | 	} | ||||||
|  | 	if isForbiddenHash(c) { | ||||||
|  | 		return false, fmt.Errorf("forbidden hash type in %q", algo) | ||||||
|  | 	} | ||||||
|  | 	return c.Available(), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newAlgorithmConstructor(algo string) (fn func(k []byte) (hash.Hash, error), c crypto.Hash, e error) { | ||||||
|  | 	ok := false | ||||||
|  | 	c, ok = stringToHash[algo] | ||||||
|  | 	if !ok { | ||||||
|  | 		e = fmt.Errorf("no match for %q", algo) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if isForbiddenHash(c) { | ||||||
|  | 		e = fmt.Errorf("forbidden hash type in %q", algo) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	algoDef, ok := hashToDef[c] | ||||||
|  | 	if !ok { | ||||||
|  | 		e = fmt.Errorf("have crypto.Hash %v but no definition", c) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	fn = func(key []byte) (hash.Hash, error) { | ||||||
|  | 		h, err := algoDef.new(key) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return h, nil | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newAlgorithm(algo string, key []byte) (hash.Hash, crypto.Hash, error) { | ||||||
|  | 	fn, c, err := newAlgorithmConstructor(algo) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, c, err | ||||||
|  | 	} | ||||||
|  | 	h, err := fn(key) | ||||||
|  | 	return h, c, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func signerFromSSHSigner(sshSigner ssh.Signer, s string) (signer, error) { | ||||||
|  | 	switch { | ||||||
|  | 	case strings.HasPrefix(s, rsaPrefix): | ||||||
|  | 		return &rsaAlgorithm{ | ||||||
|  | 			sshSigner: sshSigner, | ||||||
|  | 		}, nil | ||||||
|  | 	case strings.HasPrefix(s, ed25519Prefix): | ||||||
|  | 		return &ed25519Algorithm{ | ||||||
|  | 			sshSigner: sshSigner, | ||||||
|  | 		}, nil | ||||||
|  | 	default: | ||||||
|  | 		return nil, fmt.Errorf("no signer matching %q", s) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // signerFromString is an internally public method constructor | ||||||
|  | func signerFromString(s string) (signer, error) { | ||||||
|  | 	s = strings.ToLower(s) | ||||||
|  | 	isEcdsa := false | ||||||
|  | 	isEd25519 := false | ||||||
|  | 	var algo string = "" | ||||||
|  | 	if strings.HasPrefix(s, ecdsaPrefix) { | ||||||
|  | 		algo = strings.TrimPrefix(s, ecdsaPrefix+"-") | ||||||
|  | 		isEcdsa = true | ||||||
|  | 	} else if strings.HasPrefix(s, rsaPrefix) { | ||||||
|  | 		algo = strings.TrimPrefix(s, rsaPrefix+"-") | ||||||
|  | 	} else if strings.HasPrefix(s, ed25519Prefix) { | ||||||
|  | 		isEd25519 = true | ||||||
|  | 		algo = "sha512" | ||||||
|  | 	} else { | ||||||
|  | 		return nil, fmt.Errorf("no signer matching %q", s) | ||||||
|  | 	} | ||||||
|  | 	hash, cHash, err := newAlgorithm(algo, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if isEd25519 { | ||||||
|  | 		return &ed25519Algorithm{}, nil | ||||||
|  | 	} | ||||||
|  | 	if isEcdsa { | ||||||
|  | 		return &ecdsaAlgorithm{ | ||||||
|  | 			Hash: hash, | ||||||
|  | 			kind: cHash, | ||||||
|  | 		}, nil | ||||||
|  | 	} | ||||||
|  | 	return &rsaAlgorithm{ | ||||||
|  | 		Hash: hash, | ||||||
|  | 		kind: cHash, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // macerFromString is an internally public method constructor | ||||||
|  | func macerFromString(s string) (macer, error) { | ||||||
|  | 	s = strings.ToLower(s) | ||||||
|  | 	if strings.HasPrefix(s, hmacPrefix) { | ||||||
|  | 		algo := strings.TrimPrefix(s, hmacPrefix+"-") | ||||||
|  | 		hashFn, cHash, err := newAlgorithmConstructor(algo) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		// Ensure below does not panic | ||||||
|  | 		_, err = hashFn(nil) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return &hmacAlgorithm{ | ||||||
|  | 			fn: func(key []byte) (hash.Hash, error) { | ||||||
|  | 				return hmac.New(func() hash.Hash { | ||||||
|  | 					h, e := hashFn(nil) | ||||||
|  | 					if e != nil { | ||||||
|  | 						panic(e) | ||||||
|  | 					} | ||||||
|  | 					return h | ||||||
|  | 				}, key), nil | ||||||
|  | 			}, | ||||||
|  | 			kind: cHash, | ||||||
|  | 		}, nil | ||||||
|  | 	} else if bl, ok := stringToHash[s]; ok && blake2Algorithms[bl] { | ||||||
|  | 		hashFn, cHash, err := newAlgorithmConstructor(s) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return &blakeMacAlgorithm{ | ||||||
|  | 			fn:   hashFn, | ||||||
|  | 			kind: cHash, | ||||||
|  | 		}, nil | ||||||
|  | 	} else { | ||||||
|  | 		return nil, fmt.Errorf("no MACer matching %q", s) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										120
									
								
								vendor/github.com/superseriousbusiness/httpsig/digest.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								vendor/github.com/superseriousbusiness/httpsig/digest.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,120 @@ | ||||||
|  | package httpsig | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"crypto" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"fmt" | ||||||
|  | 	"hash" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type DigestAlgorithm string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	DigestSha256 DigestAlgorithm = "SHA-256" | ||||||
|  | 	DigestSha512                 = "SHA-512" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var digestToDef = map[DigestAlgorithm]crypto.Hash{ | ||||||
|  | 	DigestSha256: crypto.SHA256, | ||||||
|  | 	DigestSha512: crypto.SHA512, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsSupportedDigestAlgorithm returns true if hte string is supported by this | ||||||
|  | // library, is not a hash known to be weak, and is supported by the hardware. | ||||||
|  | func IsSupportedDigestAlgorithm(algo string) bool { | ||||||
|  | 	uc := DigestAlgorithm(strings.ToUpper(algo)) | ||||||
|  | 	c, ok := digestToDef[uc] | ||||||
|  | 	return ok && c.Available() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getHash(alg DigestAlgorithm) (h hash.Hash, toUse DigestAlgorithm, err error) { | ||||||
|  | 	upper := DigestAlgorithm(strings.ToUpper(string(alg))) | ||||||
|  | 	c, ok := digestToDef[upper] | ||||||
|  | 	if !ok { | ||||||
|  | 		err = fmt.Errorf("unknown or unsupported Digest algorithm: %s", alg) | ||||||
|  | 	} else if !c.Available() { | ||||||
|  | 		err = fmt.Errorf("unavailable Digest algorithm: %s", alg) | ||||||
|  | 	} else { | ||||||
|  | 		h = c.New() | ||||||
|  | 		toUse = upper | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	digestHeader = "Digest" | ||||||
|  | 	digestDelim  = "=" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func addDigest(r *http.Request, algo DigestAlgorithm, b []byte) (err error) { | ||||||
|  | 	_, ok := r.Header[digestHeader] | ||||||
|  | 	if ok { | ||||||
|  | 		err = fmt.Errorf("cannot add Digest: Digest is already set") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var h hash.Hash | ||||||
|  | 	var a DigestAlgorithm | ||||||
|  | 	h, a, err = getHash(algo) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	h.Write(b) | ||||||
|  | 	sum := h.Sum(nil) | ||||||
|  | 	r.Header.Add(digestHeader, | ||||||
|  | 		fmt.Sprintf("%s%s%s", | ||||||
|  | 			a, | ||||||
|  | 			digestDelim, | ||||||
|  | 			base64.StdEncoding.EncodeToString(sum[:]))) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func addDigestResponse(r http.ResponseWriter, algo DigestAlgorithm, b []byte) (err error) { | ||||||
|  | 	_, ok := r.Header()[digestHeader] | ||||||
|  | 	if ok { | ||||||
|  | 		err = fmt.Errorf("cannot add Digest: Digest is already set") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var h hash.Hash | ||||||
|  | 	var a DigestAlgorithm | ||||||
|  | 	h, a, err = getHash(algo) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	h.Write(b) | ||||||
|  | 	sum := h.Sum(nil) | ||||||
|  | 	r.Header().Add(digestHeader, | ||||||
|  | 		fmt.Sprintf("%s%s%s", | ||||||
|  | 			a, | ||||||
|  | 			digestDelim, | ||||||
|  | 			base64.StdEncoding.EncodeToString(sum[:]))) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func verifyDigest(r *http.Request, body *bytes.Buffer) (err error) { | ||||||
|  | 	d := r.Header.Get(digestHeader) | ||||||
|  | 	if len(d) == 0 { | ||||||
|  | 		err = fmt.Errorf("cannot verify Digest: request has no Digest header") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	elem := strings.SplitN(d, digestDelim, 2) | ||||||
|  | 	if len(elem) != 2 { | ||||||
|  | 		err = fmt.Errorf("cannot verify Digest: malformed Digest: %s", d) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var h hash.Hash | ||||||
|  | 	h, _, err = getHash(DigestAlgorithm(elem[0])) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	h.Write(body.Bytes()) | ||||||
|  | 	sum := h.Sum(nil) | ||||||
|  | 	encSum := base64.StdEncoding.EncodeToString(sum[:]) | ||||||
|  | 	if encSum != elem[1] { | ||||||
|  | 		err = fmt.Errorf("cannot verify Digest: header Digest does not match the digest of the request body") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										413
									
								
								vendor/github.com/superseriousbusiness/httpsig/httpsig.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										413
									
								
								vendor/github.com/superseriousbusiness/httpsig/httpsig.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,413 @@ | ||||||
|  | // Implements HTTP request and response signing and verification. Supports the | ||||||
|  | // major MAC and asymmetric key signature algorithms. It has several safety | ||||||
|  | // restrictions: One, none of the widely known non-cryptographically safe | ||||||
|  | // algorithms are permitted; Two, the RSA SHA256 algorithms must be available in | ||||||
|  | // the binary (and it should, barring export restrictions); Finally, the library | ||||||
|  | // assumes either the 'Authorizationn' or 'Signature' headers are to be set (but | ||||||
|  | // not both). | ||||||
|  | package httpsig | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/crypto/ssh" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Algorithm specifies a cryptography secure algorithm for signing HTTP requests | ||||||
|  | // and responses. | ||||||
|  | type Algorithm string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// MAC-based algoirthms. | ||||||
|  | 	HMAC_SHA224      Algorithm = hmacPrefix + "-" + sha224String | ||||||
|  | 	HMAC_SHA256      Algorithm = hmacPrefix + "-" + sha256String | ||||||
|  | 	HMAC_SHA384      Algorithm = hmacPrefix + "-" + sha384String | ||||||
|  | 	HMAC_SHA512      Algorithm = hmacPrefix + "-" + sha512String | ||||||
|  | 	HMAC_RIPEMD160   Algorithm = hmacPrefix + "-" + ripemd160String | ||||||
|  | 	HMAC_SHA3_224    Algorithm = hmacPrefix + "-" + sha3_224String | ||||||
|  | 	HMAC_SHA3_256    Algorithm = hmacPrefix + "-" + sha3_256String | ||||||
|  | 	HMAC_SHA3_384    Algorithm = hmacPrefix + "-" + sha3_384String | ||||||
|  | 	HMAC_SHA3_512    Algorithm = hmacPrefix + "-" + sha3_512String | ||||||
|  | 	HMAC_SHA512_224  Algorithm = hmacPrefix + "-" + sha512_224String | ||||||
|  | 	HMAC_SHA512_256  Algorithm = hmacPrefix + "-" + sha512_256String | ||||||
|  | 	HMAC_BLAKE2S_256 Algorithm = hmacPrefix + "-" + blake2s_256String | ||||||
|  | 	HMAC_BLAKE2B_256 Algorithm = hmacPrefix + "-" + blake2b_256String | ||||||
|  | 	HMAC_BLAKE2B_384 Algorithm = hmacPrefix + "-" + blake2b_384String | ||||||
|  | 	HMAC_BLAKE2B_512 Algorithm = hmacPrefix + "-" + blake2b_512String | ||||||
|  | 	BLAKE2S_256      Algorithm = blake2s_256String | ||||||
|  | 	BLAKE2B_256      Algorithm = blake2b_256String | ||||||
|  | 	BLAKE2B_384      Algorithm = blake2b_384String | ||||||
|  | 	BLAKE2B_512      Algorithm = blake2b_512String | ||||||
|  | 	// RSA-based algorithms. | ||||||
|  | 	RSA_SHA1   Algorithm = rsaPrefix + "-" + sha1String | ||||||
|  | 	RSA_SHA224 Algorithm = rsaPrefix + "-" + sha224String | ||||||
|  | 	// RSA_SHA256 is the default algorithm. | ||||||
|  | 	RSA_SHA256    Algorithm = rsaPrefix + "-" + sha256String | ||||||
|  | 	RSA_SHA384    Algorithm = rsaPrefix + "-" + sha384String | ||||||
|  | 	RSA_SHA512    Algorithm = rsaPrefix + "-" + sha512String | ||||||
|  | 	RSA_RIPEMD160 Algorithm = rsaPrefix + "-" + ripemd160String | ||||||
|  | 	// ECDSA algorithms | ||||||
|  | 	ECDSA_SHA224    Algorithm = ecdsaPrefix + "-" + sha224String | ||||||
|  | 	ECDSA_SHA256    Algorithm = ecdsaPrefix + "-" + sha256String | ||||||
|  | 	ECDSA_SHA384    Algorithm = ecdsaPrefix + "-" + sha384String | ||||||
|  | 	ECDSA_SHA512    Algorithm = ecdsaPrefix + "-" + sha512String | ||||||
|  | 	ECDSA_RIPEMD160 Algorithm = ecdsaPrefix + "-" + ripemd160String | ||||||
|  | 	// ED25519 algorithms | ||||||
|  | 	// can only be SHA512 | ||||||
|  | 	ED25519 Algorithm = ed25519Prefix | ||||||
|  | 
 | ||||||
|  | 	// Just because you can glue things together, doesn't mean they will | ||||||
|  | 	// work. The following options are not supported. | ||||||
|  | 	rsa_SHA3_224    Algorithm = rsaPrefix + "-" + sha3_224String | ||||||
|  | 	rsa_SHA3_256    Algorithm = rsaPrefix + "-" + sha3_256String | ||||||
|  | 	rsa_SHA3_384    Algorithm = rsaPrefix + "-" + sha3_384String | ||||||
|  | 	rsa_SHA3_512    Algorithm = rsaPrefix + "-" + sha3_512String | ||||||
|  | 	rsa_SHA512_224  Algorithm = rsaPrefix + "-" + sha512_224String | ||||||
|  | 	rsa_SHA512_256  Algorithm = rsaPrefix + "-" + sha512_256String | ||||||
|  | 	rsa_BLAKE2S_256 Algorithm = rsaPrefix + "-" + blake2s_256String | ||||||
|  | 	rsa_BLAKE2B_256 Algorithm = rsaPrefix + "-" + blake2b_256String | ||||||
|  | 	rsa_BLAKE2B_384 Algorithm = rsaPrefix + "-" + blake2b_384String | ||||||
|  | 	rsa_BLAKE2B_512 Algorithm = rsaPrefix + "-" + blake2b_512String | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // HTTP Signatures can be applied to different HTTP headers, depending on the | ||||||
|  | // expected application behavior. | ||||||
|  | type SignatureScheme string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// Signature will place the HTTP Signature into the 'Signature' HTTP | ||||||
|  | 	// header. | ||||||
|  | 	Signature SignatureScheme = "Signature" | ||||||
|  | 	// Authorization will place the HTTP Signature into the 'Authorization' | ||||||
|  | 	// HTTP header. | ||||||
|  | 	Authorization SignatureScheme = "Authorization" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// The HTTP Signatures specification uses the "Signature" auth-scheme | ||||||
|  | 	// for the Authorization header. This is coincidentally named, but not | ||||||
|  | 	// semantically the same, as the "Signature" HTTP header value. | ||||||
|  | 	signatureAuthScheme = "Signature" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // There are subtle differences to the values in the header. The Authorization | ||||||
|  | // header has an 'auth-scheme' value that must be prefixed to the rest of the | ||||||
|  | // key and values. | ||||||
|  | func (s SignatureScheme) authScheme() string { | ||||||
|  | 	switch s { | ||||||
|  | 	case Authorization: | ||||||
|  | 		return signatureAuthScheme | ||||||
|  | 	default: | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type SignatureOption struct { | ||||||
|  | 	// ExcludeQueryStringFromPathPseudoHeader omits the query parameters from the | ||||||
|  | 	// `:path` pseudo-header in the HTTP signature. | ||||||
|  | 	// | ||||||
|  | 	// The query string is optional in the `:path` pseudo-header. | ||||||
|  | 	// https://www.rfc-editor.org/rfc/rfc9113#section-8.3.1-2.4.1 | ||||||
|  | 	ExcludeQueryStringFromPathPseudoHeader bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Signers will sign HTTP requests or responses based on the algorithms and | ||||||
|  | // headers selected at creation time. | ||||||
|  | // | ||||||
|  | // Signers are not safe to use between multiple goroutines. | ||||||
|  | // | ||||||
|  | // Note that signatures do set the deprecated 'algorithm' parameter for | ||||||
|  | // backwards compatibility. | ||||||
|  | type Signer interface { | ||||||
|  | 	// SignRequest signs the request using a private key. The public key id | ||||||
|  | 	// is used by the HTTP server to identify which key to use to verify the | ||||||
|  | 	// signature. | ||||||
|  | 	// | ||||||
|  | 	// If the Signer was created using a MAC based algorithm, then the key | ||||||
|  | 	// is expected to be of type []byte. If the Signer was created using an | ||||||
|  | 	// RSA based algorithm, then the private key is expected to be of type | ||||||
|  | 	// *rsa.PrivateKey. | ||||||
|  | 	// | ||||||
|  | 	// A Digest (RFC 3230) will be added to the request. The body provided | ||||||
|  | 	// must match the body used in the request, and is allowed to be nil. | ||||||
|  | 	// The Digest ensures the request body is not tampered with in flight, | ||||||
|  | 	// and if the signer is created to also sign the "Digest" header, the | ||||||
|  | 	// HTTP Signature will then ensure both the Digest and body are not both | ||||||
|  | 	// modified to maliciously represent different content. | ||||||
|  | 	SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error | ||||||
|  | 	// SignResponse signs the response using a private key. The public key | ||||||
|  | 	// id is used by the HTTP client to identify which key to use to verify | ||||||
|  | 	// the signature. | ||||||
|  | 	// | ||||||
|  | 	// If the Signer was created using a MAC based algorithm, then the key | ||||||
|  | 	// is expected to be of type []byte. If the Signer was created using an | ||||||
|  | 	// RSA based algorithm, then the private key is expected to be of type | ||||||
|  | 	// *rsa.PrivateKey. | ||||||
|  | 	// | ||||||
|  | 	// A Digest (RFC 3230) will be added to the response. The body provided | ||||||
|  | 	// must match the body written in the response, and is allowed to be | ||||||
|  | 	// nil. The Digest ensures the response body is not tampered with in | ||||||
|  | 	// flight, and if the signer is created to also sign the "Digest" | ||||||
|  | 	// header, the HTTP Signature will then ensure both the Digest and body | ||||||
|  | 	// are not both modified to maliciously represent different content. | ||||||
|  | 	SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type SignerWithOptions interface { | ||||||
|  | 	Signer | ||||||
|  | 
 | ||||||
|  | 	// SignRequestWithOptions signs the request using a private key. The public key id | ||||||
|  | 	// is used by the HTTP server to identify which key to use to verify the | ||||||
|  | 	// signature. | ||||||
|  | 	// | ||||||
|  | 	// If the Signer was created using a MAC based algorithm, then the key | ||||||
|  | 	// is expected to be of type []byte. If the Signer was created using an | ||||||
|  | 	// RSA based algorithm, then the private key is expected to be of type | ||||||
|  | 	// *rsa.PrivateKey. | ||||||
|  | 	// | ||||||
|  | 	// A Digest (RFC 3230) will be added to the request. The body provided | ||||||
|  | 	// must match the body used in the request, and is allowed to be nil. | ||||||
|  | 	// The Digest ensures the request body is not tampered with in flight, | ||||||
|  | 	// and if the signer is created to also sign the "Digest" header, the | ||||||
|  | 	// HTTP Signature will then ensure both the Digest and body are not both | ||||||
|  | 	// modified to maliciously represent different content. | ||||||
|  | 	SignRequestWithOptions(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte, opts SignatureOption) error | ||||||
|  | 	// SignResponseWithOptions signs the response using a private key. The public key | ||||||
|  | 	// id is used by the HTTP client to identify which key to use to verify | ||||||
|  | 	// the signature. | ||||||
|  | 	// | ||||||
|  | 	// If the Signer was created using a MAC based algorithm, then the key | ||||||
|  | 	// is expected to be of type []byte. If the Signer was created using an | ||||||
|  | 	// RSA based algorithm, then the private key is expected to be of type | ||||||
|  | 	// *rsa.PrivateKey. | ||||||
|  | 	// | ||||||
|  | 	// A Digest (RFC 3230) will be added to the response. The body provided | ||||||
|  | 	// must match the body written in the response, and is allowed to be | ||||||
|  | 	// nil. The Digest ensures the response body is not tampered with in | ||||||
|  | 	// flight, and if the signer is created to also sign the "Digest" | ||||||
|  | 	// header, the HTTP Signature will then ensure both the Digest and body | ||||||
|  | 	// are not both modified to maliciously represent different content. | ||||||
|  | 	SignResponseWithOptions(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte, opts SignatureOption) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewSigner creates a new Signer with the provided algorithm preferences to | ||||||
|  | // make HTTP signatures. Only the first available algorithm will be used, which | ||||||
|  | // is returned by this function along with the Signer. If none of the preferred | ||||||
|  | // algorithms were available, then the default algorithm is used. The headers | ||||||
|  | // specified will be included into the HTTP signatures. | ||||||
|  | // | ||||||
|  | // The Digest will also be calculated on a request's body using the provided | ||||||
|  | // digest algorithm, if "Digest" is one of the headers listed. | ||||||
|  | // | ||||||
|  | // The provided scheme determines which header is populated with the HTTP | ||||||
|  | // Signature. | ||||||
|  | // | ||||||
|  | // An error is returned if an unknown or a known cryptographically insecure | ||||||
|  | // Algorithm is provided. | ||||||
|  | func NewSigner(prefs []Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SignerWithOptions, Algorithm, error) { | ||||||
|  | 	for _, pref := range prefs { | ||||||
|  | 		s, err := newSigner(pref, dAlgo, headers, scheme, expiresIn) | ||||||
|  | 		if err != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		return s, pref, err | ||||||
|  | 	} | ||||||
|  | 	s, err := newSigner(defaultAlgorithm, dAlgo, headers, scheme, expiresIn) | ||||||
|  | 	return s, defaultAlgorithm, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Signers will sign HTTP requests or responses based on the algorithms and | ||||||
|  | // headers selected at creation time. | ||||||
|  | // | ||||||
|  | // Signers are not safe to use between multiple goroutines. | ||||||
|  | // | ||||||
|  | // Note that signatures do set the deprecated 'algorithm' parameter for | ||||||
|  | // backwards compatibility. | ||||||
|  | type SSHSigner interface { | ||||||
|  | 	// SignRequest signs the request using ssh.Signer. | ||||||
|  | 	// The public key id is used by the HTTP server to identify which key to use | ||||||
|  | 	// to verify the signature. | ||||||
|  | 	// | ||||||
|  | 	// A Digest (RFC 3230) will be added to the request. The body provided | ||||||
|  | 	// must match the body used in the request, and is allowed to be nil. | ||||||
|  | 	// The Digest ensures the request body is not tampered with in flight, | ||||||
|  | 	// and if the signer is created to also sign the "Digest" header, the | ||||||
|  | 	// HTTP Signature will then ensure both the Digest and body are not both | ||||||
|  | 	// modified to maliciously represent different content. | ||||||
|  | 	SignRequest(pubKeyId string, r *http.Request, body []byte) error | ||||||
|  | 	// SignResponse signs the response using ssh.Signer. The public key | ||||||
|  | 	// id is used by the HTTP client to identify which key to use to verify | ||||||
|  | 	// the signature. | ||||||
|  | 	// | ||||||
|  | 	// A Digest (RFC 3230) will be added to the response. The body provided | ||||||
|  | 	// must match the body written in the response, and is allowed to be | ||||||
|  | 	// nil. The Digest ensures the response body is not tampered with in | ||||||
|  | 	// flight, and if the signer is created to also sign the "Digest" | ||||||
|  | 	// header, the HTTP Signature will then ensure both the Digest and body | ||||||
|  | 	// are not both modified to maliciously represent different content. | ||||||
|  | 	SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewwSSHSigner creates a new Signer using the specified ssh.Signer | ||||||
|  | // At the moment only ed25519 ssh keys are supported. | ||||||
|  | // The headers specified will be included into the HTTP signatures. | ||||||
|  | // | ||||||
|  | // The Digest will also be calculated on a request's body using the provided | ||||||
|  | // digest algorithm, if "Digest" is one of the headers listed. | ||||||
|  | // | ||||||
|  | // The provided scheme determines which header is populated with the HTTP | ||||||
|  | // Signature. | ||||||
|  | func NewSSHSigner(s ssh.Signer, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, Algorithm, error) { | ||||||
|  | 	sshAlgo := getSSHAlgorithm(s.PublicKey().Type()) | ||||||
|  | 	if sshAlgo == "" { | ||||||
|  | 		return nil, "", fmt.Errorf("key type: %s not supported yet.", s.PublicKey().Type()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	signer, err := newSSHSigner(s, sshAlgo, dAlgo, headers, scheme, expiresIn) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, "", err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return signer, sshAlgo, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getSSHAlgorithm(pkType string) Algorithm { | ||||||
|  | 	switch { | ||||||
|  | 	case strings.HasPrefix(pkType, sshPrefix+"-"+ed25519Prefix): | ||||||
|  | 		return ED25519 | ||||||
|  | 	case strings.HasPrefix(pkType, sshPrefix+"-"+rsaPrefix): | ||||||
|  | 		return RSA_SHA1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Verifier verifies HTTP Signatures. | ||||||
|  | // | ||||||
|  | // It will determine which of the supported headers has the parameters | ||||||
|  | // that define the signature. | ||||||
|  | // | ||||||
|  | // Verifiers are not safe to use between multiple goroutines. | ||||||
|  | // | ||||||
|  | // Note that verification ignores the deprecated 'algorithm' parameter. | ||||||
|  | type Verifier interface { | ||||||
|  | 	// KeyId gets the public key id that the signature is signed with. | ||||||
|  | 	// | ||||||
|  | 	// Note that the application is expected to determine the algorithm | ||||||
|  | 	// used based on metadata or out-of-band information for this key id. | ||||||
|  | 	KeyId() string | ||||||
|  | 	// Verify accepts the public key specified by KeyId and returns an | ||||||
|  | 	// error if verification fails or if the signature is malformed. The | ||||||
|  | 	// algorithm must be the one used to create the signature in order to | ||||||
|  | 	// pass verification. The algorithm is determined based on metadata or | ||||||
|  | 	// out-of-band information for the key id. | ||||||
|  | 	// | ||||||
|  | 	// If the signature was created using a MAC based algorithm, then the | ||||||
|  | 	// key is expected to be of type []byte. If the signature was created | ||||||
|  | 	// using an RSA based algorithm, then the public key is expected to be | ||||||
|  | 	// of type *rsa.PublicKey. | ||||||
|  | 	Verify(pKey crypto.PublicKey, algo Algorithm) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type VerifierWithOptions interface { | ||||||
|  | 	Verifier | ||||||
|  | 
 | ||||||
|  | 	VerifyWithOptions(pKey crypto.PublicKey, algo Algorithm, opts SignatureOption) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// host is treated specially because golang may not include it in the | ||||||
|  | 	// request header map on the server side of a request. | ||||||
|  | 	hostHeader = "Host" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // NewVerifier verifies the given request. It returns an error if the HTTP | ||||||
|  | // Signature parameters are not present in any headers, are present in more than | ||||||
|  | // one header, are malformed, or are missing required parameters. It ignores | ||||||
|  | // unknown HTTP Signature parameters. | ||||||
|  | func NewVerifier(r *http.Request) (VerifierWithOptions, error) { | ||||||
|  | 	h := r.Header | ||||||
|  | 	if _, hasHostHeader := h[hostHeader]; len(r.Host) > 0 && !hasHostHeader { | ||||||
|  | 		h[hostHeader] = []string{r.Host} | ||||||
|  | 	} | ||||||
|  | 	return newVerifier(h, func(h http.Header, toInclude []string, created int64, expires int64, opts SignatureOption) (string, error) { | ||||||
|  | 		return signatureString(h, toInclude, addRequestTarget(r, opts), created, expires) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewResponseVerifier verifies the given response. It returns errors under the | ||||||
|  | // same conditions as NewVerifier. | ||||||
|  | func NewResponseVerifier(r *http.Response) (Verifier, error) { | ||||||
|  | 	return newVerifier(r.Header, func(h http.Header, toInclude []string, created int64, expires int64, _ SignatureOption) (string, error) { | ||||||
|  | 		return signatureString(h, toInclude, requestTargetNotPermitted, created, expires) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newSSHSigner(sshSigner ssh.Signer, algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, error) { | ||||||
|  | 	var expires, created int64 = 0, 0 | ||||||
|  | 
 | ||||||
|  | 	if expiresIn != 0 { | ||||||
|  | 		created = time.Now().Unix() | ||||||
|  | 		expires = created + expiresIn | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	s, err := signerFromSSHSigner(sshSigner, string(algo)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("no crypto implementation available for ssh algo %q: %s", algo, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	a := &asymmSSHSigner{ | ||||||
|  | 		asymmSigner: &asymmSigner{ | ||||||
|  | 			s:            s, | ||||||
|  | 			dAlgo:        dAlgo, | ||||||
|  | 			headers:      headers, | ||||||
|  | 			targetHeader: scheme, | ||||||
|  | 			prefix:       scheme.authScheme(), | ||||||
|  | 			created:      created, | ||||||
|  | 			expires:      expires, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return a, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newSigner(algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SignerWithOptions, error) { | ||||||
|  | 
 | ||||||
|  | 	var expires, created int64 = 0, 0 | ||||||
|  | 	if expiresIn != 0 { | ||||||
|  | 		created = time.Now().Unix() | ||||||
|  | 		expires = created + expiresIn | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	s, err := signerFromString(string(algo)) | ||||||
|  | 	if err == nil { | ||||||
|  | 		a := &asymmSigner{ | ||||||
|  | 			s:            s, | ||||||
|  | 			dAlgo:        dAlgo, | ||||||
|  | 			headers:      headers, | ||||||
|  | 			targetHeader: scheme, | ||||||
|  | 			prefix:       scheme.authScheme(), | ||||||
|  | 			created:      created, | ||||||
|  | 			expires:      expires, | ||||||
|  | 		} | ||||||
|  | 		return a, nil | ||||||
|  | 	} | ||||||
|  | 	m, err := macerFromString(string(algo)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("no crypto implementation available for %q: %s", algo, err) | ||||||
|  | 	} | ||||||
|  | 	c := &macSigner{ | ||||||
|  | 		m:            m, | ||||||
|  | 		dAlgo:        dAlgo, | ||||||
|  | 		headers:      headers, | ||||||
|  | 		targetHeader: scheme, | ||||||
|  | 		prefix:       scheme.authScheme(), | ||||||
|  | 		created:      created, | ||||||
|  | 		expires:      expires, | ||||||
|  | 	} | ||||||
|  | 	return c, nil | ||||||
|  | } | ||||||
							
								
								
									
										350
									
								
								vendor/github.com/superseriousbusiness/httpsig/signing.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										350
									
								
								vendor/github.com/superseriousbusiness/httpsig/signing.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,350 @@ | ||||||
|  | package httpsig | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"crypto" | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/textproto" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// Signature Parameters | ||||||
|  | 	keyIdParameter            = "keyId" | ||||||
|  | 	algorithmParameter        = "algorithm" | ||||||
|  | 	headersParameter          = "headers" | ||||||
|  | 	signatureParameter        = "signature" | ||||||
|  | 	prefixSeparater           = " " | ||||||
|  | 	parameterKVSeparater      = "=" | ||||||
|  | 	parameterValueDelimiter   = "\"" | ||||||
|  | 	parameterSeparater        = "," | ||||||
|  | 	headerParameterValueDelim = " " | ||||||
|  | 	// RequestTarget specifies to include the http request method and | ||||||
|  | 	// entire URI in the signature. Pass it as a header to NewSigner. | ||||||
|  | 	RequestTarget = "(request-target)" | ||||||
|  | 	createdKey    = "created" | ||||||
|  | 	expiresKey    = "expires" | ||||||
|  | 	dateHeader    = "date" | ||||||
|  | 
 | ||||||
|  | 	// Signature String Construction | ||||||
|  | 	headerFieldDelimiter   = ": " | ||||||
|  | 	headersDelimiter       = "\n" | ||||||
|  | 	headerValueDelimiter   = ", " | ||||||
|  | 	requestTargetSeparator = " " | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var defaultHeaders = []string{dateHeader} | ||||||
|  | 
 | ||||||
|  | var _ SignerWithOptions = &macSigner{} | ||||||
|  | 
 | ||||||
|  | type macSigner struct { | ||||||
|  | 	m            macer | ||||||
|  | 	makeDigest   bool | ||||||
|  | 	dAlgo        DigestAlgorithm | ||||||
|  | 	headers      []string | ||||||
|  | 	targetHeader SignatureScheme | ||||||
|  | 	prefix       string | ||||||
|  | 	created      int64 | ||||||
|  | 	expires      int64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *macSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error { | ||||||
|  | 	return m.SignRequestWithOptions(pKey, pubKeyId, r, body, SignatureOption{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *macSigner) SignRequestWithOptions(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte, opts SignatureOption) error { | ||||||
|  | 	if body != nil { | ||||||
|  | 		err := addDigest(r, m.dAlgo, body) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	s, err := m.signatureString(r, opts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	enc, err := m.signSignature(pKey, s) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	setSignatureHeader(r.Header, string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *macSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error { | ||||||
|  | 	return m.SignResponseWithOptions(pKey, pubKeyId, r, body, SignatureOption{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *macSigner) SignResponseWithOptions(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte, _ SignatureOption) error { | ||||||
|  | 	if body != nil { | ||||||
|  | 		err := addDigestResponse(r, m.dAlgo, body) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	s, err := m.signatureStringResponse(r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	enc, err := m.signSignature(pKey, s) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	setSignatureHeader(r.Header(), string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *macSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) { | ||||||
|  | 	pKeyBytes, ok := pKey.([]byte) | ||||||
|  | 	if !ok { | ||||||
|  | 		return "", fmt.Errorf("private key for MAC signing must be of type []byte") | ||||||
|  | 	} | ||||||
|  | 	sig, err := m.m.Sign([]byte(s), pKeyBytes) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	enc := base64.StdEncoding.EncodeToString(sig) | ||||||
|  | 	return enc, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *macSigner) signatureString(r *http.Request, opts SignatureOption) (string, error) { | ||||||
|  | 	return signatureString(r.Header, m.headers, addRequestTarget(r, opts), m.created, m.expires) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *macSigner) signatureStringResponse(r http.ResponseWriter) (string, error) { | ||||||
|  | 	return signatureString(r.Header(), m.headers, requestTargetNotPermitted, m.created, m.expires) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ SignerWithOptions = &asymmSigner{} | ||||||
|  | 
 | ||||||
|  | type asymmSigner struct { | ||||||
|  | 	s            signer | ||||||
|  | 	makeDigest   bool | ||||||
|  | 	dAlgo        DigestAlgorithm | ||||||
|  | 	headers      []string | ||||||
|  | 	targetHeader SignatureScheme | ||||||
|  | 	prefix       string | ||||||
|  | 	created      int64 | ||||||
|  | 	expires      int64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *asymmSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error { | ||||||
|  | 	return a.SignRequestWithOptions(pKey, pubKeyId, r, body, SignatureOption{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *asymmSigner) SignRequestWithOptions(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte, opts SignatureOption) error { | ||||||
|  | 	if body != nil { | ||||||
|  | 		err := addDigest(r, a.dAlgo, body) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	s, err := a.signatureString(r, opts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	enc, err := a.signSignature(pKey, s) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	setSignatureHeader(r.Header, string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *asymmSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error { | ||||||
|  | 	return a.SignResponseWithOptions(pKey, pubKeyId, r, body, SignatureOption{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *asymmSigner) SignResponseWithOptions(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte, _ SignatureOption) error { | ||||||
|  | 	if body != nil { | ||||||
|  | 		err := addDigestResponse(r, a.dAlgo, body) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	s, err := a.signatureStringResponse(r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	enc, err := a.signSignature(pKey, s) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	setSignatureHeader(r.Header(), string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *asymmSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) { | ||||||
|  | 	sig, err := a.s.Sign(rand.Reader, pKey, []byte(s)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	enc := base64.StdEncoding.EncodeToString(sig) | ||||||
|  | 	return enc, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *asymmSigner) signatureString(r *http.Request, opts SignatureOption) (string, error) { | ||||||
|  | 	return signatureString(r.Header, a.headers, addRequestTarget(r, opts), a.created, a.expires) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *asymmSigner) signatureStringResponse(r http.ResponseWriter) (string, error) { | ||||||
|  | 	return signatureString(r.Header(), a.headers, requestTargetNotPermitted, a.created, a.expires) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ SSHSigner = &asymmSSHSigner{} | ||||||
|  | 
 | ||||||
|  | type asymmSSHSigner struct { | ||||||
|  | 	*asymmSigner | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *asymmSSHSigner) SignRequest(pubKeyId string, r *http.Request, body []byte) error { | ||||||
|  | 	return a.asymmSigner.SignRequest(nil, pubKeyId, r, body) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *asymmSSHSigner) SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error { | ||||||
|  | 	return a.asymmSigner.SignResponse(nil, pubKeyId, r, body) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func setSignatureHeader(h http.Header, targetHeader, prefix, pubKeyId, algo, enc string, headers []string, created int64, expires int64) { | ||||||
|  | 	if len(headers) == 0 { | ||||||
|  | 		headers = defaultHeaders | ||||||
|  | 	} | ||||||
|  | 	var b bytes.Buffer | ||||||
|  | 	// KeyId | ||||||
|  | 	b.WriteString(prefix) | ||||||
|  | 	if len(prefix) > 0 { | ||||||
|  | 		b.WriteString(prefixSeparater) | ||||||
|  | 	} | ||||||
|  | 	b.WriteString(keyIdParameter) | ||||||
|  | 	b.WriteString(parameterKVSeparater) | ||||||
|  | 	b.WriteString(parameterValueDelimiter) | ||||||
|  | 	b.WriteString(pubKeyId) | ||||||
|  | 	b.WriteString(parameterValueDelimiter) | ||||||
|  | 	b.WriteString(parameterSeparater) | ||||||
|  | 	// Algorithm | ||||||
|  | 	b.WriteString(algorithmParameter) | ||||||
|  | 	b.WriteString(parameterKVSeparater) | ||||||
|  | 	b.WriteString(parameterValueDelimiter) | ||||||
|  | 	b.WriteString("hs2019") //real algorithm is hidden, see newest version of spec draft | ||||||
|  | 	b.WriteString(parameterValueDelimiter) | ||||||
|  | 	b.WriteString(parameterSeparater) | ||||||
|  | 
 | ||||||
|  | 	hasCreated := false | ||||||
|  | 	hasExpires := false | ||||||
|  | 	for _, h := range headers { | ||||||
|  | 		val := strings.ToLower(h) | ||||||
|  | 		if val == "("+createdKey+")" { | ||||||
|  | 			hasCreated = true | ||||||
|  | 		} else if val == "("+expiresKey+")" { | ||||||
|  | 			hasExpires = true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Created | ||||||
|  | 	if hasCreated == true { | ||||||
|  | 		b.WriteString(createdKey) | ||||||
|  | 		b.WriteString(parameterKVSeparater) | ||||||
|  | 		b.WriteString(strconv.FormatInt(created, 10)) | ||||||
|  | 		b.WriteString(parameterSeparater) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Expires | ||||||
|  | 	if hasExpires == true { | ||||||
|  | 		b.WriteString(expiresKey) | ||||||
|  | 		b.WriteString(parameterKVSeparater) | ||||||
|  | 		b.WriteString(strconv.FormatInt(expires, 10)) | ||||||
|  | 		b.WriteString(parameterSeparater) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Headers | ||||||
|  | 	b.WriteString(headersParameter) | ||||||
|  | 	b.WriteString(parameterKVSeparater) | ||||||
|  | 	b.WriteString(parameterValueDelimiter) | ||||||
|  | 	for i, h := range headers { | ||||||
|  | 		b.WriteString(strings.ToLower(h)) | ||||||
|  | 		if i != len(headers)-1 { | ||||||
|  | 			b.WriteString(headerParameterValueDelim) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	b.WriteString(parameterValueDelimiter) | ||||||
|  | 	b.WriteString(parameterSeparater) | ||||||
|  | 	// Signature | ||||||
|  | 	b.WriteString(signatureParameter) | ||||||
|  | 	b.WriteString(parameterKVSeparater) | ||||||
|  | 	b.WriteString(parameterValueDelimiter) | ||||||
|  | 	b.WriteString(enc) | ||||||
|  | 	b.WriteString(parameterValueDelimiter) | ||||||
|  | 	h.Add(targetHeader, b.String()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func requestTargetNotPermitted(b *bytes.Buffer) error { | ||||||
|  | 	return fmt.Errorf("cannot sign with %q on anything other than an http request", RequestTarget) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func addRequestTarget(r *http.Request, opts SignatureOption) func(b *bytes.Buffer) error { | ||||||
|  | 	return func(b *bytes.Buffer) error { | ||||||
|  | 		b.WriteString(RequestTarget) | ||||||
|  | 		b.WriteString(headerFieldDelimiter) | ||||||
|  | 		b.WriteString(strings.ToLower(r.Method)) | ||||||
|  | 		b.WriteString(requestTargetSeparator) | ||||||
|  | 		b.WriteString(r.URL.Path) | ||||||
|  | 
 | ||||||
|  | 		if !opts.ExcludeQueryStringFromPathPseudoHeader && r.URL.RawQuery != "" { | ||||||
|  | 			b.WriteString("?") | ||||||
|  | 			b.WriteString(r.URL.RawQuery) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func signatureString(values http.Header, include []string, requestTargetFn func(b *bytes.Buffer) error, created int64, expires int64) (string, error) { | ||||||
|  | 	if len(include) == 0 { | ||||||
|  | 		include = defaultHeaders | ||||||
|  | 	} | ||||||
|  | 	var b bytes.Buffer | ||||||
|  | 	for n, i := range include { | ||||||
|  | 		i := strings.ToLower(i) | ||||||
|  | 		if i == RequestTarget { | ||||||
|  | 			err := requestTargetFn(&b) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return "", err | ||||||
|  | 			} | ||||||
|  | 		} else if i == "("+expiresKey+")" { | ||||||
|  | 			if expires == 0 { | ||||||
|  | 				return "", fmt.Errorf("missing expires value") | ||||||
|  | 			} | ||||||
|  | 			b.WriteString(i) | ||||||
|  | 			b.WriteString(headerFieldDelimiter) | ||||||
|  | 			b.WriteString(strconv.FormatInt(expires, 10)) | ||||||
|  | 		} else if i == "("+createdKey+")" { | ||||||
|  | 			if created == 0 { | ||||||
|  | 				return "", fmt.Errorf("missing created value") | ||||||
|  | 			} | ||||||
|  | 			b.WriteString(i) | ||||||
|  | 			b.WriteString(headerFieldDelimiter) | ||||||
|  | 			b.WriteString(strconv.FormatInt(created, 10)) | ||||||
|  | 		} else { | ||||||
|  | 			hv, ok := values[textproto.CanonicalMIMEHeaderKey(i)] | ||||||
|  | 			if !ok { | ||||||
|  | 				return "", fmt.Errorf("missing header %q", i) | ||||||
|  | 			} | ||||||
|  | 			b.WriteString(i) | ||||||
|  | 			b.WriteString(headerFieldDelimiter) | ||||||
|  | 			for i, v := range hv { | ||||||
|  | 				b.WriteString(strings.TrimSpace(v)) | ||||||
|  | 				if i < len(hv)-1 { | ||||||
|  | 					b.WriteString(headerValueDelimiter) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if n < len(include)-1 { | ||||||
|  | 			b.WriteString(headersDelimiter) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return b.String(), nil | ||||||
|  | } | ||||||
							
								
								
									
										188
									
								
								vendor/github.com/superseriousbusiness/httpsig/verifying.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								vendor/github.com/superseriousbusiness/httpsig/verifying.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,188 @@ | ||||||
|  | package httpsig | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var _ VerifierWithOptions = &verifier{} | ||||||
|  | 
 | ||||||
|  | type verifier struct { | ||||||
|  | 	header      http.Header | ||||||
|  | 	kId         string | ||||||
|  | 	signature   string | ||||||
|  | 	created     int64 | ||||||
|  | 	expires     int64 | ||||||
|  | 	headers     []string | ||||||
|  | 	sigStringFn func(http.Header, []string, int64, int64, SignatureOption) (string, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newVerifier(h http.Header, sigStringFn func(http.Header, []string, int64, int64, SignatureOption) (string, error)) (*verifier, error) { | ||||||
|  | 	scheme, s, err := getSignatureScheme(h) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	kId, sig, headers, created, expires, err := getSignatureComponents(scheme, s) | ||||||
|  | 	if created != 0 { | ||||||
|  | 		//check if created is not in the future, we assume a maximum clock offset of 10 seconds | ||||||
|  | 		now := time.Now().Unix() | ||||||
|  | 		if created-now > 10 { | ||||||
|  | 			return nil, errors.New("created is in the future") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if expires != 0 { | ||||||
|  | 		//check if expires is in the past, we assume a maximum clock offset of 10 seconds | ||||||
|  | 		now := time.Now().Unix() | ||||||
|  | 		if now-expires > 10 { | ||||||
|  | 			return nil, errors.New("signature expired") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &verifier{ | ||||||
|  | 		header:      h, | ||||||
|  | 		kId:         kId, | ||||||
|  | 		signature:   sig, | ||||||
|  | 		created:     created, | ||||||
|  | 		expires:     expires, | ||||||
|  | 		headers:     headers, | ||||||
|  | 		sigStringFn: sigStringFn, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *verifier) KeyId() string { | ||||||
|  | 	return v.kId | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *verifier) Verify(pKey crypto.PublicKey, algo Algorithm) error { | ||||||
|  | 	return v.VerifyWithOptions(pKey, algo, SignatureOption{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *verifier) VerifyWithOptions(pKey crypto.PublicKey, algo Algorithm, opts SignatureOption) error { | ||||||
|  | 	s, err := signerFromString(string(algo)) | ||||||
|  | 	if err == nil { | ||||||
|  | 		return v.asymmVerify(s, pKey, opts) | ||||||
|  | 	} | ||||||
|  | 	m, err := macerFromString(string(algo)) | ||||||
|  | 	if err == nil { | ||||||
|  | 		return v.macVerify(m, pKey, opts) | ||||||
|  | 	} | ||||||
|  | 	return fmt.Errorf("no crypto implementation available for %q: %s", algo, err) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *verifier) macVerify(m macer, pKey crypto.PublicKey, opts SignatureOption) error { | ||||||
|  | 	key, ok := pKey.([]byte) | ||||||
|  | 	if !ok { | ||||||
|  | 		return fmt.Errorf("public key for MAC verifying must be of type []byte") | ||||||
|  | 	} | ||||||
|  | 	signature, err := v.sigStringFn(v.header, v.headers, v.created, v.expires, opts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	actualMAC, err := base64.StdEncoding.DecodeString(v.signature) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	ok, err = m.Equal([]byte(signature), actualMAC, key) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} else if !ok { | ||||||
|  | 		return fmt.Errorf("invalid http signature") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *verifier) asymmVerify(s signer, pKey crypto.PublicKey, opts SignatureOption) error { | ||||||
|  | 	toHash, err := v.sigStringFn(v.header, v.headers, v.created, v.expires, opts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	signature, err := base64.StdEncoding.DecodeString(v.signature) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err = s.Verify(pKey, []byte(toHash), signature) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getSignatureScheme(h http.Header) (scheme SignatureScheme, val string, err error) { | ||||||
|  | 	s := h.Get(string(Signature)) | ||||||
|  | 	sigHasAll := strings.Contains(s, keyIdParameter) || | ||||||
|  | 		strings.Contains(s, headersParameter) || | ||||||
|  | 		strings.Contains(s, signatureParameter) | ||||||
|  | 	a := h.Get(string(Authorization)) | ||||||
|  | 	authHasAll := strings.Contains(a, keyIdParameter) || | ||||||
|  | 		strings.Contains(a, headersParameter) || | ||||||
|  | 		strings.Contains(a, signatureParameter) | ||||||
|  | 	if sigHasAll && authHasAll { | ||||||
|  | 		err = fmt.Errorf("both %q and %q have signature parameters", Signature, Authorization) | ||||||
|  | 		return | ||||||
|  | 	} else if !sigHasAll && !authHasAll { | ||||||
|  | 		err = fmt.Errorf("neither %q nor %q have signature parameters", Signature, Authorization) | ||||||
|  | 		return | ||||||
|  | 	} else if sigHasAll { | ||||||
|  | 		val = s | ||||||
|  | 		scheme = Signature | ||||||
|  | 		return | ||||||
|  | 	} else { // authHasAll | ||||||
|  | 		val = a | ||||||
|  | 		scheme = Authorization | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getSignatureComponents(scheme SignatureScheme, s string) (kId, sig string, headers []string, created int64, expires int64, err error) { | ||||||
|  | 	if as := scheme.authScheme(); len(as) > 0 { | ||||||
|  | 		s = strings.TrimPrefix(s, as+prefixSeparater) | ||||||
|  | 	} | ||||||
|  | 	params := strings.Split(s, parameterSeparater) | ||||||
|  | 	for _, p := range params { | ||||||
|  | 		kv := strings.SplitN(p, parameterKVSeparater, 2) | ||||||
|  | 		if len(kv) != 2 { | ||||||
|  | 			err = fmt.Errorf("malformed http signature parameter: %v", kv) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		k := kv[0] | ||||||
|  | 		v := strings.Trim(kv[1], parameterValueDelimiter) | ||||||
|  | 		switch k { | ||||||
|  | 		case keyIdParameter: | ||||||
|  | 			kId = v | ||||||
|  | 		case createdKey: | ||||||
|  | 			created, err = strconv.ParseInt(v, 10, 64) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		case expiresKey: | ||||||
|  | 			expires, err = strconv.ParseInt(v, 10, 64) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		case algorithmParameter: | ||||||
|  | 			// Deprecated, ignore | ||||||
|  | 		case headersParameter: | ||||||
|  | 			headers = strings.Split(v, headerParameterValueDelim) | ||||||
|  | 		case signatureParameter: | ||||||
|  | 			sig = v | ||||||
|  | 		default: | ||||||
|  | 			// Ignore unrecognized parameters | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(kId) == 0 { | ||||||
|  | 		err = fmt.Errorf("missing %q parameter in http signature", keyIdParameter) | ||||||
|  | 	} else if len(sig) == 0 { | ||||||
|  | 		err = fmt.Errorf("missing %q parameter in http signature", signatureParameter) | ||||||
|  | 	} else if len(headers) == 0 { // Optional | ||||||
|  | 		headers = defaultHeaders | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								vendor/modules.txt
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/modules.txt
									
										
									
									
										vendored
									
									
								
							|  | @ -676,6 +676,9 @@ github.com/superseriousbusiness/go-jpeg-image-structure/v2 | ||||||
| # github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB | # github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB | ||||||
| ## explicit; go 1.12 | ## explicit; go 1.12 | ||||||
| github.com/superseriousbusiness/go-png-image-structure/v2 | github.com/superseriousbusiness/go-png-image-structure/v2 | ||||||
|  | # github.com/superseriousbusiness/httpsig v1.2.0-SSB | ||||||
|  | ## explicit; go 1.21 | ||||||
|  | github.com/superseriousbusiness/httpsig | ||||||
| # github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 | # github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 | ||||||
| ## explicit; go 1.13 | ## explicit; go 1.13 | ||||||
| github.com/superseriousbusiness/oauth2/v4 | github.com/superseriousbusiness/oauth2/v4 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue