mirror of
				https://github.com/elyby/chrly.git
				synced 2025-05-31 14:11:51 +05:30 
			
		
		
		
	Replace /signature-verification-key endpoint with extension-specific ones /signature-verification-key.{der|pem}.
Fix Content-Disposition header
This commit is contained in:
		| @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||||||
| ### Added | ### Added | ||||||
| - `/profile/{username}` endpoint, which returns a profile and its textures, equivalent of the Mojang's | - `/profile/{username}` endpoint, which returns a profile and its textures, equivalent of the Mojang's | ||||||
|   [UUID -> Profile + Skin/Cape endpoint](https://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape). |   [UUID -> Profile + Skin/Cape endpoint](https://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape). | ||||||
| - `/signature-verification-key` endpoint, which returns the public key in `DER` format for signature verification. | - `/signature-verification-key.der` and `/signature-verification-key.pem` endpoints, which returns the public key in | ||||||
|  |   `DER` or `PEM` formats for signature verification. | ||||||
|  |  | ||||||
| ### Fixed | ### Fixed | ||||||
| - [#28](https://github.com/elyby/chrly/issues/28): Added handling of corrupted data from the Mojang's username to UUID | - [#28](https://github.com/elyby/chrly/issues/28): Added handling of corrupted data from the Mojang's username to UUID | ||||||
|   | |||||||
| @@ -280,11 +280,15 @@ Note that this endpoint will try to use the UUID for the stored profile in the d | |||||||
| to the situation where the user is available in the database but has no textures, which caused them to be retrieved | to the situation where the user is available in the database but has no textures, which caused them to be retrieved | ||||||
| from the Mojang's API. | from the Mojang's API. | ||||||
|  |  | ||||||
| #### `GET /signature-verification-key` | #### `GET /signature-verification-key.der` | ||||||
|  |  | ||||||
| This endpoint returns a public key that can be used to verify textures signatures. The key is provided in `DER` format, | This endpoint returns a public key that can be used to verify textures signatures. The key is provided in `DER` format, | ||||||
| so it can be used directly in the Authlib, without modifying the signature checking algorithm. | so it can be used directly in the Authlib, without modifying the signature checking algorithm. | ||||||
|  |  | ||||||
|  | #### `GET /signature-verification-key.pem` | ||||||
|  |  | ||||||
|  | The same endpoint as the previous one, except that it returns the key in `PEM` format. | ||||||
|  |  | ||||||
| #### `GET /textures/signed/{username}` | #### `GET /textures/signed/{username}` | ||||||
|  |  | ||||||
| Actually, this is the [Ely.by](https://ely.by)'s feature called | Actually, this is the [Ely.by](https://ely.by)'s feature called | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import ( | |||||||
| 	"crypto/x509" | 	"crypto/x509" | ||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"encoding/pem" | ||||||
| 	"github.com/elyby/chrly/utils" | 	"github.com/elyby/chrly/utils" | ||||||
| 	"io" | 	"io" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| @@ -71,7 +72,8 @@ func (ctx *Skinsystem) Handler() *mux.Router { | |||||||
| 	router.HandleFunc("/skins", ctx.skinGetHandler).Methods(http.MethodGet) | 	router.HandleFunc("/skins", ctx.skinGetHandler).Methods(http.MethodGet) | ||||||
| 	router.HandleFunc("/cloaks", ctx.capeGetHandler).Methods(http.MethodGet) | 	router.HandleFunc("/cloaks", ctx.capeGetHandler).Methods(http.MethodGet) | ||||||
| 	// Utils | 	// Utils | ||||||
| 	router.HandleFunc("/signature-verification-key", ctx.signatureVerificationKeyHandler).Methods(http.MethodGet) | 	router.HandleFunc("/signature-verification-key.der", ctx.signatureVerificationKeyHandler).Methods(http.MethodGet) | ||||||
|  | 	router.HandleFunc("/signature-verification-key.pem", ctx.signatureVerificationKeyHandler).Methods(http.MethodGet) | ||||||
|  |  | ||||||
| 	return router | 	return router | ||||||
| } | } | ||||||
| @@ -244,9 +246,21 @@ func (ctx *Skinsystem) signatureVerificationKeyHandler(response http.ResponseWri | |||||||
| 		panic(err) | 		panic(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	_, _ = response.Write(asn1Bytes) | 	if strings.HasSuffix(request.URL.Path, ".pem") { | ||||||
| 	response.Header().Set("Content-Type", "application/octet-stream") | 		publicKeyBlock := pem.Block{ | ||||||
| 	response.Header().Set("Content-Disposition", "attachment; filename=\"yggdrasil_session_pubkey.der\"") | 			Type:  "PUBLIC KEY", | ||||||
|  | 			Bytes: asn1Bytes, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		publicKeyPemBytes := pem.EncodeToMemory(&publicKeyBlock) | ||||||
|  |  | ||||||
|  | 		response.Header().Set("Content-Disposition", "attachment; filename=\"yggdrasil_session_pubkey.pem\"") | ||||||
|  | 		_, _ = response.Write(publicKeyPemBytes) | ||||||
|  | 	} else { | ||||||
|  | 		response.Header().Set("Content-Type", "application/octet-stream") | ||||||
|  | 		response.Header().Set("Content-Disposition", "attachment; filename=\"yggdrasil_session_pubkey.der\"") | ||||||
|  | 		_, _ = response.Write(asn1Bytes) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // TODO: in v5 should be extracted into some ProfileProvider interface, | // TODO: in v5 should be extracted into some ProfileProvider interface, | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import ( | |||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
|  | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| @@ -1083,9 +1084,18 @@ func (suite *skinsystemTestSuite) TestProfile() { | |||||||
|  * Get profile tests cases * |  * Get profile tests cases * | ||||||
|  ***************************/ |  ***************************/ | ||||||
|  |  | ||||||
| var signingKeyTestsCases = []*skinsystemTestCase{ | type signingKeyTestCase struct { | ||||||
|  | 	Name       string | ||||||
|  | 	KeyFormat  string | ||||||
|  | 	BeforeTest func(suite *skinsystemTestSuite) | ||||||
|  | 	PanicErr   string | ||||||
|  | 	AfterTest  func(suite *skinsystemTestSuite, response *http.Response) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var signingKeyTestsCases = []*signingKeyTestCase{ | ||||||
| 	{ | 	{ | ||||||
| 		Name: "Get public key", | 		Name:      "Get public key in DER format", | ||||||
|  | 		KeyFormat: "DER", | ||||||
| 		BeforeTest: func(suite *skinsystemTestSuite) { | 		BeforeTest: func(suite *skinsystemTestSuite) { | ||||||
| 			pubPem, _ := pem.Decode([]byte("-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----")) | 			pubPem, _ := pem.Decode([]byte("-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----")) | ||||||
| 			publicKey, _ := x509.ParsePKIXPublicKey(pubPem.Bytes) | 			publicKey, _ := x509.ParsePKIXPublicKey(pubPem.Bytes) | ||||||
| @@ -1095,12 +1105,31 @@ var signingKeyTestsCases = []*skinsystemTestCase{ | |||||||
| 		AfterTest: func(suite *skinsystemTestSuite, response *http.Response) { | 		AfterTest: func(suite *skinsystemTestSuite, response *http.Response) { | ||||||
| 			suite.Equal(200, response.StatusCode) | 			suite.Equal(200, response.StatusCode) | ||||||
| 			suite.Equal("application/octet-stream", response.Header.Get("Content-Type")) | 			suite.Equal("application/octet-stream", response.Header.Get("Content-Type")) | ||||||
|  | 			suite.Equal("attachment; filename=\"yggdrasil_session_pubkey.der\"", response.Header.Get("Content-Disposition")) | ||||||
| 			body, _ := ioutil.ReadAll(response.Body) | 			body, _ := ioutil.ReadAll(response.Body) | ||||||
| 			suite.Equal([]byte{48, 92, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 75, 0, 48, 72, 2, 65, 0, 214, 212, 165, 80, 153, 144, 194, 169, 126, 246, 25, 211, 197, 183, 150, 233, 157, 1, 166, 49, 44, 25, 230, 80, 57, 115, 28, 20, 7, 220, 58, 88, 121, 254, 86, 8, 237, 246, 76, 53, 58, 125, 226, 9, 231, 192, 52, 148, 12, 176, 130, 214, 120, 195, 8, 182, 116, 97, 206, 207, 253, 97, 2, 247, 2, 3, 1, 0, 1}, body) | 			suite.Equal([]byte{48, 92, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 75, 0, 48, 72, 2, 65, 0, 214, 212, 165, 80, 153, 144, 194, 169, 126, 246, 25, 211, 197, 183, 150, 233, 157, 1, 166, 49, 44, 25, 230, 80, 57, 115, 28, 20, 7, 220, 58, 88, 121, 254, 86, 8, 237, 246, 76, 53, 58, 125, 226, 9, 231, 192, 52, 148, 12, 176, 130, 214, 120, 195, 8, 182, 116, 97, 206, 207, 253, 97, 2, 247, 2, 3, 1, 0, 1}, body) | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Name: "Error while obtaining public key", | 		Name:      "Get public key in PEM format", | ||||||
|  | 		KeyFormat: "PEM", | ||||||
|  | 		BeforeTest: func(suite *skinsystemTestSuite) { | ||||||
|  | 			pubPem, _ := pem.Decode([]byte("-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----")) | ||||||
|  | 			publicKey, _ := x509.ParsePKIXPublicKey(pubPem.Bytes) | ||||||
|  |  | ||||||
|  | 			suite.TexturesSigner.On("GetPublicKey").Return(publicKey, nil) | ||||||
|  | 		}, | ||||||
|  | 		AfterTest: func(suite *skinsystemTestSuite, response *http.Response) { | ||||||
|  | 			suite.Equal(200, response.StatusCode) | ||||||
|  | 			suite.Equal("text/plain; charset=utf-8", response.Header.Get("Content-Type")) | ||||||
|  | 			suite.Equal("attachment; filename=\"yggdrasil_session_pubkey.pem\"", response.Header.Get("Content-Disposition")) | ||||||
|  | 			body, _ := ioutil.ReadAll(response.Body) | ||||||
|  | 			suite.Equal("-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----\n", string(body)) | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		Name:      "Error while obtaining public key", | ||||||
|  | 		KeyFormat: "DER", | ||||||
| 		BeforeTest: func(suite *skinsystemTestSuite) { | 		BeforeTest: func(suite *skinsystemTestSuite) { | ||||||
| 			suite.TexturesSigner.On("GetPublicKey").Return(nil, errors.New("textures signer error")) | 			suite.TexturesSigner.On("GetPublicKey").Return(nil, errors.New("textures signer error")) | ||||||
| 		}, | 		}, | ||||||
| @@ -1113,7 +1142,7 @@ func (suite *skinsystemTestSuite) TestSignatureVerificationKey() { | |||||||
| 		suite.RunSubTest(testCase.Name, func() { | 		suite.RunSubTest(testCase.Name, func() { | ||||||
| 			testCase.BeforeTest(suite) | 			testCase.BeforeTest(suite) | ||||||
|  |  | ||||||
| 			req := httptest.NewRequest("GET", "http://chrly/signature-verification-key", nil) | 			req := httptest.NewRequest("GET", "http://chrly/signature-verification-key."+strings.ToLower(testCase.KeyFormat), nil) | ||||||
| 			w := httptest.NewRecorder() | 			w := httptest.NewRecorder() | ||||||
|  |  | ||||||
| 			if testCase.PanicErr != "" { | 			if testCase.PanicErr != "" { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user