@@ -33,6 +33,7 @@ import {
3333} from "../../src/shared/util.js" ;
3434import { calculateMD5 } from "../../src/core/calculate_md5.js" ;
3535import { calculateSHA256 } from "../../src/core/calculate_sha256.js" ;
36+ import { saslPrep } from "../../src/core/sasl_prep.js" ;
3637
3738describe ( "crypto" , function ( ) {
3839 // RFC 1321, A.5 Test suite
@@ -519,6 +520,47 @@ describe("crypto", function () {
519520 } ) ;
520521} ) ;
521522
523+ describe ( "saslPrep" , function ( ) {
524+ it ( "should leave ASCII strings unchanged" , function ( ) {
525+ expect ( saslPrep ( "" ) ) . toEqual ( "" ) ;
526+ expect ( saslPrep ( "password" ) ) . toEqual ( "password" ) ;
527+ } ) ;
528+
529+ it ( "should map non-ASCII space characters to U+0020" , function ( ) {
530+ expect ( saslPrep ( "a\u00A0b" ) ) . toEqual ( "a b" ) ;
531+ expect ( saslPrep ( "a\u2003b" ) ) . toEqual ( "a b" ) ;
532+ expect ( saslPrep ( "a\u3000b" ) ) . toEqual ( "a b" ) ;
533+ } ) ;
534+
535+ it ( "should remove characters commonly mapped to nothing" , function ( ) {
536+ expect ( saslPrep ( "pass\u00ADword" ) ) . toEqual ( "password" ) ;
537+ expect ( saslPrep ( "a\u200Db" ) ) . toEqual ( "ab" ) ;
538+ expect ( saslPrep ( "a\uFEFFb" ) ) . toEqual ( "ab" ) ;
539+ } ) ;
540+
541+ it ( "should apply NFKC normalization" , function ( ) {
542+ expect ( saslPrep ( "\u00AA" ) ) . toEqual ( "a" ) ;
543+ expect ( saslPrep ( "\u2168" ) ) . toEqual ( "IX" ) ;
544+ expect ( saslPrep ( "\uFB01" ) ) . toEqual ( "fi" ) ;
545+ } ) ;
546+
547+ it ( "should tolerate bidirectional text rejected by SASLprep" , function ( ) {
548+ // A RandALCat character followed by a non-RandALCat character would fail
549+ // the SASLprep bidirectional-text check, which we intentionally omit.
550+ expect ( saslPrep ( "\u0627\u0031" ) ) . toEqual ( "\u0627\u0031" ) ;
551+ } ) ;
552+
553+ it ( "should normalize using the runtime Unicode version" , function ( ) {
554+ // U+1D2C was unassigned in Unicode 3.2, on which SASLprep is based, but
555+ // current Unicode normalization maps it to LATIN CAPITAL LETTER A.
556+ expect ( saslPrep ( "\u1D2C" ) ) . toEqual ( "A" ) ;
557+ } ) ;
558+
559+ it ( "should prepare the password `SªSLprep`" , function ( ) {
560+ expect ( saslPrep ( "SªSLprep" ) ) . toEqual ( "SaSLprep" ) ;
561+ } ) ;
562+ } ) ;
563+
522564describe ( "CipherTransformFactory" , function ( ) {
523565 function buildDict ( map ) {
524566 const dict = new Dict ( ) ;
@@ -617,6 +659,7 @@ describe("CipherTransformFactory", function () {
617659 let fileId1 , fileId2 , dict1 , dict2 , dict3 ;
618660 let aes256Dict , aes256IsoDict , aes256BlankDict , aes256IsoBlankDict ;
619661 let aes256UnicodeDict ;
662+ let aes256SaslPrepDict , aes256SaslPrepFallbackDict ;
620663
621664 beforeAll ( function ( ) {
622665 fileId1 = unescape ( "%F6%C6%AF%17%F3rR%8DRM%9A%80%D1%EF%DF%18" ) ;
@@ -780,12 +823,63 @@ describe("CipherTransformFactory", function () {
780823 P : - 1084 ,
781824 R : 5 ,
782825 } ) ;
826+ aes256SaslPrepDict = buildDict ( {
827+ Filter : Name . get ( "Standard" ) ,
828+ V : 5 ,
829+ Length : 256 ,
830+ O : unescape (
831+ "%21%14%DF%F5%89%2A%5F%97%B6%B7%EB%F8P%02%7DY%FD%95%82%CA%BD" +
832+ "%1C%A6T%5E%8Bq%01%FC%9D%D2%00%84z%F4%89%E8%3EX%DFq%CF%CE%F2F" +
833+ "%A6%B2%95"
834+ ) ,
835+ U : unescape (
836+ "%98%1E%80%12%14Y%93D%0By%2F%07q%5E%CC%7B%D1%CDg%2C%5B2%DD%B7" +
837+ "%C0%7D%C7%D5%1F5Q%23%BD%CA1%FDm%7B%B7%D1%60%29v%91%D23%E1%B4"
838+ ) ,
839+ OE : unescape (
840+ "A%F1%BF%91%AA%8B%F03%D8m%CF%0B%14%19%D6%BF%8B%2AO%FAln%AAb%85" +
841+ "%F2SB%7C%CF%FBX"
842+ ) ,
843+ UE : unescape (
844+ "w%20v%EF%AD%EC%02%AD%C6%1B%06%DEP%DE%BE8%DD%A5%DB%C77%8E%035L" +
845+ "%1D%FD%DD%13%83%AC%9B"
846+ ) ,
847+ Perms : unescape ( "%0E%FE%02%22%FB5f%A68t%0Ca%11%17Jf" ) ,
848+ P : - 4 ,
849+ R : 6 ,
850+ } ) ;
851+ aes256SaslPrepFallbackDict = buildDict ( {
852+ Filter : Name . get ( "Standard" ) ,
853+ V : 5 ,
854+ Length : 256 ,
855+ O : unescape (
856+ "%B1O%A6A%BD%E9%19%09%EC%FFuG%3C%BEWZ%2CN%F6%C7Cz%9A%E1%84%15" +
857+ "%0C66%AE%B4%E6%C0%C1%C2%C3%C4%C5%C6%C7%D0%D1%D2%D3%D4%D5%D6%D7"
858+ ) ,
859+ U : unescape (
860+ "i%0C%B0%B9%B7%90%AAXN%C916E%2E%C2%CF%BEG%DE%1Dqf%8F%3A%BF%92" +
861+ "%FB%FA%C8o%23%11%A0%A1%A2%A3%A4%A5%A6%A7%B0%B1%B2%B3%B4%B5%B6" +
862+ "%B7"
863+ ) ,
864+ OE : unescape (
865+ "%0BE%22%A4%AAX%DA%C5%D4%DAL%A0%91Q%3E%3AE%EE%B7%97W%CFm%B8%AC" +
866+ "%8Ak%80%B4%5D%EB%0F"
867+ ) ,
868+ UE : unescape (
869+ "%D0%C0%E4rM%F7%E7%D2%FA%A0%90%7B%BC%BE%86p5%B5%27%A1%10%8A%86" +
870+ "%86%95%2B%92%B4%E1%F4%A5%9A"
871+ ) ,
872+ Perms : unescape ( "%ED%D4%5FM%ED%9E%DE%F9%82%C8%A9%7F%C1%CC%C4%B4" ) ,
873+ P : - 4 ,
874+ R : 6 ,
875+ } ) ;
783876 } ) ;
784877
785878 afterAll ( function ( ) {
786879 fileId1 = fileId2 = dict1 = dict2 = dict3 = null ;
787880 aes256Dict = aes256IsoDict = aes256BlankDict = aes256IsoBlankDict = null ;
788881 aes256UnicodeDict = null ;
882+ aes256SaslPrepDict = aes256SaslPrepFallbackDict = null ;
789883 } ) ;
790884
791885 describe ( "#ctor" , function ( ) {
@@ -844,6 +938,29 @@ describe("CipherTransformFactory", function () {
844938 } ) ;
845939 } ) ;
846940
941+ describe ( "AES256 Revision 6 with SASLprep" , function ( ) {
942+ it ( "should accept a password requiring SASLprep" , function ( ) {
943+ ensurePasswordCorrect (
944+ aes256SaslPrepDict ,
945+ fileId1 ,
946+ "S\u00AASL\u00ADprep"
947+ ) ;
948+ } ) ;
949+ it ( "should accept an already prepared password" , function ( ) {
950+ ensurePasswordCorrect ( aes256SaslPrepDict , fileId1 , "SaSLprep" ) ;
951+ } ) ;
952+ it ( "should not accept wrong password" , function ( ) {
953+ ensurePasswordIncorrect ( aes256SaslPrepDict , fileId1 , "wrong" ) ;
954+ } ) ;
955+ it ( "should fall back to the raw password" , function ( ) {
956+ ensurePasswordCorrect (
957+ aes256SaslPrepFallbackDict ,
958+ fileId1 ,
959+ "pass\u00ADword"
960+ ) ;
961+ } ) ;
962+ } ) ;
963+
847964 it ( "should accept user password" , function ( ) {
848965 ensurePasswordCorrect ( dict1 , fileId1 , "123456" ) ;
849966 } ) ;
0 commit comments