@softvisio/core
Version:
Softisio core
89 lines (69 loc) • 2.96 kB
JavaScript
import crypto from "node:crypto";
import Sasl from "#lib/sasl";
export default class SaslScramSha256 extends Sasl {
#nonce;
#signature;
// properties
get type () {
return "SCRAM-SHA-256";
}
// public
continue ( response ) {
if ( !response ) {
this.#nonce ??= crypto.randomBytes( 18 ).toString( "base64" );
return "n,,n=*,r=" + this.#nonce;
}
else {
response = this.#parseResponse( response );
if ( response.r ) {
if ( !response.r.startsWith( this.#nonce ) ) return;
if ( response.r.length === this.#nonce.length ) return;
const salt = Buffer.from( response.s, "base64" ),
saltedPassword = this.#hi( this.password, salt, response.i ),
clientKey = this.#hmacSha256( saltedPassword, "Client Key" ),
storedKey = this.#sha256( clientKey ),
clientFirstMessageBare = "n=*,r=" + this.#nonce,
serverFirstMessage = "r=" + response.r + ",s=" + response.s + ",i=" + response.i,
clientFinalMessageWithoutProof = "c=biws,r=" + response.r,
authMessage = clientFirstMessageBare + "," + serverFirstMessage + "," + clientFinalMessageWithoutProof,
clientSignature = this.#hmacSha256( storedKey, authMessage ),
clientProofBytes = this.#xorBuffers( clientKey, clientSignature ),
clientProof = clientProofBytes.toString( "base64" ),
serverKey = this.#hmacSha256( saltedPassword, "Server Key" ),
serverSignatureBytes = this.#hmacSha256( serverKey, authMessage );
this.#signature = serverSignatureBytes.toString( "base64" );
return clientFinalMessageWithoutProof + ",p=" + clientProof;
}
else {
if ( response.v !== this.#signature ) return;
return true;
}
}
}
// private
#parseResponse ( response ) {
const data = {};
for ( const token of response.split( "," ) ) {
data[ token[ 0 ] ] = token.slice( 2 );
}
return data;
}
#hi ( password, saltBytes, iterations ) {
var ui1 = this.#hmacSha256( password, Buffer.concat( [ saltBytes, Buffer.from( [ 0, 0, 0, 1 ] ) ] ) ),
ui = ui1;
for ( var i = 0; i < iterations - 1; i++ ) {
ui1 = this.#hmacSha256( password, ui1 );
ui = this.#xorBuffers( ui, ui1 );
}
return ui;
}
#xorBuffers ( a, b ) {
return Buffer.from( a.map( ( _, i ) => a[ i ] ^ b[ i ] ) );
}
#sha256 ( text ) {
return crypto.createHash( "SHA256" ).update( text ).digest();
}
#hmacSha256 ( key, msg ) {
return crypto.createHmac( "SHA256", key ).update( msg ).digest();
}
}