secure/precis: update to RFCs 8264, 8265, 8266

See:

https://www.rfc-editor.org/info/rfc8264 (Obsoletes RFC 7564)
https://www.rfc-editor.org/info/rfc8265 (Obsoletes RFC 7613)
https://www.rfc-editor.org/info/rfc8266 (Obsoletes RFC 7700)

benchmark                                                    old ns/op     new ns/op     delta
BenchmarkString/FreeForm/ASCII-4                             91.4          94.4          +3.28%
BenchmarkString/FreeForm/NotNormalized-4                     642           648           +0.93%
BenchmarkString/FreeForm/Arabic-4                            187           191           +2.14%
BenchmarkString/FreeForm/Hangul-4                            453           453           +0.00%
BenchmarkString/Nickname/ASCII-4                             363           370           +1.93%
BenchmarkString/Nickname/NotNormalized-4                     938           2095          +123.35%
BenchmarkString/Nickname/Arabic-4                            371           1319          +255.53%
BenchmarkString/Nickname/Hangul-4                            704           2564          +264.20%
BenchmarkString/OpaqueString/ASCII-4                         188           195           +3.72%
BenchmarkString/OpaqueString/NotNormalized-4                 775           775           +0.00%
BenchmarkString/OpaqueString/Arabic-4                        260           265           +1.92%
BenchmarkString/OpaqueString/Hangul-4                        577           577           +0.00%
BenchmarkString/UsernameCaseMapped/ASCII-4                   95.2          96.5          +1.37%
BenchmarkString/UsernameCaseMapped/NotNormalized-4           853           857           +0.47%
BenchmarkString/UsernameCaseMapped/Arabic-4                  339           345           +1.77%
BenchmarkString/UsernameCaseMapped/Hangul-4                  748           756           +1.07%
BenchmarkString/UsernameCasePreserved/ASCII-4                94.1          95.4          +1.38%
BenchmarkString/UsernameCasePreserved/NotNormalized-4        728           738           +1.37%
BenchmarkString/UsernameCasePreserved/Arabic-4               260           262           +0.77%
BenchmarkString/UsernameCasePreserved/Hangul-4               587           592           +0.85%
BenchmarkBytes/FreeForm/ASCII-4                              81.3          81.4          +0.12%
BenchmarkBytes/FreeForm/NotNormalized-4                      590           585           -0.85%
BenchmarkBytes/FreeForm/Arabic-4                             178           176           -1.12%
BenchmarkBytes/FreeForm/Hangul-4                             420           428           +1.90%
BenchmarkBytes/Nickname/ASCII-4                              321           320           -0.31%
BenchmarkBytes/Nickname/NotNormalized-4                      885           2038          +130.28%
BenchmarkBytes/Nickname/Arabic-4                             338           1278          +278.11%
BenchmarkBytes/Nickname/Hangul-4                             655           2540          +287.79%
BenchmarkBytes/OpaqueString/ASCII-4                          187           181           -3.21%
BenchmarkBytes/OpaqueString/NotNormalized-4                  717           713           -0.56%
BenchmarkBytes/OpaqueString/Arabic-4                         250           249           -0.40%
BenchmarkBytes/OpaqueString/Hangul-4                         542           546           +0.74%
BenchmarkBytes/UsernameCaseMapped/ASCII-4                    82.4          82.0          -0.49%
BenchmarkBytes/UsernameCaseMapped/NotNormalized-4            797           800           +0.38%
BenchmarkBytes/UsernameCaseMapped/Arabic-4                   325           329           +1.23%
BenchmarkBytes/UsernameCaseMapped/Hangul-4                   720           724           +0.56%
BenchmarkBytes/UsernameCasePreserved/ASCII-4                 82.1          82.1          +0.00%
BenchmarkBytes/UsernameCasePreserved/NotNormalized-4         672           674           +0.30%
BenchmarkBytes/UsernameCasePreserved/Arabic-4                244           250           +2.46%
BenchmarkBytes/UsernameCasePreserved/Hangul-4                550           553           +0.55%
BenchmarkAppend/FreeForm/ASCII-4                             67.6          65.5          -3.11%
BenchmarkAppend/FreeForm/NotNormalized-4                     590           590           +0.00%
BenchmarkAppend/FreeForm/Arabic-4                            155           155           +0.00%
BenchmarkAppend/FreeForm/Hangul-4                            409           399           -2.44%
BenchmarkAppend/Nickname/ASCII-4                             321           320           -0.31%
BenchmarkAppend/Nickname/NotNormalized-4                     886           2051          +131.49%
BenchmarkAppend/Nickname/Arabic-4                            340           1270          +273.53%
BenchmarkAppend/Nickname/Hangul-4                            652           2537          +289.11%
BenchmarkAppend/OpaqueString/ASCII-4                         154           153           -0.65%
BenchmarkAppend/OpaqueString/NotNormalized-4                 720           723           +0.42%
BenchmarkAppend/OpaqueString/Arabic-4                        219           220           +0.46%
BenchmarkAppend/OpaqueString/Hangul-4                        517           520           +0.58%
BenchmarkAppend/UsernameCaseMapped/ASCII-4                   68.1          66.0          -3.08%
BenchmarkAppend/UsernameCaseMapped/NotNormalized-4           795           801           +0.75%
BenchmarkAppend/UsernameCaseMapped/Arabic-4                  287           299           +4.18%
BenchmarkAppend/UsernameCaseMapped/Hangul-4                  683           700           +2.49%
BenchmarkAppend/UsernameCasePreserved/ASCII-4                65.9          67.1          +1.82%
BenchmarkAppend/UsernameCasePreserved/NotNormalized-4        668           679           +1.65%
BenchmarkAppend/UsernameCasePreserved/Arabic-4               217           211           -2.76%
BenchmarkAppend/UsernameCasePreserved/Hangul-4               536           517           -3.54%
BenchmarkTransform/FreeForm/ASCII-4                          103           101           -1.94%
BenchmarkTransform/FreeForm/NotNormalized-4                  488           494           +1.23%
BenchmarkTransform/FreeForm/Arabic-4                         171           168           -1.75%
BenchmarkTransform/FreeForm/Hangul-4                         412           410           -0.49%
BenchmarkTransform/Nickname/ASCII-4                          237           919           +287.76%
BenchmarkTransform/Nickname/NotNormalized-4                  660           1554          +135.45%
BenchmarkTransform/Nickname/Arabic-4                         229           919           +301.31%
BenchmarkTransform/Nickname/Hangul-4                         551           2107          +282.40%
BenchmarkTransform/OpaqueString/ASCII-4                      195           188           -3.59%
BenchmarkTransform/OpaqueString/NotNormalized-4              614           616           +0.33%
BenchmarkTransform/OpaqueString/Arabic-4                     234           229           -2.14%
BenchmarkTransform/OpaqueString/Hangul-4                     583           536           -8.06%
BenchmarkTransform/UsernameCaseMapped/ASCII-4                345           334           -3.19%
BenchmarkTransform/UsernameCaseMapped/NotNormalized-4        816           820           +0.49%
BenchmarkTransform/UsernameCaseMapped/Arabic-4               389           377           -3.08%
BenchmarkTransform/UsernameCaseMapped/Hangul-4               848           840           -0.94%
BenchmarkTransform/UsernameCasePreserved/ASCII-4             175           173           -1.14%
BenchmarkTransform/UsernameCasePreserved/NotNormalized-4     621           620           -0.16%
BenchmarkTransform/UsernameCasePreserved/Arabic-4            268           278           +3.73%
BenchmarkTransform/UsernameCasePreserved/Hangul-4            599           593           -1.00%

Change-Id: I98ee577c3c33fce547d839505c4f04477ab74e83
Reviewed-on: https://go-review.googlesource.com/68790
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/secure/precis/doc.go b/secure/precis/doc.go
index 48500fe..939ff22 100644
--- a/secure/precis/doc.go
+++ b/secure/precis/doc.go
@@ -4,8 +4,8 @@
 
 // Package precis contains types and functions for the preparation,
 // enforcement, and comparison of internationalized strings ("PRECIS") as
-// defined in RFC 7564. It also contains several pre-defined profiles for
-// passwords, nicknames, and usernames as defined in RFC 7613 and RFC 7700.
+// defined in RFC 8264. It also contains several pre-defined profiles for
+// passwords, nicknames, and usernames as defined in RFC 8265 and RFC 8266.
 //
 // BE ADVISED: This package is under construction and the API may change in
 // backwards incompatible ways and without notice.
diff --git a/secure/precis/nickname.go b/secure/precis/nickname.go
index cd54b9e..11e0ccb 100644
--- a/secure/precis/nickname.go
+++ b/secure/precis/nickname.go
@@ -23,24 +23,26 @@
 }
 
 func (t *nickAdditionalMapping) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
-	// RFC 7700 §2.1.  Rules
+	// RFC 8266 §2.1.  Rules
 	//
 	// 2.  Additional Mapping Rule: The additional mapping rule consists of
-	//                              the following sub-rules.
+	//     the following sub-rules.
 	//
-	//        1.  Any instances of non-ASCII space MUST be mapped to ASCII
-	//            space (U+0020); a non-ASCII space is any Unicode code point
-	//            having a general category of "Zs", naturally with the
-	//            exception of U+0020.
+	//     a.  Map any instances of non-ASCII space to SPACE (U+0020); a
+	//         non-ASCII space is any Unicode code point having a general
+	//         category of "Zs", naturally with the exception of SPACE
+	//         (U+0020).  (The inclusion of only ASCII space prevents
+	//         confusion with various non-ASCII space code points, many of
+	//         which are difficult to reproduce across different input
+	//         methods.)
 	//
-	//        2.  Any instances of the ASCII space character at the beginning
-	//            or end of a nickname MUST be removed (e.g., "stpeter " is
-	//            mapped to "stpeter").
+	//     b.  Remove any instances of the ASCII space character at the
+	//         beginning or end of a nickname (e.g., "stpeter " is mapped to
+	//         "stpeter").
 	//
-	//        3.  Interior sequences of more than one ASCII space character
-	//            MUST be mapped to a single ASCII space character (e.g.,
-	//            "St  Peter" is mapped to "St Peter").
-
+	//     c.  Map interior sequences of more than one ASCII space character
+	//         to a single ASCII space character (e.g., "St  Peter" is
+	//         mapped to "St Peter").
 	for nSrc < len(src) {
 		r, size := utf8.DecodeRune(src[nSrc:])
 		if size == 0 { // Incomplete UTF-8 encoding
diff --git a/secure/precis/options.go b/secure/precis/options.go
index 488f0b1..26143db 100644
--- a/secure/precis/options.go
+++ b/secure/precis/options.go
@@ -28,6 +28,7 @@
 	width         transform.SpanningTransformer
 	disallowEmpty bool
 	bidiRule      bool
+	repeat        bool
 
 	// Comparison options
 	ignorecase bool
@@ -78,6 +79,9 @@
 	bidiRule = func(o *options) {
 		o.bidiRule = true
 	}
+	repeat = func(o *options) {
+		o.repeat = true
+	}
 )
 
 // TODO: move this logic to package transform
diff --git a/secure/precis/profile.go b/secure/precis/profile.go
index bf10253..0419159 100644
--- a/secure/precis/profile.go
+++ b/secure/precis/profile.go
@@ -60,26 +60,44 @@
 	// These transforms are applied in the order defined in
 	// https://tools.ietf.org/html/rfc7564#section-7
 
-	if p.options.foldWidth {
-		ts = append(ts, width.Fold)
+	// RFC 8266 §2.1:
+	//
+	//     Implementation experience has shown that applying the rules for the
+	//     Nickname profile is not an idempotent procedure for all code points.
+	//     Therefore, an implementation SHOULD apply the rules repeatedly until
+	//     the output string is stable; if the output string does not stabilize
+	//     after reapplying the rules three (3) additional times after the first
+	//     application, the implementation SHOULD terminate application of the
+	//     rules and reject the input string as invalid.
+	//
+	// There is no known string that will change indefinitely, so repeat 4 times
+	// and rely on the Span method to keep things relatively performant.
+	r := 1
+	if p.options.repeat {
+		r = 4
 	}
+	for ; r > 0; r-- {
+		if p.options.foldWidth {
+			ts = append(ts, width.Fold)
+		}
 
-	for _, f := range p.options.additional {
-		ts = append(ts, f())
+		for _, f := range p.options.additional {
+			ts = append(ts, f())
+		}
+
+		if p.options.cases != nil {
+			ts = append(ts, p.options.cases)
+		}
+
+		ts = append(ts, p.options.norm)
+
+		if p.options.bidiRule {
+			ts = append(ts, bidirule.New())
+		}
+
+		ts = append(ts, &checker{p: p, allowed: p.Allowed()})
 	}
 
-	if p.options.cases != nil {
-		ts = append(ts, p.options.cases)
-	}
-
-	ts = append(ts, p.options.norm)
-
-	if p.options.bidiRule {
-		ts = append(ts, bidirule.New())
-	}
-
-	ts = append(ts, &checker{p: p, allowed: p.Allowed()})
-
 	// TODO: Add the disallow empty rule with a dummy transformer?
 
 	return &Transformer{transform.Chain(ts...)}
@@ -162,42 +180,48 @@
 	}
 
 	// These transforms are applied in the order defined in
-	// https://tools.ietf.org/html/rfc7564#section-7
+	// https://tools.ietf.org/html/rfc8264#section-7
 
-	// TODO: allow different width transforms options.
-	if p.options.foldWidth || (p.options.ignorecase && comparing) {
-		b.apply(foldWidthT)
+	r := 1
+	if p.options.repeat {
+		r = 4
 	}
-	for _, f := range p.options.additional {
-		if err = b.apply(f()); err != nil {
+	for ; r > 0; r-- {
+		// TODO: allow different width transforms options.
+		if p.options.foldWidth || (p.options.ignorecase && comparing) {
+			b.apply(foldWidthT)
+		}
+		for _, f := range p.options.additional {
+			if err = b.apply(f()); err != nil {
+				return nil, err
+			}
+		}
+		if p.options.cases != nil {
+			b.apply(p.options.cases)
+		}
+		if comparing && p.options.ignorecase {
+			b.apply(lowerCaseT)
+		}
+		b.apply(p.norm)
+		if p.options.bidiRule && !bidirule.Valid(b.src) {
+			return nil, bidirule.ErrInvalid
+		}
+		c := checker{p: p}
+		if _, err := c.span(b.src, true); err != nil {
 			return nil, err
 		}
-	}
-	if p.options.cases != nil {
-		b.apply(p.options.cases)
-	}
-	if comparing && p.options.ignorecase {
-		b.apply(lowerCaseT)
-	}
-	b.apply(p.norm)
-	if p.options.bidiRule && !bidirule.Valid(b.src) {
-		return nil, bidirule.ErrInvalid
-	}
-	c := checker{p: p}
-	if _, err := c.span(b.src, true); err != nil {
-		return nil, err
-	}
-	if p.disallow != nil {
-		for i := 0; i < len(b.src); {
-			r, size := utf8.DecodeRune(b.src[i:])
-			if p.disallow.Contains(r) {
-				return nil, errDisallowedRune
+		if p.disallow != nil {
+			for i := 0; i < len(b.src); {
+				r, size := utf8.DecodeRune(b.src[i:])
+				if p.disallow.Contains(r) {
+					return nil, errDisallowedRune
+				}
+				i += size
 			}
-			i += size
 		}
-	}
-	if p.options.disallowEmpty && len(b.src) == 0 {
-		return nil, errEmptyString
+		if p.options.disallowEmpty && len(b.src) == 0 {
+			return nil, errEmptyString
+		}
 	}
 	return b.src, nil
 }
diff --git a/secure/precis/profile_test.go b/secure/precis/profile_test.go
index 916fc8b..4edb28a 100644
--- a/secure/precis/profile_test.go
+++ b/secure/precis/profile_test.go
@@ -103,9 +103,9 @@
 
 		// After applying the Nickname profile, \u00a8  becomes \u0020\u0308,
 		// however because the nickname profile is not idempotent, applying it again
-		// to \u0020\u0308 results in \u0308. This behavior is "correct", even if it
-		// is unexpected.
-		{"\u00a8", "\u0020\u0308", false},
+		// to \u0020\u0308 results in \u0308.
+		{"\u00a8", "\u0020\u0308", true},
+		{"\u00a8", "\u0308", true},
 		{"\u0020\u0308", "\u0308", true},
 	}},
 }
diff --git a/secure/precis/profiles.go b/secure/precis/profiles.go
index 8601002..061936d 100644
--- a/secure/precis/profiles.go
+++ b/secure/precis/profiles.go
@@ -13,18 +13,17 @@
 )
 
 var (
-	// Implements the Nickname profile specified in RFC 7700.
-	// The nickname profile is not idempotent and may need to be applied multiple
-	// times before being used for comparisons.
+	// Implements the Nickname profile specified in RFC 8266.
 	Nickname *Profile = nickname
 
-	// Implements the UsernameCaseMapped profile specified in RFC 7613.
+	// Implements the UsernameCaseMapped profile specified in RFC 8265.
 	UsernameCaseMapped *Profile = usernameCaseMap
 
-	// Implements the UsernameCasePreserved profile specified in RFC 7613.
+	// Implements the UsernameCasePreserved profile specified in RFC 8265.
 	UsernameCasePreserved *Profile = usernameNoCaseMap
 
-	// Implements the OpaqueString profile defined in RFC 7613 for passwords and other secure labels.
+	// Implements the OpaqueString profile defined in RFC 8265 for passwords and
+	// other secure labels.
 	OpaqueString *Profile = opaquestring
 )
 
@@ -37,6 +36,7 @@
 			IgnoreCase,
 			Norm(norm.NFKC),
 			DisallowEmpty,
+			repeat,
 		),
 		class: freeform,
 	}