| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | // GNU Affero General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | package middleware | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/gin-gonic/gin" | 
					
						
							|  |  |  | 	"github.com/go-fed/httpsig" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | const ( | 
					
						
							|  |  |  | 	sigHeader  = string(httpsig.Signature) | 
					
						
							|  |  |  | 	authHeader = string(httpsig.Authorization) | 
					
						
							|  |  |  | 	// untyped error returned by httpsig when no signature is present | 
					
						
							|  |  |  | 	noSigError = "neither \"" + sigHeader + "\" nor \"" + authHeader + "\" have signature parameters" | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SignatureCheck returns a gin middleware for checking http signatures. | 
					
						
							|  |  |  | // | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | // The middleware first checks whether an incoming http request has been | 
					
						
							|  |  |  | // http-signed with a well-formed signature. If so, it will check if the | 
					
						
							|  |  |  | // domain that signed the request is permitted to access the server, using | 
					
						
							|  |  |  | // the provided uriBlocked function. If the domain is blocked, the middleware | 
					
						
							|  |  |  | // will abort the request chain with http code 403 forbidden. If it is not | 
					
						
							|  |  |  | // blocked, the handler will set the key verifier and the signature in the | 
					
						
							|  |  |  | // context for use down the line. | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | // | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | // In case of an error, the request will be aborted with http code 500. | 
					
						
							| 
									
										
										
										
											2023-07-25 09:34:05 +01:00
										 |  |  | func SignatureCheck(uriBlocked func(context.Context, *url.URL) (bool, error)) func(*gin.Context) { | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | 	return func(c *gin.Context) { | 
					
						
							| 
									
										
										
										
											2023-02-17 12:02:29 +01:00
										 |  |  | 		ctx := c.Request.Context() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | 		// Create the signature verifier from the request; | 
					
						
							|  |  |  | 		// this will error if the request wasn't signed. | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | 		verifier, err := httpsig.NewVerifier(c.Request) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | 			// Only actually *abort* the request with 401 | 
					
						
							|  |  |  | 			// if a signature was present but malformed. | 
					
						
							|  |  |  | 			// Otherwise proceed with an unsigned request; | 
					
						
							|  |  |  | 			// it's up to other functions to reject this. | 
					
						
							|  |  |  | 			if err.Error() != noSigError { | 
					
						
							| 
									
										
										
										
											2023-02-17 12:02:29 +01:00
										 |  |  | 				log.Debugf(ctx, "http signature was present but invalid: %s", err) | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | 				c.AbortWithStatus(http.StatusUnauthorized) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | 		// The request was signed! The key ID should be given | 
					
						
							|  |  |  | 		// in the signature so that we know where to fetch it | 
					
						
							|  |  |  | 		// from the remote server. This will be something like: | 
					
						
							|  |  |  | 		// https://example.org/users/some_remote_user#main-key | 
					
						
							|  |  |  | 		pubKeyIDStr := verifier.KeyId() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Key can sometimes be nil, according to url parse | 
					
						
							|  |  |  | 		// func: 'Trying to parse a hostname and path without | 
					
						
							|  |  |  | 		// a scheme is invalid but may not necessarily return | 
					
						
							|  |  |  | 		// an error, due to parsing ambiguities'. Catch this. | 
					
						
							|  |  |  | 		pubKeyID, err := url.Parse(pubKeyIDStr) | 
					
						
							|  |  |  | 		if err != nil || pubKeyID == nil { | 
					
						
							|  |  |  | 			log.Warnf(ctx, "pubkey id %s could not be parsed as a url", pubKeyIDStr) | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | 			c.AbortWithStatus(http.StatusUnauthorized) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | 		// If the domain is blocked we want to bail as fast as | 
					
						
							|  |  |  | 		// possible without the request proceeding further. | 
					
						
							|  |  |  | 		blocked, err := uriBlocked(ctx, pubKeyID) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Errorf(ctx, "error checking block for domain %s: %s", pubKeyID.Host, err) | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | 			c.AbortWithStatus(http.StatusInternalServerError) | 
					
						
							|  |  |  | 			return | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if blocked { | 
					
						
							|  |  |  | 			log.Infof(ctx, "domain %s is blocked", pubKeyID.Host) | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | 			c.AbortWithStatus(http.StatusForbidden) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | 		// Assume signature was set on Signature header, | 
					
						
							|  |  |  | 		// but fall back to Authorization header if necessary. | 
					
						
							|  |  |  | 		signature := c.GetHeader(sigHeader) | 
					
						
							|  |  |  | 		if signature == "" { | 
					
						
							|  |  |  | 			signature = c.GetHeader(authHeader) | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | 		// Set relevant values on the request context | 
					
						
							|  |  |  | 		// to save some work further down the line. | 
					
						
							|  |  |  | 		ctx = gtscontext.SetHTTPSignatureVerifier(ctx, verifier) | 
					
						
							|  |  |  | 		ctx = gtscontext.SetHTTPSignature(ctx, signature) | 
					
						
							|  |  |  | 		ctx = gtscontext.SetHTTPSignaturePubKeyID(ctx, pubKeyID) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Replace request with a shallow | 
					
						
							|  |  |  | 		// copy with the new context. | 
					
						
							|  |  |  | 		c.Request = c.Request.WithContext(ctx) | 
					
						
							| 
									
										
										
										
											2023-01-02 13:10:50 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } |