HTTPS SSH

A Java implementation of the "PRECIS Framework: Preparation, Enforcement, and Comparison of Internationalized Strings in Application Protocols" and profiles thereof.

About

PRECIS validates and prepares Unicode strings in a way, so that they can safely be used in application protocols, e.g. when dealing with usernames and passwords.

For example, if strings are used for authentication and authorization decisions, the security of an application could be compromised if an entity providing a given string is connected to the wrong account or online resource based on different interpretations of the string.

PRECIS takes care of such issues.

This library supports the following specifications:

  • RFC 8264: PRECIS Framework: Preparation, Enforcement, and Comparison of Internationalized Strings in Application Protocols
  • RFC 8265: Preparation, Enforcement, and Comparison of Internationalized Strings Representing Usernames and Passwords
  • RFC 8266: Preparation, Enforcement, and Comparison of Internationalized Strings Representing Nicknames
  • RFC 5893: Right-to-Left Scripts for Internationalized Domain Names for Applications (IDNA)

PRECIS obsoletes Stringprep (RFC 3454) and this library obsoletes software like Libidn's Stringprep class.

License

This software is licensed under the MIT license.

Build

This project is Maven based, you can simply build it with common Maven commands, e.g.

mvn clean install

Maven Dependency

Maven Central

<dependency>
    <groupId>rocks.xmpp</groupId>
    <artifactId>precis</artifactId>
    <version>1.0.0</version>
</dependency>

API & Samples

For most cases, all you need to do is to choose an existing profile from the PrecisProfiles class and then prepare or enforce a string:

PrecisProfile profile1 = PrecisProfiles.USERNAME_CASE_MAPPED;
PrecisProfile profile2 = PrecisProfiles.USERNAME_CASE_PRESERVED;
PrecisProfile profile3 = PrecisProfiles.OPAQUE_STRING;
PrecisProfile profile4 = PrecisProfiles.NICKNAME;

PrecisProfile is an abstract class, which you could derive from for defining your custom profile (which however is discouraged by RFC 8264).

JavaDoc can be found here.

Preparation

Preparation ensures, that characters are allowed, but (usually) does not apply any mapping rules. The following throws an exception because the string contains a character, which is in the Unicode category Lt, which is disallowed.

PrecisProfiles.USERNAME_CASE_MAPPED.prepare("\u01C5"); // CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON

Enforcement

Enforcement applies a set of rules (e.g. Unicode normalization, width-mapping, case-folding, ...) to a string in order to transform it to a canonical form and to compare two strings, e.g. for the purpose of authentication.

String enforced = PrecisProfiles.USERNAME_CASE_MAPPED.enforce("UpperCaseUsername"); // => uppercaseusername

Here, only simple mapping to lower case is applied. But enforcement does more:

String ang = PrecisProfiles.USERNAME_CASE_MAPPED.enforce("\u212B");     // ANGSTROM SIGN
String a = PrecisProfiles.USERNAME_CASE_MAPPED.enforce("\u0041\u030A"); // LATIN CAPITAL LETTER A + COMBINING RING ABOVE
String aRing = PrecisProfiles.USERNAME_CASE_MAPPED.enforce("\u00C5");   // LATIN CAPITAL LETTER A WITH RING ABOVE

// ang.equals(a) == true
// a.equals(aRing) == true

All three result in LATIN SMALL LETTER A WITH RING ABOVE (U+00E5) and are therefore equal after enforcement.

The following throws an InvalidDirectionalityException because it violates the Bidi Rule (RFC 5893).

PrecisProfiles.USERNAME_CASE_MAPPED.enforce("\u0786test");

If a string contains prohibited code points, e.g. symbols in usernames, a InvalidCodePointException is thrown, either during preparation or enforcement.

Comparison

You can use PrecisProfile#toComparableString(CharSequence) to check, if two strings compare to each other, e.g.:

PrecisProfile profile = PrecisProfiles.USERNAME_CASE_MAPPED;
if (profile.toComparableString("foobar").equals(profile.toComparableString("FooBar"))) {
    // username already exists.
}

Or you can use PrecisProfile as a java.util.Comparator:

if (profile.compare("foobar", "FooBar") == 0) {
    // username already exists.
}

Note that a profile may use different rules during comparison than during enforcement (as the Nickname profile, RFC 8266).