diff --git a/crypto.go b/crypto.go index 326f8e7..0eb6f12 100644 --- a/crypto.go +++ b/crypto.go @@ -17,23 +17,30 @@ import ( func decryptToken(encryptedToken []byte) (*nexToken, error) { // Split the encoded token into it's parts - cryptoConfig := encryptedToken[:0x90] - signature := encryptedToken[0x90:0xA4] - encryptedBody := encryptedToken[0xA4:] + cryptoConfig := encryptedToken[:0x82] + signature := encryptedToken[0x82:0x96] + encryptedBody := encryptedToken[0x96:] + // Parse crypto config into parts encryptedAESKey := cryptoConfig[:128] - iv := cryptoConfig[128:] + point1 := cryptoConfig[128] + point2 := cryptoConfig[129] + + // Rebuild the IV + iv := make([]byte, 0) + iv = append(iv, encryptedAESKey[point1:point1+8]...) + iv = append(iv, encryptedAESKey[point2:point2+8]...) // Decrypt the AES key decryptedAESKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, rsaPrivateKey, encryptedAESKey, nil) if err != nil { - return &nexToken{}, err + return nil, err } // Decrypt the token body block, err := aes.NewCipher(decryptedAESKey) if err != nil { - return &nexToken{}, err + return nil, err } decryptedBody := make([]byte, len(encryptedBody)) @@ -45,7 +52,7 @@ func decryptToken(encryptedToken []byte) (*nexToken, error) { // Verify the token body err = verifySignature(decryptedBody, signature, hmacSecret) if err != nil { - return &nexToken{}, err + return nil, err } // Unpack the token body to struct @@ -54,7 +61,7 @@ func decryptToken(encryptedToken []byte) (*nexToken, error) { err = binary.Read(tokenReader, binary.LittleEndian, token) if err != nil { - return &nexToken{}, err + return nil, err } return token, nil diff --git a/main.go b/main.go index f33b406..d23c07e 100644 --- a/main.go +++ b/main.go @@ -29,25 +29,31 @@ func main() { secureServer = nexproto.NewSecureProtocol(nexServer) accountManagementServer := nexproto.NewAccountManagementProtocol(nexServer) friendsServer := nexproto.NewFriendsProtocol(nexServer) + friends3DSServer := nexproto.NewFriends3DSProtocol(nexServer) // Handle PRUDP CONNECT packet (not an RMC method) nexServer.On("Connect", connect) // Account Management protocol handles - accountManagementServer.NintendoCreateAccount(nintendoCreateAccount) + accountManagementServer.NintendoCreateAccount3DS(nintendoCreateAccount3DS) // Secure protocol handles - - // Handle RegisterEx RMC method secureServer.Register(register) secureServer.RegisterEx(registerEx) // Friends (WiiU) protocol handles - friendsServer.UpdateAndGetAllInformation(updateAndGetAllInformation) - friendsServer.CheckSettingStatus(checkSettingStatus) + // Friends (3DS) protocol handles + friends3DSServer.UpdateProfile(updateProfile) + friends3DSServer.UpdateMii(updateMii) + friends3DSServer.UpdatePreference(updatePreference) + friends3DSServer.SyncFriend(syncFriend) + friends3DSServer.UpdatePresence(updatePresence) + friends3DSServer.UpdateFavoriteGameKey(updateFavoriteGameKey) + friends3DSServer.UpdateComment(updateComment) + nexServer.Listen(":60001") } diff --git a/nintendo_create_account.go b/nintendo_create_account.go index 2707cf3..d48be42 100644 --- a/nintendo_create_account.go +++ b/nintendo_create_account.go @@ -12,6 +12,10 @@ import ( ) func nintendoCreateAccount(err error, client *nex.Client, callID uint32, username string, key string, groups uint32, email string, nintendoCreateAccountData *nexproto.NintendoCreateAccountData) { + if err != nil { + panic(err) + } + tokenBase64 := nintendoCreateAccountData.Token encryptedToken, _ := base64.StdEncoding.DecodeString(tokenBase64) diff --git a/nintendo_create_account_3ds.go b/nintendo_create_account_3ds.go new file mode 100644 index 0000000..e8c4418 --- /dev/null +++ b/nintendo_create_account_3ds.go @@ -0,0 +1,69 @@ +package main + +import ( + "crypto/hmac" + "crypto/md5" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "fmt" + "strings" + + nex "github.com/PretendoNetwork/nex-go" + nexproto "github.com/PretendoNetwork/nex-protocols-go" +) + +func nintendoCreateAccount3DS(err error, client *nex.Client, callID uint32, strPrincipalName string, strKey string, uiGroups uint32, strEmail string, oAuthData *nexproto.AccountExtraInfo) { + if err != nil { + panic(err) + } + + fmt.Println(oAuthData.NEXToken) + + tokenBase64 := oAuthData.NEXToken + tokenBase64 = strings.Replace(tokenBase64, ".", "+", -1) + tokenBase64 = strings.Replace(tokenBase64, "-", "/", -1) + tokenBase64 = strings.Replace(tokenBase64, "*", "=", -1) + + encryptedToken, _ := base64.StdEncoding.DecodeString(tokenBase64) + + decryptedToken, err := decryptToken(encryptedToken) + if err != nil { + panic(err) + } + + pid := decryptedToken.UserPID + + pidByteArray := make([]byte, 4) + binary.LittleEndian.PutUint32(pidByteArray, pid) + + mac := hmac.New(md5.New, []byte(strKey)) + mac.Write(pidByteArray) + + pidHmac := hex.EncodeToString(mac.Sum(nil)) + + rmcResponseStream := nex.NewStreamOut(nexServer) + + rmcResponseStream.WriteUInt32LE(pid) + rmcResponseStream.WriteString(pidHmac) + + rmcResponseBody := rmcResponseStream.Bytes() + + rmcResponse := nex.NewRMCResponse(nexproto.AccountManagementProtocolID, callID) + rmcResponse.SetSuccess(nexproto.AccountManagementMethodNintendoCreateAccount, rmcResponseBody) + + rmcResponseBytes := rmcResponse.Bytes() + + responsePacket, _ := nex.NewPacketV0(client, nil) + + responsePacket.SetVersion(0) + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + nexServer.Send(responsePacket) +} diff --git a/sig.go b/sig.go new file mode 100644 index 0000000..d575440 --- /dev/null +++ b/sig.go @@ -0,0 +1,140 @@ +package main + +/* +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/binary" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "strings" +) + +type nexToken struct { + SystemType uint8 + TokenType uint8 + UserPID uint32 + TitleID uint64 + CreatTime uint64 +} + +var rsaPrivateKeyBytes []byte +var rsaPrivateKey *rsa.PrivateKey +var hmacSecret []byte + +func parseRsaPrivateKey(keyBytes []byte) (*rsa.PrivateKey, error) { + block, _ := pem.Decode(keyBytes) + if block == nil { + return nil, errors.New("Failed to parse RSA key") + } + + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + return privateKey, nil +} + +func verifySignature(body []byte, expectedSignature []byte, secret []byte) error { + + mac := hmac.New(sha1.New, secret) + mac.Write(body) + + calculatedSignature := mac.Sum(nil) + + if !bytes.Equal(expectedSignature, calculatedSignature) { + return errors.New("[ERROR] Calculated signature did not match") + } + + return nil +} + +func init() { + var err error + + rsaPrivateKeyBytes, err = ioutil.ReadFile("private.pem") + if err != nil { + panic(err) + } + + rsaPrivateKey, err = parseRsaPrivateKey(rsaPrivateKeyBytes) + if err != nil { + panic(err) + } + + hmacSecret, err = ioutil.ReadFile("secret.key") + if err != nil { + panic(err) + } +} + +func main() { + tokenBase64 := "Smbh6g2tL93Ro6RAvCBCGtLzGw.40vhPIjGNyZh9LkineqBFQ-PJGVXZ8Xg.hUZk92KTtrisA0.OnzGi92K-tMud5piu08XWa7hwSaxXNGjRJ.o6HWwi1xapFndyhWOHs5kXtmUiFmyc1AOlcfo8nvSHsPTsEDX2Qc9BeooBJrUeMxDc2LDch0HyiMKGdPnpBg2-Lpekma6fNvCg0ylTqUMqonI.smHCXikr4IhWvvwy9QZDJLM*" + tokenBase64 = strings.Replace(tokenBase64, ".", "+", -1) + tokenBase64 = strings.Replace(tokenBase64, "-", "/", -1) + tokenBase64 = strings.Replace(tokenBase64, "*", "=", -1) + + encryptedToken, _ := base64.StdEncoding.DecodeString(tokenBase64) + + // Split the encoded token into it's parts + cryptoConfig := encryptedToken[:0x82] + signature := encryptedToken[0x82:0x96] + encryptedBody := encryptedToken[0x96:] + + // Parse crypto config into parts + encryptedAESKey := cryptoConfig[:128] + point1 := cryptoConfig[128] + point2 := cryptoConfig[129] + + // Rebuild the IV + iv := make([]byte, 0) + iv = append(iv, encryptedAESKey[point1:point1+8]...) + iv = append(iv, encryptedAESKey[point2:point2+8]...) + + // Decrypt the AES key + decryptedAESKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, rsaPrivateKey, encryptedAESKey, nil) + if err != nil { + panic(err) + } + + // Decrypt the token body + block, err := aes.NewCipher(decryptedAESKey) + if err != nil { + panic(err) + } + + decryptedBody := make([]byte, len(encryptedBody)) + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(decryptedBody, encryptedBody) + + decryptedBody = decryptedBody[:0x16] // Remove AES padding + + // Verify the token body + err = verifySignature(decryptedBody, signature, hmacSecret) + if err != nil { + panic(err) + } + + // Unpack the token body to struct + token := &nexToken{} + tokenReader := bytes.NewBuffer(decryptedBody) + + err = binary.Read(tokenReader, binary.LittleEndian, token) + if err != nil { + panic(err) + } + + fmt.Println(token) +} +*/ diff --git a/sync_friend.go b/sync_friend.go new file mode 100644 index 0000000..42778d9 --- /dev/null +++ b/sync_friend.go @@ -0,0 +1,34 @@ +package main + +import ( + nex "github.com/PretendoNetwork/nex-go" + nexproto "github.com/PretendoNetwork/nex-protocols-go" +) + +func syncFriend(err error, client *nex.Client, callID uint32, unknown1 uint64, pids []uint32, unknown3 []uint64) { + // TODO: Do something with this + + rmcResponseStream := nex.NewStreamOut(nexServer) + + rmcResponseStream.WriteUInt32LE(0) // List length 0 + + rmcResponseBody := rmcResponseStream.Bytes() + + rmcResponse := nex.NewRMCResponse(nexproto.Friends3DSProtocolID, callID) + rmcResponse.SetSuccess(nexproto.Friends3DSMethodSyncFriend, rmcResponseBody) + + rmcResponseBytes := rmcResponse.Bytes() + + responsePacket, _ := nex.NewPacketV0(client, nil) + + responsePacket.SetVersion(0) + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + nexServer.Send(responsePacket) +} diff --git a/update_comment.go b/update_comment.go new file mode 100644 index 0000000..6c506db --- /dev/null +++ b/update_comment.go @@ -0,0 +1,28 @@ +package main + +import ( + nex "github.com/PretendoNetwork/nex-go" + nexproto "github.com/PretendoNetwork/nex-protocols-go" +) + +func updateComment(err error, client *nex.Client, callID uint32, comment string) { + // TODO: Do something with this + + rmcResponse := nex.NewRMCResponse(nexproto.Friends3DSProtocolID, callID) + rmcResponse.SetSuccess(nexproto.Friends3DSMethodUpdateComment, nil) + + rmcResponseBytes := rmcResponse.Bytes() + + responsePacket, _ := nex.NewPacketV0(client, nil) + + responsePacket.SetVersion(0) + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + nexServer.Send(responsePacket) +} diff --git a/update_favorite_game_key.go b/update_favorite_game_key.go new file mode 100644 index 0000000..90ddc2d --- /dev/null +++ b/update_favorite_game_key.go @@ -0,0 +1,28 @@ +package main + +import ( + nex "github.com/PretendoNetwork/nex-go" + nexproto "github.com/PretendoNetwork/nex-protocols-go" +) + +func updateFavoriteGameKey(err error, client *nex.Client, callID uint32, gameKey *nexproto.GameKey) { + // TODO: Do something with this + + rmcResponse := nex.NewRMCResponse(nexproto.Friends3DSProtocolID, callID) + rmcResponse.SetSuccess(nexproto.Friends3DSMethodUpdateFavoriteGameKey, nil) + + rmcResponseBytes := rmcResponse.Bytes() + + responsePacket, _ := nex.NewPacketV0(client, nil) + + responsePacket.SetVersion(0) + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + nexServer.Send(responsePacket) +} diff --git a/update_mii.go b/update_mii.go new file mode 100644 index 0000000..d347d96 --- /dev/null +++ b/update_mii.go @@ -0,0 +1,28 @@ +package main + +import ( + nex "github.com/PretendoNetwork/nex-go" + nexproto "github.com/PretendoNetwork/nex-protocols-go" +) + +func updateMii(err error, client *nex.Client, callID uint32, mii *nexproto.Mii) { + // TODO: Do something with this + + rmcResponse := nex.NewRMCResponse(nexproto.Friends3DSProtocolID, callID) + rmcResponse.SetSuccess(nexproto.Friends3DSMethodUpdateMii, nil) + + rmcResponseBytes := rmcResponse.Bytes() + + responsePacket, _ := nex.NewPacketV0(client, nil) + + responsePacket.SetVersion(0) + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + nexServer.Send(responsePacket) +} diff --git a/update_preference.go b/update_preference.go new file mode 100644 index 0000000..8e3052e --- /dev/null +++ b/update_preference.go @@ -0,0 +1,28 @@ +package main + +import ( + nex "github.com/PretendoNetwork/nex-go" + nexproto "github.com/PretendoNetwork/nex-protocols-go" +) + +func updatePreference(err error, client *nex.Client, callID uint32, unknown1 bool, unknown2 bool, unknown3 bool) { + // TODO: Do something with this + + rmcResponse := nex.NewRMCResponse(nexproto.Friends3DSProtocolID, callID) + rmcResponse.SetSuccess(nexproto.Friends3DSMethodUpdatePreference, nil) + + rmcResponseBytes := rmcResponse.Bytes() + + responsePacket, _ := nex.NewPacketV0(client, nil) + + responsePacket.SetVersion(0) + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + nexServer.Send(responsePacket) +} diff --git a/update_presence.go b/update_presence.go new file mode 100644 index 0000000..8377029 --- /dev/null +++ b/update_presence.go @@ -0,0 +1,28 @@ +package main + +import ( + nex "github.com/PretendoNetwork/nex-go" + nexproto "github.com/PretendoNetwork/nex-protocols-go" +) + +func updatePresence(err error, client *nex.Client, callID uint32, presense *nexproto.NintendoPresence, unknown bool) { + // TODO: Do something with this + + rmcResponse := nex.NewRMCResponse(nexproto.Friends3DSProtocolID, callID) + rmcResponse.SetSuccess(nexproto.Friends3DSMethodUpdatePresence, nil) + + rmcResponseBytes := rmcResponse.Bytes() + + responsePacket, _ := nex.NewPacketV0(client, nil) + + responsePacket.SetVersion(0) + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + nexServer.Send(responsePacket) +} diff --git a/update_profile.go b/update_profile.go new file mode 100644 index 0000000..2e03ff8 --- /dev/null +++ b/update_profile.go @@ -0,0 +1,28 @@ +package main + +import ( + nex "github.com/PretendoNetwork/nex-go" + nexproto "github.com/PretendoNetwork/nex-protocols-go" +) + +func updateProfile(err error, client *nex.Client, callID uint32, profileData *nexproto.MyProfile) { + // TODO: Do something with this + + rmcResponse := nex.NewRMCResponse(nexproto.Friends3DSProtocolID, callID) + rmcResponse.SetSuccess(nexproto.Friends3DSMethodUpdateProfile, nil) + + rmcResponseBytes := rmcResponse.Bytes() + + responsePacket, _ := nex.NewPacketV0(client, nil) + + responsePacket.SetVersion(0) + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + nexServer.Send(responsePacket) +}