// Initializes the SASL handshake using the specified mechanism func Init(mechanism string, data []byte) ([]byte, []byte, error) { switch mechanism { case "SCRAM-SHA-1": msg, err := auth.DecodeBase64(data) if err != nil { log.Println(err) return nil, nil, AuthFailError } nonce := genNonce() response, err := InitialResponseSCRAMSHA1(msg, nonce) if err != nil { return nil, nil, err } splitMsg := strings.Split(string(msg), ",") data := []byte(splitMsg[2] + "," + splitMsg[3] + "," + string(response)) return auth.EncodeBase64(response), data, nil default: return nil, nil, UnsupportedAuthenticationMechanism(mechanism) } log.Printf("Impossible program flow!\n") return nil, nil, nil }
func FinalResponseSCRAMSHA1(clientFinalMsg, prevMsg []byte) ([]byte, error) { oldMsg := strings.Split(string(prevMsg), ",") components := strings.Split(string(clientFinalMsg), ",") if oldMsg[2] != components[1] { log.Println("session nonce does not match") return nil, AuthFailError } authMsg := string(prevMsg) + "," + components[0] + "," + components[1] username := oldMsg[2][2:] user := records.Users.FindByName(username) // for some reason there are null characters at the end? tmp := strings.TrimRight(components[2][2:], "\x00") clientProof, err := auth.DecodeBase64([]byte(tmp)) if err != nil { log.Println(err) return nil, AuthFailError } clientKey := auth.HMAC(user.Pass.Key, "Client Key") storedKey := auth.H(clientKey) clientSignature := auth.HMAC(storedKey, authMsg) length := len(storedKey) authFailed := false for i := 0; i < length; i++ { v := clientKey[i] ^ clientSignature[i] if clientProof[i] != v { authFailed = true } } // avoid early exit when comparing password related data if authFailed { log.Println("Invalid Client Proof") return nil, AuthFailError } serverKey := auth.HMAC(user.Pass.Key, "Server Key") signature := auth.HMAC(serverKey, authMsg) armouredSignature := auth.EncodeBase64(signature) return []byte("v=" + string(armouredSignature)), nil }
// Continues the SASL handshake func Respond(mech string, data []byte, storedData []byte) ([]byte, error) { switch mech { case "SCRAM-SHA-1": raw, err := auth.DecodeBase64(data) if err != nil { log.Println(err) return nil, AuthFailError } response, err := FinalResponseSCRAMSHA1(raw, storedData) if err != nil { return nil, err } return auth.EncodeBase64(response), nil default: return nil, UnsupportedAuthenticationMechanism(mech) } }
func InitialResponseSCRAMSHA1(initialMsg, nonce []byte) ([]byte, error) { // first message must start with either "n", "y" or "p" if initialMsg[0] != 'n' { // && initialMsg[0] != 'p' && initialMsg[0] != 'y' { log.Println("invalid initial response") return nil, AuthFailError } s := strings.Split(string(initialMsg), ",") // well formed messages will result in 4 components if len(s) != 4 { log.Println("invalid initial response") return nil, AuthFailError } username := string(s[2][2:]) clientNonce := string(s[3][2:]) user := records.Users.FindByName(username) encodedSalt := string(auth.EncodeBase64(user.Pass.Salt)) return []byte("r=" + clientNonce + string(nonce) + ",s=" + encodedSalt + ",i=" + strconv.Itoa(auth.PASSWORD_ITERATIONS)), nil }
It("should not generate an error", func() { Ω(err).To(BeNil()) }) It("should have exactly 3 components", func() { Ω(len(components)).To(Equal(3)) }) It("should set the first component to be the combined nonce", func() { Ω(components[0]).To(Equal("r=" + string(clientNonce) + string(nonce))) }) It("should set the second component to the users salt", func() { raw := StubUser.Pass.Salt encBase64 := auth.EncodeBase64(raw) Ω(components[1][2:]).To(Equal(string(encBase64))) }) It("should contain the correct number of iterations", func() { iters, _ := strconv.Atoi(components[2][2:]) Ω(iters).To(Equal(auth.PASSWORD_ITERATIONS)) }) }) Context("with invalid requests", func() { It("should throw an error when it receives malformed requests", func() { _, err := InitialResponseSCRAMSHA1([]byte("thisissomerandommessage"), nonce) Expect(err).NotTo(BeNil()) }) })
var _ = Describe("Anonymous connections", func() { Context("authentication", func() { salt, _ := auth.DecodeBase64([]byte("QSXCR+Q6sek8bf92")) user := CreateStubUserWithSalt("user", "pencil", salt) connection := NewAnonymousConnection() BeforeEach(func() { connection.ClearMessages() }) It("should respond to the first message in the authentication handshake", func() { clientMsg := "n,,u=" + user.Name + ",n=fyko+d2lbbFgONRv9qkxdawL" msg := auth.EncodeBase64([]byte(clientMsg)) err := connection.Send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='SCRAM-SHA-1'>" + string(msg) + "</auth>") if err != nil { panic(err) } Expect(connection.LastMessage()).To(MatchRegexp("^<challenge(.*)challenge>$")) }) It("should respond with successful authentication on the second part of the handshake", func() { clientFinalMsg := "c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=" msg := auth.EncodeBase64([]byte(clientFinalMsg)) err := connection.Send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + string(msg) + "</response>") if err != nil { panic(err) }