From 148b1014df6da1811b543bae3dde4f2e7ff2132a Mon Sep 17 00:00:00 2001 From: Nathan Lecoanet Date: Tue, 18 Aug 2026 15:11:59 +0200 Subject: [PATCH 1/4] Update project to agp8 and kotlin 1.9 --- app/build.gradle | 7 +- app/src/main/AndroidManifest.xml | 6 +- build.gradle | 18 +- demoappkotlin/build.gradle | 10 + demoappkotlin/src/main/AndroidManifest.xml | 4 +- deploymentScripts/publish-mavencentral.gradle | 12 +- gradle/wrapper/gradle-wrapper.jar | Bin 53636 -> 43705 bytes gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 309 ++++++++++++------ gradlew.bat | 88 ++--- trustkit/build.gradle | 15 +- trustkit/src/androidTest/AndroidManifest.xml | 2 +- .../android/trustkit/HttpLibrariesTest.java | 4 +- trustkit/src/main/AndroidManifest.xml | 2 +- 14 files changed, 298 insertions(+), 184 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9a91e0e..148a5c1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,8 @@ apply plugin: 'com.android.application' android { + namespace = 'com.datatheorem.android.trustkit.demoapp' compileSdkVersion toolVersions.android.compileSdk - buildToolsVersion toolVersions.android.buildTools defaultConfig { applicationId "com.datatheorem.android.trustkit.demoapp" @@ -17,6 +17,11 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } } dependencies { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 15ae82b..7a837ba 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + @@ -14,6 +13,7 @@ @@ -23,4 +23,4 @@ - \ No newline at end of file + diff --git a/build.gradle b/build.gradle index c0aca7e..99ac843 100644 --- a/build.gradle +++ b/build.gradle @@ -1,17 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.21' + ext.kotlin_version = '1.9.25' repositories { mavenCentral() - jcenter() // TODO: Remove when org.jetbrains.trove4j:trove4j moves to Maven Central google() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.1' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' + classpath 'com.android.tools.build:gradle:8.13.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -19,11 +16,8 @@ buildscript { allprojects { repositories { mavenCentral() - jcenter() // TODO: Remove when org.jetbrains.trove4j:trove4j moves to Maven Central google() } - apply plugin: 'maven' - apply plugin: 'maven-publish' } @@ -36,14 +30,12 @@ ext{ demoAppKotlinTrustKitVersionCode = 2 demoAppKotlinTrustKitVersionName = "1.1" - javaSourceCompatibilty = '1.6' + javaSourceCompatibilty = '11' toolVersions = [ android : [ - compileSdk : 28, - gradlePlugin : '2.1.0', - buildTools : '28.0.3', + compileSdk : 36, minSdk : 15, - targetSdk: 28 + targetSdk: 36 ] ] diff --git a/demoappkotlin/build.gradle b/demoappkotlin/build.gradle index 1aa585c..3fadfc8 100644 --- a/demoappkotlin/build.gradle +++ b/demoappkotlin/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { + namespace = 'com.datatheorem.android.trustkit.demoappkotlin' compileSdkVersion toolVersions.android.compileSdk @@ -21,6 +22,15 @@ android { } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = '11' + } + } dependencies { diff --git a/demoappkotlin/src/main/AndroidManifest.xml b/demoappkotlin/src/main/AndroidManifest.xml index 883fc1b..0a3d4f6 100644 --- a/demoappkotlin/src/main/AndroidManifest.xml +++ b/demoappkotlin/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + @@ -13,6 +12,7 @@ android:networkSecurityConfig="@xml/network_security_config"> diff --git a/deploymentScripts/publish-mavencentral.gradle b/deploymentScripts/publish-mavencentral.gradle index 713edea..9788f2c 100644 --- a/deploymentScripts/publish-mavencentral.gradle +++ b/deploymentScripts/publish-mavencentral.gradle @@ -65,9 +65,9 @@ publishing { publications { release(MavenPublication) { // 'groupId' = namespace, 'artifactId' = library name, 'version' = library version - groupId PUBLISH_GROUP_ID - artifactId PUBLISH_ARTIFACT_ID - version PUBLISH_VERSION + groupId = PUBLISH_GROUP_ID + artifactId = PUBLISH_ARTIFACT_ID + version = PUBLISH_VERSION // Two artifacts, the truskit '.aar' and the sources '.jar' artifact("$buildDir/outputs/aar/${project.getName()}-release.aar") artifact androidSourcesJar @@ -125,8 +125,8 @@ publishing { name = "mavencentral" url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" credentials { - username ossrhUsername - password ossrhPassword + username = ossrhUsername + password = ossrhPassword } } } @@ -135,4 +135,4 @@ publishing { signing { sign publishing.publications -} \ No newline at end of file +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 13372aef5e24af05341d49695ee84e5f9b594659..9bbc975c742b298b441bfb90dbc124400a3751b9 100644 GIT binary patch literal 43705 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 DHx0+F literal 53636 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ⅅ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 H-+%o-(+5aQ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cc1060b..ed4c299 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Tue Mar 10 21:49:17 PDT 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip diff --git a/gradlew b/gradlew index 9d82f78..faf9300 100755 --- a/gradlew +++ b/gradlew @@ -1,74 +1,129 @@ -#!/usr/bin/env bash +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -77,84 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 8a0b282..9d21a21 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,4 +1,22 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -8,26 +26,30 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -35,54 +57,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/trustkit/build.gradle b/trustkit/build.gradle index 3faae6c..f222bdd 100644 --- a/trustkit/build.gradle +++ b/trustkit/build.gradle @@ -1,14 +1,25 @@ apply plugin: 'com.android.library' android { + namespace = 'com.datatheorem.android.trustkit' + compileSdkVersion toolVersions.android.compileSdk + + buildFeatures { + buildConfig = true + } + defaultConfig { - compileSdkVersion toolVersions.android.compileSdk - buildToolsVersion toolVersions.android.buildTools minSdkVersion toolVersions.android.minSdk versionCode trustkitVersionCode versionName trustkitVersionName + buildConfigField 'String', 'VERSION_NAME', "\"${trustkitVersionName}\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } } dependencies { diff --git a/trustkit/src/androidTest/AndroidManifest.xml b/trustkit/src/androidTest/AndroidManifest.xml index 27c74c5..da10be4 100644 --- a/trustkit/src/androidTest/AndroidManifest.xml +++ b/trustkit/src/androidTest/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/HttpLibrariesTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/HttpLibrariesTest.java index 055b751..1b2e118 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/HttpLibrariesTest.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/HttpLibrariesTest.java @@ -5,8 +5,8 @@ import static org.mockito.Mockito.verify; import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.filters.SdkSuppress; import android.os.Build; -import androidx.annotation.RequiresApi; import com.datatheorem.android.trustkit.pinning.PinningValidationResult; import com.datatheorem.android.trustkit.reporting.BackgroundReporter; @@ -180,7 +180,7 @@ public void testOkhttp3WithTrustKit() throws MalformedURLException { // https://github.com/square/okhttp/issues/2323#issuecomment-185055040/ // More information about they're trying to extract all the SSL needed object here : // https://github.com/square/okhttp/blob/okhttp_31/okhttp/src/main/java/okhttp3/internal/Platform.java - @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN) @Test public void testOkhttp3WithTrustKitOldBuilder() throws MalformedURLException { if (Build.VERSION.SDK_INT < 17) { diff --git a/trustkit/src/main/AndroidManifest.xml b/trustkit/src/main/AndroidManifest.xml index 5567873..9b65eb0 100644 --- a/trustkit/src/main/AndroidManifest.xml +++ b/trustkit/src/main/AndroidManifest.xml @@ -1 +1 @@ - + From 8f19815a714789ddc597313fa60a1a9e04963195 Mon Sep 17 00:00:00 2001 From: Nathan Lecoanet Date: Tue, 18 Aug 2026 15:26:10 +0200 Subject: [PATCH 2/4] Spotless apply --- .../trustkit/demoapp/DemoMainActivity.java | 20 +- ...PinningFailureReportBroadcastReceiver.java | 9 +- build.gradle | 48 +- demoappkotlin/build.gradle | 1 - .../demoappkotlin/DemoMainActivity.kt | 19 +- .../PinningFailureReportBroadcastReceiver.kt | 8 +- .../android/trustkit/CertificateUtils.java | 122 +- .../android/trustkit/HttpLibrariesTest.java | 106 +- .../android/trustkit/TestableTrustKit.java | 42 +- .../android/trustkit/TrustKitTest.java | 23 +- .../config/DomainPinningPolicyTest.java | 33 +- .../trustkit/config/PublicKeyPinTest.java | 55 +- .../config/TestableTrustKitConfiguration.java | 8 +- .../config/TrustKitConfigurationTest.java | 494 +-- .../pinning/SSLSocketFactoryTest.java | 607 ++-- .../pinning/TestableTrustManagerBuilder.java | 1 - .../reporting/BackgroundReporterTaskTest.java | 51 +- .../reporting/BackgroundReporterTest.java | 148 +- .../reporting/ReportRateLimiterTest.java | 210 +- .../reporting/TestableBackgroundReporter.java | 7 +- .../reporting/TestableReportRateLimiter.java | 1 - .../trustkit/utils/VendorIdentifierTest.java | 9 +- .../android/trustkit/TrustKit.java | 355 +- .../config/ConfigurationException.java | 8 +- .../trustkit/config/DomainPinningPolicy.java | 77 +- .../trustkit/config/DomainValidator.java | 2881 +++++++++-------- .../android/trustkit/config/PublicKeyPin.java | 7 +- .../trustkit/config/RegexValidator.java | 111 +- .../config/TrustKitConfiguration.java | 30 +- .../config/TrustKitConfigurationParser.java | 107 +- .../pinning/DebugOverridesTrustManager.java | 11 +- .../pinning/DistinguishedNameParser.java | 56 +- .../trustkit/pinning/OkHostnameVerifier.java | 32 +- .../trustkit/pinning/OkHttp2Helper.java | 22 +- .../pinning/OkHttp2PinningInterceptor.java | 5 +- .../trustkit/pinning/OkHttp3Helper.java | 22 +- .../pinning/OkHttp3PinningInterceptor.java | 5 +- .../pinning/OkHttpRootTrustManager.java | 28 +- .../trustkit/pinning/PinningTrustManager.java | 64 +- .../pinning/PinningValidationResult.java | 1 - .../trustkit/pinning/SystemTrustManager.java | 18 +- .../trustkit/pinning/TrustManagerBuilder.java | 16 +- .../android/trustkit/pinning/Utils.java | 35 +- .../reporting/BackgroundReporter.java | 62 +- .../reporting/BackgroundReporterTask.java | 16 +- .../reporting/PinningFailureReport.java | 36 +- .../trustkit/reporting/ReportRateLimiter.java | 8 +- .../android/trustkit/utils/TrustKitLog.java | 1 - .../trustkit/utils/VendorIdentifier.java | 3 - 49 files changed, 3137 insertions(+), 2902 deletions(-) diff --git a/app/src/main/java/com/datatheorem/android/trustkit/demoapp/DemoMainActivity.java b/app/src/main/java/com/datatheorem/android/trustkit/demoapp/DemoMainActivity.java index f877cbb..98091d1 100644 --- a/app/src/main/java/com/datatheorem/android/trustkit/demoapp/DemoMainActivity.java +++ b/app/src/main/java/com/datatheorem/android/trustkit/demoapp/DemoMainActivity.java @@ -3,30 +3,26 @@ import android.content.IntentFilter; import android.os.AsyncTask; import android.os.Bundle; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; - +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.datatheorem.android.trustkit.TrustKit; import com.datatheorem.android.trustkit.reporting.BackgroundReporter; - import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; - import javax.net.ssl.HttpsURLConnection; - public class DemoMainActivity extends AppCompatActivity { protected static final String DEBUG_TAG = "TrustKit-Demo"; - private static final PinningFailureReportBroadcastReceiver pinningFailureReportBroadcastReceiver - = new PinningFailureReportBroadcastReceiver(); + private static final PinningFailureReportBroadcastReceiver + pinningFailureReportBroadcastReceiver = new PinningFailureReportBroadcastReceiver(); @Override protected void onCreate(Bundle savedInstanceState) { @@ -49,7 +45,7 @@ protected void onCreate(Bundle savedInstanceState) { IntentFilter intentFilter = new IntentFilter(BackgroundReporter.REPORT_VALIDATION_EVENT); LocalBroadcastManager.getInstance(getApplicationContext()) - .registerReceiver(pinningFailureReportBroadcastReceiver,intentFilter); + .registerReceiver(pinningFailureReportBroadcastReceiver, intentFilter); } @Override @@ -67,7 +63,8 @@ protected String doInBackground(String... params) { URL url = new URL(params[0]); HttpsURLConnection connection = null; connection = (HttpsURLConnection) url.openConnection(); - connection.setSSLSocketFactory(TrustKit.getInstance().getSSLSocketFactory(url.getHost())); + connection.setSSLSocketFactory( + TrustKit.getInstance().getSSLSocketFactory(url.getHost())); InputStream inputStream = connection.getInputStream(); } catch (MalformedURLException e) { e.printStackTrace(); @@ -107,4 +104,3 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } } - diff --git a/app/src/main/java/com/datatheorem/android/trustkit/demoapp/PinningFailureReportBroadcastReceiver.java b/app/src/main/java/com/datatheorem/android/trustkit/demoapp/PinningFailureReportBroadcastReceiver.java index 362976e..6fe1419 100644 --- a/app/src/main/java/com/datatheorem/android/trustkit/demoapp/PinningFailureReportBroadcastReceiver.java +++ b/app/src/main/java/com/datatheorem/android/trustkit/demoapp/PinningFailureReportBroadcastReceiver.java @@ -5,17 +5,14 @@ import android.content.Intent; import android.util.Log; import com.datatheorem.android.trustkit.reporting.BackgroundReporter; - import java.io.Serializable; /** * Class that provides an example broadcast receiver * - *

- * Applications using TrustKit can listen for local broadcasts and receive the same report that - * would be sent to the report_url. - *

- **/ + *

Applications using TrustKit can listen for local broadcasts and receive the same report that + * would be sent to the report_url. + */ class PinningFailureReportBroadcastReceiver extends BroadcastReceiver { @Override diff --git a/build.gradle b/build.gradle index 99ac843..8a5d4b2 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,32 @@ buildscript { } } +plugins { + id("com.diffplug.spotless") version "8.10.0" +} + allprojects { + apply { plugin("com.diffplug.spotless") } + + spotless { + java { + target("src/**/*.java") + googleJavaFormat("1.15.0").aosp() + } + kotlin { + target("src/**/*.kt") + ktfmt("0.61").kotlinlangStyle() + } + groovyGradle { + target("*.gradle") + greclipse().configProperties("org.eclipse.jdt.core.formatter.tabulation.char=space") + } + kotlinGradle { + target("*.gradle.kts") + ktfmt("0.61").kotlinlangStyle() + } + } + repositories { mavenCentral() google() @@ -32,17 +57,17 @@ ext{ demoAppKotlinTrustKitVersionName = "1.1" javaSourceCompatibilty = '11' toolVersions = [ - android : [ - compileSdk : 36, - minSdk : 15, - targetSdk: 36 - ] + android : [ + compileSdk : 36, + minSdk : 15, + targetSdk: 36 + ] ] libVersions = [ junit: '4.12', mockito : [ - android: '1.10.19' + android: '1.10.19' ], dexmaker : '1.4', androidx : [ @@ -53,16 +78,15 @@ ext{ preference: '1.0.0' ], testing: [ - okhttp3: '3.11.0', - 'playServicesBase': '11.0.0', + okhttp3: '3.11.0', + 'playServicesBase': '11.0.0', ], google: [ - material: '1.0.0' + material: '1.0.0' ], squareup: [ - okhttp3: '3.11.0', - okhttp2: '2.4.0' + okhttp3: '3.11.0', + okhttp2: '2.4.0' ] ] - } diff --git a/demoappkotlin/build.gradle b/demoappkotlin/build.gradle index 3fadfc8..3c0584c 100644 --- a/demoappkotlin/build.gradle +++ b/demoappkotlin/build.gradle @@ -30,7 +30,6 @@ android { kotlinOptions { jvmTarget = '11' } - } dependencies { diff --git a/demoappkotlin/src/main/java/com/datatheorem/android/trustkit/demoappkotlin/DemoMainActivity.kt b/demoappkotlin/src/main/java/com/datatheorem/android/trustkit/demoappkotlin/DemoMainActivity.kt index 1c64a71..76f9254 100644 --- a/demoappkotlin/src/main/java/com/datatheorem/android/trustkit/demoappkotlin/DemoMainActivity.kt +++ b/demoappkotlin/src/main/java/com/datatheorem/android/trustkit/demoappkotlin/DemoMainActivity.kt @@ -3,14 +3,14 @@ package com.datatheorem.android.trustkit.demoappkotlin import android.content.IntentFilter import android.os.AsyncTask import android.os.Bundle -import androidx.localbroadcastmanager.content.LocalBroadcastManager -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.Toolbar import android.util.Log import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.datatheorem.android.trustkit.TrustKit import com.datatheorem.android.trustkit.reporting.BackgroundReporter import java.io.IOException @@ -18,9 +18,9 @@ import java.net.MalformedURLException import java.net.URL import javax.net.ssl.HttpsURLConnection - class DemoMainActivity : AppCompatActivity() { - private lateinit var pinningFailureReportBroadcastReceiver: PinningFailureReportBroadcastReceiver + private lateinit var pinningFailureReportBroadcastReceiver: + PinningFailureReportBroadcastReceiver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -41,15 +41,15 @@ class DemoMainActivity : AppCompatActivity() { textView.text = "Connection results are in the logs" // Adding a local broadcast receiver to listen for validation report events - pinningFailureReportBroadcastReceiver = PinningFailureReportBroadcastReceiver() + pinningFailureReportBroadcastReceiver = PinningFailureReportBroadcastReceiver() val intentFilter = IntentFilter(BackgroundReporter.REPORT_VALIDATION_EVENT) LocalBroadcastManager.getInstance(this.applicationContext) - .registerReceiver(pinningFailureReportBroadcastReceiver,intentFilter) + .registerReceiver(pinningFailureReportBroadcastReceiver, intentFilter) } override fun onDestroy() { LocalBroadcastManager.getInstance(this.applicationContext) - .unregisterReceiver(pinningFailureReportBroadcastReceiver) + .unregisterReceiver(pinningFailureReportBroadcastReceiver) super.onDestroy() } @@ -90,11 +90,9 @@ class DemoMainActivity : AppCompatActivity() { // as you specify a parent activity in AndroidManifest.xml. val id = item.itemId - return if (id == R.id.action_settings) { true } else super.onOptionsItemSelected(item) - } companion object { @@ -102,4 +100,3 @@ class DemoMainActivity : AppCompatActivity() { internal const val DEBUG_TAG = "TrustKit-Demo" } } - diff --git a/demoappkotlin/src/main/java/com/datatheorem/android/trustkit/demoappkotlin/PinningFailureReportBroadcastReceiver.kt b/demoappkotlin/src/main/java/com/datatheorem/android/trustkit/demoappkotlin/PinningFailureReportBroadcastReceiver.kt index acbab09..689915b 100644 --- a/demoappkotlin/src/main/java/com/datatheorem/android/trustkit/demoappkotlin/PinningFailureReportBroadcastReceiver.kt +++ b/demoappkotlin/src/main/java/com/datatheorem/android/trustkit/demoappkotlin/PinningFailureReportBroadcastReceiver.kt @@ -10,15 +10,13 @@ import com.datatheorem.android.trustkit.reporting.BackgroundReporter * Class that provides an example broadcast receiver * *

- * Applications using TrustKit can listen for local broadcasts and receive the same report that - * would be sent to the report_url. - *

- **/ + * Applications using TrustKit can listen for local broadcasts and receive the same report that + * would be sent to the report_url.

+ */ class PinningFailureReportBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val result = intent.getSerializableExtra(BackgroundReporter.EXTRA_REPORT) Log.i(DemoMainActivity.DEBUG_TAG, result.toString()) } - } diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/CertificateUtils.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/CertificateUtils.java index 64c48aa..a88350d 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/CertificateUtils.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/CertificateUtils.java @@ -11,66 +11,72 @@ public class CertificateUtils { - static private final String intermediatePem = - "-----BEGIN CERTIFICATE-----\n" + - "MIID8DCCAtigAwIBAgIDAjqSMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlVT\n" + - "MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i\n" + - "YWwgQ0EwHhcNMTUwNDAxMDAwMDAwWhcNMTcxMjMxMjM1OTU5WjBJMQswCQYDVQQG\n" + - "EwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy\n" + - "bmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" + - "AJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP\n" + - "VaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv\n" + - "h8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE\n" + - "ahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ\n" + - "EASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC\n" + - "DTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB5zCB5DAfBgNVHSMEGDAWgBTAephojYn7\n" + - "qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wDgYD\n" + - "VR0PAQH/BAQDAgEGMC4GCCsGAQUFBwEBBCIwIDAeBggrBgEFBQcwAYYSaHR0cDov\n" + - "L2cuc3ltY2QuY29tMBIGA1UdEwEB/wQIMAYBAf8CAQAwNQYDVR0fBC4wLDAqoCig\n" + - "JoYkaHR0cDovL2cuc3ltY2IuY29tL2NybHMvZ3RnbG9iYWwuY3JsMBcGA1UdIAQQ\n" + - "MA4wDAYKKwYBBAHWeQIFATANBgkqhkiG9w0BAQsFAAOCAQEACE4Ep4B/EBZDXgKt\n" + - "10KA9LCO0q6z6xF9kIQYfeeQFftJf6iZBZG7esnWPDcYCZq2x5IgBzUzCeQoY3IN\n" + - "tOAynIeYxBt2iWfBUFiwE6oTGhsypb7qEZVMSGNJ6ZldIDfM/ippURaVS6neSYLA\n" + - "EHD0LPPsvCQk0E6spdleHm2SwaesSDWB+eXknGVpzYekQVA/LlelkVESWA6MCaGs\n" + - "eqQSpSfzmhCXfVUDBvdmWF9fZOGrXW2lOUh1mEwpWjqN0yvKnFUEv/TmFNWArCbt\n" + - "F4mmk2xcpMy48GaOZON9muIAs0nH5Aqq3VuDx3CQRk6+0NtZlmwu9RY23nHMAcIS\n" + - "wSHGFg==\n" + - "-----END CERTIFICATE-----"; + private static final String intermediatePem = + "-----BEGIN CERTIFICATE-----\n" + + "MIID8DCCAtigAwIBAgIDAjqSMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlVT\n" + + "MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i\n" + + "YWwgQ0EwHhcNMTUwNDAxMDAwMDAwWhcNMTcxMjMxMjM1OTU5WjBJMQswCQYDVQQG\n" + + "EwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy\n" + + "bmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" + + "AJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP\n" + + "VaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv\n" + + "h8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE\n" + + "ahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ\n" + + "EASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC\n" + + "DTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB5zCB5DAfBgNVHSMEGDAWgBTAephojYn7\n" + + "qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wDgYD\n" + + "VR0PAQH/BAQDAgEGMC4GCCsGAQUFBwEBBCIwIDAeBggrBgEFBQcwAYYSaHR0cDov\n" + + "L2cuc3ltY2QuY29tMBIGA1UdEwEB/wQIMAYBAf8CAQAwNQYDVR0fBC4wLDAqoCig\n" + + "JoYkaHR0cDovL2cuc3ltY2IuY29tL2NybHMvZ3RnbG9iYWwuY3JsMBcGA1UdIAQQ\n" + + "MA4wDAYKKwYBBAHWeQIFATANBgkqhkiG9w0BAQsFAAOCAQEACE4Ep4B/EBZDXgKt\n" + + "10KA9LCO0q6z6xF9kIQYfeeQFftJf6iZBZG7esnWPDcYCZq2x5IgBzUzCeQoY3IN\n" + + "tOAynIeYxBt2iWfBUFiwE6oTGhsypb7qEZVMSGNJ6ZldIDfM/ippURaVS6neSYLA\n" + + "EHD0LPPsvCQk0E6spdleHm2SwaesSDWB+eXknGVpzYekQVA/LlelkVESWA6MCaGs\n" + + "eqQSpSfzmhCXfVUDBvdmWF9fZOGrXW2lOUh1mEwpWjqN0yvKnFUEv/TmFNWArCbt\n" + + "F4mmk2xcpMy48GaOZON9muIAs0nH5Aqq3VuDx3CQRk6+0NtZlmwu9RY23nHMAcIS\n" + + "wSHGFg==\n" + + "-----END CERTIFICATE-----"; - static private final String leafPem = - "-----BEGIN CERTIFICATE-----\n" + - "MIID1jCCAr6gAwIBAgIIAPCznYJ9GMQwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE\n" + - "BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl\n" + - "cm5ldCBBdXRob3JpdHkgRzIwHhcNMTYwOTI5MTcwMjI5WhcNMTYxMjIyMTYzNzAw\n" + - "WjBpMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN\n" + - "TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEYMBYGA1UEAwwPbWFp\n" + - "bC5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8W4z2S50gAvv\n" + - "0VC6l7isbjD0Q7d7BiKWeOQwqfY+dLTmxZvpxBpcrfPlh170R3ai+qn/BE7t4+k2\n" + - "a+LrfYag8KOCAWswggFnMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAs\n" + - "BgNVHREEJTAjgg9tYWlsLmdvb2dsZS5jb22CEGluYm94Lmdvb2dsZS5jb20wCwYD\n" + - "VR0PBAQDAgeAMGgGCCsGAQUFBwEBBFwwWjArBggrBgEFBQcwAoYfaHR0cDovL3Br\n" + - "aS5nb29nbGUuY29tL0dJQUcyLmNydDArBggrBgEFBQcwAYYfaHR0cDovL2NsaWVu\n" + - "dHMxLmdvb2dsZS5jb20vb2NzcDAdBgNVHQ4EFgQUTuRBMq9TH5Dh82EGV6U37V8b\n" + - "3AcwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRK3QYWG7z2aLV29YG2u2IaulqB\n" + - "LzAhBgNVHSAEGjAYMAwGCisGAQQB1nkCBQEwCAYGZ4EMAQICMDAGA1UdHwQpMCcw\n" + - "JaAjoCGGH2h0dHA6Ly9wa2kuZ29vZ2xlLmNvbS9HSUFHMi5jcmwwDQYJKoZIhvcN\n" + - "AQELBQADggEBADqnYrHvHoCc7ltooq4XVj3yEyFX+n/hgrdQMmOgVcl3bHNYV5EG\n" + - "IqOClo5g1RyWcRfji8RQGv0hvFb6L2Zef5sOpQs3COEVW05kmCdwWSlCCpp6pJma\n" + - "yf6Nf4TreI8gpokoJgTNNgmq5OgT9K+G16I2L/CKv8rTh9HaoOOXWx90s5rAn/G/\n" + - "JrRRcgICjonU7m+ab22vdVilOJlEuMdX7x1CBPtHY/c214oJ32AxSTewXUjjDsWY\n" + - "c2azHgSpG5uA/TocrImlvajjrdZwQjtj8wO4av35BdaaOlfMo/xqa6VQfpA6W9ml\n" + - "jTL1zvT0Sv8mKow3b3blztbbeaVifTHShrA=\n" + - "-----END CERTIFICATE-----"; + private static final String leafPem = + "-----BEGIN CERTIFICATE-----\n" + + "MIID1jCCAr6gAwIBAgIIAPCznYJ9GMQwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE\n" + + "BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl\n" + + "cm5ldCBBdXRob3JpdHkgRzIwHhcNMTYwOTI5MTcwMjI5WhcNMTYxMjIyMTYzNzAw\n" + + "WjBpMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN\n" + + "TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEYMBYGA1UEAwwPbWFp\n" + + "bC5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8W4z2S50gAvv\n" + + "0VC6l7isbjD0Q7d7BiKWeOQwqfY+dLTmxZvpxBpcrfPlh170R3ai+qn/BE7t4+k2\n" + + "a+LrfYag8KOCAWswggFnMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAs\n" + + "BgNVHREEJTAjgg9tYWlsLmdvb2dsZS5jb22CEGluYm94Lmdvb2dsZS5jb20wCwYD\n" + + "VR0PBAQDAgeAMGgGCCsGAQUFBwEBBFwwWjArBggrBgEFBQcwAoYfaHR0cDovL3Br\n" + + "aS5nb29nbGUuY29tL0dJQUcyLmNydDArBggrBgEFBQcwAYYfaHR0cDovL2NsaWVu\n" + + "dHMxLmdvb2dsZS5jb20vb2NzcDAdBgNVHQ4EFgQUTuRBMq9TH5Dh82EGV6U37V8b\n" + + "3AcwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRK3QYWG7z2aLV29YG2u2IaulqB\n" + + "LzAhBgNVHSAEGjAYMAwGCisGAQQB1nkCBQEwCAYGZ4EMAQICMDAGA1UdHwQpMCcw\n" + + "JaAjoCGGH2h0dHA6Ly9wa2kuZ29vZ2xlLmNvbS9HSUFHMi5jcmwwDQYJKoZIhvcN\n" + + "AQELBQADggEBADqnYrHvHoCc7ltooq4XVj3yEyFX+n/hgrdQMmOgVcl3bHNYV5EG\n" + + "IqOClo5g1RyWcRfji8RQGv0hvFb6L2Zef5sOpQs3COEVW05kmCdwWSlCCpp6pJma\n" + + "yf6Nf4TreI8gpokoJgTNNgmq5OgT9K+G16I2L/CKv8rTh9HaoOOXWx90s5rAn/G/\n" + + "JrRRcgICjonU7m+ab22vdVilOJlEuMdX7x1CBPtHY/c214oJ32AxSTewXUjjDsWY\n" + + "c2azHgSpG5uA/TocrImlvajjrdZwQjtj8wO4av35BdaaOlfMo/xqa6VQfpA6W9ml\n" + + "jTL1zvT0Sv8mKow3b3blztbbeaVifTHShrA=\n" + + "-----END CERTIFICATE-----"; - static public final ArrayList testCertChainPem = new ArrayList() {{ - add(leafPem); - add(intermediatePem); - }}; + public static final ArrayList testCertChainPem = + new ArrayList() { + { + add(leafPem); + add(intermediatePem); + } + }; - static public final ArrayList testCertChain = new ArrayList() {{ - add((X509Certificate) certificateFromPem(leafPem)); - add((X509Certificate) certificateFromPem(intermediatePem)); - }}; + public static final ArrayList testCertChain = + new ArrayList() { + { + add((X509Certificate) certificateFromPem(leafPem)); + add((X509Certificate) certificateFromPem(intermediatePem)); + } + }; public static Certificate certificateFromPem(String pemCertificate) { pemCertificate = pemCertificate.replace("-----BEGIN CERTIFICATE-----\n", ""); @@ -80,7 +86,7 @@ public static Certificate certificateFromPem(String pemCertificate) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); return cf.generateCertificate(is); } catch (CertificateException e) { - throw new RuntimeException("Should never happen"); + throw new RuntimeException("Should never happen"); } } } diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/HttpLibrariesTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/HttpLibrariesTest.java index 1b2e118..e9839e3 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/HttpLibrariesTest.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/HttpLibrariesTest.java @@ -4,10 +4,9 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.filters.SdkSuppress; import android.os.Build; - +import androidx.test.filters.SdkSuppress; +import androidx.test.platform.app.InstrumentationRegistry; import com.datatheorem.android.trustkit.pinning.PinningValidationResult; import com.datatheorem.android.trustkit.reporting.BackgroundReporter; import java.io.IOException; @@ -29,10 +28,10 @@ @SuppressWarnings("unchecked") public class HttpLibrariesTest { - @Mock - private BackgroundReporter reporter; + @Mock private BackgroundReporter reporter; + + private static final URL testUrl; - static private final URL testUrl; static { try { // The network policy for the tests has invalid pins configured for this domain @@ -44,7 +43,7 @@ public class HttpLibrariesTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.initMocks(this); TestableTrustKit.reset(); } @@ -56,7 +55,7 @@ public void testHttpsUrlConnectionWithTrustKit() throws MalformedURLException { } // Initialize TrustKit TestableTrustKit.initializeWithNetworkSecurityConfiguration( - InstrumentationRegistry.getInstrumentation().getContext(), reporter); + InstrumentationRegistry.getInstrumentation().getContext(), reporter); // Test a connection HttpsURLConnection connection = null; @@ -64,8 +63,7 @@ public void testHttpsUrlConnectionWithTrustKit() throws MalformedURLException { try { connection = (HttpsURLConnection) testUrl.openConnection(); connection.setSSLSocketFactory( - TestableTrustKit.getInstance().getSSLSocketFactory(testUrl.getHost()) - ); + TestableTrustKit.getInstance().getSSLSocketFactory(testUrl.getHost())); InputStream inputStream = connection.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); @@ -88,17 +86,19 @@ public void testHttpsUrlConnectionWithTrustKit() throws MalformedURLException { assertTrue(didReceiveHandshakeError); // Ensure the reporter was called - verify(reporter).pinValidationFailed( - eq(testUrl.getHost()), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration() - .getPolicyForHostname(testUrl.getHost())), - eq(PinningValidationResult.FAILED)); + verify(reporter) + .pinValidationFailed( + eq(testUrl.getHost()), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(testUrl.getHost())), + eq(PinningValidationResult.FAILED)); } - @Test public void testHttpsUrlConnectionWithTrustKitApiLevelUnder17() throws IOException { if (Build.VERSION.SDK_INT >= 17) { @@ -116,8 +116,7 @@ public void testHttpsUrlConnectionWithTrustKitApiLevelUnder17() throws IOExcepti try { connection = (HttpsURLConnection) testUrl.openConnection(); connection.setSSLSocketFactory( - TestableTrustKit.getInstance().getSSLSocketFactory(testUrl.getHost()) - ); + TestableTrustKit.getInstance().getSSLSocketFactory(testUrl.getHost())); InputStream inputStream = connection.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); @@ -134,7 +133,6 @@ public void testHttpsUrlConnectionWithTrustKitApiLevelUnder17() throws IOExcepti } } - @Test public void testOkhttp3WithTrustKit() throws MalformedURLException { if (Build.VERSION.SDK_INT < 17) { @@ -147,9 +145,13 @@ public void testOkhttp3WithTrustKit() throws MalformedURLException { // Test a connection boolean didReceiveHandshakeError = false; - OkHttpClient client = new OkHttpClient().newBuilder() - .sslSocketFactory(TestableTrustKit.getInstance().getSSLSocketFactory(testUrl.getHost())) - .build(); + OkHttpClient client = + new OkHttpClient() + .newBuilder() + .sslSocketFactory( + TestableTrustKit.getInstance() + .getSSLSocketFactory(testUrl.getHost())) + .build(); try { Request request = new Request.Builder().url(testUrl).build(); client.newCall(request).execute(); @@ -163,18 +165,22 @@ public void testOkhttp3WithTrustKit() throws MalformedURLException { assertTrue(didReceiveHandshakeError); // Ensure the reporter was called - verify(reporter).pinValidationFailed( - eq(testUrl.getHost()), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration() - .getPolicyForHostname(testUrl.getHost())), - eq(PinningValidationResult.FAILED)); + verify(reporter) + .pinValidationFailed( + eq(testUrl.getHost()), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(testUrl.getHost())), + eq(PinningValidationResult.FAILED)); } // A specific test is needed for the previous version of the OkHttpClient Builder. - // The previous one signature only asks for a SSLSocketFactory compare to the newBuilder asking for + // The previous one signature only asks for a SSLSocketFactory compare to the newBuilder asking + // for // a SSLSocketFactory and a TrustManager. // It's a common issue with this version of OkHttp : // https://github.com/square/okhttp/issues/2323#issuecomment-185055040/ @@ -189,19 +195,22 @@ public void testOkhttp3WithTrustKitOldBuilder() throws MalformedURLException { } // Initialize TrustKit TestableTrustKit.initializeWithNetworkSecurityConfiguration( - InstrumentationRegistry.getInstrumentation().getContext(), reporter); + InstrumentationRegistry.getInstrumentation().getContext(), reporter); // Test a connection boolean didReceiveHandshakeError = false; - OkHttpClient client = new OkHttpClient.Builder() - .sslSocketFactory(TestableTrustKit.getInstance().getSSLSocketFactory(testUrl.getHost())) - .build(); + OkHttpClient client = + new OkHttpClient.Builder() + .sslSocketFactory( + TestableTrustKit.getInstance() + .getSSLSocketFactory(testUrl.getHost())) + .build(); try { Request request = new Request.Builder().url(testUrl).build(); client.newCall(request).execute(); } catch (IOException e) { if ((e.getCause() instanceof CertificateException - && (e.getCause().getMessage().startsWith("Pin verification failed")))) { + && (e.getCause().getMessage().startsWith("Pin verification failed")))) { didReceiveHandshakeError = true; } } @@ -209,13 +218,16 @@ public void testOkhttp3WithTrustKitOldBuilder() throws MalformedURLException { assertTrue(didReceiveHandshakeError); // Ensure the reporter was called - verify(reporter).pinValidationFailed( - eq(testUrl.getHost()), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration() - .getPolicyForHostname(testUrl.getHost())), - eq(PinningValidationResult.FAILED)); + verify(reporter) + .pinValidationFailed( + eq(testUrl.getHost()), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(testUrl.getHost())), + eq(PinningValidationResult.FAILED)); } } diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/TestableTrustKit.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/TestableTrustKit.java index 6c7fa74..13cf1d4 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/TestableTrustKit.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/TestableTrustKit.java @@ -1,6 +1,5 @@ package com.datatheorem.android.trustkit; - import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -12,18 +11,18 @@ import java.security.cert.Certificate; import java.util.Set; - // The main TrustKit class with some extra utility methods needed in the tests public class TestableTrustKit extends TrustKit { - private TestableTrustKit(Context context, TrustKitConfiguration trustKitConfiguration, - BackgroundReporter reporter) { + private TestableTrustKit( + Context context, + TrustKitConfiguration trustKitConfiguration, + BackgroundReporter reporter) { super(context, trustKitConfiguration); TestableTrustManagerBuilder.setReporter(reporter); } - - public static TrustKit initializeWithNetworkSecurityConfiguration(@NonNull Context context, - BackgroundReporter reporter) { + public static TrustKit initializeWithNetworkSecurityConfiguration( + @NonNull Context context, BackgroundReporter reporter) { TrustKit.initializeWithNetworkSecurityConfiguration(context); TestableTrustManagerBuilder.setReporter(reporter); return TrustKit.getInstance(); @@ -31,21 +30,26 @@ public static TrustKit initializeWithNetworkSecurityConfiguration(@NonNull Conte // This lets us directly specify domain settings without parsing an XML file and inject/mock // the background reporter - public static void init(@NonNull Set domainConfigSet, - @NonNull Context context, - BackgroundReporter reporter) { - trustKitInstance = new TrustKit(context, new TestableTrustKitConfiguration(domainConfigSet)); + public static void init( + @NonNull Set domainConfigSet, + @NonNull Context context, + BackgroundReporter reporter) { + trustKitInstance = + new TrustKit(context, new TestableTrustKitConfiguration(domainConfigSet)); TestableTrustManagerBuilder.setReporter(reporter); } - - public static void init(@NonNull Set domainConfigSet, - boolean shouldOverridePins, - @Nullable Set debugCaCerts, - @NonNull Context context, - BackgroundReporter reporter) { - trustKitInstance = new TrustKit(context, new TestableTrustKitConfiguration(domainConfigSet, - shouldOverridePins, debugCaCerts)); + public static void init( + @NonNull Set domainConfigSet, + boolean shouldOverridePins, + @Nullable Set debugCaCerts, + @NonNull Context context, + BackgroundReporter reporter) { + trustKitInstance = + new TrustKit( + context, + new TestableTrustKitConfiguration( + domainConfigSet, shouldOverridePins, debugCaCerts)); TestableTrustManagerBuilder.setReporter(reporter); } diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/TrustKitTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/TrustKitTest.java index 8053220..2b95dd1 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/TrustKitTest.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/TrustKitTest.java @@ -1,19 +1,16 @@ package com.datatheorem.android.trustkit; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; + import android.content.Context; import android.content.res.Resources; import android.os.Build; - import androidx.test.platform.app.InstrumentationRegistry; - import com.datatheorem.android.trustkit.config.ConfigurationException; - import org.junit.Before; import org.junit.Test; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertTrue; - public class TrustKitTest { @Before @@ -44,10 +41,12 @@ public void testInitializeWithDefaultXmlFile() { @Test public void testInitializeWithValidXmlFile() { Context context = InstrumentationRegistry.getInstrumentation().getContext(); - int networkSecurityConfigId = context.getResources().getIdentifier( - "network_security_config", "xml", context.getPackageName()); - TrustKit trustkit = TrustKit.initializeWithNetworkSecurityConfiguration(context, - networkSecurityConfigId); + int networkSecurityConfigId = + context.getResources() + .getIdentifier("network_security_config", "xml", context.getPackageName()); + TrustKit trustkit = + TrustKit.initializeWithNetworkSecurityConfiguration( + context, networkSecurityConfigId); assertNotNull(trustkit); } @@ -72,8 +71,8 @@ public void testInitializeWithBadResourceId() { @Test public void testInitializeWithBadFile() { Context context = InstrumentationRegistry.getInstrumentation().getContext(); - int pemFileId = context.getResources().getIdentifier("cacertorg", "raw", - context.getPackageName()); + int pemFileId = + context.getResources().getIdentifier("cacertorg", "raw", context.getPackageName()); boolean didInitFail = false; try { diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/DomainPinningPolicyTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/DomainPinningPolicyTest.java index 272520c..adf31ac 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/DomainPinningPolicyTest.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/DomainPinningPolicyTest.java @@ -1,41 +1,39 @@ package com.datatheorem.android.trustkit.config; - -import org.junit.Test; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; import java.net.MalformedURLException; import java.net.URL; import java.util.Date; import java.util.HashSet; import java.util.Set; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; - +import org.junit.Test; public class DomainPinningPolicyTest { - private final static Set pins = new HashSet<>(); + private static final Set pins = new HashSet<>(); + static { pins.add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); pins.add("rFjc3wG7lTZe43zeYTvPq8k4xdDEutCmIhI5dn4oCeE="); } - private final static Set reportUris = new HashSet<>(); + private static final Set reportUris = new HashSet<>(); + static { reportUris.add("https://www.test.com"); reportUris.add("https://www.test2.com"); } - private final static Date date = new Date(); + private static final Date date = new Date(); @Test public void testValidPolicy() throws MalformedURLException { // Given a valid policy for a domain // When parsing it, it succeeds - DomainPinningPolicy policy = new DomainPinningPolicy( - "www.test.com", true, pins, true, date, reportUris, false - ); + DomainPinningPolicy policy = + new DomainPinningPolicy("www.test.com", true, pins, true, date, reportUris, false); // And the right configuration was saved assertEquals("www.test.com", policy.getHostname()); assertEquals(date, policy.getExpirationDate()); @@ -46,7 +44,6 @@ public void testValidPolicy() throws MalformedURLException { Set expectedPins = new HashSet<>(); for (String pinStr : pins) { expectedPins.add(new PublicKeyPin(pinStr)); - } assertEquals(expectedPins, policy.getPublicKeyPins()); @@ -65,9 +62,9 @@ public void testValidPolicyInternationalizeHostname() throws MalformedURLExcepti String internationalDomain = "českárepublika.icom.museum"; // When parsing it, it succeeds - DomainPinningPolicy policy = new DomainPinningPolicy( - internationalDomain, true, pins, true, date, reportUris, false - ); + DomainPinningPolicy policy = + new DomainPinningPolicy( + internationalDomain, true, pins, true, date, reportUris, false); assertEquals(policy.getHostname(), internationalDomain); assertEquals(policy.getHostname(), "českárepublika.icom.museum"); } @@ -92,7 +89,6 @@ public void testBadPolicyOnlyOnePin() throws MalformedURLException { assertTrue(didReceiveConfigError); } - @Test public void testNoPinsButPinningEnforceDisabledShouldBeValid() throws MalformedURLException { // Given a bad policy for a domain that has one pins at all @@ -121,8 +117,7 @@ public void testBadPolicyPinTld() throws MalformedURLException { boolean didReceiveConfigError = false; try { new DomainPinningPolicy(badDomain, true, pins, true, date, reportUris, false); - } - catch (ConfigurationException e) { + } catch (ConfigurationException e) { if (e.getMessage().startsWith("Tried to pin an invalid domain")) { didReceiveConfigError = true; } else { diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/PublicKeyPinTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/PublicKeyPinTest.java index ae09e61..ea69ee5 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/PublicKeyPinTest.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/PublicKeyPinTest.java @@ -1,39 +1,35 @@ package com.datatheorem.android.trustkit.config; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; import com.datatheorem.android.trustkit.CertificateUtils; - -import org.junit.Test; - import java.security.cert.Certificate; import java.security.cert.CertificateException; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; - +import org.junit.Test; public class PublicKeyPinTest { @Test public void testFromCertificate() throws CertificateException { String pemCertificate = - "MIIDGTCCAgGgAwIBAgIJAI1jD1qixIPLMA0GCSqGSIb3DQEBBQUAMCMxITAfBgNV\n" + - "BAMMGGV2aWxjZXJ0LmRhdGF0aGVvcmVtLmNvbTAeFw0xNTEyMjAxMzU4NDNaFw0y\n" + - "NTEyMTcxMzU4NDNaMCMxITAfBgNVBAMMGGV2aWxjZXJ0LmRhdGF0aGVvcmVtLmNv\n" + - "bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMdltqsRJtO7Nqypkehh\n" + - "4DSEirp9RM+hJXkBE9nRleTO+utV/snWqX/0wsUrz0wgWyPnAHybGOOXvkrWfXSt\n" + - "c2/8PyONOeFEU/9S/lWBXGZkaPhgTvkEzPmOOhf06rBMTwXUMGNDI45gKFgkO6Br\n" + - "bGPeSCuheQj0TKeWdwwNoJ+kczUE06IKu2tcuFRjHXci6VeHjANJzrfKro4ivIRy\n" + - "bewOGJj1onnpKbui/EOytsmW9MPpOSEXMoVksHOKBQ9nhpL6cDODRvG+t8u7qfFt\n" + - "mhphemK3IYNMNA4MMXpbJ+Au2hnPApZPEOit34bAwOiGi/batcS3iA+nl06dPYA9\n" + - "nPkCAwEAAaNQME4wHQYDVR0OBBYEFANxdSXS1JSvjdNtNbYBbRlgii93MB8GA1Ud\n" + - "IwQYMBaAFANxdSXS1JSvjdNtNbYBbRlgii93MAwGA1UdEwQFMAMBAf8wDQYJKoZI\n" + - "hvcNAQEFBQADggEBAAM78Bt2aLUgl2Yq4KMIGDeHdWYcRB7QPQ8sp3Q1TOQQzw0i\n" + - "AukRccl9iYNLgaSJDvlVMapD76jo3okydoWgDogWJhtZpMU/9xegIpukmu5hvF6i\n" + - "NpqE99PFO5E8BpMkNz+2nskwu//D0as6P9F3tA/o3jC6n6fWX0gt/e9th2ZgVwNQ\n" + - "9JTH1ZcyFbX9hdBI4xPAtzFX51AsSa8dpRdG+8DmI41Q/1ludoMZboExHldlUbQH\n" + - "zUuHKF8/T+aNo/9FfpqDz1fFnuoF7tuwyRh73B0YDyDVTNuq7LJ4tmzpVvqIt2tn\n" + - "RJnQoL4pLQ40SQsoUi4FYG/gxJMoQX6ROWe2nyg="; + "MIIDGTCCAgGgAwIBAgIJAI1jD1qixIPLMA0GCSqGSIb3DQEBBQUAMCMxITAfBgNV\n" + + "BAMMGGV2aWxjZXJ0LmRhdGF0aGVvcmVtLmNvbTAeFw0xNTEyMjAxMzU4NDNaFw0y\n" + + "NTEyMTcxMzU4NDNaMCMxITAfBgNVBAMMGGV2aWxjZXJ0LmRhdGF0aGVvcmVtLmNv\n" + + "bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMdltqsRJtO7Nqypkehh\n" + + "4DSEirp9RM+hJXkBE9nRleTO+utV/snWqX/0wsUrz0wgWyPnAHybGOOXvkrWfXSt\n" + + "c2/8PyONOeFEU/9S/lWBXGZkaPhgTvkEzPmOOhf06rBMTwXUMGNDI45gKFgkO6Br\n" + + "bGPeSCuheQj0TKeWdwwNoJ+kczUE06IKu2tcuFRjHXci6VeHjANJzrfKro4ivIRy\n" + + "bewOGJj1onnpKbui/EOytsmW9MPpOSEXMoVksHOKBQ9nhpL6cDODRvG+t8u7qfFt\n" + + "mhphemK3IYNMNA4MMXpbJ+Au2hnPApZPEOit34bAwOiGi/batcS3iA+nl06dPYA9\n" + + "nPkCAwEAAaNQME4wHQYDVR0OBBYEFANxdSXS1JSvjdNtNbYBbRlgii93MB8GA1Ud\n" + + "IwQYMBaAFANxdSXS1JSvjdNtNbYBbRlgii93MAwGA1UdEwQFMAMBAf8wDQYJKoZI\n" + + "hvcNAQEFBQADggEBAAM78Bt2aLUgl2Yq4KMIGDeHdWYcRB7QPQ8sp3Q1TOQQzw0i\n" + + "AukRccl9iYNLgaSJDvlVMapD76jo3okydoWgDogWJhtZpMU/9xegIpukmu5hvF6i\n" + + "NpqE99PFO5E8BpMkNz+2nskwu//D0as6P9F3tA/o3jC6n6fWX0gt/e9th2ZgVwNQ\n" + + "9JTH1ZcyFbX9hdBI4xPAtzFX51AsSa8dpRdG+8DmI41Q/1ludoMZboExHldlUbQH\n" + + "zUuHKF8/T+aNo/9FfpqDz1fFnuoF7tuwyRh73B0YDyDVTNuq7LJ4tmzpVvqIt2tn\n" + + "RJnQoL4pLQ40SQsoUi4FYG/gxJMoQX6ROWe2nyg="; Certificate cert = CertificateUtils.certificateFromPem(pemCertificate); PublicKeyPin pin = new PublicKeyPin(cert); assertEquals("Ckvh+UFO2eHunqaB2w0jsrwrJJQcSoES+p9FUhVoszQ=", pin.toString()); @@ -41,9 +37,8 @@ public void testFromCertificate() throws CertificateException { @Test public void testFromString() { - PublicKeyPin pin = - new PublicKeyPin("rFjc3wG7lTZe43zeYTvPq8k4xdDEutCmIhI5dn4oCeE="); - assertEquals(pin.toString(),"rFjc3wG7lTZe43zeYTvPq8k4xdDEutCmIhI5dn4oCeE="); + PublicKeyPin pin = new PublicKeyPin("rFjc3wG7lTZe43zeYTvPq8k4xdDEutCmIhI5dn4oCeE="); + assertEquals(pin.toString(), "rFjc3wG7lTZe43zeYTvPq8k4xdDEutCmIhI5dn4oCeE="); } @Test @@ -54,8 +49,7 @@ public void testFromBadStringNotBase64() { } catch (IllegalArgumentException e) { if (e.getMessage().startsWith("bad base-64")) { didReturnError = true; - } - else { + } else { throw e; } } @@ -70,8 +64,7 @@ public void testFromBadStringBadLength() { } catch (IllegalArgumentException e) { if (e.getMessage().startsWith("Invalid pin")) { didReturnError = true; - } - else { + } else { throw e; } } diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/TestableTrustKitConfiguration.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/TestableTrustKitConfiguration.java index 7043e08..62308ea 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/TestableTrustKitConfiguration.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/TestableTrustKitConfiguration.java @@ -2,7 +2,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import java.security.cert.Certificate; import java.util.Set; @@ -11,9 +10,10 @@ public TestableTrustKitConfiguration(@NonNull Set domainCon super(domainConfigSet); } - public TestableTrustKitConfiguration(@NonNull Set domainConfigSet, - boolean shouldOverridePins, - @Nullable Set debugCaCerts) { + public TestableTrustKitConfiguration( + @NonNull Set domainConfigSet, + boolean shouldOverridePins, + @Nullable Set debugCaCerts) { super(domainConfigSet, shouldOverridePins, debugCaCerts); } } diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/TrustKitConfigurationTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/TrustKitConfigurationTest.java index d14c9ec..ceab4ae 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/TrustKitConfigurationTest.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/config/TrustKitConfigurationTest.java @@ -1,14 +1,13 @@ package com.datatheorem.android.trustkit.config; -import android.content.Context; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import android.content.Context; import androidx.test.platform.app.InstrumentationRegistry; - -import org.junit.Test; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - import java.io.IOException; import java.io.InputStream; import java.io.StringReader; @@ -21,12 +20,10 @@ import java.util.Date; import java.util.HashSet; import java.util.Locale; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertTrue; +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; public class TrustKitConfigurationTest { @@ -35,27 +32,29 @@ private XmlPullParser parseXmlString(String xmlString) throws XmlPullParserExcep factory.setNamespaceAware(true); XmlPullParser xpp = factory.newPullParser(); - String test = xmlString.replace("\n","").replace(" ",""); + String test = xmlString.replace("\n", "").replace(" ", ""); xpp.setInput(new StringReader(test)); return xpp; } @Test - public void testBadHostnameValidation() throws XmlPullParserException, IOException, CertificateException { + public void testBadHostnameValidation() + throws XmlPullParserException, IOException, CertificateException { Context context = InstrumentationRegistry.getInstrumentation().getContext(); - String xml = "" + - "\n" + - "\n" + - " \n" + - " www.datatheorem.com\n" + - " \n" + - " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + - " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + - " \n" + - " \n" + - ""; - TrustKitConfiguration config = TrustKitConfiguration.fromXmlPolicy(context, - parseXmlString(xml)); + String xml = + "" + + "\n" + + "\n" + + " \n" + + " www.datatheorem.com\n" + + " \n" + + " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + + " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + + " \n" + + " \n" + + ""; + TrustKitConfiguration config = + TrustKitConfiguration.fromXmlPolicy(context, parseXmlString(xml)); // Ensure that something that isn't a domain (such as a URL) gets rejected boolean wasBadDomainRejected = false; @@ -68,25 +67,26 @@ public void testBadHostnameValidation() throws XmlPullParserException, IOExcepti } @Test - public void testDefaultValues() throws XmlPullParserException, IOException, ParseException, - CertificateException { + public void testDefaultValues() + throws XmlPullParserException, IOException, ParseException, CertificateException { Context context = InstrumentationRegistry.getInstrumentation().getContext(); - String xml = "" + - "\n" + - "\n" + - " \n" + - " www.datatheorem.com\n" + - " \n" + - " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + - " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + - " \n" + - " \n" + - " https://some.reportdomain.com/\n" + - " \n" + - " \n" + - ""; - TrustKitConfiguration config = TrustKitConfiguration.fromXmlPolicy(context, - parseXmlString(xml)); + String xml = + "" + + "\n" + + "\n" + + " \n" + + " www.datatheorem.com\n" + + " \n" + + " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + + " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + + " \n" + + " \n" + + " https://some.reportdomain.com/\n" + + " \n" + + " \n" + + ""; + TrustKitConfiguration config = + TrustKitConfiguration.fromXmlPolicy(context, parseXmlString(xml)); // Validate the domain's configuration DomainPinningPolicy domainConfig = config.getPolicyForHostname("www.datatheorem.com"); @@ -97,37 +97,44 @@ public void testDefaultValues() throws XmlPullParserException, IOException, Pars assertFalse(domainConfig.shouldIncludeSubdomains()); assertFalse(domainConfig.shouldEnforcePinning()); - HashSet expectedUri = new HashSet() {{ - add(new java.net.URL("https://some.reportdomain.com/")); - // The default report URI should be there too - add(new java.net.URL("https://overmind.datatheorem.com/trustkit/report")); - }}; + HashSet expectedUri = + new HashSet() { + { + add(new java.net.URL("https://some.reportdomain.com/")); + // The default report URI should be there too + add(new java.net.URL("https://overmind.datatheorem.com/trustkit/report")); + } + }; assertEquals(expectedUri, domainConfig.getReportUris()); - HashSet expectedPins = new HashSet() {{ - add(new PublicKeyPin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")); - add(new PublicKeyPin("grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=")); - }}; + HashSet expectedPins = + new HashSet() { + { + add(new PublicKeyPin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")); + add(new PublicKeyPin("grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=")); + } + }; assertEquals(expectedPins, domainConfig.getPublicKeyPins()); } @Test - public void testIncludeSubdomainsAndNoTrustkitTag() throws XmlPullParserException, IOException, - ParseException, CertificateException { + public void testIncludeSubdomainsAndNoTrustkitTag() + throws XmlPullParserException, IOException, ParseException, CertificateException { Context context = InstrumentationRegistry.getInstrumentation().getContext(); - String xml = "" + - "\n" + - "\n" + - " \n" + - " datatheorem.com\n" + - " \n" + - " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + - " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + - " \n" + - " \n" + - ""; - TrustKitConfiguration config = TrustKitConfiguration.fromXmlPolicy(context, - parseXmlString(xml)); + String xml = + "" + + "\n" + + "\n" + + " \n" + + " datatheorem.com\n" + + " \n" + + " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + + " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + + " \n" + + " \n" + + ""; + TrustKitConfiguration config = + TrustKitConfiguration.fromXmlPolicy(context, parseXmlString(xml)); // Ensure a valid subdomain gets the policy DomainPinningPolicy domainConfig = config.getPolicyForHostname("subdomain.datatheorem.com"); @@ -146,48 +153,49 @@ public void testIncludeSubdomainsAndNoTrustkitTag() throws XmlPullParserExceptio } @Test - public void testEnforcePinning() throws XmlPullParserException, IOException, - ParseException, CertificateException { + public void testEnforcePinning() + throws XmlPullParserException, IOException, ParseException, CertificateException { Context context = InstrumentationRegistry.getInstrumentation().getContext(); - String xml = "" + - "\n" + - "\n" + - " \n" + - " www.datatheorem.com\n" + - " \n" + - " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + - " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; - TrustKitConfiguration config = TrustKitConfiguration.fromXmlPolicy(context, - parseXmlString(xml)); + String xml = + "" + + "\n" + + "\n" + + " \n" + + " www.datatheorem.com\n" + + " \n" + + " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + + " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + TrustKitConfiguration config = + TrustKitConfiguration.fromXmlPolicy(context, parseXmlString(xml)); DomainPinningPolicy domainConfig = config.getPolicyForHostname("www.datatheorem.com"); assertNotNull(domainConfig); assertTrue(domainConfig.shouldEnforcePinning()); } - @Test - public void testExpirationDate() throws XmlPullParserException, IOException, - ParseException, CertificateException { + public void testExpirationDate() + throws XmlPullParserException, IOException, ParseException, CertificateException { Context context = InstrumentationRegistry.getInstrumentation().getContext(); - String xml = "" + - "\n" + - "\n" + - " \n" + - " www.datatheorem.com\n" + - " \n" + - " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + - " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + - " \n" + - " \n" + - ""; - TrustKitConfiguration config = TrustKitConfiguration.fromXmlPolicy(context, - parseXmlString(xml)); + String xml = + "" + + "\n" + + "\n" + + " \n" + + " www.datatheorem.com\n" + + " \n" + + " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + + " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + + " \n" + + " \n" + + ""; + TrustKitConfiguration config = + TrustKitConfiguration.fromXmlPolicy(context, parseXmlString(xml)); SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd", Locale.US); Date expectedDate = parser.parse("2018-01-01"); @@ -197,24 +205,25 @@ public void testExpirationDate() throws XmlPullParserException, IOException, } @Test - public void testDisableDefaultReportUri() throws XmlPullParserException, IOException, - ParseException, CertificateException { + public void testDisableDefaultReportUri() + throws XmlPullParserException, IOException, ParseException, CertificateException { Context context = InstrumentationRegistry.getInstrumentation().getContext(); - String xml = "" + - "\n" + - "\n" + - " \n" + - " www.datatheorem.com\n" + - " \n" + - " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + - " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; - TrustKitConfiguration config = TrustKitConfiguration.fromXmlPolicy(context, - parseXmlString(xml)); + String xml = + "" + + "\n" + + "\n" + + " \n" + + " www.datatheorem.com\n" + + " \n" + + " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + + " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + TrustKitConfiguration config = + TrustKitConfiguration.fromXmlPolicy(context, parseXmlString(xml)); // Ensure the list of report URIs is empty DomainPinningPolicy domainConfig = config.getPolicyForHostname("www.datatheorem.com"); @@ -223,30 +232,32 @@ public void testDisableDefaultReportUri() throws XmlPullParserException, IOExcep } @Test - public void testDebugOverrides() throws XmlPullParserException, IOException, - ParseException, CertificateException { + public void testDebugOverrides() + throws XmlPullParserException, IOException, ParseException, CertificateException { Context context = InstrumentationRegistry.getInstrumentation().getContext(); - String xml = "" + - "\n" + - "\n" + - " \n" + - " www.datatheorem.com\n" + - " \n" + - " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + - " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - // We ignore src=sytem or user - " \n" + - " \n" + - " \n" + - ""; - TrustKitConfiguration config = TrustKitConfiguration.fromXmlPolicy(context, - parseXmlString(xml)); + String xml = + "" + + "\n" + + "\n" + + " \n" + + " www.datatheorem.com\n" + + " \n" + + " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + + " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + + // We ignore src=sytem or user + " \n" + + " \n" + + " \n" + + ""; + TrustKitConfiguration config = + TrustKitConfiguration.fromXmlPolicy(context, parseXmlString(xml)); // Validate the debug overrides configuration int goodCertResId = @@ -262,59 +273,68 @@ public void testDebugOverrides() throws XmlPullParserException, IOException, CertificateFactory.getInstance("X.509").generateCertificate(caCertStream); assertTrue(config.shouldOverridePins()); - HashSet expectedCertificates = new HashSet() {{ - add(goodCert); - add(caCert); - }}; + HashSet expectedCertificates = + new HashSet() { + { + add(goodCert); + add(caCert); + } + }; assertEquals(expectedCertificates, config.getDebugCaCertificates()); } @Test - public void testNestedDomainConfig() throws XmlPullParserException, IOException, - ParseException, CertificateException { + public void testNestedDomainConfig() + throws XmlPullParserException, IOException, ParseException, CertificateException { Context context = InstrumentationRegistry.getInstrumentation().getContext(); - String xml = "" + - "\n" + - "\n" + - " \n" + - // A more specific domain-config for a subdomain is nested here - " \n" + - " other.datatheorem.com\n" + - " \n" + - " CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=\n" + - " DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " datatheorem.com\n" + - " \n" + - " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + - " BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\n" + - " \n" + - " \n" + - " \n" + - // A more specific domain-config for an unrelated domain is nested here - " \n" + - " unrelated.domain.com\n" + - " \n" + - " https://some.reportdomain.com/\n" + - " \n" + - " \n" + - " \n" + - ""; - TrustKitConfiguration config = TrustKitConfiguration.fromXmlPolicy(context, - parseXmlString(xml)); + String xml = + "" + + "\n" + + "\n" + + " \n" + + + // A more specific domain-config for a subdomain is nested here + " \n" + + " other.datatheorem.com\n" + + " \n" + + " CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=\n" + + " DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " datatheorem.com\n" + + " \n" + + " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + + " BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\n" + + " \n" + + " \n" + + " \n" + + + // A more specific domain-config for an unrelated domain is nested here + " \n" + + " unrelated.domain.com\n" + + " \n" + + " https://some.reportdomain.com/\n" + + " \n" + + " \n" + + " \n" + + ""; + TrustKitConfiguration config = + TrustKitConfiguration.fromXmlPolicy(context, parseXmlString(xml)); // Validate the configuration of the parent domain-config DomainPinningPolicy domainConfig = config.getPolicyForHostname("datatheorem.com"); assertNotNull(domainConfig); assertEquals(new HashSet<>(), domainConfig.getReportUris()); - HashSet expectedPins = new HashSet() {{ - add(new PublicKeyPin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")); - add(new PublicKeyPin("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")); - }}; + HashSet expectedPins = + new HashSet() { + { + add(new PublicKeyPin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")); + add(new PublicKeyPin("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")); + } + }; assertEquals(expectedPins, domainConfig.getPublicKeyPins()); // Validate the configuration of the parent domain-config for a subdomain @@ -326,17 +346,23 @@ public void testNestedDomainConfig() throws XmlPullParserException, IOException, // Validate the configuration of a nested domain-config for a subdomain domainConfig = config.getPolicyForHostname("other.datatheorem.com"); - HashSet expectedOtherPins = new HashSet() {{ - add(new PublicKeyPin("CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=")); - add(new PublicKeyPin("DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=")); - }}; + HashSet expectedOtherPins = + new HashSet() { + { + add(new PublicKeyPin("CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=")); + add(new PublicKeyPin("DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=")); + } + }; assertNotNull(domainConfig); assertEquals(expectedOtherPins, domainConfig.getPublicKeyPins()); - HashSet expectedUri = new HashSet() {{ - // The default report URI should be there - add(new java.net.URL("https://overmind.datatheorem.com/trustkit/report")); - }}; + HashSet expectedUri = + new HashSet() { + { + // The default report URI should be there + add(new java.net.URL("https://overmind.datatheorem.com/trustkit/report")); + } + }; assertEquals(expectedUri, domainConfig.getReportUris()); // Validate the configuration of a nested domain-config for an unrelated domain @@ -344,40 +370,44 @@ public void testNestedDomainConfig() throws XmlPullParserException, IOException, assertNotNull(domainConfig); assertEquals(expectedPins, domainConfig.getPublicKeyPins()); - HashSet expectedUnrelatedUri = new HashSet() {{ - // The default report URI should be there - add(new java.net.URL("https://some.reportdomain.com/")); - }}; + HashSet expectedUnrelatedUri = + new HashSet() { + { + // The default report URI should be there + add(new java.net.URL("https://some.reportdomain.com/")); + } + }; assertEquals(expectedUnrelatedUri, domainConfig.getReportUris()); } @Test - public void testIgnoreDomainWithNoPins( - ) throws XmlPullParserException, IOException, CertificateException { + public void testIgnoreDomainWithNoPins() + throws XmlPullParserException, IOException, CertificateException { Context context = InstrumentationRegistry.getInstrumentation().getContext(); // Given a valid network security config - String xml = "" + - "\n" + - "\n" + - " \n" + - " www.datatheorem.com\n" + - " \n" + - " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + - " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + - " \n" + - " \n" + - - // That has a domain-config entry with no pin-set - " \n" + - " localhost\n" + - " 10.0.2.2\n" + - " \n" + - ""; + String xml = + "" + + "\n" + + "\n" + + " \n" + + " www.datatheorem.com\n" + + " \n" + + " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" + + " grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=\n" + + " \n" + + " \n" + + + + // That has a domain-config entry with no pin-set + " \n" + + " localhost\n" + + " 10.0.2.2\n" + + " \n" + + ""; // When parsing the config - TrustKitConfiguration config = TrustKitConfiguration.fromXmlPolicy( - context, parseXmlString(xml) - ); + TrustKitConfiguration config = + TrustKitConfiguration.fromXmlPolicy(context, parseXmlString(xml)); // It succeeds DomainPinningPolicy datathDomainConfig = config.getPolicyForHostname("www.datatheorem.com"); @@ -389,23 +419,23 @@ context, parseXmlString(xml) } @Test - public void testAllowsEmptyPinningConfig( - ) throws XmlPullParserException, IOException, CertificateException { + public void testAllowsEmptyPinningConfig() + throws XmlPullParserException, IOException, CertificateException { Context context = InstrumentationRegistry.getInstrumentation().getContext(); // Given a valid network security config that has no entries related to pinning - String xml = "" + - "\n" + - "\n" + - " \n" + - " localhost\n" + - " 10.0.2.2\n" + - " \n" + - ""; + String xml = + "" + + "\n" + + "\n" + + " \n" + + " localhost\n" + + " 10.0.2.2\n" + + " \n" + + ""; // When parsing the config - TrustKitConfiguration config = TrustKitConfiguration.fromXmlPolicy( - context, parseXmlString(xml) - ); + TrustKitConfiguration config = + TrustKitConfiguration.fromXmlPolicy(context, parseXmlString(xml)); // It succeeds and no domains have any pinning config DomainPinningPolicy datathDomainConfig = config.getPolicyForHostname("www.datatheorem.com"); diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/pinning/SSLSocketFactoryTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/pinning/SSLSocketFactoryTest.java index fe215b6..b71b67e 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/pinning/SSLSocketFactoryTest.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/pinning/SSLSocketFactoryTest.java @@ -1,10 +1,16 @@ package com.datatheorem.android.trustkit.pinning; +import static junit.framework.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + import android.content.Context; import android.os.Build; - import androidx.test.platform.app.InstrumentationRegistry; - import com.datatheorem.android.trustkit.CertificateUtils; import com.datatheorem.android.trustkit.TestableTrustKit; import com.datatheorem.android.trustkit.config.DomainPinningPolicy; @@ -12,13 +18,6 @@ import com.google.android.gms.common.GooglePlayServicesNotAvailableException; import com.google.android.gms.common.GooglePlayServicesRepairableException; import com.google.android.gms.security.ProviderInstaller; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - import java.io.IOException; import java.net.Socket; import java.security.cert.Certificate; @@ -26,77 +25,71 @@ import java.security.cert.X509Certificate; import java.util.HashSet; import java.util.List; - import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSocketFactory; - -import static junit.framework.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; /** * Tests TrustKit's SSLSocketFactory. * - * The general testing strategy used here is to connect to live websites. This provides a variety of - * valid certificate chains that can then have different pins applied to each. This requires no + *

The general testing strategy used here is to connect to live websites. This provides a variety + * of valid certificate chains that can then have different pins applied to each. This requires no * special mock servers or mock CA setup, but it is dependent on the domains being live and having * valid certificate chains. */ @SuppressWarnings("unchecked") public class SSLSocketFactoryTest { - @Mock - private BackgroundReporter mockReporter; + @Mock private BackgroundReporter mockReporter; // The root CA for cacert.org; useful to test connections with a custom CA private final String caCertDotOrgRootPem = - "MIIHbDCCBVSgAwIBAgIDAsGhMA0GCSqGSIb3DQEBDQUAMFQxFDASBgNVBAoTC0NB\n" + - "Y2VydCBJbmMuMR4wHAYDVQQLExVodHRwOi8vd3d3LkNBY2VydC5vcmcxHDAaBgNV\n" + - "BAMTE0NBY2VydCBDbGFzcyAzIFJvb3QwHhcNMTgwNDA1MTk0MjQxWhcNMjAwNDA0\n" + - "MTk0MjQxWjBbMQswCQYDVQQGEwJBVTEMMAoGA1UECBMDTlNXMQ8wDQYDVQQHEwZT\n" + - "eWRuZXkxFDASBgNVBAoTC0NBY2VydCBJbmMuMRcwFQYDVQQDEw53d3cuY2FjZXJ0\n" + - "Lm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANwriThHegmvvYFB\n" + - "2X281mJ5d+F2AEEZwaBSSSWoq75BYRJ5l5ke8QHGcx3c8CZDPlPjopyYCIy8LRhA\n" + - "75IfVhRnR5imikVG4Gsvp57vAzwrxBtiAh8IqZKSlok30IaZ062G7uPNXaxwNZGY\n" + - "c4CcAD2MRmTAxBbVan+wa+h/NTwTa/OfZwjaVdU4mDFJpegGl6tqm10+AdZW7bvP\n" + - "Hbg5GPnn8WON0UzR5avrGDkU8013ruFH/Y0G/FlqnAsFAkf20rFYDLRLXzb29Olh\n" + - "f6arkF+HOrsnanfyqjwyv5sgvZva3iXmEo0a7NhK2dGM1pO9Pd2AqkvjGARMI0ud\n" + - "WrQkDThvoGEV2BvgBqQpF8WYBhlxMr7ToG4y2Dxc+wXgXSy6zPIgZqVwq9OZ4qit\n" + - "TeXIiwWQp6nAYlJcPWuDNX2EoTi0FUKn2xCzbDr+i2ZtfZ6NYytxUq+ZwSOZ/o18\n" + - "AXnMk82YO95WUFzFbTXrYKF6Sae8caHO92ptjl2tVxLPPRzsIDBMEh2/97fp1jxO\n" + - "RjgwWMnBISwznbgIlG9/lY7/DaPHCYlAnIfsqvAasH3SRm5XedmGW4kyOD7D1Cpo\n" + - "6vTSk4gs3MyaNvGt9wYATuunqwRjJVX83L/JfrDfxZ8CCb1s+JyYgTPMpbtyvZbN\n" + - "1DHYLVfpFL5Nwtx3sZzuMteflQ7NAgMBAAGjggI+MIICOjAMBgNVHRMBAf8EAjAA\n" + - "MA4GA1UdDwEB/wQEAwIDqDA0BgNVHSUELTArBggrBgEFBQcDAgYIKwYBBQUHAwEG\n" + - "CWCGSAGG+EIEAQYKKwYBBAGCNwoDAzAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUH\n" + - "MAGGF2h0dHA6Ly9vY3NwLmNhY2VydC5vcmcvMDgGA1UdHwQxMC8wLaAroCmGJ2h0\n" + - "dHA6Ly9jcmwuY2FjZXJ0Lm9yZy9jbGFzczMtcmV2b2tlLmNybDCCAXMGA1UdEQSC\n" + - "AWowggFmgg53d3cuY2FjZXJ0Lm9yZ6AcBggrBgEFBQcIBaAQDA53d3cuY2FjZXJ0\n" + - "Lm9yZ4IRc2VjdXJlLmNhY2VydC5vcmegHwYIKwYBBQUHCAWgEwwRc2VjdXJlLmNh\n" + - "Y2VydC5vcmeCEnd3d21haWwuY2FjZXJ0Lm9yZ6AgBggrBgEFBQcIBaAUDBJ3d3dt\n" + - "YWlsLmNhY2VydC5vcmeCCmNhY2VydC5vcmegGAYIKwYBBQUHCAWgDAwKY2FjZXJ0\n" + - "Lm9yZ4IOd3d3LmNhY2VydC5uZXSgHAYIKwYBBQUHCAWgEAwOd3d3LmNhY2VydC5u\n" + - "ZXSCCmNhY2VydC5uZXSgGAYIKwYBBQUHCAWgDAwKY2FjZXJ0Lm5ldIIOd3d3LmNh\n" + - "Y2VydC5jb22gHAYIKwYBBQUHCAWgEAwOd3d3LmNhY2VydC5jb22CCmNhY2VydC5j\n" + - "b22gGAYIKwYBBQUHCAWgDAwKY2FjZXJ0LmNvbTANBgkqhkiG9w0BAQ0FAAOCAgEA\n" + - "pEFsiLHeLxNrP12BIG1QqZja9i1IrBCnWyVvlDmbUMdVHcscAQhWE5sTYkAD+1D7\n" + - "VAodoYXo23paZrDKgKoFgZMNLMQ4m93WlCLrInEfENjCxNaPWI5LmsajeZR/5T7C\n" + - "5nUqYklCY+3Bc6SBGHXIRDVnGw9AhWgI9f3hSpQhECyokbLwZ17aIGmznTeKx7lV\n" + - "DYwaBeyFjZ/AIqovRSkcPTMf1L8LT/SZXuc1urgETbBa+F4tSMGjdGJg2jayojs0\n" + - "kD2EFZVGdKYUzOH/rNoQmnTyDEudswp+nim7jgfugztl5KbKeowDFN9KpeineJUW\n" + - "lthzARWpWr2gkIH8mGmgvOsIngYGof1sJMJsxcgdrowTrSPW6W/lOWRc6nSGnjg0\n" + - "gnshQg3gDN902Kps0OBwbTCrbC4sYu3Xywk0QVxYtcDF2asnsERuSFaZuLWUf2WS\n" + - "JYRDGbMuyw6MY+Uoukbee9fJ5Yq77+N0ZeeHRXvRG+PIVyl5KbujznNo6pCGeb3d\n" + - "atDvi507zOiRJAWwHTXOEqpJ71ZjuV7XyRTvFe+qb70+t7FiohcZJZhE1hFZrIeV\n" + - "iZX2MyJJPaWx2fjx8u/FpaKo01OYNrVOCcnhXzd5jUs+99zxrUVh1CEgC8KIN2zF\n" + - "VgnYWDmu4r7D5JbJwxqccicVF9oUa+4HGHvcHdAwfRg="; - private final Certificate caCertDotOrgRoot - = CertificateUtils.certificateFromPem(caCertDotOrgRootPem); + "MIIHbDCCBVSgAwIBAgIDAsGhMA0GCSqGSIb3DQEBDQUAMFQxFDASBgNVBAoTC0NB\n" + + "Y2VydCBJbmMuMR4wHAYDVQQLExVodHRwOi8vd3d3LkNBY2VydC5vcmcxHDAaBgNV\n" + + "BAMTE0NBY2VydCBDbGFzcyAzIFJvb3QwHhcNMTgwNDA1MTk0MjQxWhcNMjAwNDA0\n" + + "MTk0MjQxWjBbMQswCQYDVQQGEwJBVTEMMAoGA1UECBMDTlNXMQ8wDQYDVQQHEwZT\n" + + "eWRuZXkxFDASBgNVBAoTC0NBY2VydCBJbmMuMRcwFQYDVQQDEw53d3cuY2FjZXJ0\n" + + "Lm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANwriThHegmvvYFB\n" + + "2X281mJ5d+F2AEEZwaBSSSWoq75BYRJ5l5ke8QHGcx3c8CZDPlPjopyYCIy8LRhA\n" + + "75IfVhRnR5imikVG4Gsvp57vAzwrxBtiAh8IqZKSlok30IaZ062G7uPNXaxwNZGY\n" + + "c4CcAD2MRmTAxBbVan+wa+h/NTwTa/OfZwjaVdU4mDFJpegGl6tqm10+AdZW7bvP\n" + + "Hbg5GPnn8WON0UzR5avrGDkU8013ruFH/Y0G/FlqnAsFAkf20rFYDLRLXzb29Olh\n" + + "f6arkF+HOrsnanfyqjwyv5sgvZva3iXmEo0a7NhK2dGM1pO9Pd2AqkvjGARMI0ud\n" + + "WrQkDThvoGEV2BvgBqQpF8WYBhlxMr7ToG4y2Dxc+wXgXSy6zPIgZqVwq9OZ4qit\n" + + "TeXIiwWQp6nAYlJcPWuDNX2EoTi0FUKn2xCzbDr+i2ZtfZ6NYytxUq+ZwSOZ/o18\n" + + "AXnMk82YO95WUFzFbTXrYKF6Sae8caHO92ptjl2tVxLPPRzsIDBMEh2/97fp1jxO\n" + + "RjgwWMnBISwznbgIlG9/lY7/DaPHCYlAnIfsqvAasH3SRm5XedmGW4kyOD7D1Cpo\n" + + "6vTSk4gs3MyaNvGt9wYATuunqwRjJVX83L/JfrDfxZ8CCb1s+JyYgTPMpbtyvZbN\n" + + "1DHYLVfpFL5Nwtx3sZzuMteflQ7NAgMBAAGjggI+MIICOjAMBgNVHRMBAf8EAjAA\n" + + "MA4GA1UdDwEB/wQEAwIDqDA0BgNVHSUELTArBggrBgEFBQcDAgYIKwYBBQUHAwEG\n" + + "CWCGSAGG+EIEAQYKKwYBBAGCNwoDAzAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUH\n" + + "MAGGF2h0dHA6Ly9vY3NwLmNhY2VydC5vcmcvMDgGA1UdHwQxMC8wLaAroCmGJ2h0\n" + + "dHA6Ly9jcmwuY2FjZXJ0Lm9yZy9jbGFzczMtcmV2b2tlLmNybDCCAXMGA1UdEQSC\n" + + "AWowggFmgg53d3cuY2FjZXJ0Lm9yZ6AcBggrBgEFBQcIBaAQDA53d3cuY2FjZXJ0\n" + + "Lm9yZ4IRc2VjdXJlLmNhY2VydC5vcmegHwYIKwYBBQUHCAWgEwwRc2VjdXJlLmNh\n" + + "Y2VydC5vcmeCEnd3d21haWwuY2FjZXJ0Lm9yZ6AgBggrBgEFBQcIBaAUDBJ3d3dt\n" + + "YWlsLmNhY2VydC5vcmeCCmNhY2VydC5vcmegGAYIKwYBBQUHCAWgDAwKY2FjZXJ0\n" + + "Lm9yZ4IOd3d3LmNhY2VydC5uZXSgHAYIKwYBBQUHCAWgEAwOd3d3LmNhY2VydC5u\n" + + "ZXSCCmNhY2VydC5uZXSgGAYIKwYBBQUHCAWgDAwKY2FjZXJ0Lm5ldIIOd3d3LmNh\n" + + "Y2VydC5jb22gHAYIKwYBBQUHCAWgEAwOd3d3LmNhY2VydC5jb22CCmNhY2VydC5j\n" + + "b22gGAYIKwYBBQUHCAWgDAwKY2FjZXJ0LmNvbTANBgkqhkiG9w0BAQ0FAAOCAgEA\n" + + "pEFsiLHeLxNrP12BIG1QqZja9i1IrBCnWyVvlDmbUMdVHcscAQhWE5sTYkAD+1D7\n" + + "VAodoYXo23paZrDKgKoFgZMNLMQ4m93WlCLrInEfENjCxNaPWI5LmsajeZR/5T7C\n" + + "5nUqYklCY+3Bc6SBGHXIRDVnGw9AhWgI9f3hSpQhECyokbLwZ17aIGmznTeKx7lV\n" + + "DYwaBeyFjZ/AIqovRSkcPTMf1L8LT/SZXuc1urgETbBa+F4tSMGjdGJg2jayojs0\n" + + "kD2EFZVGdKYUzOH/rNoQmnTyDEudswp+nim7jgfugztl5KbKeowDFN9KpeineJUW\n" + + "lthzARWpWr2gkIH8mGmgvOsIngYGof1sJMJsxcgdrowTrSPW6W/lOWRc6nSGnjg0\n" + + "gnshQg3gDN902Kps0OBwbTCrbC4sYu3Xywk0QVxYtcDF2asnsERuSFaZuLWUf2WS\n" + + "JYRDGbMuyw6MY+Uoukbee9fJ5Yq77+N0ZeeHRXvRG+PIVyl5KbujznNo6pCGeb3d\n" + + "atDvi507zOiRJAWwHTXOEqpJ71ZjuV7XyRTvFe+qb70+t7FiohcZJZhE1hFZrIeV\n" + + "iZX2MyJJPaWx2fjx8u/FpaKo01OYNrVOCcnhXzd5jUs+99zxrUVh1CEgC8KIN2zF\n" + + "VgnYWDmu4r7D5JbJwxqccicVF9oUa+4HGHvcHdAwfRg="; + private final Certificate caCertDotOrgRoot = + CertificateUtils.certificateFromPem(caCertDotOrgRootPem); @BeforeClass public static void runOnceBeforeClass() { @@ -104,7 +97,8 @@ public static void runOnceBeforeClass() { // in the SSLSocketFactory; otherwise some tests fail because the server requires TLS 1.2 if (Build.VERSION.SDK_INT < 20) { try { - ProviderInstaller.installIfNeeded(InstrumentationRegistry.getInstrumentation().getContext()); + ProviderInstaller.installIfNeeded( + InstrumentationRegistry.getInstrumentation().getContext()); } catch (GooglePlayServicesRepairableException e) { e.printStackTrace(); } catch (GooglePlayServicesNotAvailableException e) { @@ -119,7 +113,7 @@ public void setUp() { TestableTrustKit.reset(); } - //region Tests for when the domain is pinned + // region Tests for when the domain is pinned @Test public void testPinnedDomainExpiredChain() throws IOException { // Initialize TrustKit @@ -146,14 +140,17 @@ public void testPinnedDomainExpiredChain() throws IOException { } // Ensure the background reporter was called - verify(mockReporter).pinValidationFailed( - eq(serverHostname), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration().getPolicyForHostname(serverHostname)), - eq(PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED) - ); + verify(mockReporter) + .pinValidationFailed( + eq(serverHostname), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(serverHostname)), + eq(PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED)); } @Test @@ -182,14 +179,17 @@ public void testPinnedDomainWrongHostnameChain() throws IOException { } // Ensure the background reporter was called - verify(mockReporter).pinValidationFailed( - eq(serverHostname), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration().getPolicyForHostname(serverHostname)), - eq(PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED) - ); + verify(mockReporter) + .pinValidationFailed( + eq(serverHostname), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(serverHostname)), + eq(PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED)); } @Test @@ -207,14 +207,17 @@ public void testPinnedDomainSuccessAnchor() throws IOException { socket.close(); // Ensure the background reporter was NOT called - verify(mockReporter, never()).pinValidationFailed( - eq(serverHostname), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration().getPolicyForHostname(serverHostname)), - eq(PinningValidationResult.FAILED) - ); + verify(mockReporter, never()) + .pinValidationFailed( + eq(serverHostname), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(serverHostname)), + eq(PinningValidationResult.FAILED)); } @Test @@ -232,14 +235,17 @@ public void testPinnedDomainSuccessLeaf() throws IOException { socket.close(); // Ensure the background reporter was NOT called - verify(mockReporter, never()).pinValidationFailed( - eq(serverHostname), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration().getPolicyForHostname(serverHostname)), - eq(PinningValidationResult.FAILED) - ); + verify(mockReporter, never()) + .pinValidationFailed( + eq(serverHostname), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(serverHostname)), + eq(PinningValidationResult.FAILED)); } @Test @@ -267,14 +273,17 @@ public void testPinnedDomainInvalidPin() throws IOException { assertTrue(didReceivePinningError); // Ensure the background reporter was called - verify(mockReporter).pinValidationFailed( - eq(serverHostname), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration().getPolicyForHostname(serverHostname)), - eq(PinningValidationResult.FAILED) - ); + verify(mockReporter) + .pinValidationFailed( + eq(serverHostname), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(serverHostname)), + eq(PinningValidationResult.FAILED)); } @Test @@ -297,14 +306,17 @@ public void testPinnedDomainInvalidPinAndPinningNotEnforced() throws IOException } // Ensure the background reporter was called - verify(mockReporter).pinValidationFailed( - eq(serverHostname), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration().getPolicyForHostname(serverHostname)), - eq(PinningValidationResult.FAILED) - ); + verify(mockReporter) + .pinValidationFailed( + eq(serverHostname), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(serverHostname)), + eq(PinningValidationResult.FAILED)); } @Test @@ -322,14 +334,14 @@ public void testPinnedDomainInvalidPinAndPolicyExpired() throws IOException { socket.close(); // Ensure the background reporter was NOT called - verify(mockReporter, never()).pinValidationFailed( - anyString(), - anyInt(), - (List) any(), - (List) any(), - any(DomainPinningPolicy.class), - any(PinningValidationResult.class) - ); + verify(mockReporter, never()) + .pinValidationFailed( + anyString(), + anyInt(), + (List) any(), + (List) any(), + any(DomainPinningPolicy.class), + any(PinningValidationResult.class)); } @Test @@ -359,18 +371,22 @@ public void testPinnedDomainUntrustedChainAndPinningNotEnforced() throws IOExcep } // Ensure the background reporter was called - verify(mockReporter).pinValidationFailed( - eq(serverHostname), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration().getPolicyForHostname(serverHostname)), - eq(PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED) - ); + verify(mockReporter) + .pinValidationFailed( + eq(serverHostname), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(serverHostname)), + eq(PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED)); } @Test - public void testDebugOverridesInvalidPinButOverridePins() throws IOException, CertificateException { + public void testDebugOverridesInvalidPinButOverridePins() + throws IOException, CertificateException { if (Build.VERSION.SDK_INT >= 24) { // This test will not work when using the Android N XML network policy because we can't // dynamically remove the debug-overrides tag defined in the XML policy which adds the @@ -383,20 +399,34 @@ public void testDebugOverridesInvalidPinButOverridePins() throws IOException, Ce } String serverHostname = "www.cacert.org"; - final DomainPinningPolicy domainPolicy = new DomainPinningPolicy.Builder() - .setHostname(serverHostname) - .setShouldEnforcePinning(true) - .setPublicKeyHashes(new HashSet() {{ - // Wrong pins - add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); - add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); - }}).build(); + final DomainPinningPolicy domainPolicy = + new DomainPinningPolicy.Builder() + .setHostname(serverHostname) + .setShouldEnforcePinning(true) + .setPublicKeyHashes( + new HashSet() { + { + // Wrong pins + add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); + add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); + } + }) + .build(); // Create a configuration with debug overrides enabled to add the cacert.org CA and to set // overridePins to true - TestableTrustKit.init(new HashSet() {{ add(domainPolicy); }}, + TestableTrustKit.init( + new HashSet() { + { + add(domainPolicy); + } + }, true, - new HashSet(){{ add(caCertDotOrgRoot); }}, + new HashSet() { + { + add(caCertDotOrgRoot); + } + }, InstrumentationRegistry.getInstrumentation().getContext(), mockReporter); @@ -411,14 +441,14 @@ public void testDebugOverridesInvalidPinButOverridePins() throws IOException, Ce socket.close(); // Ensure the background reporter was NOT called - verify(mockReporter, never()).pinValidationFailed( - anyString(), - anyInt(), - (List) any(), - (List) any(), - any(DomainPinningPolicy.class), - any(PinningValidationResult.class) - ); + verify(mockReporter, never()) + .pinValidationFailed( + anyString(), + anyInt(), + (List) any(), + (List) any(), + any(DomainPinningPolicy.class), + any(PinningValidationResult.class)); } @Test @@ -435,23 +465,37 @@ public void testDebugOverridesButAppNotDebuggable() throws IOException, Certific } String serverHostname = "www.cacert.org"; - final DomainPinningPolicy domainPolicy = new DomainPinningPolicy.Builder() - .setHostname(serverHostname) - .setShouldEnforcePinning(true) - .setPublicKeyHashes(new HashSet() {{ - // Wrong pins - add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); - add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); - }}).build(); + final DomainPinningPolicy domainPolicy = + new DomainPinningPolicy.Builder() + .setHostname(serverHostname) + .setShouldEnforcePinning(true) + .setPublicKeyHashes( + new HashSet() { + { + // Wrong pins + add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); + add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); + } + }) + .build(); // Create a configuration with debug overrides enabled to add the cacert.org CA but // make the App's debuggable flag disabled to mock a production App Context mockContext = InstrumentationRegistry.getInstrumentation().getContext(); int originalAppFlags = mockContext.getApplicationInfo().flags; mockContext.getApplicationInfo().flags = 0; - TestableTrustKit.init(new HashSet() {{ add(domainPolicy); }}, + TestableTrustKit.init( + new HashSet() { + { + add(domainPolicy); + } + }, true, - new HashSet(){{ add(caCertDotOrgRoot); }}, + new HashSet() { + { + add(caCertDotOrgRoot); + } + }, mockContext, mockReporter); mockContext.getApplicationInfo().flags = originalAppFlags; @@ -468,14 +512,17 @@ public void testDebugOverridesButAppNotDebuggable() throws IOException, Certific assertTrue(didReceiveHandshakeError); // Ensure the background reporter was called - verify(mockReporter).pinValidationFailed( - eq(serverHostname), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration().getPolicyForHostname(serverHostname)), - eq(PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED) - ); + verify(mockReporter) + .pinValidationFailed( + eq(serverHostname), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(serverHostname)), + eq(PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED)); } @Test @@ -491,20 +538,34 @@ public void testDebugOverridesInvalidPin() throws IOException, CertificateExcept } String serverHostname = "www.cacert.org"; - final DomainPinningPolicy domainPolicy = new DomainPinningPolicy.Builder() - .setHostname(serverHostname) - .setShouldEnforcePinning(true) - .setPublicKeyHashes(new HashSet() {{ - // Wrong pins - add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); - add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); - }}).build(); + final DomainPinningPolicy domainPolicy = + new DomainPinningPolicy.Builder() + .setHostname(serverHostname) + .setShouldEnforcePinning(true) + .setPublicKeyHashes( + new HashSet() { + { + // Wrong pins + add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); + add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); + } + }) + .build(); // Create a configuration with debug overrides enabled to add the cacert.org CA and to set // overridePins to false, making the connection fail - TestableTrustKit.init(new HashSet() {{ add(domainPolicy); }}, + TestableTrustKit.init( + new HashSet() { + { + add(domainPolicy); + } + }, false, - new HashSet(){{ add(caCertDotOrgRoot); }}, + new HashSet() { + { + add(caCertDotOrgRoot); + } + }, InstrumentationRegistry.getInstrumentation().getContext(), mockReporter); @@ -524,31 +585,44 @@ public void testDebugOverridesInvalidPin() throws IOException, CertificateExcept assertTrue(didReceivePinningError); // Ensure the background reporter was called - verify(mockReporter).pinValidationFailed( - eq(serverHostname), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration().getPolicyForHostname(serverHostname)), - eq(PinningValidationResult.FAILED) - ); + verify(mockReporter) + .pinValidationFailed( + eq(serverHostname), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(serverHostname)), + eq(PinningValidationResult.FAILED)); } - //endregion + // endregion - //region Tests for when the domain is NOT pinned + // region Tests for when the domain is NOT pinned @Test public void testNonPinnedDomainUntrustedRootChain() throws IOException { String serverHostname = "www.cacert.org"; - final DomainPinningPolicy domainPolicy = new DomainPinningPolicy.Builder() - .setHostname("other.domain.com") - .setShouldEnforcePinning(true) - .setPublicKeyHashes(new HashSet() {{ - // Wrong pins - add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); - add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); - }}).build(); - - TestableTrustKit.init(new HashSet() {{ add(domainPolicy); }}, + final DomainPinningPolicy domainPolicy = + new DomainPinningPolicy.Builder() + .setHostname("other.domain.com") + .setShouldEnforcePinning(true) + .setPublicKeyHashes( + new HashSet() { + { + // Wrong pins + add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); + add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); + } + }) + .build(); + + TestableTrustKit.init( + new HashSet() { + { + add(domainPolicy); + } + }, InstrumentationRegistry.getInstrumentation().getContext(), mockReporter); @@ -567,14 +641,17 @@ public void testNonPinnedDomainUntrustedRootChain() throws IOException { assertTrue(didReceiveHandshakeError); // Ensure the background reporter was NOT called as we only want reports for pinned domains - verify(mockReporter, never()).pinValidationFailed( - eq(serverHostname), - eq(0), - (List) org.mockito.Matchers.isNotNull(), - (List) org.mockito.Matchers.isNotNull(), - eq(TestableTrustKit.getInstance().getConfiguration().getPolicyForHostname(serverHostname)), - eq(PinningValidationResult.FAILED) - ); + verify(mockReporter, never()) + .pinValidationFailed( + eq(serverHostname), + eq(0), + (List) org.mockito.Matchers.isNotNull(), + (List) org.mockito.Matchers.isNotNull(), + eq( + TestableTrustKit.getInstance() + .getConfiguration() + .getPolicyForHostname(serverHostname)), + eq(PinningValidationResult.FAILED)); } @Test @@ -593,14 +670,14 @@ public void testNonPinnedDomainSuccess() throws IOException { socket.close(); // Ensure the background reporter was NOT called - verify(mockReporter, never()).pinValidationFailed( - anyString(), - anyInt(), - (List) any(), - (List) any(), - any(DomainPinningPolicy.class), - any(PinningValidationResult.class) - ); + verify(mockReporter, never()) + .pinValidationFailed( + anyString(), + anyInt(), + (List) any(), + (List) any(), + any(DomainPinningPolicy.class), + any(PinningValidationResult.class)); } @Test @@ -618,19 +695,33 @@ public void testDebugOverrides() throws IOException, CertificateException { String serverHostname = "www.cacert.org"; // Create a policy for a different domain - final DomainPinningPolicy domainPolicy = new DomainPinningPolicy.Builder() - .setHostname("other.domain.com") - .setShouldEnforcePinning(true) - .setPublicKeyHashes(new HashSet() {{ - // Wrong pins - add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); - add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); - }}).build(); + final DomainPinningPolicy domainPolicy = + new DomainPinningPolicy.Builder() + .setHostname("other.domain.com") + .setShouldEnforcePinning(true) + .setPublicKeyHashes( + new HashSet() { + { + // Wrong pins + add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); + add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); + } + }) + .build(); // Create a configuration with debug overrides enabled to add the cacert.org CA - TestableTrustKit.init(new HashSet() {{ add(domainPolicy); }}, + TestableTrustKit.init( + new HashSet() { + { + add(domainPolicy); + } + }, false, - new HashSet(){{ add(caCertDotOrgRoot); }}, + new HashSet() { + { + add(caCertDotOrgRoot); + } + }, InstrumentationRegistry.getInstrumentation().getContext(), mockReporter); @@ -644,14 +735,14 @@ public void testDebugOverrides() throws IOException, CertificateException { socket.close(); // Ensure the background reporter was NOT called - verify(mockReporter, never()).pinValidationFailed( - anyString(), - anyInt(), - (List) any(), - (List) any(), - any(DomainPinningPolicy.class), - any(PinningValidationResult.class) - ); + verify(mockReporter, never()) + .pinValidationFailed( + anyString(), + anyInt(), + (List) any(), + (List) any(), + any(DomainPinningPolicy.class), + any(PinningValidationResult.class)); } @Test @@ -665,19 +756,33 @@ public void testDebugOverridesSystemCa() throws IOException, CertificateExceptio String serverHostname = "www.google.com"; // Create a policy for a different domain - final DomainPinningPolicy domainPolicy = new DomainPinningPolicy.Builder() - .setHostname("other.domain.com") - .setShouldEnforcePinning(true) - .setPublicKeyHashes(new HashSet() {{ - // Wrong pins - add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); - add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); - }}).build(); + final DomainPinningPolicy domainPolicy = + new DomainPinningPolicy.Builder() + .setHostname("other.domain.com") + .setShouldEnforcePinning(true) + .setPublicKeyHashes( + new HashSet() { + { + // Wrong pins + add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); + add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); + } + }) + .build(); // Create a configuration with debug overrides enabled to add the cacert.org CA - TestableTrustKit.init(new HashSet() {{ add(domainPolicy); }}, + TestableTrustKit.init( + new HashSet() { + { + add(domainPolicy); + } + }, false, - new HashSet(){{ add(caCertDotOrgRoot); }}, + new HashSet() { + { + add(caCertDotOrgRoot); + } + }, InstrumentationRegistry.getInstrumentation().getContext(), mockReporter); @@ -691,14 +796,14 @@ public void testDebugOverridesSystemCa() throws IOException, CertificateExceptio socket.close(); // Ensure the background reporter was NOT called - verify(mockReporter, never()).pinValidationFailed( - anyString(), - anyInt(), - (List) any(), - (List) any(), - any(DomainPinningPolicy.class), - any(PinningValidationResult.class) - ); + verify(mockReporter, never()) + .pinValidationFailed( + anyString(), + anyInt(), + (List) any(), + (List) any(), + any(DomainPinningPolicy.class), + any(PinningValidationResult.class)); } - //endregion + // endregion } diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/pinning/TestableTrustManagerBuilder.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/pinning/TestableTrustManagerBuilder.java index 3f6f679..abea41e 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/pinning/TestableTrustManagerBuilder.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/pinning/TestableTrustManagerBuilder.java @@ -1,6 +1,5 @@ package com.datatheorem.android.trustkit.pinning; - import com.datatheorem.android.trustkit.reporting.BackgroundReporter; public class TestableTrustManagerBuilder extends TrustManagerBuilder { diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTaskTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTaskTest.java index 570cdfc..8c01c99 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTaskTest.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTaskTest.java @@ -1,39 +1,48 @@ package com.datatheorem.android.trustkit.reporting; -import android.os.Build; +import static com.datatheorem.android.trustkit.CertificateUtils.testCertChainPem; +import static junit.framework.Assert.assertEquals; +import android.os.Build; import androidx.test.platform.app.InstrumentationRegistry; - import com.datatheorem.android.trustkit.TestableTrustKit; import com.datatheorem.android.trustkit.config.PublicKeyPin; import com.datatheorem.android.trustkit.pinning.PinningValidationResult; import com.datatheorem.android.trustkit.utils.VendorIdentifier; - -import org.junit.Before; -import org.junit.Test; - import java.net.MalformedURLException; import java.net.URL; import java.sql.Date; import java.util.ArrayList; import java.util.HashSet; - -import static com.datatheorem.android.trustkit.CertificateUtils.testCertChainPem; -import static junit.framework.Assert.assertEquals; - +import org.junit.Before; +import org.junit.Test; public class BackgroundReporterTaskTest { - private final HashSet knownPins = new HashSet() {{ - add(new PublicKeyPin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")); - add(new PublicKeyPin("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")); - }}; - - private final PinningFailureReport report = new PinningFailureReport("com.unit.test", "1.2", - VendorIdentifier.getOrCreate(InstrumentationRegistry.getInstrumentation().getContext()), - "www.datatheorem.com", 0, "datatheorem.com", true, true, - testCertChainPem, testCertChainPem, new Date(System.currentTimeMillis()), knownPins, - PinningValidationResult.FAILED); + private final HashSet knownPins = + new HashSet() { + { + add(new PublicKeyPin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")); + add(new PublicKeyPin("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")); + } + }; + + private final PinningFailureReport report = + new PinningFailureReport( + "com.unit.test", + "1.2", + VendorIdentifier.getOrCreate( + InstrumentationRegistry.getInstrumentation().getContext()), + "www.datatheorem.com", + 0, + "datatheorem.com", + true, + true, + testCertChainPem, + testCertChainPem, + new Date(System.currentTimeMillis()), + knownPins, + PinningValidationResult.FAILED); @Before public void setUp() { @@ -129,5 +138,3 @@ public void testExecuteFailedNoConnection() throws MalformedURLException { assertEquals(null, lastResponseCode); } } - - diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTest.java index cc0d79d..efe9ca6 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTest.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTest.java @@ -1,47 +1,41 @@ package com.datatheorem.android.trustkit.reporting; +import static com.datatheorem.android.trustkit.CertificateUtils.testCertChain; +import static com.datatheorem.android.trustkit.CertificateUtils.testCertChainPem; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Build; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; import androidx.localbroadcastmanager.content.LocalBroadcastManager; - +import androidx.test.platform.app.InstrumentationRegistry; import com.datatheorem.android.trustkit.TestableTrustKit; import com.datatheorem.android.trustkit.config.DomainPinningPolicy; import com.datatheorem.android.trustkit.pinning.PinningValidationResult; import com.datatheorem.android.trustkit.utils.VendorIdentifier; - +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.awaitility.Awaitility; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import java.io.Serializable; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import static com.datatheorem.android.trustkit.CertificateUtils.testCertChain; -import static com.datatheorem.android.trustkit.CertificateUtils.testCertChainPem; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertTrue; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - - public class BackgroundReporterTest { @Before @@ -58,44 +52,69 @@ public void testPinValidationFailed() throws MalformedURLException, JSONExceptio Context context = InstrumentationRegistry.getInstrumentation().getContext(); // Initialize TrustKit String serverHostname = "mail.google.com"; - final DomainPinningPolicy domainPolicy = new DomainPinningPolicy.Builder() - .setHostname("google.com") - .setShouldIncludeSubdomains(true) - .setShouldEnforcePinning(true) - .setPublicKeyHashes(new HashSet() {{ - // Wrong pins - add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); - add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); - }}) - .setShouldDisableDefaultReportUri(true) - .setReportUris(new HashSet() {{ add("https://overmind.datatheorem.com"); }}) - .build(); - - final PinningValidationReportTestBroadcastReceiver receiver = new PinningValidationReportTestBroadcastReceiver(); + final DomainPinningPolicy domainPolicy = + new DomainPinningPolicy.Builder() + .setHostname("google.com") + .setShouldIncludeSubdomains(true) + .setShouldEnforcePinning(true) + .setPublicKeyHashes( + new HashSet() { + { + // Wrong pins + add("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); + add("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="); + } + }) + .setShouldDisableDefaultReportUri(true) + .setReportUris( + new HashSet() { + { + add("https://overmind.datatheorem.com"); + } + }) + .build(); + + final PinningValidationReportTestBroadcastReceiver receiver = + new PinningValidationReportTestBroadcastReceiver(); LocalBroadcastManager.getInstance(context) - .registerReceiver(receiver, new IntentFilter(BackgroundReporter.REPORT_VALIDATION_EVENT)); + .registerReceiver( + receiver, new IntentFilter(BackgroundReporter.REPORT_VALIDATION_EVENT)); - TestableBackgroundReporter reporter = new TestableBackgroundReporter( context, - "com.unit.tests", - "1.2", - VendorIdentifier.getOrCreate(context)); + TestableBackgroundReporter reporter = + new TestableBackgroundReporter( + context, "com.unit.tests", "1.2", VendorIdentifier.getOrCreate(context)); TestableBackgroundReporter reporterSpy = Mockito.spy(reporter); // Call the method twice to also test the report rate limiter - reporterSpy.pinValidationFailed(serverHostname, 443, testCertChain, testCertChain, - domainPolicy, PinningValidationResult.FAILED); - reporterSpy.pinValidationFailed(serverHostname, 443, testCertChain, testCertChain, - domainPolicy, PinningValidationResult.FAILED); + reporterSpy.pinValidationFailed( + serverHostname, + 443, + testCertChain, + testCertChain, + domainPolicy, + PinningValidationResult.FAILED); + reporterSpy.pinValidationFailed( + serverHostname, + 443, + testCertChain, + testCertChain, + domainPolicy, + PinningValidationResult.FAILED); ArgumentCaptor reportSent = ArgumentCaptor.forClass(PinningFailureReport.class); // Ensure the sendReport() method was only called once, to make sure the rate limiter // blocked the second, identical report - verify(reporterSpy, times(1)).sendReport( - reportSent.capture(), - eq(new HashSet() {{ add(new URL("https://overmind.datatheorem.com")); }} ) - ); + verify(reporterSpy, times(1)) + .sendReport( + reportSent.capture(), + eq( + new HashSet() { + { + add(new URL("https://overmind.datatheorem.com")); + } + })); validateSentReport(reportSent.getValue()); @@ -116,7 +135,8 @@ private void validateSentReport(PinningFailureReport reportSent) throws JSONExce assertEquals(443, reportSentJson.getInt("port")); assertTrue(reportSentJson.getBoolean("include-subdomains")); assertTrue(reportSentJson.getBoolean("enforce-pinning")); - assertEquals(PinningValidationResult.FAILED.ordinal(), + assertEquals( + PinningValidationResult.FAILED.ordinal(), reportSentJson.getInt("validation-result")); assertEquals("google.com", reportSentJson.getString("noted-hostname")); @@ -126,16 +146,20 @@ private void validateSentReport(PinningFailureReport reportSent) throws JSONExce JSONArray validatedChain = reportSentJson.getJSONArray("validated-certificate-chain"); assertEquals(2, validatedChain.length()); - assertEquals(testCertChainPem.get(0).replace("\n", ""), + assertEquals( + testCertChainPem.get(0).replace("\n", ""), validatedChain.getString(0).replace("\n", "")); - assertEquals(testCertChainPem.get(1).replace("\n", ""), + assertEquals( + testCertChainPem.get(1).replace("\n", ""), validatedChain.getString(1).replace("\n", "")); JSONArray servedChain = reportSentJson.getJSONArray("served-certificate-chain"); assertEquals(2, servedChain.length()); - assertEquals(testCertChainPem.get(0).replace("\n", ""), + assertEquals( + testCertChainPem.get(0).replace("\n", ""), servedChain.getString(0).replace("\n", "")); - assertEquals(testCertChainPem.get(1).replace("\n", ""), + assertEquals( + testCertChainPem.get(1).replace("\n", ""), servedChain.getString(1).replace("\n", "")); JSONArray knownPins = reportSentJson.getJSONArray("known-pins"); @@ -144,13 +168,15 @@ private void validateSentReport(PinningFailureReport reportSent) throws JSONExce pinsTestable.add(knownPins.getString(i)); } assertEquals(2, knownPins.length()); - assertTrue(pinsTestable - .contains("pin-sha256=\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"")); - assertTrue(pinsTestable - .contains("pin-sha256=\"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\"")); + assertTrue( + pinsTestable.contains( + "pin-sha256=\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"")); + assertTrue( + pinsTestable.contains( + "pin-sha256=\"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\"")); } - private class PinningValidationReportTestBroadcastReceiver extends BroadcastReceiver{ + private class PinningValidationReportTestBroadcastReceiver extends BroadcastReceiver { public AtomicBoolean broadcastReceived = new AtomicBoolean(false); public Serializable containedReport; @@ -161,5 +187,3 @@ public void onReceive(Context context, Intent intent) { } } } - - diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/ReportRateLimiterTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/ReportRateLimiterTest.java index 09ea3e6..f97e572 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/ReportRateLimiterTest.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/ReportRateLimiterTest.java @@ -1,86 +1,103 @@ package com.datatheorem.android.trustkit.reporting; - import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import androidx.test.runner.AndroidJUnit4; import com.datatheorem.android.trustkit.config.PublicKeyPin; import com.datatheorem.android.trustkit.pinning.PinningValidationResult; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import org.junit.Test; -import org.junit.runner.RunWith; - public class ReportRateLimiterTest { - private final HashSet pinList = new HashSet() {{ - add(new PublicKeyPin("rFjc3wG7lTZe43zeYTvPq8k4xdDEutCmIhI5dn4oCeE=")); - add(new PublicKeyPin("0SDf3cRToyZJaMsoS17oF72VMavLxj/N7WBNasNuiR8=")); - }}; - - private final ArrayList pemCertificateList1 = new ArrayList() {{ - add("-----BEGIN CERTIFICATE-----\n"+ - "MIIDGTCCAgGgAwIBAgIJAI1jD1qixIPLMA0GCSqGSIb3DQEBBQUAMCMxITAfBgNV\n"+ - "BAMMGGV2aWxjZXJ0LmRhdGF0aGVvcmVtLmNvbTAeFw0xNTEyMjAxMzU4NDNaFw0y\n"+ - "NTEyMTcxMzU4NDNaMCMxITAfBgNVBAMMGGV2aWxjZXJ0LmRhdGF0aGVvcmVtLmNv\n"+ - "bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMdltqsRJtO7Nqypkehh\n"+ - "4DSEirp9RM+hJXkBE9nRleTO+utV/snWqX/0wsUrz0wgWyPnAHybGOOXvkrWfXSt\n"+ - "c2/8PyONOeFEU/9S/lWBXGZkaPhgTvkEzPmOOhf06rBMTwXUMGNDI45gKFgkO6Br\n"+ - "bGPeSCuheQj0TKeWdwwNoJ+kczUE06IKu2tcuFRjHXci6VeHjANJzrfKro4ivIRy\n"+ - "bewOGJj1onnpKbui/EOytsmW9MPpOSEXMoVksHOKBQ9nhpL6cDODRvG+t8u7qfFt\n"+ - "mhphemK3IYNMNA4MMXpbJ+Au2hnPApZPEOit34bAwOiGi/batcS3iA+nl06dPYA9\n"+ - "nPkCAwEAAaNQME4wHQYDVR0OBBYEFANxdSXS1JSvjdNtNbYBbRlgii93MB8GA1Ud\n"+ - "IwQYMBaAFANxdSXS1JSvjdNtNbYBbRlgii93MAwGA1UdEwQFMAMBAf8wDQYJKoZI\n"+ - "hvcNAQEFBQADggEBAAM78Bt2aLUgl2Yq4KMIGDeHdWYcRB7QPQ8sp3Q1TOQQzw0i\n"+ - "AukRccl9iYNLgaSJDvlVMapD76jo3okydoWgDogWJhtZpMU/9xegIpukmu5hvF6i\n"+ - "NpqE99PFO5E8BpMkNz+2nskwu//D0as6P9F3tA/o3jC6n6fWX0gt/e9th2ZgVwNQ\n"+ - "9JTH1ZcyFbX9hdBI4xPAtzFX51AsSa8dpRdG+8DmI41Q/1ludoMZboExHldlUbQH\n"+ - "zUuHKF8/T+aNo/9FfpqDz1fFnuoF7tuwyRh73B0YDyDVTNuq7LJ4tmzpVvqIt2tn\n"+ - "RJnQoL4pLQ40SQsoUi4FYG/gxJMoQX6ROWe2nyg=\n"+ - "-----END CERTIFICATE-----"); - }}; - - private final ArrayList pemCertificateList2 = new ArrayList() {{ - add("-----BEGIN CERTIFICATE-----\n" + - "MIIE2TCCA8GgAwIBAgIQFVDTs9tHXX3ivhstjNW2zANBgkqhkiG9w0BAQUFADA8\n" + - "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMuMRYwFAYDVQQDEw1U\n" + - "aGF3dGUgU1NMIENBMB4XDTE0MTAwMjAwMDAwMFoXDTE1MTEwMTIzNTk1OVowgZcx\n" + - "CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQHFAlQYWxv\n" + - "IEFsdG8xGzAZBgNVBAoUEkRhdGEgVGhlb3JlbSwgSW5jLjEkMCIGA1UECxQbU2Nh\n" + - "biBhbmQgU2VjdXJlIE1vYmlsZSBBcHBzMRwwGgYDVQQDFBN3d3cuZGF0YXRoZW9y\n" + - "ZW0uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5bCuLK3XOnNs\n" + - "i8CJvHU4H5yY3d4G1qzq7EeMydKuScMM8Nqsp4CySKTbrUhi/uIc08II9yBxM+q4\n" + - "NmrEg0tgVvTqvUjmMN/MrYQrSGVLxPq5gadI7UxfWeGSo9DpvgXaw1Vvehs2jGFK\n" + - "jLzDYbzJOhv/pqpv4UCV/xfeuqmTNqqzsp+tB5Zn6gXIvIFsxfpjbeId4OWviLnC\n" + - "q957++coddvqBZd2sWkyzE2un5itXRKfnMGSBTB0cU9/9fXeGhzA+u01Xj+BfpHR\n" + - "uP/eX+rHsgc3a4hbsSWDG5278ujJ5+4To9Bn/rTZy7uALTM2oBZvsFX4567RhB1\n" + - "IYbMDE5y8QIDAQABo4IBeTCCAXUwHgYDVR0RBBcwFYITd3d3LmRhdGF0aGVvcmVt\n" + - "LmNvbTAJBgNVHRMEAjAAMHIGA1UdIARrMGkwZwYKYIZIAYb4RQEHNjBZMCYGCCsG\n" + - "AQUFBwIBFhpodHRwczovL3d3dy50aGF3dGUuY29tL2NwczAvBggrBgEFBQcCAjAj\n" + - "DCFodHRwczovL3d3dy50aGF3dGUuY29tL3JlcG9zaXRvcnkwDgYDVR0PAQH/BAQD\n" + - "AgWgMB8GA1UdIwQYMBaAFKeig7s0RUA9/NUwTxK5PqEBn/bbMCsGA1UdHwQkMCIw\n" + - "IKAeoByGGmh0dHA6Ly90Yi5zeW1jYi5jb20vdGIuY3JsMB0GA1UdJQQWMBQGCCsG\n" + - "AQUFBwMBBggrBgEFBQcDAjBXBggrBgEFBQcBAQRLMEkwHwYIKwYBBQUHMAGGE2h0\n" + - "dHA6Ly90Yi5zeW1jZC5jb20wJgYIKwYBBQUHMAKGGmh0dHA6Ly90Yi5zeW1jYi5j\n" + - "b20vdGIuY3J0MA0GCSqGSIb3DQEBBQUAA4IBAQB2qnnrsAICkV9HNuBdXe+cThHV\n" + - "8+5+LBz3zGDpC1rCyq/DIGu0vaa/gasM+MswPj+AEI4f1K1x9K9KedjilVfXH+QI\n" + - "tfRzLO8iR0TbPsC6Y1avuXhal1BuvZ9UQayHRDPUEncsf+SHbIOD2GJzXy7vVk5a\n" + - "VjkvxLtjMprWIi+P7Hbn2qj03qX9KM1DnNsB28jqg7r2rpXNUPUKsxekfrMTaJgg\n" + - "zTnCN/EQvF5eGvAjjHckr1SlogV9o/y4k0x6YmPWR/vopMEPyOj+JhflKCdg+6w3\n" + - "79ESvZUhmgT2285c1Nu5vJjtr8x51zCNIpEoVqdkCU4c1aVZGZogSWl1rAIi\n" + - "-----END CERTIFICATE-----"); - }}; - + private final HashSet pinList = + new HashSet() { + { + add(new PublicKeyPin("rFjc3wG7lTZe43zeYTvPq8k4xdDEutCmIhI5dn4oCeE=")); + add(new PublicKeyPin("0SDf3cRToyZJaMsoS17oF72VMavLxj/N7WBNasNuiR8=")); + } + }; + + private final ArrayList pemCertificateList1 = + new ArrayList() { + { + add( + "-----BEGIN CERTIFICATE-----\n" + + "MIIDGTCCAgGgAwIBAgIJAI1jD1qixIPLMA0GCSqGSIb3DQEBBQUAMCMxITAfBgNV\n" + + "BAMMGGV2aWxjZXJ0LmRhdGF0aGVvcmVtLmNvbTAeFw0xNTEyMjAxMzU4NDNaFw0y\n" + + "NTEyMTcxMzU4NDNaMCMxITAfBgNVBAMMGGV2aWxjZXJ0LmRhdGF0aGVvcmVtLmNv\n" + + "bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMdltqsRJtO7Nqypkehh\n" + + "4DSEirp9RM+hJXkBE9nRleTO+utV/snWqX/0wsUrz0wgWyPnAHybGOOXvkrWfXSt\n" + + "c2/8PyONOeFEU/9S/lWBXGZkaPhgTvkEzPmOOhf06rBMTwXUMGNDI45gKFgkO6Br\n" + + "bGPeSCuheQj0TKeWdwwNoJ+kczUE06IKu2tcuFRjHXci6VeHjANJzrfKro4ivIRy\n" + + "bewOGJj1onnpKbui/EOytsmW9MPpOSEXMoVksHOKBQ9nhpL6cDODRvG+t8u7qfFt\n" + + "mhphemK3IYNMNA4MMXpbJ+Au2hnPApZPEOit34bAwOiGi/batcS3iA+nl06dPYA9\n" + + "nPkCAwEAAaNQME4wHQYDVR0OBBYEFANxdSXS1JSvjdNtNbYBbRlgii93MB8GA1Ud\n" + + "IwQYMBaAFANxdSXS1JSvjdNtNbYBbRlgii93MAwGA1UdEwQFMAMBAf8wDQYJKoZI\n" + + "hvcNAQEFBQADggEBAAM78Bt2aLUgl2Yq4KMIGDeHdWYcRB7QPQ8sp3Q1TOQQzw0i\n" + + "AukRccl9iYNLgaSJDvlVMapD76jo3okydoWgDogWJhtZpMU/9xegIpukmu5hvF6i\n" + + "NpqE99PFO5E8BpMkNz+2nskwu//D0as6P9F3tA/o3jC6n6fWX0gt/e9th2ZgVwNQ\n" + + "9JTH1ZcyFbX9hdBI4xPAtzFX51AsSa8dpRdG+8DmI41Q/1ludoMZboExHldlUbQH\n" + + "zUuHKF8/T+aNo/9FfpqDz1fFnuoF7tuwyRh73B0YDyDVTNuq7LJ4tmzpVvqIt2tn\n" + + "RJnQoL4pLQ40SQsoUi4FYG/gxJMoQX6ROWe2nyg=\n" + + "-----END CERTIFICATE-----"); + } + }; + + private final ArrayList pemCertificateList2 = + new ArrayList() { + { + add( + "-----BEGIN CERTIFICATE-----\n" + + "MIIE2TCCA8GgAwIBAgIQFVDTs9tHXX3ivhstjNW2zANBgkqhkiG9w0BAQUFADA8\n" + + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMuMRYwFAYDVQQDEw1U\n" + + "aGF3dGUgU1NMIENBMB4XDTE0MTAwMjAwMDAwMFoXDTE1MTEwMTIzNTk1OVowgZcx\n" + + "CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQHFAlQYWxv\n" + + "IEFsdG8xGzAZBgNVBAoUEkRhdGEgVGhlb3JlbSwgSW5jLjEkMCIGA1UECxQbU2Nh\n" + + "biBhbmQgU2VjdXJlIE1vYmlsZSBBcHBzMRwwGgYDVQQDFBN3d3cuZGF0YXRoZW9y\n" + + "ZW0uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5bCuLK3XOnNs\n" + + "i8CJvHU4H5yY3d4G1qzq7EeMydKuScMM8Nqsp4CySKTbrUhi/uIc08II9yBxM+q4\n" + + "NmrEg0tgVvTqvUjmMN/MrYQrSGVLxPq5gadI7UxfWeGSo9DpvgXaw1Vvehs2jGFK\n" + + "jLzDYbzJOhv/pqpv4UCV/xfeuqmTNqqzsp+tB5Zn6gXIvIFsxfpjbeId4OWviLnC\n" + + "q957++coddvqBZd2sWkyzE2un5itXRKfnMGSBTB0cU9/9fXeGhzA+u01Xj+BfpHR\n" + + "uP/eX+rHsgc3a4hbsSWDG5278ujJ5+4To9Bn/rTZy7uALTM2oBZvsFX4567RhB1\n" + + "IYbMDE5y8QIDAQABo4IBeTCCAXUwHgYDVR0RBBcwFYITd3d3LmRhdGF0aGVvcmVt\n" + + "LmNvbTAJBgNVHRMEAjAAMHIGA1UdIARrMGkwZwYKYIZIAYb4RQEHNjBZMCYGCCsG\n" + + "AQUFBwIBFhpodHRwczovL3d3dy50aGF3dGUuY29tL2NwczAvBggrBgEFBQcCAjAj\n" + + "DCFodHRwczovL3d3dy50aGF3dGUuY29tL3JlcG9zaXRvcnkwDgYDVR0PAQH/BAQD\n" + + "AgWgMB8GA1UdIwQYMBaAFKeig7s0RUA9/NUwTxK5PqEBn/bbMCsGA1UdHwQkMCIw\n" + + "IKAeoByGGmh0dHA6Ly90Yi5zeW1jYi5jb20vdGIuY3JsMB0GA1UdJQQWMBQGCCsG\n" + + "AQUFBwMBBggrBgEFBQcDAjBXBggrBgEFBQcBAQRLMEkwHwYIKwYBBQUHMAGGE2h0\n" + + "dHA6Ly90Yi5zeW1jZC5jb20wJgYIKwYBBQUHMAKGGmh0dHA6Ly90Yi5zeW1jYi5j\n" + + "b20vdGIuY3J0MA0GCSqGSIb3DQEBBQUAA4IBAQB2qnnrsAICkV9HNuBdXe+cThHV\n" + + "8+5+LBz3zGDpC1rCyq/DIGu0vaa/gasM+MswPj+AEI4f1K1x9K9KedjilVfXH+QI\n" + + "tfRzLO8iR0TbPsC6Y1avuXhal1BuvZ9UQayHRDPUEncsf+SHbIOD2GJzXy7vVk5a\n" + + "VjkvxLtjMprWIi+P7Hbn2qj03qX9KM1DnNsB28jqg7r2rpXNUPUKsxekfrMTaJgg\n" + + "zTnCN/EQvF5eGvAjjHckr1SlogV9o/y4k0x6YmPWR/vopMEPyOj+JhflKCdg+6w3\n" + + "79ESvZUhmgT2285c1Nu5vJjtr8x51zCNIpEoVqdkCU4c1aVZGZogSWl1rAIi\n" + + "-----END CERTIFICATE-----"); + } + }; @Test public void test() { - PinningFailureReport report = new PinningFailureReport("com.test", "1.2.3", "vendorId", - "www.host.com", 443, "host.com", true, true, - pemCertificateList1, pemCertificateList1, new Date(), - pinList, PinningValidationResult.FAILED); + PinningFailureReport report = + new PinningFailureReport( + "com.test", + "1.2.3", + "vendorId", + "www.host.com", + 443, + "host.com", + true, + true, + pemCertificateList1, + pemCertificateList1, + new Date(), + pinList, + PinningValidationResult.FAILED); // Ensure the same report will not be sent twice in a row assertFalse(ReportRateLimiter.shouldRateLimit(report)); @@ -88,34 +105,65 @@ pemCertificateList1, pemCertificateList1, new Date(), // Set the last time the cache was reset to more than 24 hours ago and ensure the report // is sent again - long oneDayAgo = System.currentTimeMillis()-25*60*60*1000; + long oneDayAgo = System.currentTimeMillis() - 25 * 60 * 60 * 1000; TestableReportRateLimiter.setLastReportsCacheResetDate(new Date(oneDayAgo)); assertFalse(ReportRateLimiter.shouldRateLimit(report)); assertTrue(ReportRateLimiter.shouldRateLimit(report)); - // Ensure the same report with a different validation result will be sent - report = new PinningFailureReport("com.test", "1.2.3", "vendorId", - "www.host.com", 443, "host.com", true, true, - pemCertificateList1, pemCertificateList1, new Date(), - pinList, PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED); + report = + new PinningFailureReport( + "com.test", + "1.2.3", + "vendorId", + "www.host.com", + 443, + "host.com", + true, + true, + pemCertificateList1, + pemCertificateList1, + new Date(), + pinList, + PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED); assertFalse(ReportRateLimiter.shouldRateLimit(report)); assertTrue(ReportRateLimiter.shouldRateLimit(report)); // Ensure the same report with a different hostname will be sent - report = new PinningFailureReport("com.test", "1.2.3", "vendorId", - "www.otherhost.com", 443, "host.com", true, true, - pemCertificateList1, pemCertificateList1, new Date(), - pinList, PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED); + report = + new PinningFailureReport( + "com.test", + "1.2.3", + "vendorId", + "www.otherhost.com", + 443, + "host.com", + true, + true, + pemCertificateList1, + pemCertificateList1, + new Date(), + pinList, + PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED); assertFalse(ReportRateLimiter.shouldRateLimit(report)); assertTrue(ReportRateLimiter.shouldRateLimit(report)); - // Ensure the same report with a different certificate chain will be sent - report = new PinningFailureReport("com.test", "1.2.3", "vendorId", - "www.otherhost.com", 443, "host.com", true, true, - pemCertificateList2, pemCertificateList2, new Date(), - pinList, PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED); + report = + new PinningFailureReport( + "com.test", + "1.2.3", + "vendorId", + "www.otherhost.com", + 443, + "host.com", + true, + true, + pemCertificateList2, + pemCertificateList2, + new Date(), + pinList, + PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED); assertFalse(ReportRateLimiter.shouldRateLimit(report)); assertTrue(ReportRateLimiter.shouldRateLimit(report)); } diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/TestableBackgroundReporter.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/TestableBackgroundReporter.java index 9127462..7594dc1 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/TestableBackgroundReporter.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/TestableBackgroundReporter.java @@ -1,18 +1,15 @@ package com.datatheorem.android.trustkit.reporting; - import android.content.Context; - import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; - import java.net.URL; import java.util.Set; - @RequiresApi(api = 16) public class TestableBackgroundReporter extends BackgroundReporter { - public TestableBackgroundReporter(Context context, String appPackageName, String appVersion, String appVendorId){ + public TestableBackgroundReporter( + Context context, String appPackageName, String appVersion, String appVendorId) { super(context, appPackageName, appVersion, appVendorId); } diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/TestableReportRateLimiter.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/TestableReportRateLimiter.java index 590dc5e..3ebad11 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/TestableReportRateLimiter.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/TestableReportRateLimiter.java @@ -1,6 +1,5 @@ package com.datatheorem.android.trustkit.reporting; - import java.util.Date; class TestableReportRateLimiter extends ReportRateLimiter { diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/utils/VendorIdentifierTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/utils/VendorIdentifierTest.java index 3755e09..97b4359 100644 --- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/utils/VendorIdentifierTest.java +++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/utils/VendorIdentifierTest.java @@ -1,14 +1,11 @@ package com.datatheorem.android.trustkit.utils; -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; - -import org.junit.Test; - import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; +import android.content.Context; +import androidx.test.platform.app.InstrumentationRegistry; +import org.junit.Test; public class VendorIdentifierTest { diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java index ccc5084..1b2b801 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java @@ -4,18 +4,14 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; -import androidx.annotation.NonNull; import android.util.Printer; - +import androidx.annotation.NonNull; import com.datatheorem.android.trustkit.config.ConfigurationException; import com.datatheorem.android.trustkit.config.TrustKitConfiguration; import com.datatheorem.android.trustkit.pinning.TrustManagerBuilder; import com.datatheorem.android.trustkit.reporting.BackgroundReporter; import com.datatheorem.android.trustkit.utils.TrustKitLog; import com.datatheorem.android.trustkit.utils.VendorIdentifier; - -import org.xmlpull.v1.XmlPullParserException; - import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStoreException; @@ -23,150 +19,130 @@ import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.Set; - import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; - +import org.xmlpull.v1.XmlPullParserException; /** * Class that provides all of the TrustKit public APIs. * - *

- * It should be used to initialize the App's SSL pinning policy and to retrieve the - * corresponding {@code SSLSocketFactory} and {@code X509TrustManager}, to be used to add SSL - * pinning validation to the App's network connections. - *

+ *

It should be used to initialize the App's SSL pinning policy and to retrieve the corresponding + * {@code SSLSocketFactory} and {@code X509TrustManager}, to be used to add SSL pinning validation + * to the App's network connections. * - *

TrustKit works by extending the + * Android N Network Security Configuration in two ways: * - *

    - *
  • It provides support for the SSL pinning functionality of the Android N Network - * Security Configuration to earlier versions of Android, down to API level 17. This - * allows Apps supporting versions of Android that earlier than N to implement SSL - * pinning in a way that is future-proof.
  • - * - *
  • It adds the ability to send reports when pinning validation failed for a specific - * connection. Reports have a format that is similar to the report-uri feature of - * HTTP - * Public Key Pinning and TrustKit - * iOS.
  • - *
+ *
    + *
  • It provides support for the SSL pinning functionality of the Android N Network Security + * Configuration to earlier versions of Android, down to API level 17. This allows Apps + * supporting versions of Android that earlier than N to implement SSL pinning in a way that + * is future-proof. + *
  • It adds the ability to send reports when pinning validation failed for a specific + * connection. Reports have a format that is similar to the report-uri feature of HTTP Public Key Pinning and TrustKit iOS. + *
* - * For better compatibility, TrustKit will also run on API levels 15 and 16 but its - * functionality will be disabled. - *

+ * For better compatibility, TrustKit will also run on API levels 15 and 16 but its functionality + * will be disabled. * *

Supported Android N Network Security Settings

* - *

- * On devices before Android N, TrustKit supports the following XML tags defined in the - * - * Android N Network Security Configuration for deploying SSL pinning: - *

+ *

On devices before Android N, TrustKit supports the following XML tags defined in the Android N Network Security Configuration for deploying SSL pinning: * *

    - *
  • {@code }.
  • - *
  • {@code } and the {@code includeSubdomains} attribute.
  • - *
  • {@code } and the {@code expiration} attribute.
  • - *
  • {@code } and the {@code digest} attribute.
  • - *
  • {@code }.
  • - *
  • {@code }, but only within a {@code } tag. Hence, custom - * trust anchors for specific domains cannot be set.
  • - *
  • {@code } and the {@code overridePins} and {@code src} attributes. Only raw - * certificate files are supported for the {@code src} attribute ({@code user} and - * {@code system} values will be ignored).
  • + *
  • {@code }. + *
  • {@code } and the {@code includeSubdomains} attribute. + *
  • {@code } and the {@code expiration} attribute. + *
  • {@code } and the {@code digest} attribute. + *
  • {@code }. + *
  • {@code }, but only within a {@code } tag. Hence, custom + * trust anchors for specific domains cannot be set. + *
  • {@code } and the {@code overridePins} and {@code src} attributes. Only raw + * certificate files are supported for the {@code src} attribute ({@code user} and {@code + * system} values will be ignored). *
* - *

- * On Android N devices, the OS' implementation is used and all XML tags are supported. - *

+ *

On Android N devices, the OS' implementation is used and all XML tags are supported. * *

Additional TrustKit Settings

* - *

- * TrustKit provides additional functionality to not enforce pinning validation and to allow - * reports to be sent by the App whenever a pinning validation failure occurred. - *

+ *

TrustKit provides additional functionality to not enforce pinning validation and to allow + * reports to be sent by the App whenever a pinning validation failure occurred. * *

{@code }

* - *

- * The main tag for specifying additional TrustKit settings, to be defined within a - * {@code } entry. It supports the following attributes: - *

+ *

The main tag for specifying additional TrustKit settings, to be defined within a {@code + * } entry. It supports the following attributes: * - *

    - *
  • {@code enforcePinning}: if set to {@code false}, TrustKit will not block SSL - * connections that caused a pinning validation error; default value is {@code false}. When - * a pinning failure occurs, pin failure reports will always be sent to the configured - * report URIs regardless of the value of {@code enforcePinning}. This behavior allows - * deploying pinning validation without the risk of locking out users due to a - * misconfiguration, while still receiving reports in order to assess how many users would - * be affected by pinning.
  • - * - *
  • {@code disableDefaultReportUri}: if set to {@code true}, the default report URL for - * sending pin failure reports will be disabled; default value is {@code false}. By default, - * pin failure reports are sent to a report server hosted by Data Theorem, for detecting - * potential CA compromises and man-in-the-middle attacks, as well as providing a free - * dashboard for developers; email - * info@datatheorem.com if you'd like a dashboard - * for your App. Only pin failure reports are sent, which contain the App's package name, - * a randomly-generated ID, and the server's hostname and certificate chain that failed - * validation.
  • - *
+ *
    + *
  • {@code enforcePinning}: if set to {@code false}, TrustKit will not block SSL connections + * that caused a pinning validation error; default value is {@code false}. When a pinning + * failure occurs, pin failure reports will always be sent to the configured report URIs + * regardless of the value of {@code enforcePinning}. This behavior allows deploying pinning + * validation without the risk of locking out users due to a misconfiguration, while still + * receiving reports in order to assess how many users would be affected by pinning. + *
  • {@code disableDefaultReportUri}: if set to {@code true}, the default report URL for sending + * pin failure reports will be disabled; default value is {@code false}. By default, pin + * failure reports are sent to a report server hosted by Data Theorem, for detecting potential + * CA compromises and man-in-the-middle attacks, as well as providing a free dashboard for + * developers; email info@datatheorem.com if you'd + * like a dashboard for your App. Only pin failure reports are sent, which contain the App's + * package name, a randomly-generated ID, and the server's hostname and certificate chain that + * failed validation. + *
* *

{@code }

* - * A URL to which pin validation failures should be reported, to be defined within a - * {@code } tag. The format of the reports is similar to the one described in - * RFC 7469 for the HPKP - * specification. A sample TrustKit report is available - * - * in the project's repository - * . + * A URL to which pin validation failures should be reported, to be defined within a {@code + * } tag. The format of the reports is similar to the one described in RFC 7469 for the HPKP + * specification. A sample TrustKit report is available in the project's repository . * *

Sample TrustKit Configuration

- *

- * The following configuration will pin the www.datatheorem.com domain without enforcing pinning - * validation, and will send pinning failure reports to report.datatheorem.com. It also defines - * a debug overrides to add a debug certificate authority to the App's trust store for easier - * debugging of the App's network traffic. - *

- *
- *     {@code
- *         
- *         
- *         
- *         
- *         
- *         
- *         www.datatheorem.com
- *         
- *         k3XnEYQCK79AtL9GYnT/nyhsabas03V+bhRQYHQbpXU=
- *         2kOi4HdYYsvTR1sTIR7RHwlf2SescTrpza9ZrWy7poQ=
- *         
- *         
- *         
- *         
- *         
- *         http://report.datatheorem.com/log_report
- *         
- *         
- *         
- *         
- *         
- *         
- *         
- *         
- *         
- *     }
- * 
* + *

The following configuration will pin the www.datatheorem.com domain without enforcing pinning + * validation, and will send pinning failure reports to report.datatheorem.com. It also defines a + * debug overrides to add a debug certificate authority to the App's trust store for easier + * debugging of the App's network traffic. + * + *

{@code
+ * 
+ * 
+ * 
+ * 
+ * 
+ * 
+ * www.datatheorem.com
+ * 
+ * k3XnEYQCK79AtL9GYnT/nyhsabas03V+bhRQYHQbpXU=
+ * 2kOi4HdYYsvTR1sTIR7RHwlf2SescTrpza9ZrWy7poQ=
+ * 
+ * 
+ * 
+ * 
+ * 
+ * http://report.datatheorem.com/log_report
+ * 
+ * 
+ * 
+ * 
+ * 
+ * 
+ * 
+ * 
+ * 
+ *
+ * }
*/ public class TrustKit { @@ -174,14 +150,14 @@ public class TrustKit { private final TrustKitConfiguration trustKitConfiguration; - protected TrustKit(@NonNull Context context, - @NonNull TrustKitConfiguration trustKitConfiguration) { + protected TrustKit( + @NonNull Context context, @NonNull TrustKitConfiguration trustKitConfiguration) { this.trustKitConfiguration = trustKitConfiguration; // Setup the debug-overrides setting if the App is debuggable // Do not use BuildConfig.DEBUG as it does not work for libraries - boolean isAppDebuggable = (0 != - (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE)); + boolean isAppDebuggable = + (0 != (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE)); Set debugCaCerts = null; boolean shouldOverridePins = false; if (isAppDebuggable) { @@ -207,28 +183,32 @@ protected TrustKit(@NonNull Context context, } String appVendorId = VendorIdentifier.getOrCreate(context); - BackgroundReporter reporter = new BackgroundReporter(context, appPackageName, appVersion, - appVendorId); + BackgroundReporter reporter = + new BackgroundReporter(context, appPackageName, appVersion, appVendorId); // Initialize the trust manager builder try { - TrustManagerBuilder.initializeBaselineTrustManager(debugCaCerts, - shouldOverridePins, reporter); - } catch (CertificateException | NoSuchAlgorithmException | KeyStoreException + TrustManagerBuilder.initializeBaselineTrustManager( + debugCaCerts, shouldOverridePins, reporter); + } catch (CertificateException + | NoSuchAlgorithmException + | KeyStoreException | IOException e) { throw new ConfigurationException("Could not parse certificates"); } } - /** Try to retrieve the Network Security Policy resource ID configured in the App's manifest. + /** + * Try to retrieve the Network Security Policy resource ID configured in the App's manifest. * - * Somewhat convoluted as other means of getting the resource ID involve using private APIs. + *

Somewhat convoluted as other means of getting the resource ID involve using private APIs. * * @param context * @return The resource ID for the XML file containing the configured Network Security Policy or - * -1 if no policy was configured in the App's manifest or if we are not running on Android N. + * -1 if no policy was configured in the App's manifest or if we are not running on Android + * N. */ - static private int getNetSecConfigResourceId(@NonNull Context context) { + private static int getNetSecConfigResourceId(@NonNull Context context) { ApplicationInfo info = context.getApplicationInfo(); // Dump the content of the ApplicationInfo, which contains the resource ID on Android N @@ -246,7 +226,9 @@ public void println(String x) { } } - private int getNetworkSecurityConfigResId() { return netSecConfigResourceId; } + private int getNetworkSecurityConfigResId() { + return netSecConfigResourceId; + } } NetSecConfigResIdRetriever retriever = new NetSecConfigResIdRetriever(); @@ -254,35 +236,38 @@ public void println(String x) { return retriever.getNetworkSecurityConfigResId(); } - /** Initialize TrustKit with the Network Security Configuration file at the default location + /** + * Initialize TrustKit with the Network Security Configuration file at the default location * res/xml/network_security_config.xml. The Network Security Configuration file must also have - * been - * added to the App's manifest. + * been added to the App's manifest. * * @param context the application's context. * @throws ConfigurationException if the policy could not be parsed or contained errors. */ @NonNull - public synchronized static TrustKit initializeWithNetworkSecurityConfiguration( + public static synchronized TrustKit initializeWithNetworkSecurityConfiguration( @NonNull Context context) { // Try to get the default network policy resource ID - int networkSecurityConfigId = context.getResources().getIdentifier( - "network_security_config", "xml", context.getPackageName()); + int networkSecurityConfigId = + context.getResources() + .getIdentifier("network_security_config", "xml", context.getPackageName()); return initializeWithNetworkSecurityConfiguration(context, networkSecurityConfigId); } - /** Initialize TrustKit with the Network Security Configuration file with the specified - * resource ID. The Network Security Configuration file must also have - * been - * added to the App's manifest. + /** + * Initialize TrustKit with the Network Security Configuration file with the specified resource + * ID. The Network Security Configuration file must also have been added to the App's manifest. * * @param context the application's context. * @param configurationResourceId the resource ID for the Network Security Configuration file to - * use. + * use. * @throws ConfigurationException if the policy could not be parsed or contained errors. */ @NonNull - public synchronized static TrustKit initializeWithNetworkSecurityConfiguration( + public static synchronized TrustKit initializeWithNetworkSecurityConfiguration( @NonNull Context context, int configurationResourceId) { if (trustKitInstance != null) { throw new IllegalStateException("TrustKit has already been initialized"); @@ -295,34 +280,37 @@ public synchronized static TrustKit initializeWithNetworkSecurityConfiguration( if (systemConfigResId == -1) { // Android did not find a policy because the supplied resource ID is wrong or the // policy file is not properly setup in the manifest, or contains bad data - throw new ConfigurationException("TrustKit was initialized with a network policy " + - "that was not properly configured for Android N - make sure it is in the " + - "App's Manifest."); - } - else if (systemConfigResId != configurationResourceId) { - throw new ConfigurationException("TrustKit was initialized with a different " + - "network policy than the one configured in the App's manifest."); + throw new ConfigurationException( + "TrustKit was initialized with a network policy " + + "that was not properly configured for Android N - make sure it is in the " + + "App's Manifest."); + } else if (systemConfigResId != configurationResourceId) { + throw new ConfigurationException( + "TrustKit was initialized with a different " + + "network policy than the one configured in the App's manifest."); } } // Then try to load the supplied policy TrustKitConfiguration trustKitConfiguration; try { - trustKitConfiguration = TrustKitConfiguration.fromXmlPolicy( - context, context.getResources().getXml(configurationResourceId) - ); + trustKitConfiguration = + TrustKitConfiguration.fromXmlPolicy( + context, context.getResources().getXml(configurationResourceId)); } catch (XmlPullParserException | IOException e) { throw new ConfigurationException("Could not parse network security policy file"); } catch (CertificateException e) { - throw new ConfigurationException("Could not find the debug certificate in the " + - "network security police file"); + throw new ConfigurationException( + "Could not find the debug certificate in the " + + "network security police file"); } trustKitInstance = new TrustKit(context, trustKitConfiguration); return trustKitInstance; } - /** Retrieve the initialized instance of TrustKit. + /** + * Retrieve the initialized instance of TrustKit. * * @throws IllegalStateException if TrustKit has not been initialized. */ @@ -334,35 +322,33 @@ public static TrustKit getInstance() { return trustKitInstance; } - /** Retrieve the current TrustKit configuration. - * - */ + /** Retrieve the current TrustKit configuration. */ @NonNull - public TrustKitConfiguration getConfiguration() { return trustKitConfiguration; } - - /** Retrieve an {@code SSLSSocketFactory} that implements SSL pinning validation based on the + public TrustKitConfiguration getConfiguration() { + return trustKitConfiguration; + } + + /** + * Retrieve an {@code SSLSSocketFactory} that implements SSL pinning validation based on the * current TrustKit configuration for the specified serverHostname. It can be used with most * network APIs (such as {@code HttpsUrlConnection}) to add SSL pinning validation to the * connections. * - *

- * The {@code SSLSocketFactory} is configured for the supplied serverHostname, and will - * enforce this domain's pinning policy even if a redirection to a different domain occurs - * during the connection. Hence validation will always fail in the case of a redirection to - * a different domain. - * However, pinning validation is only meant to be used on the App's API server(s), and - * redirections to other domains should not happen in this scenario. - *

+ *

The {@code SSLSocketFactory} is configured for the supplied serverHostname, and will + * enforce this domain's pinning policy even if a redirection to a different domain occurs + * during the connection. Hence validation will always fail in the case of a redirection to a + * different domain. However, pinning validation is only meant to be used on the App's API + * server(s), and redirections to other domains should not happen in this scenario. * * @param serverHostname the server's hostname that the {@code SSLSocketFactory} will be used to - * connect to. This hostname will be used to retrieve the pinning policy - * from the current TrustKit configuration. + * connect to. This hostname will be used to retrieve the pinning policy from the current + * TrustKit configuration. */ @NonNull public SSLSocketFactory getSSLSocketFactory(@NonNull String serverHostname) { try { SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, new TrustManager[]{getTrustManager(serverHostname)}, null); + sslContext.init(null, new TrustManager[] {getTrustManager(serverHostname)}, null); return sslContext.getSocketFactory(); } catch (NoSuchAlgorithmException | KeyManagementException e) { @@ -371,23 +357,20 @@ public SSLSocketFactory getSSLSocketFactory(@NonNull String serverHostname) { } } - - /** Retrieve an {@code X509TrustManager} that implements SSL pinning validation based on the + /** + * Retrieve an {@code X509TrustManager} that implements SSL pinning validation based on the * current TrustKit configuration for the supplied hostname. It can be used with some network * APIs that let developers supply a trust manager to customize SSL validation. * - *

- * The {@code X509TrustManager} is configured for the supplied serverHostname, and will - * enforce this domain's pinning policy even if a redirection to a different domain occurs - * during the connection. Hence validation will always fail in the case of a redirection to - * a different domain. - * However, pinning validation is only meant to be used on the App's API server(s), and - * redirections to other domains should not happen in this scenario. - *

+ *

The {@code X509TrustManager} is configured for the supplied serverHostname, and will + * enforce this domain's pinning policy even if a redirection to a different domain occurs + * during the connection. Hence validation will always fail in the case of a redirection to a + * different domain. However, pinning validation is only meant to be used on the App's API + * server(s), and redirections to other domains should not happen in this scenario. * * @param serverHostname the server's hostname that the {@code X509TrustManager} will be used to - * connect to. This hostname will be used to retrieve the pinning policy - * from the current TrustKit configuration. + * connect to. This hostname will be used to retrieve the pinning policy from the current + * TrustKit configuration. */ @NonNull public X509TrustManager getTrustManager(@NonNull String serverHostname) { diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/ConfigurationException.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/ConfigurationException.java index 8775b23..c48eec5 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/ConfigurationException.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/ConfigurationException.java @@ -1,7 +1,7 @@ package com.datatheorem.android.trustkit.config; public final class ConfigurationException extends RuntimeException { - public ConfigurationException(String detailMessage) { - super(detailMessage); - } -} \ No newline at end of file + public ConfigurationException(String detailMessage) { + super(detailMessage); + } +} diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/DomainPinningPolicy.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/DomainPinningPolicy.java index 16f9b28..f31eb0c 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/DomainPinningPolicy.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/DomainPinningPolicy.java @@ -9,11 +9,11 @@ import java.util.HashSet; import java.util.Set; - public final class DomainPinningPolicy { // The default URL to submit pin failure report to private static final URL DEFAULT_REPORTING_URL; + static { java.net.URL defaultUrl; try { @@ -31,13 +31,14 @@ public final class DomainPinningPolicy { private final boolean shouldEnforcePinning; @NonNull private final Set reportUris; - DomainPinningPolicy(@NonNull String hostname, - Boolean shouldIncludeSubdomains, - Set publicKeyHashStrList, - Boolean shouldEnforcePinning, - @Nullable Date expirationDate, - @Nullable Set reportUriStrList, - Boolean shouldDisableDefaultReportUri) + DomainPinningPolicy( + @NonNull String hostname, + Boolean shouldIncludeSubdomains, + Set publicKeyHashStrList, + Boolean shouldEnforcePinning, + @Nullable Date expirationDate, + @Nullable Set reportUriStrList, + Boolean shouldDisableDefaultReportUri) throws MalformedURLException { // Run some sanity checks on the configuration // Check if the hostname seems valid @@ -50,8 +51,7 @@ public final class DomainPinningPolicy { // Due to the fact some configurations could be added without any pin (e.g. localhost) // the publicKeyHashStrList would be null. // Thus we're managing these cases as an empty set of pins. - if (publicKeyHashStrList == null) - publicKeyHashStrList = new HashSet<>(); + if (publicKeyHashStrList == null) publicKeyHashStrList = new HashSet<>(); // Parse boolean settings and handle default values if (shouldEnforcePinning == null) { @@ -65,28 +65,33 @@ public final class DomainPinningPolicy { this.shouldIncludeSubdomains = shouldIncludeSubdomains; } - // Check if the configuration has a empty pin-set and still would enforce pinning // TrustKit should not work if the configuration contains both (opposite behaviors) if (publicKeyHashStrList.isEmpty() && this.shouldEnforcePinning) { - throw new ConfigurationException("An empty pin-set was supplied "+ - "for domain " + this.hostname + " with the enforcePinning set to true. " + - "An empty pin-set disables pinning and can't be use with enforcePinning set to true."); + throw new ConfigurationException( + "An empty pin-set was supplied " + + "for domain " + + this.hostname + + " with the enforcePinning set to true. " + + "An empty pin-set disables pinning and can't be use with enforcePinning set to true."); } // Check if the configuration has at least two pins (including a backup pin) // TrustKit should not work if the configuration contains only one pin // more info (https://tools.ietf.org/html/rfc7469#page-21) if (publicKeyHashStrList.size() < 2 && this.shouldEnforcePinning) { - throw new ConfigurationException("Less than two pins were supplied "+ - "for domain " + this.hostname + ". This might " + - "brick your App; please review the Getting Started guide in " + - "./docs/getting-started.md"); + throw new ConfigurationException( + "Less than two pins were supplied " + + "for domain " + + this.hostname + + ". This might " + + "brick your App; please review the Getting Started guide in " + + "./docs/getting-started.md"); } // Parse the supplied pins publicKeyPins = new HashSet<>(); - for (String pinStr : publicKeyHashStrList) { + for (String pinStr : publicKeyHashStrList) { publicKeyPins.add(new PublicKeyPin(pinStr)); } @@ -99,7 +104,7 @@ public final class DomainPinningPolicy { } // Add the default report URL - if ((shouldDisableDefaultReportUri == null) || (!shouldDisableDefaultReportUri) ) { + if ((shouldDisableDefaultReportUri == null) || (!shouldDisableDefaultReportUri)) { reportUris.add(DEFAULT_REPORTING_URL); } @@ -136,17 +141,25 @@ public Date getExpirationDate() { @Override public String toString() { - return "DomainPinningPolicy{" + - "hostname = " + hostname + "\n" + - "knownPins = " + Arrays.toString(publicKeyPins.toArray()) + - "\n" + - "shouldEnforcePinning = " + shouldEnforcePinning + "\n" + - "reportUris = " + reportUris + "\n" + - "shouldIncludeSubdomains = " + shouldIncludeSubdomains + "\n" + - "}"; + return "DomainPinningPolicy{" + + "hostname = " + + hostname + + "\n" + + "knownPins = " + + Arrays.toString(publicKeyPins.toArray()) + + "\n" + + "shouldEnforcePinning = " + + shouldEnforcePinning + + "\n" + + "reportUris = " + + reportUris + + "\n" + + "shouldIncludeSubdomains = " + + shouldIncludeSubdomains + + "\n" + + "}"; } - public static final class Builder { // The domain must always be specified in domain-config private String hostname; @@ -189,7 +202,8 @@ public DomainPinningPolicy build() throws MalformedURLException { } if (shouldDisableDefaultReportUri == null) { - shouldDisableDefaultReportUri = parentBuilder.getShouldDisableDefaultReportUri(); + shouldDisableDefaultReportUri = + parentBuilder.getShouldDisableDefaultReportUri(); } } @@ -204,8 +218,7 @@ public DomainPinningPolicy build() throws MalformedURLException { shouldEnforcePinning, expirationDate, reportUris, - shouldDisableDefaultReportUri - ); + shouldDisableDefaultReportUri); } public Builder setParent(Builder parent) { diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/DomainValidator.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/DomainValidator.java index e5f7ec3..e63d486 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/DomainValidator.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/DomainValidator.java @@ -1,4 +1,5 @@ -// TrustKit: Taken from https://apache.googlesource.com/commons-validator/+/VALIDATOR_1_5_1/src/main/java/org/apache/commons/validator/routines/DomainValidator.java +// TrustKit: Taken from +// https://apache.googlesource.com/commons-validator/+/VALIDATOR_1_5_1/src/main/java/org/apache/commons/validator/routines/DomainValidator.java package com.datatheorem.android.trustkit.config; /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -17,47 +18,37 @@ * limitations under the License. */ - import java.io.Serializable; import java.net.IDN; import java.util.Arrays; import java.util.Locale; + /** - *

Domain name validation routines.

+ * Domain name validation routines. * - *

- * This validator provides methods for validating Internet domain names - * and top-level domains. - *

+ *

This validator provides methods for validating Internet domain names and top-level domains. * - *

Domain names are evaluated according - * to the standards RFC1034, - * section 3, and RFC1123, - * section 2.1. No accommodation is provided for the specialized needs of - * other applications; if the domain name has been URL-encoded, for example, - * validation will fail even though the equivalent plaintext version of the - * same name would have passed. - *

+ *

Domain names are evaluated according to the standards RFC1034, section 3, and RFC1123, section 2.1. No accommodation is provided + * for the specialized needs of other applications; if the domain name has been URL-encoded, for + * example, validation will fail even though the equivalent plaintext version of the same name would + * have passed. * - *

- * Validation is also provided for top-level domains (TLDs) as defined and - * maintained by the Internet Assigned Numbers Authority (IANA): - *

+ *

Validation is also provided for top-level domains (TLDs) as defined and maintained by the + * Internet Assigned Numbers Authority (IANA): * - *

    - *
  • {@link #isValidInfrastructureTld} - validates infrastructure TLDs - * (.arpa, etc.)
  • - *
  • {@link #isValidGenericTld} - validates generic TLDs - * (.com, .org, etc.)
  • - *
  • {@link #isValidCountryCodeTld} - validates country code TLDs - * (.us, .uk, .cn, etc.)
  • - *
+ *
    + *
  • {@link #isValidInfrastructureTld} - validates infrastructure TLDs (.arpa, + * etc.) + *
  • {@link #isValidGenericTld} - validates generic TLDs (.com, .org, etc.) + *
  • {@link #isValidCountryCodeTld} - validates country code TLDs (.us, .uk, .cn, + * etc.) + *
* - *

- * (NOTE: This class does not provide IP address lookup for domain names or - * methods to ensure that a given domain name matches a specific IP; see - * {@link java.net.InetAddress} for that functionality.) - *

+ *

(NOTE: This class does not provide IP address lookup for domain names or methods to + * ensure that a given domain name matches a specific IP; see {@link java.net.InetAddress} for that + * functionality.) * * @version $Revision$ * @since Validator 1.4 @@ -82,30 +73,19 @@ class DomainValidator implements Serializable { private static final String DOMAIN_NAME_REGEX = "^(?:" + DOMAIN_LABEL_REGEX + "\\.)+" + "(" + TOP_LABEL_REGEX + ")\\.?$"; private final boolean allowLocal; - /** - * Singleton instance of this validator, which - * doesn't consider local addresses as valid. - */ + /** Singleton instance of this validator, which doesn't consider local addresses as valid. */ private static final DomainValidator DOMAIN_VALIDATOR = new DomainValidator(false); - /** - * Singleton instance of this validator, which does - * consider local addresses valid. - */ + /** Singleton instance of this validator, which does consider local addresses valid. */ private static final DomainValidator DOMAIN_VALIDATOR_WITH_LOCAL = new DomainValidator(true); - /** - * RegexValidator for matching domains. - */ - private final RegexValidator domainRegex = - new RegexValidator(DOMAIN_NAME_REGEX); - /** - * RegexValidator for matching a local hostname - */ + /** RegexValidator for matching domains. */ + private final RegexValidator domainRegex = new RegexValidator(DOMAIN_NAME_REGEX); + /** RegexValidator for matching a local hostname */ // RFC1123 sec 2.1 allows hostnames to start with a digit - private final RegexValidator hostnameRegex = - new RegexValidator(DOMAIN_LABEL_REGEX); + private final RegexValidator hostnameRegex = new RegexValidator(DOMAIN_LABEL_REGEX); /** - * Returns the singleton instance of this validator. It - * will not consider local addresses as valid. + * Returns the singleton instance of this validator. It will not consider local addresses as + * valid. + * * @return the singleton instance of this validator */ public static synchronized DomainValidator getInstance() { @@ -113,14 +93,14 @@ public static synchronized DomainValidator getInstance() { return DOMAIN_VALIDATOR; } /** - * Returns the singleton instance of this validator, - * with local validation as required. + * Returns the singleton instance of this validator, with local validation as required. + * * @param allowLocal Should local addresses be considered valid? * @return the singleton instance of this validator */ public static synchronized DomainValidator getInstance(boolean allowLocal) { inUse = true; - if(allowLocal) { + if (allowLocal) { return DOMAIN_VALIDATOR_WITH_LOCAL; } return DOMAIN_VALIDATOR; @@ -130,9 +110,9 @@ private DomainValidator(boolean allowLocal) { this.allowLocal = allowLocal; } /** - * Returns true if the specified String parses - * as a valid domain name with a recognized top-level domain. - * The parsing is case-insensitive. + * Returns true if the specified String parses as a valid domain name with a + * recognized top-level domain. The parsing is case-insensitive. + * * @param domain the parameter to check for domain name syntax * @return true if the parameter is a valid domain name */ @@ -169,19 +149,18 @@ final boolean isValidDomainSyntax(String domain) { return false; } String[] groups = domainRegex.match(domain); - return (groups != null && groups.length > 0) - || hostnameRegex.isValid(domain); + return (groups != null && groups.length > 0) || hostnameRegex.isValid(domain); } /** - * Returns true if the specified String matches any - * IANA-defined top-level domain. Leading dots are ignored if present. - * The search is case-insensitive. + * Returns true if the specified String matches any IANA-defined top-level domain. + * Leading dots are ignored if present. The search is case-insensitive. + * * @param tld the parameter to check for TLD status, not null * @return true if the parameter is a TLD */ public boolean isValidTld(String tld) { tld = unicodeToASCII(tld); - if(allowLocal && isValidLocalTld(tld)) { + if (allowLocal && isValidLocalTld(tld)) { return true; } return isValidInfrastructureTld(tld) @@ -189,9 +168,9 @@ public boolean isValidTld(String tld) { || isValidCountryCodeTld(tld); } /** - * Returns true if the specified String matches any - * IANA-defined infrastructure top-level domain. Leading dots are - * ignored if present. The search is case-insensitive. + * Returns true if the specified String matches any IANA-defined infrastructure + * top-level domain. Leading dots are ignored if present. The search is case-insensitive. + * * @param iTld the parameter to check for infrastructure TLD status, not null * @return true if the parameter is an infrastructure TLD */ @@ -200,9 +179,9 @@ public boolean isValidInfrastructureTld(String iTld) { return arrayContains(INFRASTRUCTURE_TLDS, key); } /** - * Returns true if the specified String matches any - * IANA-defined generic top-level domain. Leading dots are ignored - * if present. The search is case-insensitive. + * Returns true if the specified String matches any IANA-defined generic top-level + * domain. Leading dots are ignored if present. The search is case-insensitive. + * * @param gTld the parameter to check for generic TLD status, not null * @return true if the parameter is a generic TLD */ @@ -212,9 +191,9 @@ public boolean isValidGenericTld(String gTld) { && !arrayContains(genericTLDsMinus, key); } /** - * Returns true if the specified String matches any - * IANA-defined country code top-level domain. Leading dots are - * ignored if present. The search is case-insensitive. + * Returns true if the specified String matches any IANA-defined country code + * top-level domain. Leading dots are ignored if present. The search is case-insensitive. + * * @param ccTld the parameter to check for country code TLD status, not null * @return true if the parameter is a country code TLD */ @@ -224,9 +203,10 @@ public boolean isValidCountryCodeTld(String ccTld) { && !arrayContains(countryCodeTLDsMinus, key); } /** - * Returns true if the specified String matches any - * widely used "local" domains (localhost or localdomain). Leading dots are - * ignored if present. The search is case-insensitive. + * Returns true if the specified String matches any widely used "local" domains + * (localhost or localdomain). Leading dots are ignored if present. The search is + * case-insensitive. + * * @param lTld the parameter to check for local TLD status, not null * @return true if the parameter is an local TLD */ @@ -234,6 +214,7 @@ public boolean isValidLocalTld(String lTld) { final String key = chompLeadingDot(unicodeToASCII(lTld).toLowerCase(Locale.ENGLISH)); return arrayContains(LOCAL_TLDS, key); } + private String chompLeadingDot(String str) { if (str.startsWith(".")) { return str.substring(1); @@ -252,1323 +233,1350 @@ private String chompLeadingDot(String str) { // For example (as of 2015-01-02): // .bl country-code Not assigned // .um country-code Not assigned - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search - private static final String[] INFRASTRUCTURE_TLDS = new String[] { - "arpa", // internet infrastructure - }; - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search - private static final String[] GENERIC_TLDS = new String[] { - // Taken from Version 2016042500, Last Updated Mon Apr 25 07:07:01 2016 UTC - "aaa", // aaa American Automobile Association, Inc. - "aarp", // aarp AARP - "abb", // abb ABB Ltd - "abbott", // abbott Abbott Laboratories, Inc. - "abbvie", // abbvie AbbVie Inc. - "abogado", // abogado Top Level Domain Holdings Limited - "abudhabi", // abudhabi Abu Dhabi Systems and Information Centre - "academy", // academy Half Oaks, LLC - "accenture", // accenture Accenture plc - "accountant", // accountant dot Accountant Limited - "accountants", // accountants Knob Town, LLC - "aco", // aco ACO Severin Ahlmann GmbH & Co. KG - "active", // active The Active Network, Inc - "actor", // actor United TLD Holdco Ltd. - "adac", // adac Allgemeiner Deutscher Automobil-Club e.V. (ADAC) - "ads", // ads Charleston Road Registry Inc. - "adult", // adult ICM Registry AD LLC - "aeg", // aeg Aktiebolaget Electrolux - "aero", // aero Societe Internationale de Telecommunications Aeronautique (SITA INC USA) - "afl", // afl Australian Football League - "agakhan", // agakhan Fondation Aga Khan (Aga Khan Foundation) - "agency", // agency Steel Falls, LLC - "aig", // aig American International Group, Inc. - "airforce", // airforce United TLD Holdco Ltd. - "airtel", // airtel Bharti Airtel Limited - "akdn", // akdn Fondation Aga Khan (Aga Khan Foundation) - "alibaba", // alibaba Alibaba Group Holding Limited - "alipay", // alipay Alibaba Group Holding Limited - "allfinanz", // allfinanz Allfinanz Deutsche Vermögensberatung Aktiengesellschaft - "ally", // ally Ally Financial Inc. - "alsace", // alsace REGION D ALSACE - "amica", // amica Amica Mutual Insurance Company - "amsterdam", // amsterdam Gemeente Amsterdam - "analytics", // analytics Campus IP LLC - "android", // android Charleston Road Registry Inc. - "anquan", // anquan QIHOO 360 TECHNOLOGY CO. LTD. - "apartments", // apartments June Maple, LLC - "app", // app Charleston Road Registry Inc. - "apple", // apple Apple Inc. - "aquarelle", // aquarelle Aquarelle.com - "aramco", // aramco Aramco Services Company - "archi", // archi STARTING DOT LIMITED - "army", // army United TLD Holdco Ltd. - "arte", // arte Association Relative à la Télévision Européenne G.E.I.E. - "asia", // asia DotAsia Organisation Ltd. - "associates", // associates Baxter Hill, LLC - "attorney", // attorney United TLD Holdco, Ltd - "auction", // auction United TLD HoldCo, Ltd. - "audi", // audi AUDI Aktiengesellschaft - "audio", // audio Uniregistry, Corp. - "author", // author Amazon Registry Services, Inc. - "auto", // auto Uniregistry, Corp. - "autos", // autos DERAutos, LLC - "avianca", // avianca Aerovias del Continente Americano S.A. Avianca - "aws", // aws Amazon Registry Services, Inc. - "axa", // axa AXA SA - "azure", // azure Microsoft Corporation - "baby", // baby Johnson & Johnson Services, Inc. - "baidu", // baidu Baidu, Inc. - "band", // band United TLD Holdco, Ltd - "bank", // bank fTLD Registry Services, LLC - "bar", // bar Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable - "barcelona", // barcelona Municipi de Barcelona - "barclaycard", // barclaycard Barclays Bank PLC - "barclays", // barclays Barclays Bank PLC - "barefoot", // barefoot Gallo Vineyards, Inc. - "bargains", // bargains Half Hallow, LLC - "bauhaus", // bauhaus Werkhaus GmbH - "bayern", // bayern Bayern Connect GmbH - "bbc", // bbc British Broadcasting Corporation - "bbva", // bbva BANCO BILBAO VIZCAYA ARGENTARIA, S.A. - "bcg", // bcg The Boston Consulting Group, Inc. - "bcn", // bcn Municipi de Barcelona - "beats", // beats Beats Electronics, LLC - "beer", // beer Top Level Domain Holdings Limited - "bentley", // bentley Bentley Motors Limited - "berlin", // berlin dotBERLIN GmbH & Co. KG - "best", // best BestTLD Pty Ltd - "bet", // bet Afilias plc - "bharti", // bharti Bharti Enterprises (Holding) Private Limited - "bible", // bible American Bible Society - "bid", // bid dot Bid Limited - "bike", // bike Grand Hollow, LLC - "bing", // bing Microsoft Corporation - "bingo", // bingo Sand Cedar, LLC - "bio", // bio STARTING DOT LIMITED - "biz", // biz Neustar, Inc. - "black", // black Afilias Limited - "blackfriday", // blackfriday Uniregistry, Corp. - "bloomberg", // bloomberg Bloomberg IP Holdings LLC - "blue", // blue Afilias Limited - "bms", // bms Bristol-Myers Squibb Company - "bmw", // bmw Bayerische Motoren Werke Aktiengesellschaft - "bnl", // bnl Banca Nazionale del Lavoro - "bnpparibas", // bnpparibas BNP Paribas - "boats", // boats DERBoats, LLC - "boehringer", // boehringer Boehringer Ingelheim International GmbH - "bom", // bom Núcleo de Informação e Coordenação do Ponto BR - NIC.br - "bond", // bond Bond University Limited - "boo", // boo Charleston Road Registry Inc. - "book", // book Amazon Registry Services, Inc. - "boots", // boots THE BOOTS COMPANY PLC - "bosch", // bosch Robert Bosch GMBH - "bostik", // bostik Bostik SA - "bot", // bot Amazon Registry Services, Inc. - "boutique", // boutique Over Galley, LLC - "bradesco", // bradesco Banco Bradesco S.A. - "bridgestone", // bridgestone Bridgestone Corporation - "broadway", // broadway Celebrate Broadway, Inc. - "broker", // broker DOTBROKER REGISTRY LTD - "brother", // brother Brother Industries, Ltd. - "brussels", // brussels DNS.be vzw - "budapest", // budapest Top Level Domain Holdings Limited - "bugatti", // bugatti Bugatti International SA - "build", // build Plan Bee LLC - "builders", // builders Atomic Madison, LLC - "business", // business Spring Cross, LLC - "buy", // buy Amazon Registry Services, INC - "buzz", // buzz DOTSTRATEGY CO. - "bzh", // bzh Association www.bzh - "cab", // cab Half Sunset, LLC - "cafe", // cafe Pioneer Canyon, LLC - "cal", // cal Charleston Road Registry Inc. - "call", // call Amazon Registry Services, Inc. - "camera", // camera Atomic Maple, LLC - "camp", // camp Delta Dynamite, LLC - "cancerresearch", // cancerresearch Australian Cancer Research Foundation - "canon", // canon Canon Inc. - "capetown", // capetown ZA Central Registry NPC trading as ZA Central Registry - "capital", // capital Delta Mill, LLC - "car", // car Cars Registry Limited - "caravan", // caravan Caravan International, Inc. - "cards", // cards Foggy Hollow, LLC - "care", // care Goose Cross, LLC - "career", // career dotCareer LLC - "careers", // careers Wild Corner, LLC - "cars", // cars Uniregistry, Corp. - "cartier", // cartier Richemont DNS Inc. - "casa", // casa Top Level Domain Holdings Limited - "cash", // cash Delta Lake, LLC - "casino", // casino Binky Sky, LLC - "cat", // cat Fundacio puntCAT - "catering", // catering New Falls. LLC - "cba", // cba COMMONWEALTH BANK OF AUSTRALIA - "cbn", // cbn The Christian Broadcasting Network, Inc. - "ceb", // ceb The Corporate Executive Board Company - "center", // center Tin Mill, LLC - "ceo", // ceo CEOTLD Pty Ltd - "cern", // cern European Organization for Nuclear Research ("CERN") - "cfa", // cfa CFA Institute - "cfd", // cfd DOTCFD REGISTRY LTD - "chanel", // chanel Chanel International B.V. - "channel", // channel Charleston Road Registry Inc. - "chase", // chase JPMorgan Chase & Co. - "chat", // chat Sand Fields, LLC - "cheap", // cheap Sand Cover, LLC - "chloe", // chloe Richemont DNS Inc. - "christmas", // christmas Uniregistry, Corp. - "chrome", // chrome Charleston Road Registry Inc. - "church", // church Holly Fileds, LLC - "cipriani", // cipriani Hotel Cipriani Srl - "circle", // circle Amazon Registry Services, Inc. - "cisco", // cisco Cisco Technology, Inc. - "citic", // citic CITIC Group Corporation - "city", // city Snow Sky, LLC - "cityeats", // cityeats Lifestyle Domain Holdings, Inc. - "claims", // claims Black Corner, LLC - "cleaning", // cleaning Fox Shadow, LLC - "click", // click Uniregistry, Corp. - "clinic", // clinic Goose Park, LLC - "clinique", // clinique The Estée Lauder Companies Inc. - "clothing", // clothing Steel Lake, LLC - "cloud", // cloud ARUBA S.p.A. - "club", // club .CLUB DOMAINS, LLC - "clubmed", // clubmed Club Méditerranée S.A. - "coach", // coach Koko Island, LLC - "codes", // codes Puff Willow, LLC - "coffee", // coffee Trixy Cover, LLC - "college", // college XYZ.COM LLC - "cologne", // cologne NetCologne Gesellschaft für Telekommunikation mbH - "com", // com VeriSign Global Registry Services - "commbank", // commbank COMMONWEALTH BANK OF AUSTRALIA - "community", // community Fox Orchard, LLC - "company", // company Silver Avenue, LLC - "compare", // compare iSelect Ltd - "computer", // computer Pine Mill, LLC - "comsec", // comsec VeriSign, Inc. - "condos", // condos Pine House, LLC - "construction", // construction Fox Dynamite, LLC - "consulting", // consulting United TLD Holdco, LTD. - "contact", // contact Top Level Spectrum, Inc. - "contractors", // contractors Magic Woods, LLC - "cooking", // cooking Top Level Domain Holdings Limited - "cool", // cool Koko Lake, LLC - "coop", // coop DotCooperation LLC - "corsica", // corsica Collectivité Territoriale de Corse - "country", // country Top Level Domain Holdings Limited - "coupon", // coupon Amazon Registry Services, Inc. - "coupons", // coupons Black Island, LLC - "courses", // courses OPEN UNIVERSITIES AUSTRALIA PTY LTD - "credit", // credit Snow Shadow, LLC - "creditcard", // creditcard Binky Frostbite, LLC - "creditunion", // creditunion CUNA Performance Resources, LLC - "cricket", // cricket dot Cricket Limited - "crown", // crown Crown Equipment Corporation - "crs", // crs Federated Co-operatives Limited - "cruises", // cruises Spring Way, LLC - "csc", // csc Alliance-One Services, Inc. - "cuisinella", // cuisinella SALM S.A.S. - "cymru", // cymru Nominet UK - "cyou", // cyou Beijing Gamease Age Digital Technology Co., Ltd. - "dabur", // dabur Dabur India Limited - "dad", // dad Charleston Road Registry Inc. - "dance", // dance United TLD Holdco Ltd. - "date", // date dot Date Limited - "dating", // dating Pine Fest, LLC - "datsun", // datsun NISSAN MOTOR CO., LTD. - "day", // day Charleston Road Registry Inc. - "dclk", // dclk Charleston Road Registry Inc. - "dealer", // dealer Dealer Dot Com, Inc. - "deals", // deals Sand Sunset, LLC - "degree", // degree United TLD Holdco, Ltd - "delivery", // delivery Steel Station, LLC - "dell", // dell Dell Inc. - "deloitte", // deloitte Deloitte Touche Tohmatsu - "delta", // delta Delta Air Lines, Inc. - "democrat", // democrat United TLD Holdco Ltd. - "dental", // dental Tin Birch, LLC - "dentist", // dentist United TLD Holdco, Ltd - "desi", // desi Desi Networks LLC - "design", // design Top Level Design, LLC - "dev", // dev Charleston Road Registry Inc. - "diamonds", // diamonds John Edge, LLC - "diet", // diet Uniregistry, Corp. - "digital", // digital Dash Park, LLC - "direct", // direct Half Trail, LLC - "directory", // directory Extra Madison, LLC - "discount", // discount Holly Hill, LLC - "dnp", // dnp Dai Nippon Printing Co., Ltd. - "docs", // docs Charleston Road Registry Inc. - "dog", // dog Koko Mill, LLC - "doha", // doha Communications Regulatory Authority (CRA) - "domains", // domains Sugar Cross, LLC -// "doosan", // doosan Doosan Corporation (retired) - "download", // download dot Support Limited - "drive", // drive Charleston Road Registry Inc. - "dubai", // dubai Dubai Smart Government Department - "durban", // durban ZA Central Registry NPC trading as ZA Central Registry - "dvag", // dvag Deutsche Vermögensberatung Aktiengesellschaft DVAG - "earth", // earth Interlink Co., Ltd. - "eat", // eat Charleston Road Registry Inc. - "edeka", // edeka EDEKA Verband kaufmännischer Genossenschaften e.V. - "edu", // edu EDUCAUSE - "education", // education Brice Way, LLC - "email", // email Spring Madison, LLC - "emerck", // emerck Merck KGaA - "energy", // energy Binky Birch, LLC - "engineer", // engineer United TLD Holdco Ltd. - "engineering", // engineering Romeo Canyon - "enterprises", // enterprises Snow Oaks, LLC - "epson", // epson Seiko Epson Corporation - "equipment", // equipment Corn Station, LLC - "erni", // erni ERNI Group Holding AG - "esq", // esq Charleston Road Registry Inc. - "estate", // estate Trixy Park, LLC - "eurovision", // eurovision European Broadcasting Union (EBU) - "eus", // eus Puntueus Fundazioa - "events", // events Pioneer Maple, LLC - "everbank", // everbank EverBank - "exchange", // exchange Spring Falls, LLC - "expert", // expert Magic Pass, LLC - "exposed", // exposed Victor Beach, LLC - "express", // express Sea Sunset, LLC - "extraspace", // extraspace Extra Space Storage LLC - "fage", // fage Fage International S.A. - "fail", // fail Atomic Pipe, LLC - "fairwinds", // fairwinds FairWinds Partners, LLC - "faith", // faith dot Faith Limited - "family", // family United TLD Holdco Ltd. - "fan", // fan Asiamix Digital Ltd - "fans", // fans Asiamix Digital Limited - "farm", // farm Just Maple, LLC - "fashion", // fashion Top Level Domain Holdings Limited - "fast", // fast Amazon Registry Services, Inc. - "feedback", // feedback Top Level Spectrum, Inc. - "ferrero", // ferrero Ferrero Trading Lux S.A. - "film", // film Motion Picture Domain Registry Pty Ltd - "final", // final Núcleo de Informação e Coordenação do Ponto BR - NIC.br - "finance", // finance Cotton Cypress, LLC - "financial", // financial Just Cover, LLC - "firestone", // firestone Bridgestone Corporation - "firmdale", // firmdale Firmdale Holdings Limited - "fish", // fish Fox Woods, LLC - "fishing", // fishing Top Level Domain Holdings Limited - "fit", // fit Minds + Machines Group Limited - "fitness", // fitness Brice Orchard, LLC - "flickr", // flickr Yahoo! Domain Services Inc. - "flights", // flights Fox Station, LLC - "florist", // florist Half Cypress, LLC - "flowers", // flowers Uniregistry, Corp. - "flsmidth", // flsmidth FLSmidth A/S - "fly", // fly Charleston Road Registry Inc. - "foo", // foo Charleston Road Registry Inc. - "football", // football Foggy Farms, LLC - "ford", // ford Ford Motor Company - "forex", // forex DOTFOREX REGISTRY LTD - "forsale", // forsale United TLD Holdco, LLC - "forum", // forum Fegistry, LLC - "foundation", // foundation John Dale, LLC - "fox", // fox FOX Registry, LLC - "fresenius", // fresenius Fresenius Immobilien-Verwaltungs-GmbH - "frl", // frl FRLregistry B.V. - "frogans", // frogans OP3FT - "frontier", // frontier Frontier Communications Corporation - "ftr", // ftr Frontier Communications Corporation - "fund", // fund John Castle, LLC - "furniture", // furniture Lone Fields, LLC - "futbol", // futbol United TLD Holdco, Ltd. - "fyi", // fyi Silver Tigers, LLC - "gal", // gal Asociación puntoGAL - "gallery", // gallery Sugar House, LLC - "gallo", // gallo Gallo Vineyards, Inc. - "gallup", // gallup Gallup, Inc. - "game", // game Uniregistry, Corp. - "garden", // garden Top Level Domain Holdings Limited - "gbiz", // gbiz Charleston Road Registry Inc. - "gdn", // gdn Joint Stock Company "Navigation-information systems" - "gea", // gea GEA Group Aktiengesellschaft - "gent", // gent COMBELL GROUP NV/SA - "genting", // genting Resorts World Inc. Pte. Ltd. - "ggee", // ggee GMO Internet, Inc. - "gift", // gift Uniregistry, Corp. - "gifts", // gifts Goose Sky, LLC - "gives", // gives United TLD Holdco Ltd. - "giving", // giving Giving Limited - "glass", // glass Black Cover, LLC - "gle", // gle Charleston Road Registry Inc. - "global", // global Dot Global Domain Registry Limited - "globo", // globo Globo Comunicação e Participações S.A - "gmail", // gmail Charleston Road Registry Inc. - "gmbh", // gmbh Extra Dynamite, LLC - "gmo", // gmo GMO Internet, Inc. - "gmx", // gmx 1&1 Mail & Media GmbH - "gold", // gold June Edge, LLC - "goldpoint", // goldpoint YODOBASHI CAMERA CO.,LTD. - "golf", // golf Lone Falls, LLC - "goo", // goo NTT Resonant Inc. - "goog", // goog Charleston Road Registry Inc. - "google", // google Charleston Road Registry Inc. - "gop", // gop Republican State Leadership Committee, Inc. - "got", // got Amazon Registry Services, Inc. - "gov", // gov General Services Administration Attn: QTDC, 2E08 (.gov Domain Registration) - "grainger", // grainger Grainger Registry Services, LLC - "graphics", // graphics Over Madison, LLC - "gratis", // gratis Pioneer Tigers, LLC - "green", // green Afilias Limited - "gripe", // gripe Corn Sunset, LLC - "group", // group Romeo Town, LLC - "gucci", // gucci Guccio Gucci S.p.a. - "guge", // guge Charleston Road Registry Inc. - "guide", // guide Snow Moon, LLC - "guitars", // guitars Uniregistry, Corp. - "guru", // guru Pioneer Cypress, LLC - "hamburg", // hamburg Hamburg Top-Level-Domain GmbH - "hangout", // hangout Charleston Road Registry Inc. - "haus", // haus United TLD Holdco, LTD. - "hdfcbank", // hdfcbank HDFC Bank Limited - "health", // health DotHealth, LLC - "healthcare", // healthcare Silver Glen, LLC - "help", // help Uniregistry, Corp. - "helsinki", // helsinki City of Helsinki - "here", // here Charleston Road Registry Inc. - "hermes", // hermes Hermes International - "hiphop", // hiphop Uniregistry, Corp. - "hitachi", // hitachi Hitachi, Ltd. - "hiv", // hiv dotHIV gemeinnuetziger e.V. - "hockey", // hockey Half Willow, LLC - "holdings", // holdings John Madison, LLC - "holiday", // holiday Goose Woods, LLC - "homedepot", // homedepot Homer TLC, Inc. - "homes", // homes DERHomes, LLC - "honda", // honda Honda Motor Co., Ltd. - "horse", // horse Top Level Domain Holdings Limited - "host", // host DotHost Inc. - "hosting", // hosting Uniregistry, Corp. - "hoteles", // hoteles Travel Reservations SRL - "hotmail", // hotmail Microsoft Corporation - "house", // house Sugar Park, LLC - "how", // how Charleston Road Registry Inc. - "hsbc", // hsbc HSBC Holdings PLC - "htc", // htc HTC corporation - "hyundai", // hyundai Hyundai Motor Company - "ibm", // ibm International Business Machines Corporation - "icbc", // icbc Industrial and Commercial Bank of China Limited - "ice", // ice IntercontinentalExchange, Inc. - "icu", // icu One.com A/S - "ifm", // ifm ifm electronic gmbh - "iinet", // iinet Connect West Pty. Ltd. - "imamat", // imamat Fondation Aga Khan (Aga Khan Foundation) - "immo", // immo Auburn Bloom, LLC - "immobilien", // immobilien United TLD Holdco Ltd. - "industries", // industries Outer House, LLC - "infiniti", // infiniti NISSAN MOTOR CO., LTD. - "info", // info Afilias Limited - "ing", // ing Charleston Road Registry Inc. - "ink", // ink Top Level Design, LLC - "institute", // institute Outer Maple, LLC - "insurance", // insurance fTLD Registry Services LLC - "insure", // insure Pioneer Willow, LLC - "int", // int Internet Assigned Numbers Authority - "international", // international Wild Way, LLC - "investments", // investments Holly Glen, LLC - "ipiranga", // ipiranga Ipiranga Produtos de Petroleo S.A. - "irish", // irish Dot-Irish LLC - "iselect", // iselect iSelect Ltd - "ismaili", // ismaili Fondation Aga Khan (Aga Khan Foundation) - "ist", // ist Istanbul Metropolitan Municipality - "istanbul", // istanbul Istanbul Metropolitan Municipality / Medya A.S. - "itau", // itau Itau Unibanco Holding S.A. - "iwc", // iwc Richemont DNS Inc. - "jaguar", // jaguar Jaguar Land Rover Ltd - "java", // java Oracle Corporation - "jcb", // jcb JCB Co., Ltd. - "jcp", // jcp JCP Media, Inc. - "jetzt", // jetzt New TLD Company AB - "jewelry", // jewelry Wild Bloom, LLC - "jlc", // jlc Richemont DNS Inc. - "jll", // jll Jones Lang LaSalle Incorporated - "jmp", // jmp Matrix IP LLC - "jnj", // jnj Johnson & Johnson Services, Inc. - "jobs", // jobs Employ Media LLC - "joburg", // joburg ZA Central Registry NPC trading as ZA Central Registry - "jot", // jot Amazon Registry Services, Inc. - "joy", // joy Amazon Registry Services, Inc. - "jpmorgan", // jpmorgan JPMorgan Chase & Co. - "jprs", // jprs Japan Registry Services Co., Ltd. - "juegos", // juegos Uniregistry, Corp. - "kaufen", // kaufen United TLD Holdco Ltd. - "kddi", // kddi KDDI CORPORATION - "kerryhotels", // kerryhotels Kerry Trading Co. Limited - "kerrylogistics", // kerrylogistics Kerry Trading Co. Limited - "kerryproperties", // kerryproperties Kerry Trading Co. Limited - "kfh", // kfh Kuwait Finance House - "kia", // kia KIA MOTORS CORPORATION - "kim", // kim Afilias Limited - "kinder", // kinder Ferrero Trading Lux S.A. - "kitchen", // kitchen Just Goodbye, LLC - "kiwi", // kiwi DOT KIWI LIMITED - "koeln", // koeln NetCologne Gesellschaft für Telekommunikation mbH - "komatsu", // komatsu Komatsu Ltd. - "kpmg", // kpmg KPMG International Cooperative (KPMG International Genossenschaft) - "kpn", // kpn Koninklijke KPN N.V. - "krd", // krd KRG Department of Information Technology - "kred", // kred KredTLD Pty Ltd - "kuokgroup", // kuokgroup Kerry Trading Co. Limited - "kyoto", // kyoto Academic Institution: Kyoto Jyoho Gakuen - "lacaixa", // lacaixa CAIXA D'ESTALVIS I PENSIONS DE BARCELONA - "lamborghini", // lamborghini Automobili Lamborghini S.p.A. - "lamer", // lamer The Estée Lauder Companies Inc. - "lancaster", // lancaster LANCASTER - "land", // land Pine Moon, LLC - "landrover", // landrover Jaguar Land Rover Ltd - "lanxess", // lanxess LANXESS Corporation - "lasalle", // lasalle Jones Lang LaSalle Incorporated - "lat", // lat ECOM-LAC Federación de Latinoamérica y el Caribe para Internet y el Comercio Electrónico - "latrobe", // latrobe La Trobe University - "law", // law Minds + Machines Group Limited - "lawyer", // lawyer United TLD Holdco, Ltd - "lds", // lds IRI Domain Management, LLC - "lease", // lease Victor Trail, LLC - "leclerc", // leclerc A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc - "legal", // legal Blue Falls, LLC - "lexus", // lexus TOYOTA MOTOR CORPORATION - "lgbt", // lgbt Afilias Limited - "liaison", // liaison Liaison Technologies, Incorporated - "lidl", // lidl Schwarz Domains und Services GmbH & Co. KG - "life", // life Trixy Oaks, LLC - "lifeinsurance", // lifeinsurance American Council of Life Insurers - "lifestyle", // lifestyle Lifestyle Domain Holdings, Inc. - "lighting", // lighting John McCook, LLC - "like", // like Amazon Registry Services, Inc. - "limited", // limited Big Fest, LLC - "limo", // limo Hidden Frostbite, LLC - "lincoln", // lincoln Ford Motor Company - "linde", // linde Linde Aktiengesellschaft - "link", // link Uniregistry, Corp. - "live", // live United TLD Holdco Ltd. - "living", // living Lifestyle Domain Holdings, Inc. - "lixil", // lixil LIXIL Group Corporation - "loan", // loan dot Loan Limited - "loans", // loans June Woods, LLC - "locus", // locus Locus Analytics LLC - "lol", // lol Uniregistry, Corp. - "london", // london Dot London Domains Limited - "lotte", // lotte Lotte Holdings Co., Ltd. - "lotto", // lotto Afilias Limited - "love", // love Merchant Law Group LLP - "ltd", // ltd Over Corner, LLC - "ltda", // ltda InterNetX Corp. - "lupin", // lupin LUPIN LIMITED - "luxe", // luxe Top Level Domain Holdings Limited - "luxury", // luxury Luxury Partners LLC - "madrid", // madrid Comunidad de Madrid - "maif", // maif Mutuelle Assurance Instituteur France (MAIF) - "maison", // maison Victor Frostbite, LLC - "makeup", // makeup L'Oréal - "man", // man MAN SE - "management", // management John Goodbye, LLC - "mango", // mango PUNTO FA S.L. - "market", // market Unitied TLD Holdco, Ltd - "marketing", // marketing Fern Pass, LLC - "markets", // markets DOTMARKETS REGISTRY LTD - "marriott", // marriott Marriott Worldwide Corporation - "mba", // mba Lone Hollow, LLC - "med", // med Medistry LLC - "media", // media Grand Glen, LLC - "meet", // meet Afilias Limited - "melbourne", // melbourne The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation - "meme", // meme Charleston Road Registry Inc. - "memorial", // memorial Dog Beach, LLC - "men", // men Exclusive Registry Limited - "menu", // menu Wedding TLD2, LLC - "meo", // meo PT Comunicacoes S.A. - "miami", // miami Top Level Domain Holdings Limited - "microsoft", // microsoft Microsoft Corporation - "mil", // mil DoD Network Information Center - "mini", // mini Bayerische Motoren Werke Aktiengesellschaft - "mls", // mls The Canadian Real Estate Association - "mma", // mma MMA IARD - "mobi", // mobi Afilias Technologies Limited dba dotMobi - "mobily", // mobily GreenTech Consultancy Company W.L.L. - "moda", // moda United TLD Holdco Ltd. - "moe", // moe Interlink Co., Ltd. - "moi", // moi Amazon Registry Services, Inc. - "mom", // mom Uniregistry, Corp. - "monash", // monash Monash University - "money", // money Outer McCook, LLC - "montblanc", // montblanc Richemont DNS Inc. - "mormon", // mormon IRI Domain Management, LLC ("Applicant") - "mortgage", // mortgage United TLD Holdco, Ltd - "moscow", // moscow Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) - "motorcycles", // motorcycles DERMotorcycles, LLC - "mov", // mov Charleston Road Registry Inc. - "movie", // movie New Frostbite, LLC - "movistar", // movistar Telefónica S.A. - "mtn", // mtn MTN Dubai Limited - "mtpc", // mtpc Mitsubishi Tanabe Pharma Corporation - "mtr", // mtr MTR Corporation Limited - "museum", // museum Museum Domain Management Association - "mutual", // mutual Northwestern Mutual MU TLD Registry, LLC - "mutuelle", // mutuelle Fédération Nationale de la Mutualité Française - "nadex", // nadex Nadex Domains, Inc - "nagoya", // nagoya GMO Registry, Inc. - "name", // name VeriSign Information Services, Inc. - "natura", // natura NATURA COSMÉTICOS S.A. - "navy", // navy United TLD Holdco Ltd. - "nec", // nec NEC Corporation - "net", // net VeriSign Global Registry Services - "netbank", // netbank COMMONWEALTH BANK OF AUSTRALIA - "network", // network Trixy Manor, LLC - "neustar", // neustar NeuStar, Inc. - "new", // new Charleston Road Registry Inc. - "news", // news United TLD Holdco Ltd. - "nexus", // nexus Charleston Road Registry Inc. - "ngo", // ngo Public Interest Registry - "nhk", // nhk Japan Broadcasting Corporation (NHK) - "nico", // nico DWANGO Co., Ltd. - "nikon", // nikon NIKON CORPORATION - "ninja", // ninja United TLD Holdco Ltd. - "nissan", // nissan NISSAN MOTOR CO., LTD. - "nissay", // nissay Nippon Life Insurance Company - "nokia", // nokia Nokia Corporation - "northwesternmutual", // northwesternmutual Northwestern Mutual Registry, LLC - "norton", // norton Symantec Corporation - "nowruz", // nowruz Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. - "nra", // nra NRA Holdings Company, INC. - "nrw", // nrw Minds + Machines GmbH - "ntt", // ntt NIPPON TELEGRAPH AND TELEPHONE CORPORATION - "nyc", // nyc The City of New York by and through the New York City Department of Information Technology & Telecommunications - "obi", // obi OBI Group Holding SE & Co. KGaA - "office", // office Microsoft Corporation - "okinawa", // okinawa BusinessRalliart inc. - "omega", // omega The Swatch Group Ltd - "one", // one One.com A/S - "ong", // ong Public Interest Registry - "onl", // onl I-REGISTRY Ltd., Niederlassung Deutschland - "online", // online DotOnline Inc. - "ooo", // ooo INFIBEAM INCORPORATION LIMITED - "oracle", // oracle Oracle Corporation - "orange", // orange Orange Brand Services Limited - "org", // org Public Interest Registry (PIR) - "organic", // organic Afilias Limited - "origins", // origins The Estée Lauder Companies Inc. - "osaka", // osaka Interlink Co., Ltd. - "otsuka", // otsuka Otsuka Holdings Co., Ltd. - "ovh", // ovh OVH SAS - "page", // page Charleston Road Registry Inc. - "pamperedchef", // pamperedchef The Pampered Chef, Ltd. - "panerai", // panerai Richemont DNS Inc. - "paris", // paris City of Paris - "pars", // pars Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. - "partners", // partners Magic Glen, LLC - "parts", // parts Sea Goodbye, LLC - "party", // party Blue Sky Registry Limited - "passagens", // passagens Travel Reservations SRL - "pet", // pet Afilias plc - "pharmacy", // pharmacy National Association of Boards of Pharmacy - "philips", // philips Koninklijke Philips N.V. - "photo", // photo Uniregistry, Corp. - "photography", // photography Sugar Glen, LLC - "photos", // photos Sea Corner, LLC - "physio", // physio PhysBiz Pty Ltd - "piaget", // piaget Richemont DNS Inc. - "pics", // pics Uniregistry, Corp. - "pictet", // pictet Pictet Europe S.A. - "pictures", // pictures Foggy Sky, LLC - "pid", // pid Top Level Spectrum, Inc. - "pin", // pin Amazon Registry Services, Inc. - "ping", // ping Ping Registry Provider, Inc. - "pink", // pink Afilias Limited - "pizza", // pizza Foggy Moon, LLC - "place", // place Snow Galley, LLC - "play", // play Charleston Road Registry Inc. - "playstation", // playstation Sony Computer Entertainment Inc. - "plumbing", // plumbing Spring Tigers, LLC - "plus", // plus Sugar Mill, LLC - "pohl", // pohl Deutsche Vermögensberatung Aktiengesellschaft DVAG - "poker", // poker Afilias Domains No. 5 Limited - "porn", // porn ICM Registry PN LLC - "post", // post Universal Postal Union - "praxi", // praxi Praxi S.p.A. - "press", // press DotPress Inc. - "pro", // pro Registry Services Corporation dba RegistryPro - "prod", // prod Charleston Road Registry Inc. - "productions", // productions Magic Birch, LLC - "prof", // prof Charleston Road Registry Inc. - "progressive", // progressive Progressive Casualty Insurance Company - "promo", // promo Afilias plc - "properties", // properties Big Pass, LLC - "property", // property Uniregistry, Corp. - "protection", // protection XYZ.COM LLC - "pub", // pub United TLD Holdco Ltd. - "pwc", // pwc PricewaterhouseCoopers LLP - "qpon", // qpon dotCOOL, Inc. - "quebec", // quebec PointQuébec Inc - "quest", // quest Quest ION Limited - "racing", // racing Premier Registry Limited - "read", // read Amazon Registry Services, Inc. - "realtor", // realtor Real Estate Domains LLC - "realty", // realty Fegistry, LLC - "recipes", // recipes Grand Island, LLC - "red", // red Afilias Limited - "redstone", // redstone Redstone Haute Couture Co., Ltd. - "redumbrella", // redumbrella Travelers TLD, LLC - "rehab", // rehab United TLD Holdco Ltd. - "reise", // reise Foggy Way, LLC - "reisen", // reisen New Cypress, LLC - "reit", // reit National Association of Real Estate Investment Trusts, Inc. - "ren", // ren Beijing Qianxiang Wangjing Technology Development Co., Ltd. - "rent", // rent XYZ.COM LLC - "rentals", // rentals Big Hollow,LLC - "repair", // repair Lone Sunset, LLC - "report", // report Binky Glen, LLC - "republican", // republican United TLD Holdco Ltd. - "rest", // rest Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable - "restaurant", // restaurant Snow Avenue, LLC - "review", // review dot Review Limited - "reviews", // reviews United TLD Holdco, Ltd. - "rexroth", // rexroth Robert Bosch GMBH - "rich", // rich I-REGISTRY Ltd., Niederlassung Deutschland - "ricoh", // ricoh Ricoh Company, Ltd. - "rio", // rio Empresa Municipal de Informática SA - IPLANRIO - "rip", // rip United TLD Holdco Ltd. - "rocher", // rocher Ferrero Trading Lux S.A. - "rocks", // rocks United TLD Holdco, LTD. - "rodeo", // rodeo Top Level Domain Holdings Limited - "room", // room Amazon Registry Services, Inc. - "rsvp", // rsvp Charleston Road Registry Inc. - "ruhr", // ruhr regiodot GmbH & Co. KG - "run", // run Snow Park, LLC - "rwe", // rwe RWE AG - "ryukyu", // ryukyu BusinessRalliart inc. - "saarland", // saarland dotSaarland GmbH - "safe", // safe Amazon Registry Services, Inc. - "safety", // safety Safety Registry Services, LLC. - "sakura", // sakura SAKURA Internet Inc. - "sale", // sale United TLD Holdco, Ltd - "salon", // salon Outer Orchard, LLC - "samsung", // samsung SAMSUNG SDS CO., LTD - "sandvik", // sandvik Sandvik AB - "sandvikcoromant", // sandvikcoromant Sandvik AB - "sanofi", // sanofi Sanofi - "sap", // sap SAP AG - "sapo", // sapo PT Comunicacoes S.A. - "sarl", // sarl Delta Orchard, LLC - "sas", // sas Research IP LLC - "saxo", // saxo Saxo Bank A/S - "sbi", // sbi STATE BANK OF INDIA - "sbs", // sbs SPECIAL BROADCASTING SERVICE CORPORATION - "sca", // sca SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ) - "scb", // scb The Siam Commercial Bank Public Company Limited ("SCB") - "schaeffler", // schaeffler Schaeffler Technologies AG & Co. KG - "schmidt", // schmidt SALM S.A.S. - "scholarships", // scholarships Scholarships.com, LLC - "school", // school Little Galley, LLC - "schule", // schule Outer Moon, LLC - "schwarz", // schwarz Schwarz Domains und Services GmbH & Co. KG - "science", // science dot Science Limited - "scor", // scor SCOR SE - "scot", // scot Dot Scot Registry Limited - "seat", // seat SEAT, S.A. (Sociedad Unipersonal) - "security", // security XYZ.COM LLC - "seek", // seek Seek Limited - "select", // select iSelect Ltd - "sener", // sener Sener Ingeniería y Sistemas, S.A. - "services", // services Fox Castle, LLC - "seven", // seven Seven West Media Ltd - "sew", // sew SEW-EURODRIVE GmbH & Co KG - "sex", // sex ICM Registry SX LLC - "sexy", // sexy Uniregistry, Corp. - "sfr", // sfr Societe Francaise du Radiotelephone - SFR - "sharp", // sharp Sharp Corporation - "shaw", // shaw Shaw Cablesystems G.P. - "shell", // shell Shell Information Technology International Inc - "shia", // shia Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. - "shiksha", // shiksha Afilias Limited - "shoes", // shoes Binky Galley, LLC - "shouji", // shouji QIHOO 360 TECHNOLOGY CO. LTD. - "show", // show Snow Beach, LLC - "shriram", // shriram Shriram Capital Ltd. - "sina", // sina Sina Corporation - "singles", // singles Fern Madison, LLC - "site", // site DotSite Inc. - "ski", // ski STARTING DOT LIMITED - "skin", // skin L'Oréal - "sky", // sky Sky International AG - "skype", // skype Microsoft Corporation - "smile", // smile Amazon Registry Services, Inc. - "sncf", // sncf SNCF (Société Nationale des Chemins de fer Francais) - "soccer", // soccer Foggy Shadow, LLC - "social", // social United TLD Holdco Ltd. - "softbank", // softbank SoftBank Group Corp. - "software", // software United TLD Holdco, Ltd - "sohu", // sohu Sohu.com Limited - "solar", // solar Ruby Town, LLC - "solutions", // solutions Silver Cover, LLC - "song", // song Amazon Registry Services, Inc. - "sony", // sony Sony Corporation - "soy", // soy Charleston Road Registry Inc. - "space", // space DotSpace Inc. - "spiegel", // spiegel SPIEGEL-Verlag Rudolf Augstein GmbH & Co. KG - "spot", // spot Amazon Registry Services, Inc. - "spreadbetting", // spreadbetting DOTSPREADBETTING REGISTRY LTD - "srl", // srl InterNetX Corp. - "stada", // stada STADA Arzneimittel AG - "star", // star Star India Private Limited - "starhub", // starhub StarHub Limited - "statebank", // statebank STATE BANK OF INDIA - "statefarm", // statefarm State Farm Mutual Automobile Insurance Company - "statoil", // statoil Statoil ASA - "stc", // stc Saudi Telecom Company - "stcgroup", // stcgroup Saudi Telecom Company - "stockholm", // stockholm Stockholms kommun - "storage", // storage Self Storage Company LLC - "store", // store DotStore Inc. - "stream", // stream dot Stream Limited - "studio", // studio United TLD Holdco Ltd. - "study", // study OPEN UNIVERSITIES AUSTRALIA PTY LTD - "style", // style Binky Moon, LLC - "sucks", // sucks Vox Populi Registry Ltd. - "supplies", // supplies Atomic Fields, LLC - "supply", // supply Half Falls, LLC - "support", // support Grand Orchard, LLC - "surf", // surf Top Level Domain Holdings Limited - "surgery", // surgery Tin Avenue, LLC - "suzuki", // suzuki SUZUKI MOTOR CORPORATION - "swatch", // swatch The Swatch Group Ltd - "swiss", // swiss Swiss Confederation - "sydney", // sydney State of New South Wales, Department of Premier and Cabinet - "symantec", // symantec Symantec Corporation - "systems", // systems Dash Cypress, LLC - "tab", // tab Tabcorp Holdings Limited - "taipei", // taipei Taipei City Government - "talk", // talk Amazon Registry Services, Inc. - "taobao", // taobao Alibaba Group Holding Limited - "tatamotors", // tatamotors Tata Motors Ltd - "tatar", // tatar Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic" - "tattoo", // tattoo Uniregistry, Corp. - "tax", // tax Storm Orchard, LLC - "taxi", // taxi Pine Falls, LLC - "tci", // tci Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. - "team", // team Atomic Lake, LLC - "tech", // tech Dot Tech LLC - "technology", // technology Auburn Falls, LLC - "tel", // tel Telnic Ltd. - "telecity", // telecity TelecityGroup International Limited - "telefonica", // telefonica Telefónica S.A. - "temasek", // temasek Temasek Holdings (Private) Limited - "tennis", // tennis Cotton Bloom, LLC - "teva", // teva Teva Pharmaceutical Industries Limited - "thd", // thd Homer TLC, Inc. - "theater", // theater Blue Tigers, LLC - "theatre", // theatre XYZ.COM LLC - "tickets", // tickets Accent Media Limited - "tienda", // tienda Victor Manor, LLC - "tiffany", // tiffany Tiffany and Company - "tips", // tips Corn Willow, LLC - "tires", // tires Dog Edge, LLC - "tirol", // tirol punkt Tirol GmbH - "tmall", // tmall Alibaba Group Holding Limited - "today", // today Pearl Woods, LLC - "tokyo", // tokyo GMO Registry, Inc. - "tools", // tools Pioneer North, LLC - "top", // top Jiangsu Bangning Science & Technology Co.,Ltd. - "toray", // toray Toray Industries, Inc. - "toshiba", // toshiba TOSHIBA Corporation - "total", // total Total SA - "tours", // tours Sugar Station, LLC - "town", // town Koko Moon, LLC - "toyota", // toyota TOYOTA MOTOR CORPORATION - "toys", // toys Pioneer Orchard, LLC - "trade", // trade Elite Registry Limited - "trading", // trading DOTTRADING REGISTRY LTD - "training", // training Wild Willow, LLC - "travel", // travel Tralliance Registry Management Company, LLC. - "travelers", // travelers Travelers TLD, LLC - "travelersinsurance", // travelersinsurance Travelers TLD, LLC - "trust", // trust Artemis Internet Inc - "trv", // trv Travelers TLD, LLC - "tube", // tube Latin American Telecom LLC - "tui", // tui TUI AG - "tunes", // tunes Amazon Registry Services, Inc. - "tushu", // tushu Amazon Registry Services, Inc. - "tvs", // tvs T V SUNDRAM IYENGAR & SONS PRIVATE LIMITED - "ubs", // ubs UBS AG - "unicom", // unicom China United Network Communications Corporation Limited - "university", // university Little Station, LLC - "uno", // uno Dot Latin LLC - "uol", // uol UBN INTERNET LTDA. - "vacations", // vacations Atomic Tigers, LLC - "vana", // vana Lifestyle Domain Holdings, Inc. - "vegas", // vegas Dot Vegas, Inc. - "ventures", // ventures Binky Lake, LLC - "verisign", // verisign VeriSign, Inc. - "versicherung", // versicherung dotversicherung-registry GmbH - "vet", // vet United TLD Holdco, Ltd - "viajes", // viajes Black Madison, LLC - "video", // video United TLD Holdco, Ltd - "vig", // vig VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe - "viking", // viking Viking River Cruises (Bermuda) Ltd. - "villas", // villas New Sky, LLC - "vin", // vin Holly Shadow, LLC - "vip", // vip Minds + Machines Group Limited - "virgin", // virgin Virgin Enterprises Limited - "vision", // vision Koko Station, LLC - "vista", // vista Vistaprint Limited - "vistaprint", // vistaprint Vistaprint Limited - "viva", // viva Saudi Telecom Company - "vlaanderen", // vlaanderen DNS.be vzw - "vodka", // vodka Top Level Domain Holdings Limited - "volkswagen", // volkswagen Volkswagen Group of America Inc. - "vote", // vote Monolith Registry LLC - "voting", // voting Valuetainment Corp. - "voto", // voto Monolith Registry LLC - "voyage", // voyage Ruby House, LLC - "vuelos", // vuelos Travel Reservations SRL - "wales", // wales Nominet UK - "walter", // walter Sandvik AB - "wang", // wang Zodiac Registry Limited - "wanggou", // wanggou Amazon Registry Services, Inc. - "watch", // watch Sand Shadow, LLC - "watches", // watches Richemont DNS Inc. - "weather", // weather The Weather Channel, LLC - "weatherchannel", // weatherchannel The Weather Channel, LLC - "webcam", // webcam dot Webcam Limited - "weber", // weber Saint-Gobain Weber SA - "website", // website DotWebsite Inc. - "wed", // wed Atgron, Inc. - "wedding", // wedding Top Level Domain Holdings Limited - "weibo", // weibo Sina Corporation - "weir", // weir Weir Group IP Limited - "whoswho", // whoswho Who's Who Registry - "wien", // wien punkt.wien GmbH - "wiki", // wiki Top Level Design, LLC - "williamhill", // williamhill William Hill Organization Limited - "win", // win First Registry Limited - "windows", // windows Microsoft Corporation - "wine", // wine June Station, LLC - "wme", // wme William Morris Endeavor Entertainment, LLC - "wolterskluwer", // wolterskluwer Wolters Kluwer N.V. - "work", // work Top Level Domain Holdings Limited - "works", // works Little Dynamite, LLC - "world", // world Bitter Fields, LLC - "wtc", // wtc World Trade Centers Association, Inc. - "wtf", // wtf Hidden Way, LLC - "xbox", // xbox Microsoft Corporation - "xerox", // xerox Xerox DNHC LLC - "xihuan", // xihuan QIHOO 360 TECHNOLOGY CO. LTD. - "xin", // xin Elegant Leader Limited - "xn--11b4c3d", // कॉम VeriSign Sarl - "xn--1ck2e1b", // セール Amazon Registry Services, Inc. - "xn--1qqw23a", // 佛山 Guangzhou YU Wei Information Technology Co., Ltd. - "xn--30rr7y", // 慈善 Excellent First Limited - "xn--3bst00m", // 集团 Eagle Horizon Limited - "xn--3ds443g", // 在线 TLD REGISTRY LIMITED - "xn--3pxu8k", // 点看 VeriSign Sarl - "xn--42c2d9a", // คอม VeriSign Sarl - "xn--45q11c", // 八卦 Zodiac Scorpio Limited - "xn--4gbrim", // موقع Suhub Electronic Establishment - "xn--55qw42g", // 公益 China Organizational Name Administration Center - "xn--55qx5d", // 公司 Computer Network Information Center of Chinese Academy of Sciences (China Internet Network Information Center) - "xn--5tzm5g", // 网站 Global Website TLD Asia Limited - "xn--6frz82g", // 移动 Afilias Limited - "xn--6qq986b3xl", // 我爱你 Tycoon Treasure Limited - "xn--80adxhks", // москва Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) - "xn--80asehdb", // онлайн CORE Association - "xn--80aswg", // сайт CORE Association - "xn--8y0a063a", // 联通 China United Network Communications Corporation Limited - "xn--9dbq2a", // קום VeriSign Sarl - "xn--9et52u", // 时尚 RISE VICTORY LIMITED - "xn--9krt00a", // 微博 Sina Corporation - "xn--b4w605ferd", // 淡马锡 Temasek Holdings (Private) Limited - "xn--bck1b9a5dre4c", // ファッション Amazon Registry Services, Inc. - "xn--c1avg", // орг Public Interest Registry - "xn--c2br7g", // नेट VeriSign Sarl - "xn--cck2b3b", // ストア Amazon Registry Services, Inc. - "xn--cg4bki", // 삼성 SAMSUNG SDS CO., LTD - "xn--czr694b", // 商标 HU YI GLOBAL INFORMATION RESOURCES(HOLDING) COMPANY.HONGKONG LIMITED - "xn--czrs0t", // 商店 Wild Island, LLC - "xn--czru2d", // 商城 Zodiac Aquarius Limited - "xn--d1acj3b", // дети The Foundation for Network Initiatives “The Smart Internet” - "xn--eckvdtc9d", // ポイント Amazon Registry Services, Inc. - "xn--efvy88h", // 新闻 Xinhua News Agency Guangdong Branch 新华通讯社广东分社 - "xn--estv75g", // 工行 Industrial and Commercial Bank of China Limited - "xn--fct429k", // 家電 Amazon Registry Services, Inc. - "xn--fhbei", // كوم VeriSign Sarl - "xn--fiq228c5hs", // 中文网 TLD REGISTRY LIMITED - "xn--fiq64b", // 中信 CITIC Group Corporation - "xn--fjq720a", // 娱乐 Will Bloom, LLC - "xn--flw351e", // 谷歌 Charleston Road Registry Inc. - "xn--g2xx48c", // 购物 Minds + Machines Group Limited - "xn--gckr3f0f", // クラウド Amazon Registry Services, Inc. - "xn--hxt814e", // 网店 Zodiac Libra Limited - "xn--i1b6b1a6a2e", // संगठन Public Interest Registry - "xn--imr513n", // 餐厅 HU YI GLOBAL INFORMATION RESOURCES (HOLDING) COMPANY. HONGKONG LIMITED - "xn--io0a7i", // 网络 Computer Network Information Center of Chinese Academy of Sciences (China Internet Network Information Center) - "xn--j1aef", // ком VeriSign Sarl - "xn--jlq61u9w7b", // 诺基亚 Nokia Corporation - "xn--jvr189m", // 食品 Amazon Registry Services, Inc. - "xn--kcrx77d1x4a", // 飞利浦 Koninklijke Philips N.V. - "xn--kpu716f", // 手表 Richemont DNS Inc. - "xn--kput3i", // 手机 Beijing RITT-Net Technology Development Co., Ltd - "xn--mgba3a3ejt", // ارامكو Aramco Services Company - "xn--mgbab2bd", // بازار CORE Association - "xn--mgbb9fbpob", // موبايلي GreenTech Consultancy Company W.L.L. - "xn--mgbca7dzdo", // ابوظبي Abu Dhabi Systems and Information Centre - "xn--mgbt3dhd", // همراه Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. - "xn--mk1bu44c", // 닷컴 VeriSign Sarl - "xn--mxtq1m", // 政府 Net-Chinese Co., Ltd. - "xn--ngbc5azd", // شبكة International Domain Registry Pty. Ltd. - "xn--ngbe9e0a", // بيتك Kuwait Finance House - "xn--nqv7f", // 机构 Public Interest Registry - "xn--nqv7fs00ema", // 组织机构 Public Interest Registry - "xn--nyqy26a", // 健康 Stable Tone Limited - "xn--p1acf", // рус Rusnames Limited - "xn--pbt977c", // 珠宝 Richemont DNS Inc. - "xn--pssy2u", // 大拿 VeriSign Sarl - "xn--q9jyb4c", // みんな Charleston Road Registry Inc. - "xn--qcka1pmc", // グーグル Charleston Road Registry Inc. - "xn--rhqv96g", // 世界 Stable Tone Limited - "xn--rovu88b", // 書籍 Amazon EU S.à r.l. - "xn--ses554g", // 网址 KNET Co., Ltd - "xn--t60b56a", // 닷넷 VeriSign Sarl - "xn--tckwe", // コム VeriSign Sarl - "xn--unup4y", // 游戏 Spring Fields, LLC - "xn--vermgensberater-ctb", // VERMöGENSBERATER Deutsche Vermögensberatung Aktiengesellschaft DVAG - "xn--vermgensberatung-pwb", // VERMöGENSBERATUNG Deutsche Vermögensberatung Aktiengesellschaft DVAG - "xn--vhquv", // 企业 Dash McCook, LLC - "xn--vuq861b", // 信息 Beijing Tele-info Network Technology Co., Ltd. - "xn--w4r85el8fhu5dnra", // 嘉里大酒店 Kerry Trading Co. Limited - "xn--xhq521b", // 广东 Guangzhou YU Wei Information Technology Co., Ltd. - "xn--zfr164b", // 政务 China Organizational Name Administration Center - "xperia", // xperia Sony Mobile Communications AB - "xxx", // xxx ICM Registry LLC - "xyz", // xyz XYZ.COM LLC - "yachts", // yachts DERYachts, LLC - "yahoo", // yahoo Yahoo! Domain Services Inc. - "yamaxun", // yamaxun Amazon Registry Services, Inc. - "yandex", // yandex YANDEX, LLC - "yodobashi", // yodobashi YODOBASHI CAMERA CO.,LTD. - "yoga", // yoga Top Level Domain Holdings Limited - "yokohama", // yokohama GMO Registry, Inc. - "you", // you Amazon Registry Services, Inc. - "youtube", // youtube Charleston Road Registry Inc. - "yun", // yun QIHOO 360 TECHNOLOGY CO. LTD. - "zara", // zara Industria de Diseño Textil, S.A. (INDITEX, S.A.) - "zero", // zero Amazon Registry Services, Inc. - "zip", // zip Charleston Road Registry Inc. - "zone", // zone Outer Falls, LLC - "zuerich", // zuerich Kanton Zürich (Canton of Zurich) - }; - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search - private static final String[] COUNTRY_CODE_TLDS = new String[] { - "ac", // Ascension Island - "ad", // Andorra - "ae", // United Arab Emirates - "af", // Afghanistan - "ag", // Antigua and Barbuda - "ai", // Anguilla - "al", // Albania - "am", // Armenia -// "an", // Netherlands Antilles (retired) - "ao", // Angola - "aq", // Antarctica - "ar", // Argentina - "as", // American Samoa - "at", // Austria - "au", // Australia (includes Ashmore and Cartier Islands and Coral Sea Islands) - "aw", // Aruba - "ax", // Åland - "az", // Azerbaijan - "ba", // Bosnia and Herzegovina - "bb", // Barbados - "bd", // Bangladesh - "be", // Belgium - "bf", // Burkina Faso - "bg", // Bulgaria - "bh", // Bahrain - "bi", // Burundi - "bj", // Benin - "bm", // Bermuda - "bn", // Brunei Darussalam - "bo", // Bolivia - "br", // Brazil - "bs", // Bahamas - "bt", // Bhutan - "bv", // Bouvet Island - "bw", // Botswana - "by", // Belarus - "bz", // Belize - "ca", // Canada - "cc", // Cocos (Keeling) Islands - "cd", // Democratic Republic of the Congo (formerly Zaire) - "cf", // Central African Republic - "cg", // Republic of the Congo - "ch", // Switzerland - "ci", // Côte d'Ivoire - "ck", // Cook Islands - "cl", // Chile - "cm", // Cameroon - "cn", // China, mainland - "co", // Colombia - "cr", // Costa Rica - "cu", // Cuba - "cv", // Cape Verde - "cw", // Curaçao - "cx", // Christmas Island - "cy", // Cyprus - "cz", // Czech Republic - "de", // Germany - "dj", // Djibouti - "dk", // Denmark - "dm", // Dominica - "do", // Dominican Republic - "dz", // Algeria - "ec", // Ecuador - "ee", // Estonia - "eg", // Egypt - "er", // Eritrea - "es", // Spain - "et", // Ethiopia - "eu", // European Union - "fi", // Finland - "fj", // Fiji - "fk", // Falkland Islands - "fm", // Federated States of Micronesia - "fo", // Faroe Islands - "fr", // France - "ga", // Gabon - "gb", // Great Britain (United Kingdom) - "gd", // Grenada - "ge", // Georgia - "gf", // French Guiana - "gg", // Guernsey - "gh", // Ghana - "gi", // Gibraltar - "gl", // Greenland - "gm", // The Gambia - "gn", // Guinea - "gp", // Guadeloupe - "gq", // Equatorial Guinea - "gr", // Greece - "gs", // South Georgia and the South Sandwich Islands - "gt", // Guatemala - "gu", // Guam - "gw", // Guinea-Bissau - "gy", // Guyana - "hk", // Hong Kong - "hm", // Heard Island and McDonald Islands - "hn", // Honduras - "hr", // Croatia (Hrvatska) - "ht", // Haiti - "hu", // Hungary - "id", // Indonesia - "ie", // Ireland (Éire) - "il", // Israel - "im", // Isle of Man - "in", // India - "io", // British Indian Ocean Territory - "iq", // Iraq - "ir", // Iran - "is", // Iceland - "it", // Italy - "je", // Jersey - "jm", // Jamaica - "jo", // Jordan - "jp", // Japan - "ke", // Kenya - "kg", // Kyrgyzstan - "kh", // Cambodia (Khmer) - "ki", // Kiribati - "km", // Comoros - "kn", // Saint Kitts and Nevis - "kp", // North Korea - "kr", // South Korea - "kw", // Kuwait - "ky", // Cayman Islands - "kz", // Kazakhstan - "la", // Laos (currently being marketed as the official domain for Los Angeles) - "lb", // Lebanon - "lc", // Saint Lucia - "li", // Liechtenstein - "lk", // Sri Lanka - "lr", // Liberia - "ls", // Lesotho - "lt", // Lithuania - "lu", // Luxembourg - "lv", // Latvia - "ly", // Libya - "ma", // Morocco - "mc", // Monaco - "md", // Moldova - "me", // Montenegro - "mg", // Madagascar - "mh", // Marshall Islands - "mk", // Republic of Macedonia - "ml", // Mali - "mm", // Myanmar - "mn", // Mongolia - "mo", // Macau - "mp", // Northern Mariana Islands - "mq", // Martinique - "mr", // Mauritania - "ms", // Montserrat - "mt", // Malta - "mu", // Mauritius - "mv", // Maldives - "mw", // Malawi - "mx", // Mexico - "my", // Malaysia - "mz", // Mozambique - "na", // Namibia - "nc", // New Caledonia - "ne", // Niger - "nf", // Norfolk Island - "ng", // Nigeria - "ni", // Nicaragua - "nl", // Netherlands - "no", // Norway - "np", // Nepal - "nr", // Nauru - "nu", // Niue - "nz", // New Zealand - "om", // Oman - "pa", // Panama - "pe", // Peru - "pf", // French Polynesia With Clipperton Island - "pg", // Papua New Guinea - "ph", // Philippines - "pk", // Pakistan - "pl", // Poland - "pm", // Saint-Pierre and Miquelon - "pn", // Pitcairn Islands - "pr", // Puerto Rico - "ps", // Palestinian territories (PA-controlled West Bank and Gaza Strip) - "pt", // Portugal - "pw", // Palau - "py", // Paraguay - "qa", // Qatar - "re", // Réunion - "ro", // Romania - "rs", // Serbia - "ru", // Russia - "rw", // Rwanda - "sa", // Saudi Arabia - "sb", // Solomon Islands - "sc", // Seychelles - "sd", // Sudan - "se", // Sweden - "sg", // Singapore - "sh", // Saint Helena - "si", // Slovenia - "sj", // Svalbard and Jan Mayen Islands Not in use (Norwegian dependencies; see .no) - "sk", // Slovakia - "sl", // Sierra Leone - "sm", // San Marino - "sn", // Senegal - "so", // Somalia - "sr", // Suriname - "st", // São Tomé and Príncipe - "su", // Soviet Union (deprecated) - "sv", // El Salvador - "sx", // Sint Maarten - "sy", // Syria - "sz", // Swaziland - "tc", // Turks and Caicos Islands - "td", // Chad - "tf", // French Southern and Antarctic Lands - "tg", // Togo - "th", // Thailand - "tj", // Tajikistan - "tk", // Tokelau - "tl", // East Timor (deprecated old code) - "tm", // Turkmenistan - "tn", // Tunisia - "to", // Tonga -// "tp", // East Timor (Retired) - "tr", // Turkey - "tt", // Trinidad and Tobago - "tv", // Tuvalu - "tw", // Taiwan, Republic of China - "tz", // Tanzania - "ua", // Ukraine - "ug", // Uganda - "uk", // United Kingdom - "us", // United States of America - "uy", // Uruguay - "uz", // Uzbekistan - "va", // Vatican City State - "vc", // Saint Vincent and the Grenadines - "ve", // Venezuela - "vg", // British Virgin Islands - "vi", // U.S. Virgin Islands - "vn", // Vietnam - "vu", // Vanuatu - "wf", // Wallis and Futuna - "ws", // Samoa (formerly Western Samoa) - "xn--3e0b707e", // 한국 KISA (Korea Internet & Security Agency) - "xn--45brj9c", // ভারত National Internet Exchange of India - "xn--80ao21a", // қаз Association of IT Companies of Kazakhstan - "xn--90a3ac", // срб Serbian National Internet Domain Registry (RNIDS) - "xn--90ais", // ??? Reliable Software Inc. - "xn--clchc0ea0b2g2a9gcd", // சிங்கப்பூர் Singapore Network Information Centre (SGNIC) Pte Ltd - "xn--d1alf", // мкд Macedonian Academic Research Network Skopje - "xn--e1a4c", // ею EURid vzw/asbl - "xn--fiqs8s", // 中国 China Internet Network Information Center - "xn--fiqz9s", // 中國 China Internet Network Information Center - "xn--fpcrj9c3d", // భారత్ National Internet Exchange of India - "xn--fzc2c9e2c", // ලංකා LK Domain Registry - "xn--gecrj9c", // ભારત National Internet Exchange of India - "xn--h2brj9c", // भारत National Internet Exchange of India - "xn--j1amh", // укр Ukrainian Network Information Centre (UANIC), Inc. - "xn--j6w193g", // 香港 Hong Kong Internet Registration Corporation Ltd. - "xn--kprw13d", // 台湾 Taiwan Network Information Center (TWNIC) - "xn--kpry57d", // 台灣 Taiwan Network Information Center (TWNIC) - "xn--l1acc", // мон Datacom Co.,Ltd - "xn--lgbbat1ad8j", // الجزائر CERIST - "xn--mgb9awbf", // عمان Telecommunications Regulatory Authority (TRA) - "xn--mgba3a4f16a", // ایران Institute for Research in Fundamental Sciences (IPM) - "xn--mgbaam7a8h", // امارات Telecommunications Regulatory Authority (TRA) - "xn--mgbayh7gpa", // الاردن National Information Technology Center (NITC) - "xn--mgbbh1a71e", // بھارت National Internet Exchange of India - "xn--mgbc0a9azcg", // المغرب Agence Nationale de Réglementation des Télécommunications (ANRT) - "xn--mgberp4a5d4ar", // السعودية Communications and Information Technology Commission - "xn--mgbpl2fh", // ????? Sudan Internet Society - "xn--mgbtx2b", // عراق Communications and Media Commission (CMC) - "xn--mgbx4cd0ab", // مليسيا MYNIC Berhad - "xn--mix891f", // 澳門 Bureau of Telecommunications Regulation (DSRT) - "xn--node", // გე Information Technologies Development Center (ITDC) - "xn--o3cw4h", // ไทย Thai Network Information Center Foundation - "xn--ogbpf8fl", // سورية National Agency for Network Services (NANS) - "xn--p1ai", // рф Coordination Center for TLD RU - "xn--pgbs0dh", // تونس Agence Tunisienne d'Internet - "xn--qxam", // ελ ICS-FORTH GR - "xn--s9brj9c", // ਭਾਰਤ National Internet Exchange of India - "xn--wgbh1c", // مصر National Telecommunication Regulatory Authority - NTRA - "xn--wgbl6a", // قطر Communications Regulatory Authority - "xn--xkc2al3hye2a", // இலங்கை LK Domain Registry - "xn--xkc2dl3a5ee0h", // இந்தியா National Internet Exchange of India - "xn--y9a3aq", // ??? Internet Society - "xn--yfro4i67o", // 新加坡 Singapore Network Information Centre (SGNIC) Pte Ltd - "xn--ygbi2ammx", // فلسطين Ministry of Telecom & Information Technology (MTIT) - "ye", // Yemen - "yt", // Mayotte - "za", // South Africa - "zm", // Zambia - "zw", // Zimbabwe - }; - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search - private static final String[] LOCAL_TLDS = new String[] { - "localdomain", // Also widely used as localhost.localdomain - "localhost", // RFC2606 defined - }; + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary + // search + private static final String[] INFRASTRUCTURE_TLDS = + new String[] { + "arpa", // internet infrastructure + }; + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary + // search + private static final String[] GENERIC_TLDS = + new String[] { + // Taken from Version 2016042500, Last Updated Mon Apr 25 07:07:01 2016 UTC + "aaa", // aaa American Automobile Association, Inc. + "aarp", // aarp AARP + "abb", // abb ABB Ltd + "abbott", // abbott Abbott Laboratories, Inc. + "abbvie", // abbvie AbbVie Inc. + "abogado", // abogado Top Level Domain Holdings Limited + "abudhabi", // abudhabi Abu Dhabi Systems and Information Centre + "academy", // academy Half Oaks, LLC + "accenture", // accenture Accenture plc + "accountant", // accountant dot Accountant Limited + "accountants", // accountants Knob Town, LLC + "aco", // aco ACO Severin Ahlmann GmbH & Co. KG + "active", // active The Active Network, Inc + "actor", // actor United TLD Holdco Ltd. + "adac", // adac Allgemeiner Deutscher Automobil-Club e.V. (ADAC) + "ads", // ads Charleston Road Registry Inc. + "adult", // adult ICM Registry AD LLC + "aeg", // aeg Aktiebolaget Electrolux + "aero", // aero Societe Internationale de Telecommunications Aeronautique (SITA INC + // USA) + "afl", // afl Australian Football League + "agakhan", // agakhan Fondation Aga Khan (Aga Khan Foundation) + "agency", // agency Steel Falls, LLC + "aig", // aig American International Group, Inc. + "airforce", // airforce United TLD Holdco Ltd. + "airtel", // airtel Bharti Airtel Limited + "akdn", // akdn Fondation Aga Khan (Aga Khan Foundation) + "alibaba", // alibaba Alibaba Group Holding Limited + "alipay", // alipay Alibaba Group Holding Limited + "allfinanz", // allfinanz Allfinanz Deutsche Vermögensberatung Aktiengesellschaft + "ally", // ally Ally Financial Inc. + "alsace", // alsace REGION D ALSACE + "amica", // amica Amica Mutual Insurance Company + "amsterdam", // amsterdam Gemeente Amsterdam + "analytics", // analytics Campus IP LLC + "android", // android Charleston Road Registry Inc. + "anquan", // anquan QIHOO 360 TECHNOLOGY CO. LTD. + "apartments", // apartments June Maple, LLC + "app", // app Charleston Road Registry Inc. + "apple", // apple Apple Inc. + "aquarelle", // aquarelle Aquarelle.com + "aramco", // aramco Aramco Services Company + "archi", // archi STARTING DOT LIMITED + "army", // army United TLD Holdco Ltd. + "arte", // arte Association Relative à la Télévision Européenne G.E.I.E. + "asia", // asia DotAsia Organisation Ltd. + "associates", // associates Baxter Hill, LLC + "attorney", // attorney United TLD Holdco, Ltd + "auction", // auction United TLD HoldCo, Ltd. + "audi", // audi AUDI Aktiengesellschaft + "audio", // audio Uniregistry, Corp. + "author", // author Amazon Registry Services, Inc. + "auto", // auto Uniregistry, Corp. + "autos", // autos DERAutos, LLC + "avianca", // avianca Aerovias del Continente Americano S.A. Avianca + "aws", // aws Amazon Registry Services, Inc. + "axa", // axa AXA SA + "azure", // azure Microsoft Corporation + "baby", // baby Johnson & Johnson Services, Inc. + "baidu", // baidu Baidu, Inc. + "band", // band United TLD Holdco, Ltd + "bank", // bank fTLD Registry Services, LLC + "bar", // bar Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable + "barcelona", // barcelona Municipi de Barcelona + "barclaycard", // barclaycard Barclays Bank PLC + "barclays", // barclays Barclays Bank PLC + "barefoot", // barefoot Gallo Vineyards, Inc. + "bargains", // bargains Half Hallow, LLC + "bauhaus", // bauhaus Werkhaus GmbH + "bayern", // bayern Bayern Connect GmbH + "bbc", // bbc British Broadcasting Corporation + "bbva", // bbva BANCO BILBAO VIZCAYA ARGENTARIA, S.A. + "bcg", // bcg The Boston Consulting Group, Inc. + "bcn", // bcn Municipi de Barcelona + "beats", // beats Beats Electronics, LLC + "beer", // beer Top Level Domain Holdings Limited + "bentley", // bentley Bentley Motors Limited + "berlin", // berlin dotBERLIN GmbH & Co. KG + "best", // best BestTLD Pty Ltd + "bet", // bet Afilias plc + "bharti", // bharti Bharti Enterprises (Holding) Private Limited + "bible", // bible American Bible Society + "bid", // bid dot Bid Limited + "bike", // bike Grand Hollow, LLC + "bing", // bing Microsoft Corporation + "bingo", // bingo Sand Cedar, LLC + "bio", // bio STARTING DOT LIMITED + "biz", // biz Neustar, Inc. + "black", // black Afilias Limited + "blackfriday", // blackfriday Uniregistry, Corp. + "bloomberg", // bloomberg Bloomberg IP Holdings LLC + "blue", // blue Afilias Limited + "bms", // bms Bristol-Myers Squibb Company + "bmw", // bmw Bayerische Motoren Werke Aktiengesellschaft + "bnl", // bnl Banca Nazionale del Lavoro + "bnpparibas", // bnpparibas BNP Paribas + "boats", // boats DERBoats, LLC + "boehringer", // boehringer Boehringer Ingelheim International GmbH + "bom", // bom Núcleo de Informação e Coordenação do Ponto BR - NIC.br + "bond", // bond Bond University Limited + "boo", // boo Charleston Road Registry Inc. + "book", // book Amazon Registry Services, Inc. + "boots", // boots THE BOOTS COMPANY PLC + "bosch", // bosch Robert Bosch GMBH + "bostik", // bostik Bostik SA + "bot", // bot Amazon Registry Services, Inc. + "boutique", // boutique Over Galley, LLC + "bradesco", // bradesco Banco Bradesco S.A. + "bridgestone", // bridgestone Bridgestone Corporation + "broadway", // broadway Celebrate Broadway, Inc. + "broker", // broker DOTBROKER REGISTRY LTD + "brother", // brother Brother Industries, Ltd. + "brussels", // brussels DNS.be vzw + "budapest", // budapest Top Level Domain Holdings Limited + "bugatti", // bugatti Bugatti International SA + "build", // build Plan Bee LLC + "builders", // builders Atomic Madison, LLC + "business", // business Spring Cross, LLC + "buy", // buy Amazon Registry Services, INC + "buzz", // buzz DOTSTRATEGY CO. + "bzh", // bzh Association www.bzh + "cab", // cab Half Sunset, LLC + "cafe", // cafe Pioneer Canyon, LLC + "cal", // cal Charleston Road Registry Inc. + "call", // call Amazon Registry Services, Inc. + "camera", // camera Atomic Maple, LLC + "camp", // camp Delta Dynamite, LLC + "cancerresearch", // cancerresearch Australian Cancer Research Foundation + "canon", // canon Canon Inc. + "capetown", // capetown ZA Central Registry NPC trading as ZA Central Registry + "capital", // capital Delta Mill, LLC + "car", // car Cars Registry Limited + "caravan", // caravan Caravan International, Inc. + "cards", // cards Foggy Hollow, LLC + "care", // care Goose Cross, LLC + "career", // career dotCareer LLC + "careers", // careers Wild Corner, LLC + "cars", // cars Uniregistry, Corp. + "cartier", // cartier Richemont DNS Inc. + "casa", // casa Top Level Domain Holdings Limited + "cash", // cash Delta Lake, LLC + "casino", // casino Binky Sky, LLC + "cat", // cat Fundacio puntCAT + "catering", // catering New Falls. LLC + "cba", // cba COMMONWEALTH BANK OF AUSTRALIA + "cbn", // cbn The Christian Broadcasting Network, Inc. + "ceb", // ceb The Corporate Executive Board Company + "center", // center Tin Mill, LLC + "ceo", // ceo CEOTLD Pty Ltd + "cern", // cern European Organization for Nuclear Research ("CERN") + "cfa", // cfa CFA Institute + "cfd", // cfd DOTCFD REGISTRY LTD + "chanel", // chanel Chanel International B.V. + "channel", // channel Charleston Road Registry Inc. + "chase", // chase JPMorgan Chase & Co. + "chat", // chat Sand Fields, LLC + "cheap", // cheap Sand Cover, LLC + "chloe", // chloe Richemont DNS Inc. + "christmas", // christmas Uniregistry, Corp. + "chrome", // chrome Charleston Road Registry Inc. + "church", // church Holly Fileds, LLC + "cipriani", // cipriani Hotel Cipriani Srl + "circle", // circle Amazon Registry Services, Inc. + "cisco", // cisco Cisco Technology, Inc. + "citic", // citic CITIC Group Corporation + "city", // city Snow Sky, LLC + "cityeats", // cityeats Lifestyle Domain Holdings, Inc. + "claims", // claims Black Corner, LLC + "cleaning", // cleaning Fox Shadow, LLC + "click", // click Uniregistry, Corp. + "clinic", // clinic Goose Park, LLC + "clinique", // clinique The Estée Lauder Companies Inc. + "clothing", // clothing Steel Lake, LLC + "cloud", // cloud ARUBA S.p.A. + "club", // club .CLUB DOMAINS, LLC + "clubmed", // clubmed Club Méditerranée S.A. + "coach", // coach Koko Island, LLC + "codes", // codes Puff Willow, LLC + "coffee", // coffee Trixy Cover, LLC + "college", // college XYZ.COM LLC + "cologne", // cologne NetCologne Gesellschaft für Telekommunikation mbH + "com", // com VeriSign Global Registry Services + "commbank", // commbank COMMONWEALTH BANK OF AUSTRALIA + "community", // community Fox Orchard, LLC + "company", // company Silver Avenue, LLC + "compare", // compare iSelect Ltd + "computer", // computer Pine Mill, LLC + "comsec", // comsec VeriSign, Inc. + "condos", // condos Pine House, LLC + "construction", // construction Fox Dynamite, LLC + "consulting", // consulting United TLD Holdco, LTD. + "contact", // contact Top Level Spectrum, Inc. + "contractors", // contractors Magic Woods, LLC + "cooking", // cooking Top Level Domain Holdings Limited + "cool", // cool Koko Lake, LLC + "coop", // coop DotCooperation LLC + "corsica", // corsica Collectivité Territoriale de Corse + "country", // country Top Level Domain Holdings Limited + "coupon", // coupon Amazon Registry Services, Inc. + "coupons", // coupons Black Island, LLC + "courses", // courses OPEN UNIVERSITIES AUSTRALIA PTY LTD + "credit", // credit Snow Shadow, LLC + "creditcard", // creditcard Binky Frostbite, LLC + "creditunion", // creditunion CUNA Performance Resources, LLC + "cricket", // cricket dot Cricket Limited + "crown", // crown Crown Equipment Corporation + "crs", // crs Federated Co-operatives Limited + "cruises", // cruises Spring Way, LLC + "csc", // csc Alliance-One Services, Inc. + "cuisinella", // cuisinella SALM S.A.S. + "cymru", // cymru Nominet UK + "cyou", // cyou Beijing Gamease Age Digital Technology Co., Ltd. + "dabur", // dabur Dabur India Limited + "dad", // dad Charleston Road Registry Inc. + "dance", // dance United TLD Holdco Ltd. + "date", // date dot Date Limited + "dating", // dating Pine Fest, LLC + "datsun", // datsun NISSAN MOTOR CO., LTD. + "day", // day Charleston Road Registry Inc. + "dclk", // dclk Charleston Road Registry Inc. + "dealer", // dealer Dealer Dot Com, Inc. + "deals", // deals Sand Sunset, LLC + "degree", // degree United TLD Holdco, Ltd + "delivery", // delivery Steel Station, LLC + "dell", // dell Dell Inc. + "deloitte", // deloitte Deloitte Touche Tohmatsu + "delta", // delta Delta Air Lines, Inc. + "democrat", // democrat United TLD Holdco Ltd. + "dental", // dental Tin Birch, LLC + "dentist", // dentist United TLD Holdco, Ltd + "desi", // desi Desi Networks LLC + "design", // design Top Level Design, LLC + "dev", // dev Charleston Road Registry Inc. + "diamonds", // diamonds John Edge, LLC + "diet", // diet Uniregistry, Corp. + "digital", // digital Dash Park, LLC + "direct", // direct Half Trail, LLC + "directory", // directory Extra Madison, LLC + "discount", // discount Holly Hill, LLC + "dnp", // dnp Dai Nippon Printing Co., Ltd. + "docs", // docs Charleston Road Registry Inc. + "dog", // dog Koko Mill, LLC + "doha", // doha Communications Regulatory Authority (CRA) + "domains", // domains Sugar Cross, LLC + // "doosan", // doosan Doosan Corporation (retired) + "download", // download dot Support Limited + "drive", // drive Charleston Road Registry Inc. + "dubai", // dubai Dubai Smart Government Department + "durban", // durban ZA Central Registry NPC trading as ZA Central Registry + "dvag", // dvag Deutsche Vermögensberatung Aktiengesellschaft DVAG + "earth", // earth Interlink Co., Ltd. + "eat", // eat Charleston Road Registry Inc. + "edeka", // edeka EDEKA Verband kaufmännischer Genossenschaften e.V. + "edu", // edu EDUCAUSE + "education", // education Brice Way, LLC + "email", // email Spring Madison, LLC + "emerck", // emerck Merck KGaA + "energy", // energy Binky Birch, LLC + "engineer", // engineer United TLD Holdco Ltd. + "engineering", // engineering Romeo Canyon + "enterprises", // enterprises Snow Oaks, LLC + "epson", // epson Seiko Epson Corporation + "equipment", // equipment Corn Station, LLC + "erni", // erni ERNI Group Holding AG + "esq", // esq Charleston Road Registry Inc. + "estate", // estate Trixy Park, LLC + "eurovision", // eurovision European Broadcasting Union (EBU) + "eus", // eus Puntueus Fundazioa + "events", // events Pioneer Maple, LLC + "everbank", // everbank EverBank + "exchange", // exchange Spring Falls, LLC + "expert", // expert Magic Pass, LLC + "exposed", // exposed Victor Beach, LLC + "express", // express Sea Sunset, LLC + "extraspace", // extraspace Extra Space Storage LLC + "fage", // fage Fage International S.A. + "fail", // fail Atomic Pipe, LLC + "fairwinds", // fairwinds FairWinds Partners, LLC + "faith", // faith dot Faith Limited + "family", // family United TLD Holdco Ltd. + "fan", // fan Asiamix Digital Ltd + "fans", // fans Asiamix Digital Limited + "farm", // farm Just Maple, LLC + "fashion", // fashion Top Level Domain Holdings Limited + "fast", // fast Amazon Registry Services, Inc. + "feedback", // feedback Top Level Spectrum, Inc. + "ferrero", // ferrero Ferrero Trading Lux S.A. + "film", // film Motion Picture Domain Registry Pty Ltd + "final", // final Núcleo de Informação e Coordenação do Ponto BR - NIC.br + "finance", // finance Cotton Cypress, LLC + "financial", // financial Just Cover, LLC + "firestone", // firestone Bridgestone Corporation + "firmdale", // firmdale Firmdale Holdings Limited + "fish", // fish Fox Woods, LLC + "fishing", // fishing Top Level Domain Holdings Limited + "fit", // fit Minds + Machines Group Limited + "fitness", // fitness Brice Orchard, LLC + "flickr", // flickr Yahoo! Domain Services Inc. + "flights", // flights Fox Station, LLC + "florist", // florist Half Cypress, LLC + "flowers", // flowers Uniregistry, Corp. + "flsmidth", // flsmidth FLSmidth A/S + "fly", // fly Charleston Road Registry Inc. + "foo", // foo Charleston Road Registry Inc. + "football", // football Foggy Farms, LLC + "ford", // ford Ford Motor Company + "forex", // forex DOTFOREX REGISTRY LTD + "forsale", // forsale United TLD Holdco, LLC + "forum", // forum Fegistry, LLC + "foundation", // foundation John Dale, LLC + "fox", // fox FOX Registry, LLC + "fresenius", // fresenius Fresenius Immobilien-Verwaltungs-GmbH + "frl", // frl FRLregistry B.V. + "frogans", // frogans OP3FT + "frontier", // frontier Frontier Communications Corporation + "ftr", // ftr Frontier Communications Corporation + "fund", // fund John Castle, LLC + "furniture", // furniture Lone Fields, LLC + "futbol", // futbol United TLD Holdco, Ltd. + "fyi", // fyi Silver Tigers, LLC + "gal", // gal Asociación puntoGAL + "gallery", // gallery Sugar House, LLC + "gallo", // gallo Gallo Vineyards, Inc. + "gallup", // gallup Gallup, Inc. + "game", // game Uniregistry, Corp. + "garden", // garden Top Level Domain Holdings Limited + "gbiz", // gbiz Charleston Road Registry Inc. + "gdn", // gdn Joint Stock Company "Navigation-information systems" + "gea", // gea GEA Group Aktiengesellschaft + "gent", // gent COMBELL GROUP NV/SA + "genting", // genting Resorts World Inc. Pte. Ltd. + "ggee", // ggee GMO Internet, Inc. + "gift", // gift Uniregistry, Corp. + "gifts", // gifts Goose Sky, LLC + "gives", // gives United TLD Holdco Ltd. + "giving", // giving Giving Limited + "glass", // glass Black Cover, LLC + "gle", // gle Charleston Road Registry Inc. + "global", // global Dot Global Domain Registry Limited + "globo", // globo Globo Comunicação e Participações S.A + "gmail", // gmail Charleston Road Registry Inc. + "gmbh", // gmbh Extra Dynamite, LLC + "gmo", // gmo GMO Internet, Inc. + "gmx", // gmx 1&1 Mail & Media GmbH + "gold", // gold June Edge, LLC + "goldpoint", // goldpoint YODOBASHI CAMERA CO.,LTD. + "golf", // golf Lone Falls, LLC + "goo", // goo NTT Resonant Inc. + "goog", // goog Charleston Road Registry Inc. + "google", // google Charleston Road Registry Inc. + "gop", // gop Republican State Leadership Committee, Inc. + "got", // got Amazon Registry Services, Inc. + "gov", // gov General Services Administration Attn: QTDC, 2E08 (.gov Domain + // Registration) + "grainger", // grainger Grainger Registry Services, LLC + "graphics", // graphics Over Madison, LLC + "gratis", // gratis Pioneer Tigers, LLC + "green", // green Afilias Limited + "gripe", // gripe Corn Sunset, LLC + "group", // group Romeo Town, LLC + "gucci", // gucci Guccio Gucci S.p.a. + "guge", // guge Charleston Road Registry Inc. + "guide", // guide Snow Moon, LLC + "guitars", // guitars Uniregistry, Corp. + "guru", // guru Pioneer Cypress, LLC + "hamburg", // hamburg Hamburg Top-Level-Domain GmbH + "hangout", // hangout Charleston Road Registry Inc. + "haus", // haus United TLD Holdco, LTD. + "hdfcbank", // hdfcbank HDFC Bank Limited + "health", // health DotHealth, LLC + "healthcare", // healthcare Silver Glen, LLC + "help", // help Uniregistry, Corp. + "helsinki", // helsinki City of Helsinki + "here", // here Charleston Road Registry Inc. + "hermes", // hermes Hermes International + "hiphop", // hiphop Uniregistry, Corp. + "hitachi", // hitachi Hitachi, Ltd. + "hiv", // hiv dotHIV gemeinnuetziger e.V. + "hockey", // hockey Half Willow, LLC + "holdings", // holdings John Madison, LLC + "holiday", // holiday Goose Woods, LLC + "homedepot", // homedepot Homer TLC, Inc. + "homes", // homes DERHomes, LLC + "honda", // honda Honda Motor Co., Ltd. + "horse", // horse Top Level Domain Holdings Limited + "host", // host DotHost Inc. + "hosting", // hosting Uniregistry, Corp. + "hoteles", // hoteles Travel Reservations SRL + "hotmail", // hotmail Microsoft Corporation + "house", // house Sugar Park, LLC + "how", // how Charleston Road Registry Inc. + "hsbc", // hsbc HSBC Holdings PLC + "htc", // htc HTC corporation + "hyundai", // hyundai Hyundai Motor Company + "ibm", // ibm International Business Machines Corporation + "icbc", // icbc Industrial and Commercial Bank of China Limited + "ice", // ice IntercontinentalExchange, Inc. + "icu", // icu One.com A/S + "ifm", // ifm ifm electronic gmbh + "iinet", // iinet Connect West Pty. Ltd. + "imamat", // imamat Fondation Aga Khan (Aga Khan Foundation) + "immo", // immo Auburn Bloom, LLC + "immobilien", // immobilien United TLD Holdco Ltd. + "industries", // industries Outer House, LLC + "infiniti", // infiniti NISSAN MOTOR CO., LTD. + "info", // info Afilias Limited + "ing", // ing Charleston Road Registry Inc. + "ink", // ink Top Level Design, LLC + "institute", // institute Outer Maple, LLC + "insurance", // insurance fTLD Registry Services LLC + "insure", // insure Pioneer Willow, LLC + "int", // int Internet Assigned Numbers Authority + "international", // international Wild Way, LLC + "investments", // investments Holly Glen, LLC + "ipiranga", // ipiranga Ipiranga Produtos de Petroleo S.A. + "irish", // irish Dot-Irish LLC + "iselect", // iselect iSelect Ltd + "ismaili", // ismaili Fondation Aga Khan (Aga Khan Foundation) + "ist", // ist Istanbul Metropolitan Municipality + "istanbul", // istanbul Istanbul Metropolitan Municipality / Medya A.S. + "itau", // itau Itau Unibanco Holding S.A. + "iwc", // iwc Richemont DNS Inc. + "jaguar", // jaguar Jaguar Land Rover Ltd + "java", // java Oracle Corporation + "jcb", // jcb JCB Co., Ltd. + "jcp", // jcp JCP Media, Inc. + "jetzt", // jetzt New TLD Company AB + "jewelry", // jewelry Wild Bloom, LLC + "jlc", // jlc Richemont DNS Inc. + "jll", // jll Jones Lang LaSalle Incorporated + "jmp", // jmp Matrix IP LLC + "jnj", // jnj Johnson & Johnson Services, Inc. + "jobs", // jobs Employ Media LLC + "joburg", // joburg ZA Central Registry NPC trading as ZA Central Registry + "jot", // jot Amazon Registry Services, Inc. + "joy", // joy Amazon Registry Services, Inc. + "jpmorgan", // jpmorgan JPMorgan Chase & Co. + "jprs", // jprs Japan Registry Services Co., Ltd. + "juegos", // juegos Uniregistry, Corp. + "kaufen", // kaufen United TLD Holdco Ltd. + "kddi", // kddi KDDI CORPORATION + "kerryhotels", // kerryhotels Kerry Trading Co. Limited + "kerrylogistics", // kerrylogistics Kerry Trading Co. Limited + "kerryproperties", // kerryproperties Kerry Trading Co. Limited + "kfh", // kfh Kuwait Finance House + "kia", // kia KIA MOTORS CORPORATION + "kim", // kim Afilias Limited + "kinder", // kinder Ferrero Trading Lux S.A. + "kitchen", // kitchen Just Goodbye, LLC + "kiwi", // kiwi DOT KIWI LIMITED + "koeln", // koeln NetCologne Gesellschaft für Telekommunikation mbH + "komatsu", // komatsu Komatsu Ltd. + "kpmg", // kpmg KPMG International Cooperative (KPMG International Genossenschaft) + "kpn", // kpn Koninklijke KPN N.V. + "krd", // krd KRG Department of Information Technology + "kred", // kred KredTLD Pty Ltd + "kuokgroup", // kuokgroup Kerry Trading Co. Limited + "kyoto", // kyoto Academic Institution: Kyoto Jyoho Gakuen + "lacaixa", // lacaixa CAIXA D'ESTALVIS I PENSIONS DE BARCELONA + "lamborghini", // lamborghini Automobili Lamborghini S.p.A. + "lamer", // lamer The Estée Lauder Companies Inc. + "lancaster", // lancaster LANCASTER + "land", // land Pine Moon, LLC + "landrover", // landrover Jaguar Land Rover Ltd + "lanxess", // lanxess LANXESS Corporation + "lasalle", // lasalle Jones Lang LaSalle Incorporated + "lat", // lat ECOM-LAC Federación de Latinoamérica y el Caribe para Internet y el + // Comercio Electrónico + "latrobe", // latrobe La Trobe University + "law", // law Minds + Machines Group Limited + "lawyer", // lawyer United TLD Holdco, Ltd + "lds", // lds IRI Domain Management, LLC + "lease", // lease Victor Trail, LLC + "leclerc", // leclerc A.C.D. LEC Association des Centres Distributeurs Edouard + // Leclerc + "legal", // legal Blue Falls, LLC + "lexus", // lexus TOYOTA MOTOR CORPORATION + "lgbt", // lgbt Afilias Limited + "liaison", // liaison Liaison Technologies, Incorporated + "lidl", // lidl Schwarz Domains und Services GmbH & Co. KG + "life", // life Trixy Oaks, LLC + "lifeinsurance", // lifeinsurance American Council of Life Insurers + "lifestyle", // lifestyle Lifestyle Domain Holdings, Inc. + "lighting", // lighting John McCook, LLC + "like", // like Amazon Registry Services, Inc. + "limited", // limited Big Fest, LLC + "limo", // limo Hidden Frostbite, LLC + "lincoln", // lincoln Ford Motor Company + "linde", // linde Linde Aktiengesellschaft + "link", // link Uniregistry, Corp. + "live", // live United TLD Holdco Ltd. + "living", // living Lifestyle Domain Holdings, Inc. + "lixil", // lixil LIXIL Group Corporation + "loan", // loan dot Loan Limited + "loans", // loans June Woods, LLC + "locus", // locus Locus Analytics LLC + "lol", // lol Uniregistry, Corp. + "london", // london Dot London Domains Limited + "lotte", // lotte Lotte Holdings Co., Ltd. + "lotto", // lotto Afilias Limited + "love", // love Merchant Law Group LLP + "ltd", // ltd Over Corner, LLC + "ltda", // ltda InterNetX Corp. + "lupin", // lupin LUPIN LIMITED + "luxe", // luxe Top Level Domain Holdings Limited + "luxury", // luxury Luxury Partners LLC + "madrid", // madrid Comunidad de Madrid + "maif", // maif Mutuelle Assurance Instituteur France (MAIF) + "maison", // maison Victor Frostbite, LLC + "makeup", // makeup L'Oréal + "man", // man MAN SE + "management", // management John Goodbye, LLC + "mango", // mango PUNTO FA S.L. + "market", // market Unitied TLD Holdco, Ltd + "marketing", // marketing Fern Pass, LLC + "markets", // markets DOTMARKETS REGISTRY LTD + "marriott", // marriott Marriott Worldwide Corporation + "mba", // mba Lone Hollow, LLC + "med", // med Medistry LLC + "media", // media Grand Glen, LLC + "meet", // meet Afilias Limited + "melbourne", // melbourne The Crown in right of the State of Victoria, represented + // by its Department of State Development, Business and Innovation + "meme", // meme Charleston Road Registry Inc. + "memorial", // memorial Dog Beach, LLC + "men", // men Exclusive Registry Limited + "menu", // menu Wedding TLD2, LLC + "meo", // meo PT Comunicacoes S.A. + "miami", // miami Top Level Domain Holdings Limited + "microsoft", // microsoft Microsoft Corporation + "mil", // mil DoD Network Information Center + "mini", // mini Bayerische Motoren Werke Aktiengesellschaft + "mls", // mls The Canadian Real Estate Association + "mma", // mma MMA IARD + "mobi", // mobi Afilias Technologies Limited dba dotMobi + "mobily", // mobily GreenTech Consultancy Company W.L.L. + "moda", // moda United TLD Holdco Ltd. + "moe", // moe Interlink Co., Ltd. + "moi", // moi Amazon Registry Services, Inc. + "mom", // mom Uniregistry, Corp. + "monash", // monash Monash University + "money", // money Outer McCook, LLC + "montblanc", // montblanc Richemont DNS Inc. + "mormon", // mormon IRI Domain Management, LLC ("Applicant") + "mortgage", // mortgage United TLD Holdco, Ltd + "moscow", // moscow Foundation for Assistance for Internet Technologies and + // Infrastructure Development (FAITID) + "motorcycles", // motorcycles DERMotorcycles, LLC + "mov", // mov Charleston Road Registry Inc. + "movie", // movie New Frostbite, LLC + "movistar", // movistar Telefónica S.A. + "mtn", // mtn MTN Dubai Limited + "mtpc", // mtpc Mitsubishi Tanabe Pharma Corporation + "mtr", // mtr MTR Corporation Limited + "museum", // museum Museum Domain Management Association + "mutual", // mutual Northwestern Mutual MU TLD Registry, LLC + "mutuelle", // mutuelle Fédération Nationale de la Mutualité Française + "nadex", // nadex Nadex Domains, Inc + "nagoya", // nagoya GMO Registry, Inc. + "name", // name VeriSign Information Services, Inc. + "natura", // natura NATURA COSMÉTICOS S.A. + "navy", // navy United TLD Holdco Ltd. + "nec", // nec NEC Corporation + "net", // net VeriSign Global Registry Services + "netbank", // netbank COMMONWEALTH BANK OF AUSTRALIA + "network", // network Trixy Manor, LLC + "neustar", // neustar NeuStar, Inc. + "new", // new Charleston Road Registry Inc. + "news", // news United TLD Holdco Ltd. + "nexus", // nexus Charleston Road Registry Inc. + "ngo", // ngo Public Interest Registry + "nhk", // nhk Japan Broadcasting Corporation (NHK) + "nico", // nico DWANGO Co., Ltd. + "nikon", // nikon NIKON CORPORATION + "ninja", // ninja United TLD Holdco Ltd. + "nissan", // nissan NISSAN MOTOR CO., LTD. + "nissay", // nissay Nippon Life Insurance Company + "nokia", // nokia Nokia Corporation + "northwesternmutual", // northwesternmutual Northwestern Mutual Registry, LLC + "norton", // norton Symantec Corporation + "nowruz", // nowruz Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "nra", // nra NRA Holdings Company, INC. + "nrw", // nrw Minds + Machines GmbH + "ntt", // ntt NIPPON TELEGRAPH AND TELEPHONE CORPORATION + "nyc", // nyc The City of New York by and through the New York City Department of + // Information Technology & Telecommunications + "obi", // obi OBI Group Holding SE & Co. KGaA + "office", // office Microsoft Corporation + "okinawa", // okinawa BusinessRalliart inc. + "omega", // omega The Swatch Group Ltd + "one", // one One.com A/S + "ong", // ong Public Interest Registry + "onl", // onl I-REGISTRY Ltd., Niederlassung Deutschland + "online", // online DotOnline Inc. + "ooo", // ooo INFIBEAM INCORPORATION LIMITED + "oracle", // oracle Oracle Corporation + "orange", // orange Orange Brand Services Limited + "org", // org Public Interest Registry (PIR) + "organic", // organic Afilias Limited + "origins", // origins The Estée Lauder Companies Inc. + "osaka", // osaka Interlink Co., Ltd. + "otsuka", // otsuka Otsuka Holdings Co., Ltd. + "ovh", // ovh OVH SAS + "page", // page Charleston Road Registry Inc. + "pamperedchef", // pamperedchef The Pampered Chef, Ltd. + "panerai", // panerai Richemont DNS Inc. + "paris", // paris City of Paris + "pars", // pars Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "partners", // partners Magic Glen, LLC + "parts", // parts Sea Goodbye, LLC + "party", // party Blue Sky Registry Limited + "passagens", // passagens Travel Reservations SRL + "pet", // pet Afilias plc + "pharmacy", // pharmacy National Association of Boards of Pharmacy + "philips", // philips Koninklijke Philips N.V. + "photo", // photo Uniregistry, Corp. + "photography", // photography Sugar Glen, LLC + "photos", // photos Sea Corner, LLC + "physio", // physio PhysBiz Pty Ltd + "piaget", // piaget Richemont DNS Inc. + "pics", // pics Uniregistry, Corp. + "pictet", // pictet Pictet Europe S.A. + "pictures", // pictures Foggy Sky, LLC + "pid", // pid Top Level Spectrum, Inc. + "pin", // pin Amazon Registry Services, Inc. + "ping", // ping Ping Registry Provider, Inc. + "pink", // pink Afilias Limited + "pizza", // pizza Foggy Moon, LLC + "place", // place Snow Galley, LLC + "play", // play Charleston Road Registry Inc. + "playstation", // playstation Sony Computer Entertainment Inc. + "plumbing", // plumbing Spring Tigers, LLC + "plus", // plus Sugar Mill, LLC + "pohl", // pohl Deutsche Vermögensberatung Aktiengesellschaft DVAG + "poker", // poker Afilias Domains No. 5 Limited + "porn", // porn ICM Registry PN LLC + "post", // post Universal Postal Union + "praxi", // praxi Praxi S.p.A. + "press", // press DotPress Inc. + "pro", // pro Registry Services Corporation dba RegistryPro + "prod", // prod Charleston Road Registry Inc. + "productions", // productions Magic Birch, LLC + "prof", // prof Charleston Road Registry Inc. + "progressive", // progressive Progressive Casualty Insurance Company + "promo", // promo Afilias plc + "properties", // properties Big Pass, LLC + "property", // property Uniregistry, Corp. + "protection", // protection XYZ.COM LLC + "pub", // pub United TLD Holdco Ltd. + "pwc", // pwc PricewaterhouseCoopers LLP + "qpon", // qpon dotCOOL, Inc. + "quebec", // quebec PointQuébec Inc + "quest", // quest Quest ION Limited + "racing", // racing Premier Registry Limited + "read", // read Amazon Registry Services, Inc. + "realtor", // realtor Real Estate Domains LLC + "realty", // realty Fegistry, LLC + "recipes", // recipes Grand Island, LLC + "red", // red Afilias Limited + "redstone", // redstone Redstone Haute Couture Co., Ltd. + "redumbrella", // redumbrella Travelers TLD, LLC + "rehab", // rehab United TLD Holdco Ltd. + "reise", // reise Foggy Way, LLC + "reisen", // reisen New Cypress, LLC + "reit", // reit National Association of Real Estate Investment Trusts, Inc. + "ren", // ren Beijing Qianxiang Wangjing Technology Development Co., Ltd. + "rent", // rent XYZ.COM LLC + "rentals", // rentals Big Hollow,LLC + "repair", // repair Lone Sunset, LLC + "report", // report Binky Glen, LLC + "republican", // republican United TLD Holdco Ltd. + "rest", // rest Punto 2012 Sociedad Anonima Promotora de Inversion de Capital + // Variable + "restaurant", // restaurant Snow Avenue, LLC + "review", // review dot Review Limited + "reviews", // reviews United TLD Holdco, Ltd. + "rexroth", // rexroth Robert Bosch GMBH + "rich", // rich I-REGISTRY Ltd., Niederlassung Deutschland + "ricoh", // ricoh Ricoh Company, Ltd. + "rio", // rio Empresa Municipal de Informática SA - IPLANRIO + "rip", // rip United TLD Holdco Ltd. + "rocher", // rocher Ferrero Trading Lux S.A. + "rocks", // rocks United TLD Holdco, LTD. + "rodeo", // rodeo Top Level Domain Holdings Limited + "room", // room Amazon Registry Services, Inc. + "rsvp", // rsvp Charleston Road Registry Inc. + "ruhr", // ruhr regiodot GmbH & Co. KG + "run", // run Snow Park, LLC + "rwe", // rwe RWE AG + "ryukyu", // ryukyu BusinessRalliart inc. + "saarland", // saarland dotSaarland GmbH + "safe", // safe Amazon Registry Services, Inc. + "safety", // safety Safety Registry Services, LLC. + "sakura", // sakura SAKURA Internet Inc. + "sale", // sale United TLD Holdco, Ltd + "salon", // salon Outer Orchard, LLC + "samsung", // samsung SAMSUNG SDS CO., LTD + "sandvik", // sandvik Sandvik AB + "sandvikcoromant", // sandvikcoromant Sandvik AB + "sanofi", // sanofi Sanofi + "sap", // sap SAP AG + "sapo", // sapo PT Comunicacoes S.A. + "sarl", // sarl Delta Orchard, LLC + "sas", // sas Research IP LLC + "saxo", // saxo Saxo Bank A/S + "sbi", // sbi STATE BANK OF INDIA + "sbs", // sbs SPECIAL BROADCASTING SERVICE CORPORATION + "sca", // sca SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ) + "scb", // scb The Siam Commercial Bank Public Company Limited ("SCB") + "schaeffler", // schaeffler Schaeffler Technologies AG & Co. KG + "schmidt", // schmidt SALM S.A.S. + "scholarships", // scholarships Scholarships.com, LLC + "school", // school Little Galley, LLC + "schule", // schule Outer Moon, LLC + "schwarz", // schwarz Schwarz Domains und Services GmbH & Co. KG + "science", // science dot Science Limited + "scor", // scor SCOR SE + "scot", // scot Dot Scot Registry Limited + "seat", // seat SEAT, S.A. (Sociedad Unipersonal) + "security", // security XYZ.COM LLC + "seek", // seek Seek Limited + "select", // select iSelect Ltd + "sener", // sener Sener Ingeniería y Sistemas, S.A. + "services", // services Fox Castle, LLC + "seven", // seven Seven West Media Ltd + "sew", // sew SEW-EURODRIVE GmbH & Co KG + "sex", // sex ICM Registry SX LLC + "sexy", // sexy Uniregistry, Corp. + "sfr", // sfr Societe Francaise du Radiotelephone - SFR + "sharp", // sharp Sharp Corporation + "shaw", // shaw Shaw Cablesystems G.P. + "shell", // shell Shell Information Technology International Inc + "shia", // shia Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "shiksha", // shiksha Afilias Limited + "shoes", // shoes Binky Galley, LLC + "shouji", // shouji QIHOO 360 TECHNOLOGY CO. LTD. + "show", // show Snow Beach, LLC + "shriram", // shriram Shriram Capital Ltd. + "sina", // sina Sina Corporation + "singles", // singles Fern Madison, LLC + "site", // site DotSite Inc. + "ski", // ski STARTING DOT LIMITED + "skin", // skin L'Oréal + "sky", // sky Sky International AG + "skype", // skype Microsoft Corporation + "smile", // smile Amazon Registry Services, Inc. + "sncf", // sncf SNCF (Société Nationale des Chemins de fer Francais) + "soccer", // soccer Foggy Shadow, LLC + "social", // social United TLD Holdco Ltd. + "softbank", // softbank SoftBank Group Corp. + "software", // software United TLD Holdco, Ltd + "sohu", // sohu Sohu.com Limited + "solar", // solar Ruby Town, LLC + "solutions", // solutions Silver Cover, LLC + "song", // song Amazon Registry Services, Inc. + "sony", // sony Sony Corporation + "soy", // soy Charleston Road Registry Inc. + "space", // space DotSpace Inc. + "spiegel", // spiegel SPIEGEL-Verlag Rudolf Augstein GmbH & Co. KG + "spot", // spot Amazon Registry Services, Inc. + "spreadbetting", // spreadbetting DOTSPREADBETTING REGISTRY LTD + "srl", // srl InterNetX Corp. + "stada", // stada STADA Arzneimittel AG + "star", // star Star India Private Limited + "starhub", // starhub StarHub Limited + "statebank", // statebank STATE BANK OF INDIA + "statefarm", // statefarm State Farm Mutual Automobile Insurance Company + "statoil", // statoil Statoil ASA + "stc", // stc Saudi Telecom Company + "stcgroup", // stcgroup Saudi Telecom Company + "stockholm", // stockholm Stockholms kommun + "storage", // storage Self Storage Company LLC + "store", // store DotStore Inc. + "stream", // stream dot Stream Limited + "studio", // studio United TLD Holdco Ltd. + "study", // study OPEN UNIVERSITIES AUSTRALIA PTY LTD + "style", // style Binky Moon, LLC + "sucks", // sucks Vox Populi Registry Ltd. + "supplies", // supplies Atomic Fields, LLC + "supply", // supply Half Falls, LLC + "support", // support Grand Orchard, LLC + "surf", // surf Top Level Domain Holdings Limited + "surgery", // surgery Tin Avenue, LLC + "suzuki", // suzuki SUZUKI MOTOR CORPORATION + "swatch", // swatch The Swatch Group Ltd + "swiss", // swiss Swiss Confederation + "sydney", // sydney State of New South Wales, Department of Premier and Cabinet + "symantec", // symantec Symantec Corporation + "systems", // systems Dash Cypress, LLC + "tab", // tab Tabcorp Holdings Limited + "taipei", // taipei Taipei City Government + "talk", // talk Amazon Registry Services, Inc. + "taobao", // taobao Alibaba Group Holding Limited + "tatamotors", // tatamotors Tata Motors Ltd + "tatar", // tatar Limited Liability Company "Coordination Center of Regional + // Domain of Tatarstan Republic" + "tattoo", // tattoo Uniregistry, Corp. + "tax", // tax Storm Orchard, LLC + "taxi", // taxi Pine Falls, LLC + "tci", // tci Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "team", // team Atomic Lake, LLC + "tech", // tech Dot Tech LLC + "technology", // technology Auburn Falls, LLC + "tel", // tel Telnic Ltd. + "telecity", // telecity TelecityGroup International Limited + "telefonica", // telefonica Telefónica S.A. + "temasek", // temasek Temasek Holdings (Private) Limited + "tennis", // tennis Cotton Bloom, LLC + "teva", // teva Teva Pharmaceutical Industries Limited + "thd", // thd Homer TLC, Inc. + "theater", // theater Blue Tigers, LLC + "theatre", // theatre XYZ.COM LLC + "tickets", // tickets Accent Media Limited + "tienda", // tienda Victor Manor, LLC + "tiffany", // tiffany Tiffany and Company + "tips", // tips Corn Willow, LLC + "tires", // tires Dog Edge, LLC + "tirol", // tirol punkt Tirol GmbH + "tmall", // tmall Alibaba Group Holding Limited + "today", // today Pearl Woods, LLC + "tokyo", // tokyo GMO Registry, Inc. + "tools", // tools Pioneer North, LLC + "top", // top Jiangsu Bangning Science & Technology Co.,Ltd. + "toray", // toray Toray Industries, Inc. + "toshiba", // toshiba TOSHIBA Corporation + "total", // total Total SA + "tours", // tours Sugar Station, LLC + "town", // town Koko Moon, LLC + "toyota", // toyota TOYOTA MOTOR CORPORATION + "toys", // toys Pioneer Orchard, LLC + "trade", // trade Elite Registry Limited + "trading", // trading DOTTRADING REGISTRY LTD + "training", // training Wild Willow, LLC + "travel", // travel Tralliance Registry Management Company, LLC. + "travelers", // travelers Travelers TLD, LLC + "travelersinsurance", // travelersinsurance Travelers TLD, LLC + "trust", // trust Artemis Internet Inc + "trv", // trv Travelers TLD, LLC + "tube", // tube Latin American Telecom LLC + "tui", // tui TUI AG + "tunes", // tunes Amazon Registry Services, Inc. + "tushu", // tushu Amazon Registry Services, Inc. + "tvs", // tvs T V SUNDRAM IYENGAR & SONS PRIVATE LIMITED + "ubs", // ubs UBS AG + "unicom", // unicom China United Network Communications Corporation Limited + "university", // university Little Station, LLC + "uno", // uno Dot Latin LLC + "uol", // uol UBN INTERNET LTDA. + "vacations", // vacations Atomic Tigers, LLC + "vana", // vana Lifestyle Domain Holdings, Inc. + "vegas", // vegas Dot Vegas, Inc. + "ventures", // ventures Binky Lake, LLC + "verisign", // verisign VeriSign, Inc. + "versicherung", // versicherung dotversicherung-registry GmbH + "vet", // vet United TLD Holdco, Ltd + "viajes", // viajes Black Madison, LLC + "video", // video United TLD Holdco, Ltd + "vig", // vig VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe + "viking", // viking Viking River Cruises (Bermuda) Ltd. + "villas", // villas New Sky, LLC + "vin", // vin Holly Shadow, LLC + "vip", // vip Minds + Machines Group Limited + "virgin", // virgin Virgin Enterprises Limited + "vision", // vision Koko Station, LLC + "vista", // vista Vistaprint Limited + "vistaprint", // vistaprint Vistaprint Limited + "viva", // viva Saudi Telecom Company + "vlaanderen", // vlaanderen DNS.be vzw + "vodka", // vodka Top Level Domain Holdings Limited + "volkswagen", // volkswagen Volkswagen Group of America Inc. + "vote", // vote Monolith Registry LLC + "voting", // voting Valuetainment Corp. + "voto", // voto Monolith Registry LLC + "voyage", // voyage Ruby House, LLC + "vuelos", // vuelos Travel Reservations SRL + "wales", // wales Nominet UK + "walter", // walter Sandvik AB + "wang", // wang Zodiac Registry Limited + "wanggou", // wanggou Amazon Registry Services, Inc. + "watch", // watch Sand Shadow, LLC + "watches", // watches Richemont DNS Inc. + "weather", // weather The Weather Channel, LLC + "weatherchannel", // weatherchannel The Weather Channel, LLC + "webcam", // webcam dot Webcam Limited + "weber", // weber Saint-Gobain Weber SA + "website", // website DotWebsite Inc. + "wed", // wed Atgron, Inc. + "wedding", // wedding Top Level Domain Holdings Limited + "weibo", // weibo Sina Corporation + "weir", // weir Weir Group IP Limited + "whoswho", // whoswho Who's Who Registry + "wien", // wien punkt.wien GmbH + "wiki", // wiki Top Level Design, LLC + "williamhill", // williamhill William Hill Organization Limited + "win", // win First Registry Limited + "windows", // windows Microsoft Corporation + "wine", // wine June Station, LLC + "wme", // wme William Morris Endeavor Entertainment, LLC + "wolterskluwer", // wolterskluwer Wolters Kluwer N.V. + "work", // work Top Level Domain Holdings Limited + "works", // works Little Dynamite, LLC + "world", // world Bitter Fields, LLC + "wtc", // wtc World Trade Centers Association, Inc. + "wtf", // wtf Hidden Way, LLC + "xbox", // xbox Microsoft Corporation + "xerox", // xerox Xerox DNHC LLC + "xihuan", // xihuan QIHOO 360 TECHNOLOGY CO. LTD. + "xin", // xin Elegant Leader Limited + "xn--11b4c3d", // कॉम VeriSign Sarl + "xn--1ck2e1b", // セール Amazon Registry Services, Inc. + "xn--1qqw23a", // 佛山 Guangzhou YU Wei Information Technology Co., Ltd. + "xn--30rr7y", // 慈善 Excellent First Limited + "xn--3bst00m", // 集团 Eagle Horizon Limited + "xn--3ds443g", // 在线 TLD REGISTRY LIMITED + "xn--3pxu8k", // 点看 VeriSign Sarl + "xn--42c2d9a", // คอม VeriSign Sarl + "xn--45q11c", // 八卦 Zodiac Scorpio Limited + "xn--4gbrim", // موقع Suhub Electronic Establishment + "xn--55qw42g", // 公益 China Organizational Name Administration Center + "xn--55qx5d", // 公司 Computer Network Information Center of Chinese Academy of + // Sciences (China Internet Network Information Center) + "xn--5tzm5g", // 网站 Global Website TLD Asia Limited + "xn--6frz82g", // 移动 Afilias Limited + "xn--6qq986b3xl", // 我爱你 Tycoon Treasure Limited + "xn--80adxhks", // москва Foundation for Assistance for Internet Technologies and + // Infrastructure Development (FAITID) + "xn--80asehdb", // онлайн CORE Association + "xn--80aswg", // сайт CORE Association + "xn--8y0a063a", // 联通 China United Network Communications Corporation Limited + "xn--9dbq2a", // קום VeriSign Sarl + "xn--9et52u", // 时尚 RISE VICTORY LIMITED + "xn--9krt00a", // 微博 Sina Corporation + "xn--b4w605ferd", // 淡马锡 Temasek Holdings (Private) Limited + "xn--bck1b9a5dre4c", // ファッション Amazon Registry Services, Inc. + "xn--c1avg", // орг Public Interest Registry + "xn--c2br7g", // नेट VeriSign Sarl + "xn--cck2b3b", // ストア Amazon Registry Services, Inc. + "xn--cg4bki", // 삼성 SAMSUNG SDS CO., LTD + "xn--czr694b", // 商标 HU YI GLOBAL INFORMATION RESOURCES(HOLDING) COMPANY.HONGKONG + // LIMITED + "xn--czrs0t", // 商店 Wild Island, LLC + "xn--czru2d", // 商城 Zodiac Aquarius Limited + "xn--d1acj3b", // дети The Foundation for Network Initiatives “The Smart Internet” + "xn--eckvdtc9d", // ポイント Amazon Registry Services, Inc. + "xn--efvy88h", // 新闻 Xinhua News Agency Guangdong Branch 新华通讯社广东分社 + "xn--estv75g", // 工行 Industrial and Commercial Bank of China Limited + "xn--fct429k", // 家電 Amazon Registry Services, Inc. + "xn--fhbei", // كوم VeriSign Sarl + "xn--fiq228c5hs", // 中文网 TLD REGISTRY LIMITED + "xn--fiq64b", // 中信 CITIC Group Corporation + "xn--fjq720a", // 娱乐 Will Bloom, LLC + "xn--flw351e", // 谷歌 Charleston Road Registry Inc. + "xn--g2xx48c", // 购物 Minds + Machines Group Limited + "xn--gckr3f0f", // クラウド Amazon Registry Services, Inc. + "xn--hxt814e", // 网店 Zodiac Libra Limited + "xn--i1b6b1a6a2e", // संगठन Public Interest Registry + "xn--imr513n", // 餐厅 HU YI GLOBAL INFORMATION RESOURCES (HOLDING) COMPANY. HONGKONG + // LIMITED + "xn--io0a7i", // 网络 Computer Network Information Center of Chinese Academy of + // Sciences (China Internet Network Information Center) + "xn--j1aef", // ком VeriSign Sarl + "xn--jlq61u9w7b", // 诺基亚 Nokia Corporation + "xn--jvr189m", // 食品 Amazon Registry Services, Inc. + "xn--kcrx77d1x4a", // 飞利浦 Koninklijke Philips N.V. + "xn--kpu716f", // 手表 Richemont DNS Inc. + "xn--kput3i", // 手机 Beijing RITT-Net Technology Development Co., Ltd + "xn--mgba3a3ejt", // ارامكو Aramco Services Company + "xn--mgbab2bd", // بازار CORE Association + "xn--mgbb9fbpob", // موبايلي GreenTech Consultancy Company W.L.L. + "xn--mgbca7dzdo", // ابوظبي Abu Dhabi Systems and Information Centre + "xn--mgbt3dhd", // همراه Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. + "xn--mk1bu44c", // 닷컴 VeriSign Sarl + "xn--mxtq1m", // 政府 Net-Chinese Co., Ltd. + "xn--ngbc5azd", // شبكة International Domain Registry Pty. Ltd. + "xn--ngbe9e0a", // بيتك Kuwait Finance House + "xn--nqv7f", // 机构 Public Interest Registry + "xn--nqv7fs00ema", // 组织机构 Public Interest Registry + "xn--nyqy26a", // 健康 Stable Tone Limited + "xn--p1acf", // рус Rusnames Limited + "xn--pbt977c", // 珠宝 Richemont DNS Inc. + "xn--pssy2u", // 大拿 VeriSign Sarl + "xn--q9jyb4c", // みんな Charleston Road Registry Inc. + "xn--qcka1pmc", // グーグル Charleston Road Registry Inc. + "xn--rhqv96g", // 世界 Stable Tone Limited + "xn--rovu88b", // 書籍 Amazon EU S.à r.l. + "xn--ses554g", // 网址 KNET Co., Ltd + "xn--t60b56a", // 닷넷 VeriSign Sarl + "xn--tckwe", // コム VeriSign Sarl + "xn--unup4y", // 游戏 Spring Fields, LLC + "xn--vermgensberater-ctb", // VERMöGENSBERATER Deutsche Vermögensberatung + // Aktiengesellschaft DVAG + "xn--vermgensberatung-pwb", // VERMöGENSBERATUNG Deutsche Vermögensberatung + // Aktiengesellschaft DVAG + "xn--vhquv", // 企业 Dash McCook, LLC + "xn--vuq861b", // 信息 Beijing Tele-info Network Technology Co., Ltd. + "xn--w4r85el8fhu5dnra", // 嘉里大酒店 Kerry Trading Co. Limited + "xn--xhq521b", // 广东 Guangzhou YU Wei Information Technology Co., Ltd. + "xn--zfr164b", // 政务 China Organizational Name Administration Center + "xperia", // xperia Sony Mobile Communications AB + "xxx", // xxx ICM Registry LLC + "xyz", // xyz XYZ.COM LLC + "yachts", // yachts DERYachts, LLC + "yahoo", // yahoo Yahoo! Domain Services Inc. + "yamaxun", // yamaxun Amazon Registry Services, Inc. + "yandex", // yandex YANDEX, LLC + "yodobashi", // yodobashi YODOBASHI CAMERA CO.,LTD. + "yoga", // yoga Top Level Domain Holdings Limited + "yokohama", // yokohama GMO Registry, Inc. + "you", // you Amazon Registry Services, Inc. + "youtube", // youtube Charleston Road Registry Inc. + "yun", // yun QIHOO 360 TECHNOLOGY CO. LTD. + "zara", // zara Industria de Diseño Textil, S.A. (INDITEX, S.A.) + "zero", // zero Amazon Registry Services, Inc. + "zip", // zip Charleston Road Registry Inc. + "zone", // zone Outer Falls, LLC + "zuerich", // zuerich Kanton Zürich (Canton of Zurich) + }; + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary + // search + private static final String[] COUNTRY_CODE_TLDS = + new String[] { + "ac", // Ascension Island + "ad", // Andorra + "ae", // United Arab Emirates + "af", // Afghanistan + "ag", // Antigua and Barbuda + "ai", // Anguilla + "al", // Albania + "am", // Armenia + // "an", // Netherlands Antilles (retired) + "ao", // Angola + "aq", // Antarctica + "ar", // Argentina + "as", // American Samoa + "at", // Austria + "au", // Australia (includes Ashmore and Cartier Islands and Coral Sea Islands) + "aw", // Aruba + "ax", // Åland + "az", // Azerbaijan + "ba", // Bosnia and Herzegovina + "bb", // Barbados + "bd", // Bangladesh + "be", // Belgium + "bf", // Burkina Faso + "bg", // Bulgaria + "bh", // Bahrain + "bi", // Burundi + "bj", // Benin + "bm", // Bermuda + "bn", // Brunei Darussalam + "bo", // Bolivia + "br", // Brazil + "bs", // Bahamas + "bt", // Bhutan + "bv", // Bouvet Island + "bw", // Botswana + "by", // Belarus + "bz", // Belize + "ca", // Canada + "cc", // Cocos (Keeling) Islands + "cd", // Democratic Republic of the Congo (formerly Zaire) + "cf", // Central African Republic + "cg", // Republic of the Congo + "ch", // Switzerland + "ci", // Côte d'Ivoire + "ck", // Cook Islands + "cl", // Chile + "cm", // Cameroon + "cn", // China, mainland + "co", // Colombia + "cr", // Costa Rica + "cu", // Cuba + "cv", // Cape Verde + "cw", // Curaçao + "cx", // Christmas Island + "cy", // Cyprus + "cz", // Czech Republic + "de", // Germany + "dj", // Djibouti + "dk", // Denmark + "dm", // Dominica + "do", // Dominican Republic + "dz", // Algeria + "ec", // Ecuador + "ee", // Estonia + "eg", // Egypt + "er", // Eritrea + "es", // Spain + "et", // Ethiopia + "eu", // European Union + "fi", // Finland + "fj", // Fiji + "fk", // Falkland Islands + "fm", // Federated States of Micronesia + "fo", // Faroe Islands + "fr", // France + "ga", // Gabon + "gb", // Great Britain (United Kingdom) + "gd", // Grenada + "ge", // Georgia + "gf", // French Guiana + "gg", // Guernsey + "gh", // Ghana + "gi", // Gibraltar + "gl", // Greenland + "gm", // The Gambia + "gn", // Guinea + "gp", // Guadeloupe + "gq", // Equatorial Guinea + "gr", // Greece + "gs", // South Georgia and the South Sandwich Islands + "gt", // Guatemala + "gu", // Guam + "gw", // Guinea-Bissau + "gy", // Guyana + "hk", // Hong Kong + "hm", // Heard Island and McDonald Islands + "hn", // Honduras + "hr", // Croatia (Hrvatska) + "ht", // Haiti + "hu", // Hungary + "id", // Indonesia + "ie", // Ireland (Éire) + "il", // Israel + "im", // Isle of Man + "in", // India + "io", // British Indian Ocean Territory + "iq", // Iraq + "ir", // Iran + "is", // Iceland + "it", // Italy + "je", // Jersey + "jm", // Jamaica + "jo", // Jordan + "jp", // Japan + "ke", // Kenya + "kg", // Kyrgyzstan + "kh", // Cambodia (Khmer) + "ki", // Kiribati + "km", // Comoros + "kn", // Saint Kitts and Nevis + "kp", // North Korea + "kr", // South Korea + "kw", // Kuwait + "ky", // Cayman Islands + "kz", // Kazakhstan + "la", // Laos (currently being marketed as the official domain for Los Angeles) + "lb", // Lebanon + "lc", // Saint Lucia + "li", // Liechtenstein + "lk", // Sri Lanka + "lr", // Liberia + "ls", // Lesotho + "lt", // Lithuania + "lu", // Luxembourg + "lv", // Latvia + "ly", // Libya + "ma", // Morocco + "mc", // Monaco + "md", // Moldova + "me", // Montenegro + "mg", // Madagascar + "mh", // Marshall Islands + "mk", // Republic of Macedonia + "ml", // Mali + "mm", // Myanmar + "mn", // Mongolia + "mo", // Macau + "mp", // Northern Mariana Islands + "mq", // Martinique + "mr", // Mauritania + "ms", // Montserrat + "mt", // Malta + "mu", // Mauritius + "mv", // Maldives + "mw", // Malawi + "mx", // Mexico + "my", // Malaysia + "mz", // Mozambique + "na", // Namibia + "nc", // New Caledonia + "ne", // Niger + "nf", // Norfolk Island + "ng", // Nigeria + "ni", // Nicaragua + "nl", // Netherlands + "no", // Norway + "np", // Nepal + "nr", // Nauru + "nu", // Niue + "nz", // New Zealand + "om", // Oman + "pa", // Panama + "pe", // Peru + "pf", // French Polynesia With Clipperton Island + "pg", // Papua New Guinea + "ph", // Philippines + "pk", // Pakistan + "pl", // Poland + "pm", // Saint-Pierre and Miquelon + "pn", // Pitcairn Islands + "pr", // Puerto Rico + "ps", // Palestinian territories (PA-controlled West Bank and Gaza Strip) + "pt", // Portugal + "pw", // Palau + "py", // Paraguay + "qa", // Qatar + "re", // Réunion + "ro", // Romania + "rs", // Serbia + "ru", // Russia + "rw", // Rwanda + "sa", // Saudi Arabia + "sb", // Solomon Islands + "sc", // Seychelles + "sd", // Sudan + "se", // Sweden + "sg", // Singapore + "sh", // Saint Helena + "si", // Slovenia + "sj", // Svalbard and Jan Mayen Islands Not in use (Norwegian dependencies; see .no) + "sk", // Slovakia + "sl", // Sierra Leone + "sm", // San Marino + "sn", // Senegal + "so", // Somalia + "sr", // Suriname + "st", // São Tomé and Príncipe + "su", // Soviet Union (deprecated) + "sv", // El Salvador + "sx", // Sint Maarten + "sy", // Syria + "sz", // Swaziland + "tc", // Turks and Caicos Islands + "td", // Chad + "tf", // French Southern and Antarctic Lands + "tg", // Togo + "th", // Thailand + "tj", // Tajikistan + "tk", // Tokelau + "tl", // East Timor (deprecated old code) + "tm", // Turkmenistan + "tn", // Tunisia + "to", // Tonga + // "tp", // East Timor (Retired) + "tr", // Turkey + "tt", // Trinidad and Tobago + "tv", // Tuvalu + "tw", // Taiwan, Republic of China + "tz", // Tanzania + "ua", // Ukraine + "ug", // Uganda + "uk", // United Kingdom + "us", // United States of America + "uy", // Uruguay + "uz", // Uzbekistan + "va", // Vatican City State + "vc", // Saint Vincent and the Grenadines + "ve", // Venezuela + "vg", // British Virgin Islands + "vi", // U.S. Virgin Islands + "vn", // Vietnam + "vu", // Vanuatu + "wf", // Wallis and Futuna + "ws", // Samoa (formerly Western Samoa) + "xn--3e0b707e", // 한국 KISA (Korea Internet & Security Agency) + "xn--45brj9c", // ভারত National Internet Exchange of India + "xn--80ao21a", // қаз Association of IT Companies of Kazakhstan + "xn--90a3ac", // срб Serbian National Internet Domain Registry (RNIDS) + "xn--90ais", // ??? Reliable Software Inc. + "xn--clchc0ea0b2g2a9gcd", // சிங்கப்பூர் Singapore Network Information Centre + // (SGNIC) Pte Ltd + "xn--d1alf", // мкд Macedonian Academic Research Network Skopje + "xn--e1a4c", // ею EURid vzw/asbl + "xn--fiqs8s", // 中国 China Internet Network Information Center + "xn--fiqz9s", // 中國 China Internet Network Information Center + "xn--fpcrj9c3d", // భారత్ National Internet Exchange of India + "xn--fzc2c9e2c", // ලංකා LK Domain Registry + "xn--gecrj9c", // ભારત National Internet Exchange of India + "xn--h2brj9c", // भारत National Internet Exchange of India + "xn--j1amh", // укр Ukrainian Network Information Centre (UANIC), Inc. + "xn--j6w193g", // 香港 Hong Kong Internet Registration Corporation Ltd. + "xn--kprw13d", // 台湾 Taiwan Network Information Center (TWNIC) + "xn--kpry57d", // 台灣 Taiwan Network Information Center (TWNIC) + "xn--l1acc", // мон Datacom Co.,Ltd + "xn--lgbbat1ad8j", // الجزائر CERIST + "xn--mgb9awbf", // عمان Telecommunications Regulatory Authority (TRA) + "xn--mgba3a4f16a", // ایران Institute for Research in Fundamental Sciences (IPM) + "xn--mgbaam7a8h", // امارات Telecommunications Regulatory Authority (TRA) + "xn--mgbayh7gpa", // الاردن National Information Technology Center (NITC) + "xn--mgbbh1a71e", // بھارت National Internet Exchange of India + "xn--mgbc0a9azcg", // المغرب Agence Nationale de Réglementation des + // Télécommunications (ANRT) + "xn--mgberp4a5d4ar", // السعودية Communications and Information Technology + // Commission + "xn--mgbpl2fh", // ????? Sudan Internet Society + "xn--mgbtx2b", // عراق Communications and Media Commission (CMC) + "xn--mgbx4cd0ab", // مليسيا MYNIC Berhad + "xn--mix891f", // 澳門 Bureau of Telecommunications Regulation (DSRT) + "xn--node", // გე Information Technologies Development Center (ITDC) + "xn--o3cw4h", // ไทย Thai Network Information Center Foundation + "xn--ogbpf8fl", // سورية National Agency for Network Services (NANS) + "xn--p1ai", // рф Coordination Center for TLD RU + "xn--pgbs0dh", // تونس Agence Tunisienne d'Internet + "xn--qxam", // ελ ICS-FORTH GR + "xn--s9brj9c", // ਭਾਰਤ National Internet Exchange of India + "xn--wgbh1c", // مصر National Telecommunication Regulatory Authority - NTRA + "xn--wgbl6a", // قطر Communications Regulatory Authority + "xn--xkc2al3hye2a", // இலங்கை LK Domain Registry + "xn--xkc2dl3a5ee0h", // இந்தியா National Internet Exchange of India + "xn--y9a3aq", // ??? Internet Society + "xn--yfro4i67o", // 新加坡 Singapore Network Information Centre (SGNIC) Pte Ltd + "xn--ygbi2ammx", // فلسطين Ministry of Telecom & Information Technology (MTIT) + "ye", // Yemen + "yt", // Mayotte + "za", // South Africa + "zm", // Zambia + "zw", // Zimbabwe + }; + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary + // search + private static final String[] LOCAL_TLDS = + new String[] { + "localdomain", // Also widely used as localhost.localdomain + "localhost", // RFC2606 defined + }; // Additional arrays to supplement or override the built in ones. // The PLUS arrays are valid keys, the MINUS arrays are invalid keys /* * This field is used to detect whether the getInstance has been called. * After this, the method updateTLDOverride is not allowed to be called. * This field does not need to be volatile since it is only accessed from - * synchronized methods. + * synchronized methods. */ private static boolean inUse = false; /* @@ -1576,28 +1584,43 @@ private String chompLeadingDot(String str) { * They can only be updated by the updateTLDOverride method, and any readers must get an instance * using the getInstance methods which are all (now) synchronised. */ - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary + // search private static volatile String[] countryCodeTLDsPlus = EMPTY_STRING_ARRAY; - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary + // search private static volatile String[] genericTLDsPlus = EMPTY_STRING_ARRAY; - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary + // search private static volatile String[] countryCodeTLDsMinus = EMPTY_STRING_ARRAY; - // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search + // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary + // search private static volatile String[] genericTLDsMinus = EMPTY_STRING_ARRAY; /** - * enum used by {@link DomainValidator#updateTLDOverride(ArrayType, String[])} - * to determine which override array to update / fetch + * enum used by {@link DomainValidator#updateTLDOverride(ArrayType, String[])} to determine + * which override array to update / fetch + * * @since 1.5.0 * @since 1.5.1 made public and added read-only array references */ public enum ArrayType { - /** Update (or get a copy of) the GENERIC_TLDS_PLUS table containing additonal generic TLDs */ + /** + * Update (or get a copy of) the GENERIC_TLDS_PLUS table containing additonal generic TLDs + */ GENERIC_PLUS, - /** Update (or get a copy of) the GENERIC_TLDS_MINUS table containing deleted generic TLDs */ + /** + * Update (or get a copy of) the GENERIC_TLDS_MINUS table containing deleted generic TLDs + */ GENERIC_MINUS, - /** Update (or get a copy of) the COUNTRY_CODE_TLDS_PLUS table containing additonal country code TLDs */ + /** + * Update (or get a copy of) the COUNTRY_CODE_TLDS_PLUS table containing additonal country + * code TLDs + */ COUNTRY_CODE_PLUS, - /** Update (or get a copy of) the COUNTRY_CODE_TLDS_MINUS table containing deleted country code TLDs */ + /** + * Update (or get a copy of) the COUNTRY_CODE_TLDS_MINUS table containing deleted country + * code TLDs + */ COUNTRY_CODE_MINUS, /** Get a copy of the generic TLDS table */ GENERIC_RO, @@ -1606,8 +1629,7 @@ public enum ArrayType { /** Get a copy of the infrastructure table */ INFRASTRUCTURE_RO, /** Get a copy of the local table */ - LOCAL_RO - ; + LOCAL_RO; }; // For use by unit test code only static synchronized void clearTLDOverrides() { @@ -1618,39 +1640,41 @@ static synchronized void clearTLDOverrides() { genericTLDsMinus = EMPTY_STRING_ARRAY; } /** - * Update one of the TLD override arrays. - * This must only be done at program startup, before any instances are accessed using getInstance. - *

- * For example: - *

- * {@code DomainValidator.updateTLDOverride(ArrayType.GENERIC_PLUS, new String[]{"apache"})} - *

- * To clear an override array, provide an empty array. + * Update one of the TLD override arrays. This must only be done at program startup, before any + * instances are accessed using getInstance. + * + *

For example: + * + *

{@code DomainValidator.updateTLDOverride(ArrayType.GENERIC_PLUS, new String[]{"apache"})} + * + *

To clear an override array, provide an empty array. + * + * @param table the table to update, see {@link DomainValidator.ArrayType} Must be one of the + * following + *

    + *
  • COUNTRY_CODE_MINUS + *
  • COUNTRY_CODE_PLUS + *
  • GENERIC_MINUS + *
  • GENERIC_PLUS + *
* - * @param table the table to update, see {@link DomainValidator.ArrayType} - * Must be one of the following - *
    - *
  • COUNTRY_CODE_MINUS
  • - *
  • COUNTRY_CODE_PLUS
  • - *
  • GENERIC_MINUS
  • - *
  • GENERIC_PLUS
  • - *
* @param tlds the array of TLDs, must not be null * @throws IllegalStateException if the method is called after getInstance * @throws IllegalArgumentException if one of the read-only tables is requested * @since 1.5.0 */ - public static synchronized void updateTLDOverride(ArrayType table, String [] tlds) { + public static synchronized void updateTLDOverride(ArrayType table, String[] tlds) { if (inUse) { - throw new IllegalStateException("Can only invoke this method before calling getInstance"); + throw new IllegalStateException( + "Can only invoke this method before calling getInstance"); } - String [] copy = new String[tlds.length]; + String[] copy = new String[tlds.length]; // Comparisons are always done with lower-case entries for (int i = 0; i < tlds.length; i++) { copy[i] = tlds[i].toLowerCase(Locale.ENGLISH); } Arrays.sort(copy); - switch(table) { + switch (table) { case COUNTRY_CODE_MINUS: countryCodeTLDsMinus = copy; break; @@ -1674,14 +1698,15 @@ public static synchronized void updateTLDOverride(ArrayType table, String [] tld } /** * Get a copy of the internal array. + * * @param table the array type (any of the enum values) * @return a copy of the array * @throws IllegalArgumentException if the table type is unexpected (should not happen) * @since 1.5.1 */ - public static String [] getTLDEntries(ArrayType table) { + public static String[] getTLDEntries(ArrayType table) { final String array[]; - switch(table) { + switch (table) { case COUNTRY_CODE_MINUS: array = countryCodeTLDsMinus; break; @@ -1712,8 +1737,8 @@ public static synchronized void updateTLDOverride(ArrayType table, String [] tld return Arrays.copyOf(array, array.length); // clone the array } /** - * Converts potentially Unicode input to punycode. - * If conversion fails, returns the original input. + * Converts potentially Unicode input to punycode. If conversion fails, returns the original + * input. * * @param input the string to convert, not null * @return converted input, or original input if conversion fails @@ -1729,7 +1754,7 @@ static String unicodeToASCII(String input) { return ascii; } final int length = input.length(); - if (length == 0) {// check there is a last character + if (length == 0) { // check there is a last character return input; } // RFC3490 3.1. 1) @@ -1737,8 +1762,8 @@ static String unicodeToASCII(String input) { // characters MUST be recognized as dots: U+002E (full stop), U+3002 // (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61 // (halfwidth ideographic full stop). - char lastChar = input.charAt(length-1);// fetch original last char - switch(lastChar) { + char lastChar = input.charAt(length - 1); // fetch original last char + switch (lastChar) { case '\u002E': // "." full stop case '\u3002': // ideographic full stop case '\uFF0E': // fullwidth full stop @@ -1751,11 +1776,13 @@ static String unicodeToASCII(String input) { return input; } } + private static class IDNBUGHOLDER { private static boolean keepsTrailingDot() { final String input = "a."; // must be a valid name return input.equals(IDN.toASCII(input)); } + private static final boolean IDN_TOASCII_PRESERVES_TRAILING_DOTS = keepsTrailingDot(); } /* @@ -1766,7 +1793,7 @@ private static boolean isOnlyASCII(String input) { if (input == null) { return true; } - for(int i=0; i < input.length(); i++) { + for (int i = 0; i < input.length(); i++) { if (input.charAt(i) > 0x7F) { // CHECKSTYLE IGNORE MagicNumber return false; } diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/PublicKeyPin.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/PublicKeyPin.java index a7aad1b..2b6144f 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/PublicKeyPin.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/PublicKeyPin.java @@ -1,12 +1,11 @@ package com.datatheorem.android.trustkit.config; -import androidx.annotation.NonNull; import android.util.Base64; +import androidx.annotation.NonNull; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; - /** * A pin is the base64-encoded SHA-256 hash of the certificate's Subject Public Key Info, as * described in the HPKP RFC https://tools.ietf.org/html/rfc7469s . @@ -50,5 +49,7 @@ public int hashCode() { } @Override - public String toString(){ return pin; } + public String toString() { + return pin; + } } diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/RegexValidator.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/RegexValidator.java index 53611dd..7e0935c 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/RegexValidator.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/RegexValidator.java @@ -1,4 +1,5 @@ -// TrustKit: Taken from https://apache.googlesource.com/commons-validator/+/VALIDATOR_1_5_1/src/main/java/org/apache/commons/validator/routines/RegexValidator.java +// TrustKit: Taken from +// https://apache.googlesource.com/commons-validator/+/VALIDATOR_1_5_1/src/main/java/org/apache/commons/validator/routines/RegexValidator.java package com.datatheorem.android.trustkit.config; /* @@ -21,15 +22,15 @@ import java.io.Serializable; import java.util.regex.Matcher; import java.util.regex.Pattern; + /** * Regular Expression validation (using JDK 1.4+ regex support). - *

- * Construct the validator either for a single regular expression or a set (array) of - * regular expressions. By default validation is case sensitive but constructors - * are provided to allow case in-sensitive validation. For example to create - * a validator which does case in-sensitive validation for a set of regular - * expressions: - *

+ * + *

Construct the validator either for a single regular expression or a set (array) of regular + * expressions. By default validation is case sensitive but constructors are provided to + * allow case in-sensitive validation. For example to create a validator which does case + * in-sensitive validation for a set of regular expressions: + * *

  * 
  * String[] regexs = new String[] {...};
@@ -38,32 +39,27 @@
  * 
* *
    - *
  • Validate true or false:
  • + *
  • Validate true or false: *
  • - *
      - *
    • boolean valid = validator.isValid(value);
    • - *
    - *
  • - *
  • Validate returning an aggregated String of the matched groups:
  • + *
      + *
    • boolean valid = validator.isValid(value); + *
    + *
  • Validate returning an aggregated String of the matched groups: *
  • - *
      - *
    • String result = validator.validate(value);
    • - *
    - *
  • - *
  • Validate returning the matched groups:
  • + *
      + *
    • String result = validator.validate(value); + *
    + *
  • Validate returning the matched groups: *
  • - *
      - *
    • String[] result = validator.match(value);
    • - *
    - *
  • + *
      + *
    • String[] result = validator.match(value); + *
    *
* * Note that patterns are matched against the entire input. * - *

- * Cached instances pre-compile and re-use {@link Pattern}(s) - which according - * to the {@link Pattern} API are safe to use in a multi-threaded environment. - *

+ *

Cached instances pre-compile and re-use {@link Pattern}(s) - which according to the {@link + * Pattern} API are safe to use in a multi-threaded environment. * * @version $Revision$ * @since Validator 1.4 @@ -73,65 +69,58 @@ class RegexValidator implements Serializable { private static final long serialVersionUID = -8832409930574867162L; private final Pattern[] patterns; /** - * Construct a case sensitive validator for a single - * regular expression. + * Construct a case sensitive validator for a single regular expression. * - * @param regex The regular expression this validator will - * validate against + * @param regex The regular expression this validator will validate against */ public RegexValidator(String regex) { this(regex, true); } /** - * Construct a validator for a single regular expression - * with the specified case sensitivity. + * Construct a validator for a single regular expression with the specified case sensitivity. * - * @param regex The regular expression this validator will - * validate against - * @param caseSensitive when true matching is case - * sensitive, otherwise matching is case in-sensitive + * @param regex The regular expression this validator will validate against + * @param caseSensitive when true matching is case sensitive, otherwise + * matching is case in-sensitive */ public RegexValidator(String regex, boolean caseSensitive) { this(new String[] {regex}, caseSensitive); } /** - * Construct a case sensitive validator that matches any one - * of the set of regular expressions. + * Construct a case sensitive validator that matches any one of the set of regular + * expressions. * - * @param regexs The set of regular expressions this validator will - * validate against + * @param regexs The set of regular expressions this validator will validate against */ public RegexValidator(String[] regexs) { this(regexs, true); } /** - * Construct a validator that matches any one of the set of regular - * expressions with the specified case sensitivity. + * Construct a validator that matches any one of the set of regular expressions with the + * specified case sensitivity. * - * @param regexs The set of regular expressions this validator will - * validate against - * @param caseSensitive when true matching is case - * sensitive, otherwise matching is case in-sensitive + * @param regexs The set of regular expressions this validator will validate against + * @param caseSensitive when true matching is case sensitive, otherwise + * matching is case in-sensitive */ public RegexValidator(String[] regexs, boolean caseSensitive) { if (regexs == null || regexs.length == 0) { throw new IllegalArgumentException("Regular expressions are missing"); } patterns = new Pattern[regexs.length]; - int flags = (caseSensitive ? 0: Pattern.CASE_INSENSITIVE); + int flags = (caseSensitive ? 0 : Pattern.CASE_INSENSITIVE); for (int i = 0; i < regexs.length; i++) { if (regexs[i] == null || regexs[i].length() == 0) { throw new IllegalArgumentException("Regular expression[" + i + "] is missing"); } - patterns[i] = Pattern.compile(regexs[i], flags); + patterns[i] = Pattern.compile(regexs[i], flags); } } /** * Validate a value against the set of regular expressions. * * @param value The value to validate. - * @return true if the value is valid - * otherwise false. + * @return true if the value is valid otherwise false. */ public boolean isValid(String value) { if (value == null) { @@ -145,12 +134,11 @@ public boolean isValid(String value) { return false; } /** - * Validate a value against the set of regular expressions - * returning the array of matched groups. + * Validate a value against the set of regular expressions returning the array of matched + * groups. * * @param value The value to validate. - * @return String array of the groups matched if - * valid or null if invalid + * @return String array of the groups matched if valid or null if invalid */ public String[] match(String value) { if (value == null) { @@ -162,7 +150,7 @@ public String[] match(String value) { int count = matcher.groupCount(); String[] groups = new String[count]; for (int j = 0; j < count; j++) { - groups[j] = matcher.group(j+1); + groups[j] = matcher.group(j + 1); } return groups; } @@ -170,12 +158,12 @@ public String[] match(String value) { return null; } /** - * Validate a value against the set of regular expressions - * returning a String value of the aggregated groups. + * Validate a value against the set of regular expressions returning a String value of the + * aggregated groups. * * @param value The value to validate. - * @return Aggregated String value comprised of the - * groups matched if valid or null if invalid + * @return Aggregated String value comprised of the groups matched if valid or null + * if invalid */ public String validate(String value) { if (value == null) { @@ -190,7 +178,7 @@ public String validate(String value) { } StringBuilder buffer = new StringBuilder(); for (int j = 0; j < count; j++) { - String component = matcher.group(j+1); + String component = matcher.group(j + 1); if (component != null) { buffer.append(component); } @@ -202,6 +190,7 @@ public String validate(String value) { } /** * Provide a String representation of this validator. + * * @return A String representation of this validator */ @Override diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/TrustKitConfiguration.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/TrustKitConfiguration.java index 93e4b73..e755231 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/TrustKitConfiguration.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/TrustKitConfiguration.java @@ -11,7 +11,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; - public class TrustKitConfiguration { @NonNull private final Set domainPolicies; @@ -21,14 +20,12 @@ public class TrustKitConfiguration { private final boolean shouldOverridePins; @Nullable private final Set debugCaCertificates; - public static TrustKitConfiguration fromXmlPolicy( - @NonNull Context context, @NonNull XmlPullParser parser - ) throws CertificateException, XmlPullParserException, IOException { + @NonNull Context context, @NonNull XmlPullParser parser) + throws CertificateException, XmlPullParserException, IOException { return TrustKitConfigurationParser.fromXmlPolicy(context, parser); } - protected TrustKitConfiguration(@NonNull Set domainConfigSet) { this(domainConfigSet, false, null); } @@ -36,13 +33,13 @@ protected TrustKitConfiguration(@NonNull Set domainConfigSe protected TrustKitConfiguration( @NonNull Set domainConfigSet, boolean shouldOverridePins, - @Nullable Set debugCaCerts - ) { + @Nullable Set debugCaCerts) { Set hostnameSet = new HashSet<>(); for (DomainPinningPolicy domainConfig : domainConfigSet) { if (hostnameSet.contains(domainConfig.getHostname())) { - throw new ConfigurationException("Policy contains the same domain defined twice: " - + domainConfig.getHostname()); + throw new ConfigurationException( + "Policy contains the same domain defined twice: " + + domainConfig.getHostname()); } hostnameSet.add(domainConfig.getHostname()); } @@ -70,13 +67,13 @@ public Set getAllPolicies() { } /** - * Get the {@link DomainPinningPolicy} corresponding to the provided hostname. - * When matching the most specific matching domain rule will be used, if no match exists - * then null will be returned. + * Get the {@link DomainPinningPolicy} corresponding to the provided hostname. When matching the + * most specific matching domain rule will be used, if no match exists then null will be + * returned. * * @param serverHostname the server's hostname * @return DomainPinningPolicy the domain's policy or null if the supplied hostname has no - * policy defined + * policy defined */ @Nullable public DomainPinningPolicy getPolicyForHostname(@NonNull String serverHostname) { @@ -100,7 +97,8 @@ public DomainPinningPolicy getPolicyForHostname(@NonNull String serverHostname) && isSubdomain(domainPolicy.getHostname(), serverHostname)) { if (bestMatchPolicy == null) { bestMatchPolicy = domainPolicy; - } else if (domainPolicy.getHostname().length() > bestMatchPolicy.getHostname().length()) { + } else if (domainPolicy.getHostname().length() + > bestMatchPolicy.getHostname().length()) { bestMatchPolicy = domainPolicy; } } @@ -109,8 +107,8 @@ && isSubdomain(domainPolicy.getHostname(), serverHostname)) { } /** - * Return true for all subdomains, including subdomains of subdomains, similar to how - * Android N handles includeSubdomains + * Return true for all subdomains, including subdomains of subdomains, similar to how Android N + * handles includeSubdomains */ private static boolean isSubdomain(@NonNull String domain, @NonNull String subdomain) { return subdomain.endsWith(domain) diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/TrustKitConfigurationParser.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/TrustKitConfigurationParser.java index 2c1f567..aca8dfa 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/config/TrustKitConfigurationParser.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/config/TrustKitConfigurationParser.java @@ -1,10 +1,8 @@ package com.datatheorem.android.trustkit.config; - import android.content.Context; -import androidx.annotation.NonNull; import android.text.TextUtils; - +import androidx.annotation.NonNull; import com.datatheorem.android.trustkit.utils.TrustKitLog; import java.io.IOException; import java.io.InputStream; @@ -22,17 +20,16 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; - class TrustKitConfigurationParser { /** - * Parse an XML TrustKit / Network Security policy and return the corresponding - * {@link TrustKitConfiguration}. + * Parse an XML TrustKit / Network Security policy and return the corresponding {@link + * TrustKitConfiguration}. */ @NonNull public static TrustKitConfiguration fromXmlPolicy( - @NonNull Context context, @NonNull XmlPullParser parser - ) throws XmlPullParserException, IOException, CertificateException { + @NonNull Context context, @NonNull XmlPullParser parser) + throws XmlPullParserException, IOException, CertificateException { // Handle nested domain config tags // https://developer.android.com/training/articles/security-config.html#ConfigInheritance List builderList = new ArrayList<>(); @@ -64,11 +61,11 @@ public static TrustKitConfiguration fromXmlPolicy( } if (debugOverridesTag != null) { - config = new TrustKitConfiguration( - domainConfigSet, - debugOverridesTag.overridePins, - debugOverridesTag.debugCaCertificates - ); + config = + new TrustKitConfiguration( + domainConfigSet, + debugOverridesTag.overridePins, + debugOverridesTag.debugCaCertificates); } else { config = new TrustKitConfiguration(domainConfigSet); } @@ -78,12 +75,12 @@ public static TrustKitConfiguration fromXmlPolicy( // Heavily inspired from // https://github.com/android/platform_frameworks_base/blob/master/core/java/android/security/net/config/XmlConfigSource.java private static List readDomainConfig( - XmlPullParser parser, DomainPinningPolicy.Builder parentBuilder - ) throws XmlPullParserException, IOException { + XmlPullParser parser, DomainPinningPolicy.Builder parentBuilder) + throws XmlPullParserException, IOException { parser.require(XmlPullParser.START_TAG, null, "domain-config"); - DomainPinningPolicy.Builder builder = new DomainPinningPolicy.Builder() - .setParent(parentBuilder); + DomainPinningPolicy.Builder builder = + new DomainPinningPolicy.Builder().setParent(parentBuilder); List builderList = new ArrayList<>(); // Put the current builder as the first one in the list, so the parent always gets built @@ -91,7 +88,8 @@ private static List readDomainConfig( builderList.add(builder); int eventType = parser.next(); - while (!((eventType == XmlPullParser.END_TAG) && "domain-config".equals(parser.getName()))) { + while (!((eventType == XmlPullParser.END_TAG) + && "domain-config".equals(parser.getName()))) { if (eventType == XmlPullParser.START_TAG) { if ("domain-config".equals(parser.getName())) { // Nested domain configuration tag @@ -122,14 +120,15 @@ private static class PinSetTag { } @NonNull - private static PinSetTag readPinSet(@NonNull XmlPullParser parser) throws IOException, - XmlPullParserException { + private static PinSetTag readPinSet(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, null, "pin-set"); PinSetTag pinSetTag = new PinSetTag(); pinSetTag.pins = new HashSet<>(); // Look for the expiration attribute - // Taken from https://github.com/android/platform_frameworks_base/blob/master/core/java/android/security/net/config/XmlConfigSource.java + // Taken from + // https://github.com/android/platform_frameworks_base/blob/master/core/java/android/security/net/config/XmlConfigSource.java String expirationDate = parser.getAttributeValue(null, "expiration"); if (expirationDate != null) { try { @@ -178,7 +177,6 @@ private static TrustkitConfigTag readTrustkitConfig(@NonNull XmlPullParser parse TrustkitConfigTag result = new TrustkitConfigTag(); Set reportUris = new HashSet<>(); - // Look for the enforcePinning attribute String enforcePinning = parser.getAttributeValue(null, "enforcePinning"); if (enforcePinning != null) { @@ -193,7 +191,8 @@ private static TrustkitConfigTag readTrustkitConfig(@NonNull XmlPullParser parse // Parse until the corresponding close trustkit-config tag int eventType = parser.next(); - while (!((eventType == XmlPullParser.END_TAG) && "trustkit-config".equals(parser.getName()))) { + while (!((eventType == XmlPullParser.END_TAG) + && "trustkit-config".equals(parser.getName()))) { // Look for the next report-uri tag if ((eventType == XmlPullParser.START_TAG) && "report-uri".equals(parser.getName())) { // Found one - parse the report-uri value @@ -212,8 +211,8 @@ private static class DomainTag { } @NonNull - private static DomainTag readDomain(@NonNull XmlPullParser parser) throws IOException, - XmlPullParserException { + private static DomainTag readDomain(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, null, "domain"); DomainTag result = new DomainTag(); @@ -234,8 +233,8 @@ private static class DebugOverridesTag { } @NonNull - private static DebugOverridesTag readDebugOverrides(@NonNull Context context, - @NonNull XmlPullParser parser) + private static DebugOverridesTag readDebugOverrides( + @NonNull Context context, @NonNull XmlPullParser parser) throws CertificateException, IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, null, "debug-overrides"); DebugOverridesTag result = new DebugOverridesTag(); @@ -243,7 +242,8 @@ private static DebugOverridesTag readDebugOverrides(@NonNull Context context, Set debugCaCertificates = new HashSet<>(); int eventType = parser.next(); - while (!((eventType == XmlPullParser.END_TAG) && "trust-anchors".equals(parser.getName()))) { + while (!((eventType == XmlPullParser.END_TAG) + && "trust-anchors".equals(parser.getName()))) { // Look for the next certificates tag if ((eventType == XmlPullParser.START_TAG) && "certificates".equals(parser.getName())) { // For simplicity, we only support one global overridePins setting, where Android N @@ -253,10 +253,11 @@ private static DebugOverridesTag readDebugOverrides(@NonNull Context context, if ((lastOverridePinsEncountered != null) && (lastOverridePinsEncountered != currentOverridePins)) { lastOverridePinsEncountered = false; - TrustKitLog.w("Warning: different values for overridePins are set in the " + - "policy but TrustKit only supports one value; using " + - "overridePins=false for all " + - "connections"); + TrustKitLog.w( + "Warning: different values for overridePins are set in the " + + "policy but TrustKit only supports one value; using " + + "overridePins=false for all " + + "connections"); } else { lastOverridePinsEncountered = currentOverridePins; } @@ -268,22 +269,28 @@ private static DebugOverridesTag readDebugOverrides(@NonNull Context context, // Parse the path to the certificate bundle for src=@raw - we ignore system or user // as the src - if (!TextUtils.isEmpty(caPathFromUser) && !caPathFromUser.equals("user") - && !caPathFromUser.equals("system") && caPathFromUser.startsWith("@raw")) { + if (!TextUtils.isEmpty(caPathFromUser) + && !caPathFromUser.equals("user") + && !caPathFromUser.equals("system") + && caPathFromUser.startsWith("@raw")) { InputStream stream = - context.getResources().openRawResource( - context.getResources().getIdentifier( - caPathFromUser.split("/")[1], "raw", - context.getPackageName())); + context.getResources() + .openRawResource( + context.getResources() + .getIdentifier( + caPathFromUser.split("/")[1], + "raw", + context.getPackageName())); - debugCaCertificates.add(CertificateFactory.getInstance("X.509") - .generateCertificate(stream)); + debugCaCertificates.add( + CertificateFactory.getInstance("X.509").generateCertificate(stream)); } else { - TrustKitLog.i("No certificates found by TrustKit." + - " Please check your @raw folder " + - "(TrustKit doesn't support system and user installed certificates)."); + TrustKitLog.i( + "No certificates found by TrustKit." + + " Please check your @raw folder " + + "(TrustKit doesn't support system and user installed certificates)."); } } eventType = parser.next(); @@ -298,11 +305,15 @@ private static DebugOverridesTag readDebugOverrides(@NonNull Context context, return result; } - private static String formatCertPathResourceWhenId(@NonNull Context context, String caPathFromUser) { - if(TextUtils.isDigitsOnly(caPathFromUser.replace("@", ""))){ - caPathFromUser = "@" + context.getResources() - .getResourceName(Integer.parseInt(caPathFromUser.replace("@", ""))) - .replace(context.getPackageName()+":", ""); + private static String formatCertPathResourceWhenId( + @NonNull Context context, String caPathFromUser) { + if (TextUtils.isDigitsOnly(caPathFromUser.replace("@", ""))) { + caPathFromUser = + "@" + + context.getResources() + .getResourceName( + Integer.parseInt(caPathFromUser.replace("@", ""))) + .replace(context.getPackageName() + ":", ""); } return caPathFromUser; diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/DebugOverridesTrustManager.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/DebugOverridesTrustManager.java index 7b506d9..d24db80 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/DebugOverridesTrustManager.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/DebugOverridesTrustManager.java @@ -13,7 +13,6 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; - /** * Used when is enabled in the network security policy and we are on a pre-N * Android device (as Android N automatically takes care of this). It returns a trust manager that @@ -22,8 +21,8 @@ */ class DebugOverridesTrustManager { - public static X509TrustManager getInstance(Set debugCaCerts) throws - CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { + public static X509TrustManager getInstance(Set debugCaCerts) + throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { X509TrustManager debugTrustManager = null; // Create a KeyStore containing our trusted CAs and the Android user and system CAs @@ -36,13 +35,13 @@ public static X509TrustManager getInstance(Set debugCaCerts) throws while (aliases.hasMoreElements()) { String alias = (String) aliases.nextElement(); X509Certificate cert = (X509Certificate) systemKeyStore.getCertificate(alias); - keyStore.setCertificateEntry(alias , cert); + keyStore.setCertificateEntry(alias, cert); } // Add the extra debug CAs to the store for (Certificate caCert : debugCaCerts) { String alias = "debug: " + ((X509Certificate) caCert).getSubjectDN().getName(); - keyStore.setCertificateEntry(alias , caCert); + keyStore.setCertificateEntry(alias, caCert); } // Create a TrustManager that trusts the CAs in our KeyStore @@ -62,4 +61,4 @@ public static X509TrustManager getInstance(Set debugCaCerts) throws } return debugTrustManager; } -} \ No newline at end of file +} diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/DistinguishedNameParser.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/DistinguishedNameParser.java index 234ce61..b84efc6 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/DistinguishedNameParser.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/DistinguishedNameParser.java @@ -1,5 +1,6 @@ package com.datatheorem.android.trustkit.pinning; -// TrustKit: imported from https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/internal/tls/DistinguishedNameParser.java +// TrustKit: imported from +// https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/internal/tls/DistinguishedNameParser.java /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -19,9 +20,10 @@ */ import javax.security.auth.x500.X500Principal; + /** - * A distinguished name (DN) parser. This parser only supports extracting a - * string value from a DN. It doesn't support values in the hex-string style. + * A distinguished name (DN) parser. This parser only supports extracting a string value from a DN. + * It doesn't support values in the hex-string style. * * @hide */ @@ -36,6 +38,7 @@ final class DistinguishedNameParser { private int cur; /** distinguished name chars */ private char[] chars; + public DistinguishedNameParser(X500Principal principal) { // RFC2253 is used to ensure we get attributes in the reverse // order of the underlying ASN.1 encoding, so that the most @@ -47,8 +50,7 @@ public DistinguishedNameParser(X500Principal principal) { private String nextAT() { // skip preceding space chars, they can present after // comma or semicolon (compatibility with RFC 1779) - for (; pos < length && chars[pos] == ' '; pos++) { - } + for (; pos < length && chars[pos] == ' '; pos++) {} if (pos == length) { return null; // reached the end of DN } @@ -68,20 +70,19 @@ private String nextAT() { // skip trailing space chars between attribute type and '=' // (compatibility with RFC 1779) if (chars[pos] == ' ') { - for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) { - } + for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) {} if (chars[pos] != '=' || pos == length) { throw new IllegalStateException("Unexpected end of DN: " + dn); } } - pos++; //skip '=' char + pos++; // skip '=' char // skip space chars between '=' and attribute value // (compatibility with RFC 1779) - for (; pos < length && chars[pos] == ' '; pos++) { - } + for (; pos < length && chars[pos] == ' '; pos++) {} // in case of oid attribute type skip its prefix: "oid." or "OID." // (compatibility with RFC 1779) - if ((end - beg > 4) && (chars[beg + 3] == '.') + if ((end - beg > 4) + && (chars[beg + 3] == '.') && (chars[beg] == 'O' || chars[beg] == 'o') && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i') && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) { @@ -113,8 +114,7 @@ private String quotedAV() { } // skip trailing space chars before comma or semicolon. // (compatibility with RFC 1779) - for (; pos < length && chars[pos] == ' '; pos++) { - } + for (; pos < length && chars[pos] == ' '; pos++) {} return new String(chars, beg, end - beg); } // gets hex string attribute value: "#" hexstring @@ -128,8 +128,7 @@ private String hexAV() { while (true) { // check for end of attribute value // looks for space and component separators - if (pos == length || chars[pos] == '+' || chars[pos] == ',' - || chars[pos] == ';') { + if (pos == length || chars[pos] == '+' || chars[pos] == ',' || chars[pos] == ';') { end = pos; break; } @@ -138,11 +137,10 @@ private String hexAV() { pos++; // skip trailing space chars before comma or semicolon. // (compatibility with RFC 1779) - for (; pos < length && chars[pos] == ' '; pos++) { - } + for (; pos < length && chars[pos] == ' '; pos++) {} break; } else if (chars[pos] >= 'A' && chars[pos] <= 'F') { - chars[pos] += 32; //to low case + chars[pos] += 32; // to low case } pos++; } @@ -188,7 +186,9 @@ private String escapedAV() { for (; pos < length && chars[pos] == ' '; pos++) { chars[end++] = ' '; } - if (pos == length || chars[pos] == ',' || chars[pos] == '+' + if (pos == length + || chars[pos] == ',' + || chars[pos] == '+' || chars[pos] == ';') { // separator char or the end of DN has been found return new String(chars, beg, cur - beg); @@ -220,7 +220,7 @@ private char getEscaped() { case '*': case '%': case '_': - //FIXME: escaping is allowed only for leading or trailing space char + // FIXME: escaping is allowed only for leading or trailing space char return chars[pos]; default: // RFC doesn't explicitly say that escaped hex pair is @@ -232,7 +232,7 @@ private char getEscaped() { // see http://www.unicode.org for UTF-8 bit distribution table private char getUTF8() { int res = getByte(pos); - pos++; //FIXME tmp + pos++; // FIXME tmp if (res < 128) { // one byte: 0-7F return (char) res; } else if (res >= 192 && res <= 247) { @@ -251,19 +251,19 @@ private char getUTF8() { for (int i = 0; i < count; i++) { pos++; if (pos == length || chars[pos] != '\\') { - return 0x3F; //FIXME failed to decode UTF-8 char - return '?' + return 0x3F; // FIXME failed to decode UTF-8 char - return '?' } pos++; b = getByte(pos); - pos++; //FIXME tmp + pos++; // FIXME tmp if ((b & 0xC0) != 0x80) { - return 0x3F; //FIXME failed to decode UTF-8 char - return '?' + return 0x3F; // FIXME failed to decode UTF-8 char - return '?' } res = (res << 6) + (b & 0x3F); } return (char) res; } else { - return 0x3F; //FIXME failed to decode UTF-8 char - return '?' + return 0x3F; // FIXME failed to decode UTF-8 char - return '?' } } // Returns byte representation of a char pair @@ -300,8 +300,8 @@ private int getByte(int position) { return (b1 << 4) + b2; } /** - * Parses the DN and returns the most significant attribute value - * for an attribute type, or null if none found. + * Parses the DN and returns the most significant attribute value for an attribute type, or null + * if none found. * * @param attributeType attribute type to look for (e.g. "ca") */ @@ -331,7 +331,7 @@ public String findMostSpecific(String attributeType) { case '+': case ',': case ';': // compatibility with RFC 1779: semicolon can separate RDNs - //empty attribute value + // empty attribute value break; default: attValue = escapedAV(); diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHostnameVerifier.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHostnameVerifier.java index 316aa64..bc08e71 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHostnameVerifier.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHostnameVerifier.java @@ -1,5 +1,6 @@ package com.datatheorem.android.trustkit.pinning; -// TrustKit: Imported from https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/internal/tls/OkHostnameVerifier.java +// TrustKit: Imported from +// https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/internal/tls/OkHostnameVerifier.java // Removed support for IP address certificates so we don't need to import more OkHttp files /* @@ -32,7 +33,6 @@ import javax.net.ssl.SSLSession; import javax.security.auth.x500.X500Principal; - /** * A HostnameVerifier consistent with RFC 2818. */ @@ -43,8 +43,7 @@ final class OkHostnameVerifier implements HostnameVerifier { private static final int ALT_DNS_NAME = 2; private static final int ALT_IPA_NAME = 7; - private OkHostnameVerifier() { - } + private OkHostnameVerifier() {} @Override public boolean verify(String host, SSLSession session) { @@ -58,8 +57,8 @@ public boolean verify(String host, SSLSession session) { public boolean verify(String host, X509Certificate certificate) { return Utils.verifyAsIpAddress(host) - ? verifyIpAddress(host, certificate) - : verifyHostname(host, certificate); + ? verifyIpAddress(host, certificate) + : verifyHostname(host, certificate); } /** Returns true if {@code certificate} matches {@code ipAddress}. */ @@ -140,17 +139,21 @@ private static List getSubjectAltNames(X509Certificate certificate, int * * @param hostname lower-case host name. * @param pattern domain name pattern from certificate. May be a wildcard pattern such as {@code - * *.android.com}. + * *.android.com}. */ private boolean verifyHostname(String hostname, String pattern) { // Basic sanity checks // Check length == 0 instead of .isEmpty() to support Java 5. - if ((hostname == null) || (hostname.length() == 0) || (hostname.startsWith(".")) + if ((hostname == null) + || (hostname.length() == 0) + || (hostname.startsWith(".")) || (hostname.endsWith(".."))) { // Invalid domain name return false; } - if ((pattern == null) || (pattern.length() == 0) || (pattern.startsWith(".")) + if ((pattern == null) + || (pattern.length() == 0) + || (pattern.startsWith(".")) || (pattern.endsWith(".."))) { // Invalid pattern/domain name return false; @@ -193,14 +196,17 @@ private boolean verifyHostname(String hostname, String pattern) { // 3. Wildcard patterns for single-label domain names are not permitted. if ((!pattern.startsWith("*.")) || (pattern.indexOf('*', 1) != -1)) { - // Asterisk (*) is only permitted in the left-most domain name label and must be the only + // Asterisk (*) is only permitted in the left-most domain name label and must be the + // only // character in that label return false; } - // Optimization: check whether hostname is too short to match the pattern. hostName must be at + // Optimization: check whether hostname is too short to match the pattern. hostName must be + // at // least as long as the pattern because asterisk must match the whole left-most label and - // hostname starts with a non-empty label. Thus, asterisk has to match one or more characters. + // hostname starts with a non-empty label. Thus, asterisk has to match one or more + // characters. if (hostname.length() < pattern.length()) { // hostname too short to match the pattern. return false; @@ -229,4 +235,4 @@ private boolean verifyHostname(String hostname, String pattern) { // hostname matches pattern return true; } -} \ No newline at end of file +} diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java index 03f2d9c..0f98afc 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java @@ -1,16 +1,12 @@ package com.datatheorem.android.trustkit.pinning; import android.os.Build; - import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; - import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.Request; - import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; - import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; @@ -28,19 +24,17 @@ public class OkHttp2Helper { /** * Retrieve an {@code SSLSSocketFactory} that implements SSL pinning validation based on the - * current TrustKit configuration. It can be used with an OkHttpClient to add SSL - * pinning validation to the connections. + * current TrustKit configuration. It can be used with an OkHttpClient to add SSL pinning + * validation to the connections. * - *

- * The {@code SSLSocketFactory} is configured for the current TrustKit configuration and - * will enforce the configuration's pinning policy. - *

+ *

The {@code SSLSocketFactory} is configured for the current TrustKit configuration and will + * enforce the configuration's pinning policy. */ @NonNull public static SSLSocketFactory getSSLSocketFactory() { try { SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, new X509TrustManager[]{trustManager}, null); + sslContext.init(null, new X509TrustManager[] {trustManager}, null); return sslContext.getSocketFactory(); } catch (NoSuchAlgorithmException | KeyManagementException e) { @@ -50,13 +44,13 @@ public static SSLSocketFactory getSSLSocketFactory() { } /** - * Returns an {@link com.squareup.okhttp.Interceptor} used to parse the hostname of the - * {@link Request} URL and then save the hostname in the {@link OkHttpRootTrustManager} which will + * Returns an {@link com.squareup.okhttp.Interceptor} used to parse the hostname of the {@link + * Request} URL and then save the hostname in the {@link OkHttpRootTrustManager} which will * later be used for Certificate Pinning. */ @NonNull @RequiresApi(api = 17) public static Interceptor getPinningInterceptor() { - return new OkHttp2PinningInterceptor((OkHttpRootTrustManager)trustManager); + return new OkHttp2PinningInterceptor((OkHttpRootTrustManager) trustManager); } } diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2PinningInterceptor.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2PinningInterceptor.java index 3ba1636..0e7b3d6 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2PinningInterceptor.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2PinningInterceptor.java @@ -1,11 +1,9 @@ package com.datatheorem.android.trustkit.pinning; import androidx.annotation.NonNull; - import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; - import java.io.IOException; /** @@ -19,7 +17,8 @@ public OkHttp2PinningInterceptor(@NonNull OkHttpRootTrustManager trustManager) { mTrustManager = trustManager; } - @Override public Response intercept(Interceptor.Chain chain) throws IOException { + @Override + public Response intercept(Interceptor.Chain chain) throws IOException { Request request = chain.request(); String serverHostname = request.url().getHost(); diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java index 5c07655..a0ffc14 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java @@ -1,17 +1,13 @@ package com.datatheorem.android.trustkit.pinning; import android.os.Build; - import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; - import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; - import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; - import okhttp3.Interceptor; import okhttp3.Request; @@ -28,19 +24,17 @@ public class OkHttp3Helper { /** * Retrieve an {@code SSLSSocketFactory} that implements SSL pinning validation based on the - * current TrustKit configuration. It can be used with an OkHttpClient to add SSL - * pinning validation to the connections. + * current TrustKit configuration. It can be used with an OkHttpClient to add SSL pinning + * validation to the connections. * - *

- * The {@code SSLSocketFactory} is configured for the current TrustKit configuration and - * will enforce the configuration's pinning policy. - *

+ *

The {@code SSLSocketFactory} is configured for the current TrustKit configuration and will + * enforce the configuration's pinning policy. */ @NonNull public static SSLSocketFactory getSSLSocketFactory() { try { SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, new X509TrustManager[]{trustManager}, null); + sslContext.init(null, new X509TrustManager[] {trustManager}, null); return sslContext.getSocketFactory(); } catch (NoSuchAlgorithmException | KeyManagementException e) { @@ -57,12 +51,10 @@ public static SSLSocketFactory getSSLSocketFactory() { @NonNull @RequiresApi(api = 17) public static Interceptor getPinningInterceptor() { - return new OkHttp3PinningInterceptor((OkHttpRootTrustManager)trustManager); + return new OkHttp3PinningInterceptor((OkHttpRootTrustManager) trustManager); } - /** - * Returns an instance of the {@link OkHttpRootTrustManager} used for Certificate Pinning. - */ + /** Returns an instance of the {@link OkHttpRootTrustManager} used for Certificate Pinning. */ @NonNull public static X509TrustManager getTrustManager() { return trustManager; diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3PinningInterceptor.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3PinningInterceptor.java index b7a44e6..5bbc1c2 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3PinningInterceptor.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3PinningInterceptor.java @@ -1,9 +1,7 @@ package com.datatheorem.android.trustkit.pinning; import androidx.annotation.NonNull; - import java.io.IOException; - import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; @@ -19,7 +17,8 @@ public OkHttp3PinningInterceptor(@NonNull OkHttpRootTrustManager trustManager) { mTrustManager = trustManager; } - @Override public Response intercept(Interceptor.Chain chain) throws IOException { + @Override + public Response intercept(Interceptor.Chain chain) throws IOException { Request request = chain.request(); String serverHostname = request.url().host(); diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttpRootTrustManager.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttpRootTrustManager.java index 64d6613..32173d2 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttpRootTrustManager.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttpRootTrustManager.java @@ -2,44 +2,44 @@ import android.net.http.X509TrustManagerExtensions; import android.os.Build; - import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; - import com.datatheorem.android.trustkit.TrustKit; import com.datatheorem.android.trustkit.config.DomainPinningPolicy; - import java.security.cert.CertificateException; import java.security.cert.X509Certificate; - import javax.net.ssl.X509TrustManager; /** * {@link X509TrustManager} used for Certificate Pinning. * *

This trust manager delegates to the appropriate {@link PinningTrustManager} decided by the - * hostname set by the {@link OkHttp3PinningInterceptor}.

+ * hostname set by the {@link OkHttp3PinningInterceptor}. */ class OkHttpRootTrustManager implements X509TrustManager { private final ThreadLocal mServerHostname = new ThreadLocal<>(); @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - TrustKit.getInstance().getTrustManager(mServerHostname.get()).checkClientTrusted(chain, authType); + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + TrustKit.getInstance() + .getTrustManager(mServerHostname.get()) + .checkClientTrusted(chain, authType); } @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { String host = mServerHostname.get(); DomainPinningPolicy serverConfig = TrustKit.getInstance().getConfiguration().getPolicyForHostname(host); X509TrustManager trustManager = TrustKit.getInstance().getTrustManager(host); - //The first check is needed for compatibility with the Platform default's implementation of - //the Trust Manager. For APIs 24 and greater, the Platform's default TrustManager states - //that it requires usage of the hostname-aware version of checkServerTrusted for app's that - //implement Android's network_security_config file. The 2nd check is to allow usage of the - //X509TrustManagerExtensions class. Any API below will default to the baseline trust manager. + // The first check is needed for compatibility with the Platform default's implementation of + // the Trust Manager. For APIs 24 and greater, the Platform's default TrustManager states + // that it requires usage of the hostname-aware version of checkServerTrusted for app's that + // implement Android's network_security_config file. The 2nd check is to allow usage of the + // X509TrustManagerExtensions class. Any API below will default to the baseline trust + // manager. if (serverConfig == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { new X509TrustManagerExtensions(trustManager).checkServerTrusted(chain, authType, host); } else { diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningTrustManager.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningTrustManager.java index 6b146af..70ace88 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningTrustManager.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningTrustManager.java @@ -15,8 +15,6 @@ import java.util.Set; import javax.net.ssl.X509TrustManager; - - @RequiresApi(api = 17) class PinningTrustManager implements X509TrustManager { @@ -26,23 +24,23 @@ class PinningTrustManager implements X509TrustManager { private final String serverHostname; private final DomainPinningPolicy serverConfig; - /** * A trust manager which implements path, hostname and pinning validation for a given hostname * and sends pinning failure reports if validation failed. * - * Before Android N, the PinningTrustManager implements pinning validation itself. On Android + *

Before Android N, the PinningTrustManager implements pinning validation itself. On Android * N and later the OS' implementation is used instead for pinning validation. * * @param serverHostname: The hostname of the server whose identity is being validated. It will - * be validated against the name(s) the leaf certificate was issued for - * when performing hostname validation. + * be validated against the name(s) the leaf certificate was issued for when performing + * hostname validation. * @param serverConfig: The pinning policy to be enforced when doing pinning validation. * @param baselineTrustManager: The trust manager to use for path validation. */ - public PinningTrustManager(@NonNull String serverHostname, - @NonNull DomainPinningPolicy serverConfig, - @NonNull X509TrustManager baselineTrustManager) { + public PinningTrustManager( + @NonNull String serverHostname, + @NonNull DomainPinningPolicy serverConfig, + @NonNull X509TrustManager baselineTrustManager) { // Store server's information this.serverHostname = serverHostname; this.serverConfig = serverConfig; @@ -65,15 +63,14 @@ public PinningTrustManager(@NonNull String serverHostname, /** * This methods gets called on Android N instead of the 2-parameter checkServerTrusted(). * - * If we ever drop support for versions before Android N (unlikely), we can use this method + *

If we ever drop support for versions before Android N (unlikely), we can use this method * to automatically get the hostname when the certificate chain needs to be validated, instead * of having to ask for the hostname a lot earlier when the trust manager (or socket factory) * gets created, making the API a lot nicer. * - * For now this is here only for documentation. - * See also: https://developer.android.com/reference/javax/net/ssl/X509ExtendedTrustManager.html - * not to be confused with X509TrustManagerExtensions! - * + *

For now this is here only for documentation. See also: + * https://developer.android.com/reference/javax/net/ssl/X509ExtendedTrustManager.html not to be + * confused with X509TrustManagerExtensions! */ /* public List checkServerTrusted(X509Certificate[] chain, String authType, @@ -87,7 +84,7 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) boolean didPinningValidationFail = false; // Store the received chain so we can send it later in a report if path validation fails - List servedServerChain = Arrays.asList((X509Certificate [])chain); + List servedServerChain = Arrays.asList((X509Certificate[]) chain); List validatedServerChain = servedServerChain; // Then do hostname validation first @@ -103,8 +100,8 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) // extra certificates an attacker might add: https://koz.io/pinning-cve-2016-2402/ try { - validatedServerChain = baselineTrustManager.checkServerTrusted(chain, authType, - serverHostname); + validatedServerChain = + baselineTrustManager.checkServerTrusted(chain, authType, serverHostname); } catch (CertificateException e) { if ((Build.VERSION.SDK_INT >= 24) @@ -122,13 +119,14 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) // validation succeeded. On Android N this was already taken care of by the netsec policy if ((Build.VERSION.SDK_INT < 24) && (!didChainValidationFail)) { - boolean hasPinningPolicyExpired = (serverConfig.getExpirationDate() != null) - && (serverConfig.getExpirationDate().compareTo(new Date()) < 0); + boolean hasPinningPolicyExpired = + (serverConfig.getExpirationDate() != null) + && (serverConfig.getExpirationDate().compareTo(new Date()) < 0); // Only do pinning validation if the policy has not expired if (!hasPinningPolicyExpired) { - didPinningValidationFail = !isPinInChain(validatedServerChain, - serverConfig.getPublicKeyPins()); + didPinningValidationFail = + !isPinInChain(validatedServerChain, serverConfig.getPublicKeyPins()); } } @@ -139,8 +137,14 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) // Hostname or path validation failed - not a pinning error validationResult = PinningValidationResult.FAILED_CERTIFICATE_CHAIN_NOT_TRUSTED; } - TrustManagerBuilder.getReporter().pinValidationFailed(serverHostname, 0, - servedServerChain, validatedServerChain, serverConfig, validationResult); + TrustManagerBuilder.getReporter() + .pinValidationFailed( + serverHostname, + 0, + servedServerChain, + validatedServerChain, + serverConfig, + validationResult); } // Throw an exception if needed @@ -148,16 +152,18 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) throw new CertificateException("Certificate validation failed for " + serverHostname); } else if ((didPinningValidationFail) && (serverConfig.shouldEnforcePinning())) { // Pinning failed and is enforced - throw an exception to cancel the handshake - StringBuilder errorBuilder = new StringBuilder() - .append("Pin verification failed") - .append("\n Configured pins: "); + StringBuilder errorBuilder = + new StringBuilder() + .append("Pin verification failed") + .append("\n Configured pins: "); for (PublicKeyPin pin : serverConfig.getPublicKeyPins()) { errorBuilder.append(pin); errorBuilder.append(" "); } errorBuilder.append("\n Peer certificate chain: "); for (Certificate certificate : validatedServerChain) { - errorBuilder.append("\n ") + errorBuilder + .append("\n ") .append(new PublicKeyPin(certificate)) .append(" - ") .append(((X509Certificate) certificate).getSubjectDN()); @@ -166,8 +172,8 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) } } - private static boolean isPinInChain(List verifiedServerChain, - Set configuredPins) { + private static boolean isPinInChain( + List verifiedServerChain, Set configuredPins) { boolean wasPinFound = false; for (Certificate certificate : verifiedServerChain) { PublicKeyPin certificatePin = new PublicKeyPin(certificate); diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningValidationResult.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningValidationResult.java index 2fdab8a..e504d22 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningValidationResult.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningValidationResult.java @@ -1,6 +1,5 @@ package com.datatheorem.android.trustkit.pinning; - public enum PinningValidationResult { // The server trust was successfully evaluated and contained at least one of the configured pins SUCCESS, diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/SystemTrustManager.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/SystemTrustManager.java index dcb9689..c278b59 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/SystemTrustManager.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/SystemTrustManager.java @@ -1,6 +1,5 @@ package com.datatheorem.android.trustkit.pinning; - import androidx.annotation.NonNull; import java.security.KeyStore; import java.security.KeyStoreException; @@ -9,16 +8,14 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; - public class SystemTrustManager { private static final X509TrustManager systemTrustManager = getSystemTrustManager(); /** - * Retrieve the platform's default trust manager. - * Depending on the device's API level, the trust manager will consecutively do path validation - * (all API levels), hostname validation (API level 16 to ???), and pinning validation if a - * network policy was configured (API level 24+). + * Retrieve the platform's default trust manager. Depending on the device's API level, the trust + * manager will consecutively do path validation (all API levels), hostname validation (API + * level 16 to ???), and pinning validation if a network policy was configured (API level 24+). * * @return the platform's default trust manager. */ @@ -31,22 +28,21 @@ private static X509TrustManager getSystemTrustManager() { X509TrustManager systemTrustManager = null; TrustManagerFactory trustManagerFactory; try { - trustManagerFactory = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm() - ); + trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Should never happen"); } try { - trustManagerFactory.init((KeyStore)null); + trustManagerFactory.init((KeyStore) null); } catch (KeyStoreException e) { throw new IllegalStateException("Should never happen"); } for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) { if (trustManager instanceof X509TrustManager) { - systemTrustManager = (X509TrustManager)trustManager; + systemTrustManager = (X509TrustManager) trustManager; } } diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/TrustManagerBuilder.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/TrustManagerBuilder.java index af052fd..37f2bc5 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/TrustManagerBuilder.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/TrustManagerBuilder.java @@ -14,8 +14,6 @@ import java.util.Set; import javax.net.ssl.X509TrustManager; - - public class TrustManagerBuilder { // The trust manager we will use to perform the default SSL validation @@ -27,17 +25,16 @@ public class TrustManagerBuilder { // The reporter that will send pinning failure reports protected static BackgroundReporter backgroundReporter = null; - public static void initializeBaselineTrustManager(@Nullable Set debugCaCerts, - boolean debugOverridePins, - @NonNull BackgroundReporter reporter) - throws CertificateException, NoSuchAlgorithmException, KeyStoreException, - IOException { + public static void initializeBaselineTrustManager( + @Nullable Set debugCaCerts, + boolean debugOverridePins, + @NonNull BackgroundReporter reporter) + throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { if (baselineTrustManager != null) { throw new IllegalStateException("TrustManagerBuilder has already been initialized"); } baselineTrustManager = SystemTrustManager.getInstance(); - if (Build.VERSION.SDK_INT < 17) { // No pinning validation or debug overrides return; @@ -73,8 +70,7 @@ public static X509TrustManager getTrustManager(@NonNull String serverHostname) { } } - /** Retrieve the background reporter to be used for sending pinning validation reports. - */ + /** Retrieve the background reporter to be used for sending pinning validation reports. */ static BackgroundReporter getReporter() { if (backgroundReporter == null) { throw new IllegalStateException("TrustManagerBuilder has not been initialized"); diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/Utils.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/Utils.java index 05d463c..26bcd93 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/Utils.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/Utils.java @@ -19,22 +19,21 @@ /** Junk drawer of utility methods. */ final class Utils { - /** - * Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation - * of Android's private InetAddress#isNumeric API. - * - *

This matches IPv6 addresses as a hex string containing at least one colon, and possibly - * including dots after the first colon. It matches IPv4 addresses as strings containing only - * decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP - * addresses nor hostnames; they will be verified as IP addresses (which is a more strict - * verification). - */ - private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile( - "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)"); + /** + * Quick and dirty pattern to differentiate IP addresses from hostnames. This is an + * approximation of Android's private InetAddress#isNumeric API. + * + *

This matches IPv6 addresses as a hex string containing at least one colon, and possibly + * including dots after the first colon. It matches IPv4 addresses as strings containing only + * decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither + * IP addresses nor hostnames; they will be verified as IP addresses (which is a more strict + * verification). + */ + private static final Pattern VERIFY_AS_IP_ADDRESS = + Pattern.compile("([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)"); - - /** Returns true if {@code host} is not a host name and might be an IP address. */ - public static boolean verifyAsIpAddress(String host) { - return VERIFY_AS_IP_ADDRESS.matcher(host).matches(); - } -} \ No newline at end of file + /** Returns true if {@code host} is not a host name and might be an IP address. */ + public static boolean verifyAsIpAddress(String host) { + return VERIFY_AS_IP_ADDRESS.matcher(host).matches(); + } +} diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/reporting/BackgroundReporter.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/reporting/BackgroundReporter.java index baae9b7..a80366c 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/reporting/BackgroundReporter.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/reporting/BackgroundReporter.java @@ -1,18 +1,14 @@ package com.datatheorem.android.trustkit.reporting; - import android.content.Context; import android.content.Intent; import android.util.Base64; - import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.localbroadcastmanager.content.LocalBroadcastManager; - import com.datatheorem.android.trustkit.config.DomainPinningPolicy; import com.datatheorem.android.trustkit.pinning.PinningValidationResult; import com.datatheorem.android.trustkit.utils.TrustKitLog; - import java.net.URL; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; @@ -21,9 +17,9 @@ import java.util.List; import java.util.Set; - public class BackgroundReporter { - public static final String REPORT_VALIDATION_EVENT = "com.datatheorem.android.trustkit.reporting.BackgroundReporter:REPORT_VALIDATION_EVENT"; + public static final String REPORT_VALIDATION_EVENT = + "com.datatheorem.android.trustkit.reporting.BackgroundReporter:REPORT_VALIDATION_EVENT"; public static final String EXTRA_REPORT = "Report"; // App meta-data to be sent with the reports @@ -32,13 +28,15 @@ public class BackgroundReporter { private final String appVendorId; private final Context context; - public BackgroundReporter(@NonNull Context context, @NonNull String appPackageName, @NonNull String appVersion, - @NonNull String appVendorId) { + public BackgroundReporter( + @NonNull Context context, + @NonNull String appPackageName, + @NonNull String appVersion, + @NonNull String appVendorId) { this.context = context; this.appPackageName = appPackageName; this.appVersion = appVersion; this.appVendorId = appVendorId; - } private static String certificateToPem(X509Certificate certificate) { @@ -46,8 +44,8 @@ private static String certificateToPem(X509Certificate certificate) { try { certificateData = certificate.getEncoded(); } catch (CertificateEncodingException e) { - throw new IllegalStateException("Should never happen - certificate was previously " + - "parsed by the system"); + throw new IllegalStateException( + "Should never happen - certificate was previously " + "parsed by the system"); } // Create the PEM string @@ -61,17 +59,18 @@ private static String certificateToPem(X509Certificate certificate) { * Try to send a pin validation failure report to the reporting servers configured for the * hostname that triggered the failure. * - * Reports are rate-limited to one identical (same host, error and certificate chain) report + *

Reports are rate-limited to one identical (same host, error and certificate chain) report * every 24 hours. Also and before Android N, only the default SSL validation is performed when * connecting to the reporting server (ie. no pinning validation). */ @RequiresApi(api = 16) - public void pinValidationFailed(@NonNull String serverHostname, - @NonNull Integer serverPort, - @NonNull List servedCertificateChain, - @NonNull List validatedCertificateChain, - @NonNull DomainPinningPolicy serverConfig, - @NonNull PinningValidationResult validationResult) { + public void pinValidationFailed( + @NonNull String serverHostname, + @NonNull Integer serverPort, + @NonNull List servedCertificateChain, + @NonNull List validatedCertificateChain, + @NonNull DomainPinningPolicy serverConfig, + @NonNull PinningValidationResult validationResult) { TrustKitLog.i("Generating pin failure report for " + serverHostname); @@ -86,12 +85,21 @@ public void pinValidationFailed(@NonNull String serverHostname, } // Generate the corresponding pin failure report - PinningFailureReport report = new PinningFailureReport(appPackageName, appVersion, - appVendorId, serverHostname, serverPort, - serverConfig.getHostname(), serverConfig.shouldIncludeSubdomains(), - serverConfig.shouldEnforcePinning(), servedCertificateChainAsPem, - validatedCertificateChainAsPem, new Date(System.currentTimeMillis()), - serverConfig.getPublicKeyPins(), validationResult); + PinningFailureReport report = + new PinningFailureReport( + appPackageName, + appVersion, + appVendorId, + serverHostname, + serverPort, + serverConfig.getHostname(), + serverConfig.shouldIncludeSubdomains(), + serverConfig.shouldEnforcePinning(), + servedCertificateChainAsPem, + validatedCertificateChainAsPem, + new Date(System.currentTimeMillis()), + serverConfig.getPublicKeyPins(), + validationResult); // If a similar report hasn't been sent recently, send it now if (!(ReportRateLimiter.shouldRateLimit(report))) { @@ -103,8 +111,8 @@ validatedCertificateChainAsPem, new Date(System.currentTimeMillis()), } @RequiresApi(api = 16) - protected void sendReport(@NonNull PinningFailureReport report, - @NonNull Set reportUriSet) { + protected void sendReport( + @NonNull PinningFailureReport report, @NonNull Set reportUriSet) { // Prepare the AsyncTask's arguments ArrayList taskParameters = new ArrayList<>(); taskParameters.add(report); @@ -113,7 +121,7 @@ protected void sendReport(@NonNull PinningFailureReport report, new BackgroundReporterTask().execute(taskParameters.toArray()); } - protected void broadcastReport(@NonNull PinningFailureReport report){ + protected void broadcastReport(@NonNull PinningFailureReport report) { Intent intent = new Intent(REPORT_VALIDATION_EVENT); intent.putExtra(EXTRA_REPORT, report); LocalBroadcastManager.getInstance(context).sendBroadcast(intent); diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTask.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTask.java index d4771e7..ca88a3e 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTask.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTask.java @@ -2,12 +2,9 @@ import android.os.AsyncTask; import android.util.Base64; - import androidx.annotation.RequiresApi; - import com.datatheorem.android.trustkit.pinning.SystemTrustManager; import com.datatheorem.android.trustkit.utils.TrustKitLog; - import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -15,13 +12,11 @@ import java.net.URL; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; - import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; - // This returns an obscure threading error on API level < 16 @RequiresApi(api = 16) class BackgroundReporterTask extends AsyncTask { @@ -36,7 +31,7 @@ protected final Integer doInBackground(Object... params) { PinningFailureReport report = (PinningFailureReport) params[0]; // Remaining parameters are report URLs - send the report to each of them - for (int i=1; i knownPins; - - PinningFailureReport(@NonNull String appBundleId, @NonNull String appVersion, - @NonNull String appVendorId, @NonNull String hostname, int port, - @NonNull String notedHostname, boolean includeSubdomains, - boolean enforcePinning, @NonNull List servedCertificateChain, - @NonNull List validatedCertificateChain, @NonNull Date dateTime, - @NonNull Set knownPins, - @NonNull PinningValidationResult validationResult) { + PinningFailureReport( + @NonNull String appBundleId, + @NonNull String appVersion, + @NonNull String appVendorId, + @NonNull String hostname, + int port, + @NonNull String notedHostname, + boolean includeSubdomains, + boolean enforcePinning, + @NonNull List servedCertificateChain, + @NonNull List validatedCertificateChain, + @NonNull Date dateTime, + @NonNull Set knownPins, + @NonNull PinningValidationResult validationResult) { this.appBundleId = appBundleId; this.appVersion = appVersion; this.appVendorId = appVendorId; diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/reporting/ReportRateLimiter.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/reporting/ReportRateLimiter.java index 03a3bd0..48584ff 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/reporting/ReportRateLimiter.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/reporting/ReportRateLimiter.java @@ -1,8 +1,6 @@ package com.datatheorem.android.trustkit.reporting; - import androidx.annotation.NonNull; - import java.util.ArrayList; import java.util.Date; import java.util.HashSet; @@ -12,11 +10,11 @@ // Very basic implementation to rate-limit identical reports to once a day class ReportRateLimiter { - private static final long MAX_SECONDS_BETWEEN_CACHE_RESET = 3600*24; + private static final long MAX_SECONDS_BETWEEN_CACHE_RESET = 3600 * 24; private static final Set> reportsCache = new HashSet<>(); protected static Date lastReportsCacheResetDate = new Date(); - synchronized static boolean shouldRateLimit(@NonNull final PinningFailureReport report) { + static synchronized boolean shouldRateLimit(@NonNull final PinningFailureReport report) { // Reset the cache if it was created more than 24 hours ago Date currentDate = new Date(); long secondsSinceLastReset = @@ -35,7 +33,7 @@ synchronized static boolean shouldRateLimit(@NonNull final PinningFailureReport cacheEntry.add(report.getValidationResult()); boolean shouldRateLimitReport = reportsCache.contains(cacheEntry); - if (!shouldRateLimitReport){ + if (!shouldRateLimitReport) { reportsCache.add(cacheEntry); } return shouldRateLimitReport; diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/utils/TrustKitLog.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/utils/TrustKitLog.java index 701faa5..739cf78 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/utils/TrustKitLog.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/utils/TrustKitLog.java @@ -3,7 +3,6 @@ import android.util.Log; import com.datatheorem.android.trustkit.BuildConfig; - public final class TrustKitLog { public static void i(String message) { diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/utils/VendorIdentifier.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/utils/VendorIdentifier.java index aa25b28..f2b9974 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/utils/VendorIdentifier.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/utils/VendorIdentifier.java @@ -1,12 +1,9 @@ package com.datatheorem.android.trustkit.utils; - import android.content.Context; import android.content.SharedPreferences; - import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; - import java.util.UUID; /** From a68fef8ef15d3816b51307ecc48dd294d0308769 Mon Sep 17 00:00:00 2001 From: Nathan Lecoanet Date: Tue, 18 Aug 2026 15:39:31 +0200 Subject: [PATCH 3/4] Migrate all versions to version catalog --- app/build.gradle | 22 +++--- build.gradle | 72 +++---------------- demoappkotlin/build.gradle | 31 ++++---- deploymentScripts/publish-mavencentral.gradle | 2 +- gradle.properties | 4 +- gradle/libs.versions.toml | 50 +++++++++++++ settings.gradle | 8 +++ trustkit/build.gradle | 48 +++++++------ 8 files changed, 122 insertions(+), 115 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/app/build.gradle b/app/build.gradle index 148a5c1..3348336 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,15 +1,17 @@ -apply plugin: 'com.android.application' +plugins { + alias(libs.plugins.android.application) +} android { namespace = 'com.datatheorem.android.trustkit.demoapp' - compileSdkVersion toolVersions.android.compileSdk + compileSdkVersion libs.versions.compileSdk.get().toInteger() defaultConfig { applicationId "com.datatheorem.android.trustkit.demoapp" - minSdkVersion toolVersions.android.minSdk - targetSdkVersion toolVersions.android.targetSdk - versionCode demoAppTrustKitVersionCode - versionName demoAppTrustKitVersionName + minSdkVersion libs.versions.minSdk.get().toInteger() + targetSdkVersion libs.versions.targetSdk.get().toInteger() + versionCode libs.versions.demoAppVersionCode.get().toInteger() + versionName libs.versions.demoAppVersionName.get() } buildTypes { release { @@ -19,14 +21,14 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 + sourceCompatibility JavaVersion.toVersion(libs.versions.jvmTarget.get()) + targetCompatibility JavaVersion.toVersion(libs.versions.jvmTarget.get()) } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation project(':trustkit') - implementation "androidx.appcompat:appcompat:$rootProject.libVersions.androidx.appcompat" - implementation "com.google.android.material:material:$rootProject.libVersions.google.material" + implementation libs.androidx.appcompat + implementation libs.google.material } diff --git a/build.gradle b/build.gradle index 8a5d4b2..fe383ee 100644 --- a/build.gradle +++ b/build.gradle @@ -1,33 +1,23 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - ext.kotlin_version = '1.9.25' - - repositories { - mavenCentral() - google() - } - dependencies { - classpath 'com.android.tools.build:gradle:8.13.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - plugins { - id("com.diffplug.spotless") version "8.10.0" + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.spotless) } allprojects { - apply { plugin("com.diffplug.spotless") } + apply { plugin(libs.plugins.spotless.get().pluginId) } spotless { java { target("src/**/*.java") - googleJavaFormat("1.15.0").aosp() + googleJavaFormat(libs.versions.google.java.format.get()).aosp() } kotlin { target("src/**/*.kt") - ktfmt("0.61").kotlinlangStyle() + ktfmt(libs.versions.ktfmt.get()).kotlinlangStyle() } groovyGradle { target("*.gradle") @@ -35,7 +25,7 @@ allprojects { } kotlinGradle { target("*.gradle.kts") - ktfmt("0.61").kotlinlangStyle() + ktfmt(libs.versions.ktfmt.get()).kotlinlangStyle() } } @@ -44,49 +34,3 @@ allprojects { google() } } - - -ext{ - trustkitVersionCode = 10 - trustkitVersionName = "1.1.5" - - demoAppTrustKitVersionCode = 2 - demoAppTrustKitVersionName = "1.1" - - demoAppKotlinTrustKitVersionCode = 2 - demoAppKotlinTrustKitVersionName = "1.1" - javaSourceCompatibilty = '11' - toolVersions = [ - android : [ - compileSdk : 36, - minSdk : 15, - targetSdk: 36 - ] - ] - - libVersions = [ - junit: '4.12', - mockito : [ - android: '1.10.19' - ], - dexmaker : '1.4', - androidx : [ - annotation: '1.0.0', - test: '1.1.0', - legacySupport: '1.0.0', - appcompat: '1.0.2', - preference: '1.0.0' - ], - testing: [ - okhttp3: '3.11.0', - 'playServicesBase': '11.0.0', - ], - google: [ - material: '1.0.0' - ], - squareup: [ - okhttp3: '3.11.0', - okhttp2: '2.4.0' - ] - ] -} diff --git a/demoappkotlin/build.gradle b/demoappkotlin/build.gradle index 3c0584c..d4eb22e 100644 --- a/demoappkotlin/build.gradle +++ b/demoappkotlin/build.gradle @@ -1,18 +1,20 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} android { namespace = 'com.datatheorem.android.trustkit.demoappkotlin' - compileSdkVersion toolVersions.android.compileSdk + compileSdkVersion libs.versions.compileSdk.get().toInteger() defaultConfig { applicationId "com.datatheorem.android.trustkit.demoappkotlin" - minSdkVersion toolVersions.android.minSdk - targetSdkVersion toolVersions.android.targetSdk - versionCode demoAppKotlinTrustKitVersionCode - versionName demoAppKotlinTrustKitVersionName + minSdkVersion libs.versions.minSdk.get().toInteger() + targetSdkVersion libs.versions.targetSdk.get().toInteger() + versionCode libs.versions.demoAppVersionCode.get().toInteger() + versionName libs.versions.demoAppVersionName.get() } buildTypes { @@ -23,22 +25,19 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 + sourceCompatibility JavaVersion.toVersion(libs.versions.jvmTarget.get()) + targetCompatibility JavaVersion.toVersion(libs.versions.jvmTarget.get()) } kotlinOptions { - jvmTarget = '11' + jvmTarget = libs.versions.jvmTarget.get() } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation "androidx.appcompat:appcompat:$rootProject.libVersions.androidx.appcompat" - implementation "com.google.android.material:material:$rootProject.libVersions.google.material" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation libs.androidx.appcompat + implementation libs.google.material + implementation libs.kotlin.stdlib.jdk7 implementation project(path: ':trustkit') } -repositories { - mavenCentral() -} diff --git a/deploymentScripts/publish-mavencentral.gradle b/deploymentScripts/publish-mavencentral.gradle index 9788f2c..1c6adfa 100644 --- a/deploymentScripts/publish-mavencentral.gradle +++ b/deploymentScripts/publish-mavencentral.gradle @@ -3,7 +3,7 @@ apply plugin: 'signing' ext { PUBLISH_GROUP_ID = 'com.datatheorem.android.trustkit' - PUBLISH_VERSION = trustkitVersionName + PUBLISH_VERSION = project.version.toString() PUBLISH_ARTIFACT_ID = 'trustkit' DESCRIPTION = 'TrustKit Android is an open source library that makes it easy to deploy SSL public key pinning and reporting in any Android App.' LICENSE_NAME = 'The MIT License (MIT)' diff --git a/gradle.properties b/gradle.properties index 915f0e6..b804431 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,11 +10,11 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m -# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file +android.useAndroidX=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..df2f745 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,50 @@ +[versions] +agp = "8.13.2" +androidx-annotation = "1.0.0" +androidx-appcompat = "1.0.2" +androidx-preference = "1.0.0" +androidx-test = "1.1.0" +awaitility = "3.1.6" +compileSdk = "36" +demoAppVersionCode = "2" +demoAppVersionName = "1.1" +dexmaker = "1.4" +google-java-format = "1.15.0" +jvmTarget = "11" +junit = "4.12" +kotlin = "1.9.25" +ktfmt = "0.61" +material = "1.0.0" +minSdk = "15" +mockito = "1.10.19" +okhttp2 = "2.4.0" +okhttp3 = "3.11.0" +play-services-base = "11.0.0" +spotless = "8.10.0" +targetSdk = "36" +trustkitVersionCode = "10" +trustkitVersionName = "1.1.5" + +[libraries] +androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" } +androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } +androidx-preference = { module = "androidx.preference:preference", version.ref = "androidx-preference" } +androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test" } +androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test" } +awaitility = { module = "org.awaitility:awaitility", version.ref = "awaitility" } +dexmaker-core = { module = "com.crittercism.dexmaker:dexmaker", version.ref = "dexmaker" } +dexmaker-dx = { module = "com.crittercism.dexmaker:dexmaker-dx", version.ref = "dexmaker" } +dexmaker-mockito = { module = "com.crittercism.dexmaker:dexmaker-mockito", version.ref = "dexmaker" } +google-material = { module = "com.google.android.material:material", version.ref = "material" } +google-play-services-base = { module = "com.google.android.gms:play-services-base", version.ref = "play-services-base" } +junit = { module = "junit:junit", version.ref = "junit" } +kotlin-stdlib-jdk7 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk7", version.ref = "kotlin" } +mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } +okhttp2 = { module = "com.squareup.okhttp:okhttp", version.ref = "okhttp2" } +okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp3" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } diff --git a/settings.gradle b/settings.gradle index deb4a9b..d1011df 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,9 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + include ':app', ':trustkit', ':demoappkotlin' diff --git a/trustkit/build.gradle b/trustkit/build.gradle index f222bdd..10fe38e 100644 --- a/trustkit/build.gradle +++ b/trustkit/build.gradle @@ -1,42 +1,46 @@ -apply plugin: 'com.android.library' +plugins { + alias(libs.plugins.android.library) +} + +version = libs.versions.trustkitVersionName.get() android { namespace = 'com.datatheorem.android.trustkit' - compileSdkVersion toolVersions.android.compileSdk + compileSdkVersion libs.versions.compileSdk.get().toInteger() buildFeatures { buildConfig = true } defaultConfig { - minSdkVersion toolVersions.android.minSdk - versionCode trustkitVersionCode - versionName trustkitVersionName - buildConfigField 'String', 'VERSION_NAME', "\"${trustkitVersionName}\"" + minSdkVersion libs.versions.minSdk.get().toInteger() + versionCode libs.versions.trustkitVersionCode.get().toInteger() + versionName project.version.toString() + buildConfigField 'String', 'VERSION_NAME', "\"${project.version}\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 + sourceCompatibility JavaVersion.toVersion(libs.versions.jvmTarget.get()) + targetCompatibility JavaVersion.toVersion(libs.versions.jvmTarget.get()) } } dependencies { - implementation "androidx.annotation:annotation:$rootProject.libVersions.androidx.annotation" - implementation "androidx.preference:preference:$rootProject.libVersions.androidx.preference" - compileOnly "com.squareup.okhttp3:okhttp:$rootProject.libVersions.squareup.okhttp3" - compileOnly "com.squareup.okhttp:okhttp:$rootProject.libVersions.squareup.okhttp2" - androidTestImplementation "junit:junit:$rootProject.libVersions.junit" - androidTestImplementation "androidx.test:runner:$rootProject.libVersions.androidx.test" - androidTestImplementation "androidx.test:rules:$rootProject.libVersions.androidx.test" - androidTestImplementation "org.mockito:mockito-core:$rootProject.libVersions.mockito.android" - androidTestImplementation "org.awaitility:awaitility:3.1.6" - androidTestImplementation "com.crittercism.dexmaker:dexmaker:$rootProject.libVersions.dexmaker" - androidTestImplementation "com.crittercism.dexmaker:dexmaker-dx:$rootProject.libVersions.dexmaker" - androidTestImplementation "com.crittercism.dexmaker:dexmaker-mockito:$rootProject.libVersions.dexmaker" - androidTestImplementation "com.squareup.okhttp3:okhttp:$rootProject.libVersions.testing.okhttp3" - androidTestImplementation "com.google.android.gms:play-services-base:$rootProject.libVersions.testing.playServicesBase" + implementation libs.androidx.annotation + implementation libs.androidx.preference + compileOnly libs.okhttp3 + compileOnly libs.okhttp2 + androidTestImplementation libs.junit + androidTestImplementation libs.androidx.test.runner + androidTestImplementation libs.androidx.test.rules + androidTestImplementation libs.mockito.core + androidTestImplementation libs.awaitility + androidTestImplementation libs.dexmaker.core + androidTestImplementation libs.dexmaker.dx + androidTestImplementation libs.dexmaker.mockito + androidTestImplementation libs.okhttp3 + androidTestImplementation libs.google.play.services.base } // MavenCentral deployment gradle script From 982fe9dc718238450fc98112fc232b5ac4dc4839 Mon Sep 17 00:00:00 2001 From: Nathan Lecoanet Date: Wed, 19 Aug 2026 09:26:37 +0200 Subject: [PATCH 4/4] don't downgrade, back to TLS1.2 --- .../main/java/com/datatheorem/android/trustkit/TrustKit.java | 2 +- .../com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java | 2 +- .../com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java index 1b2b801..7a1c438 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java @@ -347,7 +347,7 @@ public TrustKitConfiguration getConfiguration() { @NonNull public SSLSocketFactory getSSLSocketFactory(@NonNull String serverHostname) { try { - SSLContext sslContext = SSLContext.getInstance("TLS"); + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(null, new TrustManager[] {getTrustManager(serverHostname)}, null); return sslContext.getSocketFactory(); diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java index 0f98afc..42ea102 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java @@ -33,7 +33,7 @@ public class OkHttp2Helper { @NonNull public static SSLSocketFactory getSSLSocketFactory() { try { - SSLContext sslContext = SSLContext.getInstance("TLS"); + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(null, new X509TrustManager[] {trustManager}, null); return sslContext.getSocketFactory(); diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java index a0ffc14..ae6ae54 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java @@ -33,7 +33,7 @@ public class OkHttp3Helper { @NonNull public static SSLSocketFactory getSSLSocketFactory() { try { - SSLContext sslContext = SSLContext.getInstance("TLS"); + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(null, new X509TrustManager[] {trustManager}, null); return sslContext.getSocketFactory();

- * TrustKit works by extending the - * - * Android N Network Security Configuration in two ways: + *