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,
}