diff --git a/libgm/manualdecrypt/main.go b/libgm/manualdecrypt/main.go new file mode 100644 index 0000000..043500d --- /dev/null +++ b/libgm/manualdecrypt/main.go @@ -0,0 +1,136 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "os/exec" + + "google.golang.org/protobuf/proto" + + "go.mau.fi/mautrix-gmessages/libgm/crypto" + "go.mau.fi/mautrix-gmessages/libgm/gmproto" + "go.mau.fi/mautrix-gmessages/libgm/pblite" +) + +func must[T any](t T, err error) T { + if err != nil { + panic(err) + } + return t +} + +func mustNoReturn(err error) { + if err != nil { + panic(err) + } +} + +var requestType = map[gmproto.ActionType]proto.Message{ + gmproto.ActionType_LIST_CONVERSATIONS: &gmproto.ListConversationsRequest{}, + gmproto.ActionType_NOTIFY_DITTO_ACTIVITY: &gmproto.NotifyDittoActivityRequest{}, + gmproto.ActionType_GET_CONVERSATION_TYPE: &gmproto.GetConversationTypeRequest{}, + gmproto.ActionType_GET_CONVERSATION: &gmproto.GetConversationRequest{}, + gmproto.ActionType_LIST_MESSAGES: &gmproto.ListMessagesRequest{}, + gmproto.ActionType_SEND_MESSAGE: &gmproto.SendMessageRequest{}, + gmproto.ActionType_SEND_REACTION: &gmproto.SendReactionRequest{}, + gmproto.ActionType_DELETE_MESSAGE: &gmproto.DeleteMessageRequest{}, + gmproto.ActionType_GET_PARTICIPANTS_THUMBNAIL: &gmproto.GetParticipantThumbnailRequest{}, + gmproto.ActionType_LIST_CONTACTS: &gmproto.ListContactsRequest{}, + gmproto.ActionType_LIST_TOP_CONTACTS: &gmproto.ListTopContactsRequest{}, + gmproto.ActionType_GET_OR_CREATE_CONVERSATION: &gmproto.GetOrCreateConversationRequest{}, + gmproto.ActionType_UPDATE_CONVERSATION: &gmproto.UpdateConversationRequest{}, +} + +func main() { + var x crypto.AESCTRHelper + file, err := os.Open("config.json") + if errors.Is(err, os.ErrNotExist) { + _ = file.Close() + _, _ = fmt.Fprintln(os.Stderr, "config.json doesn't exist") + _, _ = fmt.Fprintln(os.Stderr, "Please find pr_crypto_msg_enc_key and pr_crypto_msg_hmac from localStorage") + _, _ = fmt.Fprintln(os.Stderr, "(make sure not to confuse it with pr_crypto_hmac)") + stdin := bufio.NewScanner(os.Stdin) + _, _ = fmt.Fprint(os.Stderr, "AES key (pr_crypto_msg_enc_key): ") + stdin.Scan() + x.AESKey = must(base64.StdEncoding.DecodeString(stdin.Text())) + if len(x.AESKey) != 32 { + _, _ = fmt.Fprintln(os.Stderr, "AES key must be 32 bytes") + return + } + _, _ = fmt.Fprint(os.Stderr, "HMAC key (pr_crypto_msg_hmac): ") + stdin.Scan() + x.HMACKey = must(base64.StdEncoding.DecodeString(stdin.Text())) + if len(x.HMACKey) != 32 { + _, _ = fmt.Fprintln(os.Stderr, "HMAC key must be 32 bytes") + return + } + file, err = os.OpenFile("config.json", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Failed to open config.json for writing") + return + } + mustNoReturn(json.NewEncoder(file).Encode(&x)) + _, _ = fmt.Fprintln(os.Stderr, "Saved keys to config.json") + } else { + mustNoReturn(json.NewDecoder(file).Decode(&x)) + } + _ = file.Close() + _, _ = fmt.Fprintln(os.Stderr, "Please paste the request body, then press Ctrl+D to close stdin") + d := must(io.ReadAll(os.Stdin)) + var decoded []byte + var typ gmproto.MessageType + if json.Valid(d) { + var orm gmproto.OutgoingRPCMessage + mustNoReturn(pblite.Unmarshal(d, &orm)) + decoded = orm.Data.MessageData + typ = orm.Data.MessageTypeData.MessageType + } else { + decoded = must(base64.StdEncoding.DecodeString(string(d))) + } + outgoing := true + if outgoing { + var ord gmproto.OutgoingRPCData + mustNoReturn(proto.Unmarshal(decoded, &ord)) + _, _ = fmt.Fprintln(os.Stderr) + _, _ = fmt.Fprintln(os.Stderr, "CHANNEL:", typ.String()) + _, _ = fmt.Fprintln(os.Stderr, "REQUEST TYPE:", ord.Action.String()) + _, _ = fmt.Fprintln(os.Stderr, "REQUEST ID:", ord.RequestID) + if ord.EncryptedProtoData == nil { + _, _ = fmt.Fprintln(os.Stderr, "No encrypted data") + return + } + decrypted := must(x.Decrypt(ord.EncryptedProtoData)) + _, _ = fmt.Fprintln(os.Stderr, "------------------------------ RAW DECRYPTED DATA ------------------------------") + fmt.Println(base64.StdEncoding.EncodeToString(decrypted)) + _, _ = fmt.Fprintln(os.Stderr, "--------------------------------- DECODED DATA ---------------------------------") + cmd := exec.Command("protoc", "--decode_raw") + cmd.Stdin = bytes.NewReader(decrypted) + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + mustNoReturn(cmd.Run()) + respType, ok := requestType[ord.Action] + if ok { + respData := respType.ProtoReflect().New().Interface() + mustNoReturn(proto.Unmarshal(decrypted, respData)) + _, _ = fmt.Fprintln(os.Stderr, "------------------------------ PARSED STRUCT DATA ------------------------------") + _, _ = fmt.Fprintf(os.Stderr, "%+v\n", respData) + } + } else { + var ird gmproto.RPCMessageData + mustNoReturn(proto.Unmarshal(decoded, &ird)) + decrypted := must(x.Decrypt(ird.EncryptedData)) + _, _ = fmt.Fprintln(os.Stderr) + _, _ = fmt.Fprintln(os.Stderr, "CHANNEL:", typ.String()) + _, _ = fmt.Fprintln(os.Stderr, "REQUEST TYPE:", ird.Action.String()) + _, _ = fmt.Fprintln(os.Stderr, "REQUEST ID:", ird.SessionID) + _, _ = fmt.Fprintln(os.Stderr, "------------------------------ RAW DECRYPTED DATA ------------------------------") + fmt.Println(base64.StdEncoding.EncodeToString(decrypted)) + } + _, _ = fmt.Fprintln(os.Stderr, "--------------------------------------------------------------------------------") +}