// 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 (a *Auth) GetUserName() (string, error) { raw, err := auth.DecodeBase64(a.Data) if err != nil { return "", err } parts := strings.Split(string(raw), ",") return parts[2][2:], 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) } }
Describe("final response", func() { It("should not accept mismatch between the input and cached nonce", func() { _, err := FinalResponseSCRAMSHA1([]byte("c=,r=abcd,p="), []byte("n=,r=,r=abc,s=,i=")) Expect(err).NotTo(BeNil()) }) It("should not accept invalid Client Proofs", func() { _, err := FinalResponseSCRAMSHA1([]byte("c=,r=abc,p=hello"), []byte("n=,r=,r=abc,s=,i=")) Expect(err).NotTo(BeNil()) }) }) Context("full handshake", func() { salt, _ := auth.DecodeBase64([]byte("QSXCR+Q6sek8bf92")) StubUser = models.User{ Pass: auth.Pass{ Salt: salt, Key: auth.Hi([]byte("pencil"), salt, auth.PASSWORD_ITERATIONS), }, } iters := strconv.Itoa(auth.PASSWORD_ITERATIONS) clientNonce := "fyko+d2lbbFgONRv9qkxdawL" nonce := "3rfcNHYJY1ZVvWVs7j" username := "******" It("should respond correctly to the first message", func() { clientMsg := "n,,u=" + username + ",n=" + clientNonce msg, err := InitialResponseSCRAMSHA1([]byte(clientMsg), []byte(nonce))