Просмотр исходного кода

fix java AIM client login

The java AIM client sends a different roasting string than the
other clients.
Mike 1 год назад
Родитель
Сommit
bcaa847fbd
4 измененных файлов с 240 добавлено и 45 удалено
  1. 31 22
      foodgroup/auth.go
  2. 181 9
      foodgroup/auth_test.go
  3. 10 1
      state/user.go
  4. 18 13
      wire/user.go

+ 31 - 22
foodgroup/auth.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"net"
 	"strconv"
+	"strings"
 	"time"
 
 	"github.com/mk6i/retro-aim-server/config"
@@ -279,12 +280,14 @@ func (s AuthService) FLAPLogin(
 
 // loginProperties represents the properties sent by the client at login.
 type loginProperties struct {
-	screenName   state.DisplayScreenName
-	clientID     string
-	isBUCPAuth   bool
-	isTOCAuth    bool
-	passwordHash []byte
-	roastedPass  []byte
+	clientID       string
+	isBUCPAuth     bool
+	isFLAPJavaAuth bool
+	isTOCAuth      bool
+	isFLAPAuth     bool
+	passwordHash   []byte
+	roastedPass    []byte
+	screenName     state.DisplayScreenName
 }
 
 // fromTLV creates an instance of loginProperties from a TLV list.
@@ -304,22 +307,26 @@ func (l *loginProperties) fromTLV(list wire.TLVList) error {
 	// get the password from the appropriate TLV. older clients have a
 	// roasted password, newer clients have a hashed password. ICQ may omit
 	// the password TLV when logging in without saved password.
-
-	// extract password hash for BUCP login
-	if passwordHash, found := list.Bytes(wire.LoginTLVTagsPasswordHash); found {
-		l.passwordHash = passwordHash
+	switch {
+	case list.HasTag(wire.LoginTLVTagsPasswordHash):
+		// extract password hash for BUCP login
+		l.passwordHash, _ = list.Bytes(wire.LoginTLVTagsPasswordHash)
 		l.isBUCPAuth = true
-	}
-
-	// extract roasted password for FLAP login
-	if roastedPass, found := list.Bytes(wire.LoginTLVTagsRoastedPassword); found {
-		l.roastedPass = roastedPass
-	}
-
-	// extract roasted password for TOC FLAP login
-	if roastedPass, found := list.Bytes(wire.LoginTLVTagsRoastedTOCPassword); found {
-		l.roastedPass = roastedPass
+	case list.HasTag(wire.LoginTLVTagsRoastedPassword):
+		// extract roasted password for FLAP login
+		l.roastedPass, _ = list.Bytes(wire.LoginTLVTagsRoastedPassword)
+		if strings.HasPrefix(l.clientID, "AOL Instant Messenger (TM) version") &&
+			strings.Contains(l.clientID, "for Java") {
+			l.isFLAPJavaAuth = true
+		} else {
+			l.isFLAPAuth = true
+		}
+	case list.HasTag(wire.LoginTLVTagsRoastedTOCPassword):
+		// extract roasted password for TOC FLAP login
+		l.roastedPass, _ = list.Bytes(wire.LoginTLVTagsRoastedTOCPassword)
 		l.isTOCAuth = true
+	default:
+		l.isFLAPAuth = true
 	}
 
 	return nil
@@ -370,10 +377,12 @@ func (s AuthService) login(
 	switch {
 	case props.isBUCPAuth:
 		loginOK = user.ValidateHash(props.passwordHash)
+	case props.isFLAPAuth:
+		loginOK = user.ValidateRoastedPass(props.roastedPass)
+	case props.isFLAPJavaAuth:
+		loginOK = user.ValidateRoastedJavaPass(props.roastedPass)
 	case props.isTOCAuth:
 		loginOK = user.ValidateRoastedTOCPass(props.roastedPass)
-	default:
-		loginOK = user.ValidateRoastedPass(props.roastedPass)
 	}
 
 	if !loginOK {

+ 181 - 9
foodgroup/auth_test.go

@@ -536,6 +536,100 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 			},
 			wantErr: io.EOF,
 		},
+		{
+			name: "login with TOC client - success",
+			cfg: config.Config{
+				OSCARHost: "127.0.0.1",
+				BOSPort:   "1234",
+			},
+			inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
+				TLVRestBlock: wire.TLVRestBlock{
+					TLVList: wire.TLVList{
+						wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
+						wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, wire.RoastTOCPassword([]byte("the_password"))),
+					},
+				},
+			},
+			mockParams: mockParams{
+				userManagerParams: userManagerParams{
+					getUserParams: getUserParams{
+						{
+							screenName: user.IdentScreenName,
+							result:     &user,
+						},
+					},
+				},
+				cookieBakerParams: cookieBakerParams{
+					cookieIssueParams: cookieIssueParams{
+						{
+							dataIn: func() []byte {
+								loginCookie := bosCookie{
+									ScreenName: user.DisplayScreenName,
+								}
+								buf := &bytes.Buffer{}
+								assert.NoError(t, wire.MarshalBE(loginCookie, buf))
+								return buf.Bytes()
+							}(),
+							cookieOut: []byte("the-cookie"),
+						},
+					},
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.BUCP,
+					SubGroup:  wire.BUCPLoginResponse,
+				},
+				Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
+					TLVRestBlock: wire.TLVRestBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
+							wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:1234"),
+							wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "login with TOC client - failed",
+			cfg: config.Config{
+				OSCARHost: "127.0.0.1",
+				BOSPort:   "1234",
+			},
+			inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
+				TLVRestBlock: wire.TLVRestBlock{
+					TLVList: wire.TLVList{
+						wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
+						wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, wire.RoastTOCPassword([]byte("the_wrong_password"))),
+					},
+				},
+			},
+			mockParams: mockParams{
+				userManagerParams: userManagerParams{
+					getUserParams: getUserParams{
+						{
+							screenName: user.IdentScreenName,
+							result:     &user,
+						},
+					},
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.BUCP,
+					SubGroup:  wire.BUCPLoginResponse,
+				},
+				Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
+					TLVRestBlock: wire.TLVRestBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.LoginTLVTagsScreenName, "screenName"),
+							wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword),
+						},
+					},
+				},
+			},
+		},
 	}
 
 	for _, tc := range cases {
@@ -578,9 +672,6 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 	}
 	assert.NoError(t, user.HashPassword("the_password"))
 
-	// roastedPassword the roasted form of "the_password"
-	roastedPassword := []byte{0x87, 0x4E, 0xE4, 0x9B, 0x49, 0xE7, 0xA8, 0xE1, 0x06, 0xCC, 0xCB, 0x82}
-
 	cases := []struct {
 		// name is the unit test name
 		name string
@@ -607,7 +698,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 			inputSNAC: wire.FLAPSignonFrame{
 				TLVRestBlock: wire.TLVRestBlock{
 					TLVList: wire.TLVList{
-						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, roastedPassword),
+						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
 						wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
 					},
 				},
@@ -655,7 +746,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 				TLVRestBlock: wire.TLVRestBlock{
 					TLVList: wire.TLVList{
 						wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"),
-						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, roastedPassword),
+						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
 						wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
 					},
 				},
@@ -734,7 +825,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 			inputSNAC: wire.FLAPSignonFrame{
 				TLVRestBlock: wire.TLVRestBlock{
 					TLVList: wire.TLVList{
-						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, roastedPassword),
+						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
 						wire.NewTLVBE(wire.LoginTLVTagsScreenName, []byte("non_existent_screen_name")),
 					},
 				},
@@ -766,7 +857,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 				TLVRestBlock: wire.TLVRestBlock{
 					TLVList: wire.TLVList{
 						wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"),
-						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, roastedPassword),
+						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
 						wire.NewTLVBE(wire.LoginTLVTagsScreenName, []byte("100003")),
 					},
 				},
@@ -798,7 +889,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 			inputSNAC: wire.FLAPSignonFrame{
 				TLVRestBlock: wire.TLVRestBlock{
 					TLVList: wire.TLVList{
-						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, roastedPassword),
+						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
 						wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
 					},
 				},
@@ -900,7 +991,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 			inputSNAC: wire.FLAPSignonFrame{
 				TLVRestBlock: wire.TLVRestBlock{
 					TLVList: wire.TLVList{
-						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, roastedPassword),
+						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
 						wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
 					},
 				},
@@ -917,6 +1008,87 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 			},
 			wantErr: io.EOF,
 		},
+		{
+			name: "login with AIM 1.1.19 for Java - success",
+			cfg: config.Config{
+				OSCARHost: "127.0.0.1",
+				BOSPort:   "1234",
+			},
+			inputSNAC: wire.FLAPSignonFrame{
+				TLVRestBlock: wire.TLVRestBlock{
+					TLVList: wire.TLVList{
+						wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "AOL Instant Messenger (TM) version 1.1.19 for Java"),
+						wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
+						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARJavaPassword([]byte("the_password"))),
+					},
+				},
+			},
+			mockParams: mockParams{
+				userManagerParams: userManagerParams{
+					getUserParams: getUserParams{
+						{
+							screenName: user.IdentScreenName,
+							result:     &user,
+						},
+					},
+				},
+				cookieBakerParams: cookieBakerParams{
+					cookieIssueParams: cookieIssueParams{
+						{
+							dataIn: func() []byte {
+								loginCookie := bosCookie{
+									ScreenName: user.DisplayScreenName,
+									ClientID:   "AOL Instant Messenger (TM) version 1.1.19 for Java",
+								}
+								buf := &bytes.Buffer{}
+								assert.NoError(t, wire.MarshalBE(loginCookie, buf))
+								return buf.Bytes()
+							}(),
+							cookieOut: []byte("the-cookie"),
+						},
+					},
+				},
+			},
+			expectOutput: wire.TLVRestBlock{
+				TLVList: wire.TLVList{
+					wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
+					wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:1234"),
+					wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
+				},
+			},
+		},
+		{
+			name: "login with AIM 1.1.19 for Java - failed",
+			cfg: config.Config{
+				OSCARHost: "127.0.0.1",
+				BOSPort:   "1234",
+			},
+			inputSNAC: wire.FLAPSignonFrame{
+				TLVRestBlock: wire.TLVRestBlock{
+					TLVList: wire.TLVList{
+						wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "AOL Instant Messenger (TM) version 1.1.19 for Java"),
+						wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
+						wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARJavaPassword([]byte("the_wrong_password"))),
+					},
+				},
+			},
+			mockParams: mockParams{
+				userManagerParams: userManagerParams{
+					getUserParams: getUserParams{
+						{
+							screenName: user.IdentScreenName,
+							result:     &user,
+						},
+					},
+				},
+			},
+			expectOutput: wire.TLVRestBlock{
+				TLVList: wire.TLVList{
+					wire.NewTLVBE(wire.LoginTLVTagsScreenName, "screenName"),
+					wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword),
+				},
+			},
+		},
 	}
 
 	for _, tc := range cases {

+ 10 - 1
state/user.go

@@ -403,7 +403,16 @@ func (u *User) ValidateHash(md5Hash []byte) bool {
 // hash of the user's actual password. A roasted password is a XOR-obfuscated
 // form of the real password, intended to add a simple layer of security.
 func (u *User) ValidateRoastedPass(roastedPass []byte) bool {
-	clearPass := wire.RoastPassword(roastedPass)
+	clearPass := wire.RoastOSCARPassword(roastedPass)
+	md5Hash := wire.WeakMD5PasswordHash(string(clearPass), u.AuthKey) // todo remove string conversion
+	return bytes.Equal(u.WeakMD5Pass, md5Hash)
+}
+
+// ValidateRoastedJavaPass checks if the provided roasted password matches the MD5
+// hash of the user's actual password. A roasted password is a XOR-obfuscated
+// form of the real password, intended to add a simple layer of security. // todo toc description
+func (u *User) ValidateRoastedJavaPass(roastedPass []byte) bool {
+	clearPass := wire.RoastOSCARJavaPassword(roastedPass)
 	md5Hash := wire.WeakMD5PasswordHash(string(clearPass), u.AuthKey) // todo remove string conversion
 	return bytes.Equal(u.WeakMD5Pass, md5Hash)
 }

+ 18 - 13
wire/user.go

@@ -29,26 +29,31 @@ func StrongMD5PasswordHash(pass, authKey string) []byte {
 	return bottom.Sum(nil)
 }
 
-// RoastPassword toggles password obfuscation using a roasting algorithm for
-// AIM v1.0-v3.0 auth. The first call obfuscates the password, and the second
-// call de-obfuscates the password, and so on.
-func RoastPassword(roastedPass []byte) []byte {
-	var roastTable = [16]byte{
+// RoastOSCARPassword roasts an OSCAR client password.
+func RoastOSCARPassword(roastedPass []byte) []byte {
+	var roastTable = []byte{
 		0xF3, 0x26, 0x81, 0xC4, 0x39, 0x86, 0xDB, 0x92,
 		0x71, 0xA3, 0xB9, 0xE6, 0x53, 0x7A, 0x95, 0x7C,
 	}
-	clearPass := make([]byte, len(roastedPass))
-	for i := range roastedPass {
-		clearPass[i] = roastedPass[i] ^ roastTable[i%len(roastTable)]
+	return roastPass(roastedPass, roastTable)
+}
+
+// RoastOSCARJavaPassword roasts a Java OSCAR client password.
+func RoastOSCARJavaPassword(roastedPass []byte) []byte {
+	var roastTable = []byte{
+		0xF3, 0xB3, 0x6C, 0x99, 0x95, 0x3F, 0xAC, 0xB6,
+		0xC5, 0xFA, 0x6B, 0x63, 0x69, 0x6C, 0xC3, 0x9A,
 	}
-	return clearPass
+	return roastPass(roastedPass, roastTable)
 }
 
-// RoastPassword toggles password obfuscation using a roasting algorithm for
-// AIM v1.0-v3.0 auth. The first call obfuscates the password, and the second
-// call de-obfuscates the password, and so on.
+// RoastTOCPassword roasts a TOC client password.
 func RoastTOCPassword(roastedPass []byte) []byte {
-	var roastTable = []byte("Tic/Toc")
+	return roastPass(roastedPass, []byte("Tic/Toc"))
+}
+
+// roastPass toggles obfuscation/deobfuscates of roastedPass.
+func roastPass(roastedPass []byte, roastTable []byte) []byte {
 	clearPass := make([]byte, len(roastedPass))
 	for i := range roastedPass {
 		clearPass[i] = roastedPass[i] ^ roastTable[i%len(roastTable)]