diff --git a/handle/yggdrasil/yggdrasil.go b/handle/yggdrasil/yggdrasil.go index 3d7b402..30e8658 100644 --- a/handle/yggdrasil/yggdrasil.go +++ b/handle/yggdrasil/yggdrasil.go @@ -69,7 +69,8 @@ func (y *Yggdrasil) YggdrasilRoot() httprouter.Handle { Homepage: "", Register: "", }, - ServerName: "test", + ServerName: "test", + EnableProfileKey: true, }, SignaturePublickey: string(y.pubkey), SkinDomains: []string{host}, diff --git a/model/yggdrasil/model.go b/model/yggdrasil/model.go index f61023b..8edefe4 100644 --- a/model/yggdrasil/model.go +++ b/model/yggdrasil/model.go @@ -75,9 +75,23 @@ type YggdrasilMeta struct { ImplementationVersion string `json:"implementationVersion"` Links YggdrasilMetaLinks `json:"links"` ServerName string `json:"serverName"` + EnableProfileKey bool `json:"feature.enable_profile_key"` } type YggdrasilMetaLinks struct { Homepage string `json:"homepage"` Register string `json:"register"` } + +type Certificates struct { + ExpiresAt string `json:"expiresAt"` + KeyPair CertificatesKeyPair `json:"keyPair"` + PublicKeySignature string `json:"publicKeySignature"` + PublicKeySignatureV2 string `json:"publicKeySignatureV2"` + RefreshedAfter string `json:"refreshedAfter"` +} + +type CertificatesKeyPair struct { + PrivateKey string `json:"privateKey"` + PublicKey string `json:"publicKey"` +} diff --git a/service/yggdrasil/user.go b/service/yggdrasil/user.go index 3738eaf..d87df11 100644 --- a/service/yggdrasil/user.go +++ b/service/yggdrasil/user.go @@ -1,9 +1,15 @@ package yggdrasil import ( + "bytes" "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/binary" "errors" "fmt" + "math/big" "net/url" "strconv" "strings" @@ -249,3 +255,62 @@ func (y *Yggdrasil) BatchProfile(ctx context.Context, names []string) ([]yggdras } }), nil } + +func (y *Yggdrasil) PlayerCertificates(ctx context.Context, token string) (yggdrasil.Certificates, error) { + t, err := sutils.Auth(ctx, yggdrasil.ValidateToken{AccessToken: token}, y.client, &y.prikey.PublicKey, false) + if err != nil { + return yggdrasil.Certificates{}, fmt.Errorf("PlayerCertificates: %w", err) + } + rsa2048, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return yggdrasil.Certificates{}, fmt.Errorf("PlayerCertificates: %w", err) + } + + s := sign.NewAuthlibSignWithKey(rsa2048) + priKey := lo.Must(s.GetPriKey()) + pubKey := lo.Must(s.GetPubKey()) + + expiresAt := time.Now().Add(24 * time.Hour) + expiresAtUnix := expiresAt.UnixMilli() + + pubV2 := publicKeySignatureV2(&rsa2048.PublicKey, t.Subject, expiresAtUnix) + pub := publicKeySignature(pubKey, expiresAtUnix) + + servicePri := sign.NewAuthlibSignWithKey(y.prikey) + + pubV2Base64 := lo.Must(servicePri.Sign(pubV2)) + pubBase64 := lo.Must(servicePri.Sign(pub)) + + return yggdrasil.Certificates{ + ExpiresAt: expiresAt.Format(time.RFC3339Nano), + KeyPair: yggdrasil.CertificatesKeyPair{ + PrivateKey: priKey, + PublicKey: pubKey, + }, + PublicKeySignature: pubBase64, + PublicKeySignatureV2: pubV2Base64, + RefreshedAfter: time.Now().Format(time.RFC3339Nano), + }, nil + +} + +func publicKeySignatureV2(key *rsa.PublicKey, uuid string, expiresAt int64) []byte { + bf := &bytes.Buffer{} + u := big.Int{} + u.SetString(uuid, 16) + bf.Write(u.Bytes()) + + eb := make([]byte, 8) + binary.BigEndian.PutUint64(eb, uint64(expiresAt)) + bf.Write(eb) + bf.Write(x509.MarshalPKCS1PublicKey(key)) + + return bf.Bytes() +} + +func publicKeySignature(key string, expiresAt int64) []byte { + bf := &bytes.Buffer{} + bf.WriteString(strconv.FormatInt(expiresAt, 10)) + bf.WriteString(key) + return bf.Bytes() +}