Replace /signature-verification-key endpoint with extension-specific ones /signature-verification-key.{der|pem}.

Fix Content-Disposition header
This commit is contained in:
ErickSkrauch 2021-03-03 13:33:56 +01:00
parent 7cf5ae13be
commit 32a9fee3e6
4 changed files with 58 additions and 10 deletions

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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 != "" {