sc .password = []byte (password )
}
buf := make ([]byte , clientNonceLen )
_, err = rand .Read (buf )
if err != nil {
return nil , err
}
sc .clientNonce = make ([]byte , base64 .RawStdEncoding .EncodedLen (len (buf )))
base64 .RawStdEncoding .Encode (sc .clientNonce , buf )
return sc , nil
}
func (sc *scramClient ) clientFirstMessage () []byte {
sc .clientFirstMessageBare = []byte (fmt .Sprintf ("n=,r=%s" , sc .clientNonce ))
return []byte (fmt .Sprintf ("n,,%s" , sc .clientFirstMessageBare ))
}
func (sc *scramClient ) recvServerFirstMessage (serverFirstMessage []byte ) error {
sc .serverFirstMessage = serverFirstMessage
buf := serverFirstMessage
if !bytes .HasPrefix (buf , []byte ("r=" )) {
return errors .New ("invalid SCRAM server-first-message received from server: did not include r=" )
}
buf = buf [2 :]
idx := bytes .IndexByte (buf , ',' )
if idx == -1 {
return errors .New ("invalid SCRAM server-first-message received from server: did not include s=" )
}
sc .clientAndServerNonce = buf [:idx ]
buf = buf [idx +1 :]
if !bytes .HasPrefix (buf , []byte ("s=" )) {
return errors .New ("invalid SCRAM server-first-message received from server: did not include s=" )
}
buf = buf [2 :]
idx = bytes .IndexByte (buf , ',' )
if idx == -1 {
return errors .New ("invalid SCRAM server-first-message received from server: did not include i=" )
}
saltStr := buf [:idx ]
buf = buf [idx +1 :]
if !bytes .HasPrefix (buf , []byte ("i=" )) {
return errors .New ("invalid SCRAM server-first-message received from server: did not include i=" )
}
buf = buf [2 :]
iterationsStr := buf
var err error
sc .salt , err = base64 .StdEncoding .DecodeString (string (saltStr ))
if err != nil {
return fmt .Errorf ("invalid SCRAM salt received from server: %w" , err )
}
sc .iterations , err = strconv .Atoi (string (iterationsStr ))
if err != nil || sc .iterations <= 0 {
return fmt .Errorf ("invalid SCRAM iteration count received from server: %w" , err )
}
if !bytes .HasPrefix (sc .clientAndServerNonce , sc .clientNonce ) {
return errors .New ("invalid SCRAM nonce: did not start with client nonce" )
}
if len (sc .clientAndServerNonce ) <= len (sc .clientNonce ) {
return errors .New ("invalid SCRAM nonce: did not include server nonce" )
}
return nil
}
func (sc *scramClient ) clientFinalMessage () string {
clientFinalMessageWithoutProof := []byte (fmt .Sprintf ("c=biws,r=%s" , sc .clientAndServerNonce ))
sc .saltedPassword = pbkdf2 .Key ([]byte (sc .password ), sc .salt , sc .iterations , 32 , sha256 .New )
sc .authMessage = bytes .Join ([][]byte {sc .clientFirstMessageBare , sc .serverFirstMessage , clientFinalMessageWithoutProof }, []byte ("," ))
clientProof := computeClientProof (sc .saltedPassword , sc .authMessage )
return fmt .Sprintf ("%s,p=%s" , clientFinalMessageWithoutProof , clientProof )
}
func (sc *scramClient ) recvServerFinalMessage (serverFinalMessage []byte ) error {
if !bytes .HasPrefix (serverFinalMessage , []byte ("v=" )) {
return errors .New ("invalid SCRAM server-final-message received from server" )
}
serverSignature := serverFinalMessage [2 :]
if !hmac .Equal (serverSignature , computeServerSignature (sc .saltedPassword , sc .authMessage )) {
return errors .New ("invalid SCRAM ServerSignature received from server" )
}
return nil
}
func computeHMAC (key , msg []byte ) []byte {
mac := hmac .New (sha256 .New , key )
mac .Write (msg )
return mac .Sum (nil )
}
func computeClientProof (saltedPassword , authMessage []byte ) []byte {
clientKey := computeHMAC (saltedPassword , []byte ("Client Key" ))
storedKey := sha256 .Sum256 (clientKey )
clientSignature := computeHMAC (storedKey [:], authMessage )
clientProof := make ([]byte , len (clientSignature ))
for i := 0 ; i < len (clientSignature ); i ++ {
clientProof [i ] = clientKey [i ] ^ clientSignature [i ]
}
buf := make ([]byte , base64 .StdEncoding .EncodedLen (len (clientProof )))
base64 .StdEncoding .Encode (buf , clientProof )
return buf
}
func computeServerSignature (saltedPassword []byte , authMessage []byte ) []byte {
serverKey := computeHMAC (saltedPassword , []byte ("Server Key" ))
serverSignature := computeHMAC (serverKey , authMessage )
buf := make ([]byte , base64 .StdEncoding .EncodedLen (len (serverSignature )))
base64 .StdEncoding .Encode (buf , serverSignature )
return buf