1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.app.admin; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.app.admin.DevicePolicyManager; 22 import android.os.Parcelable; 23 import android.os.Parcel; 24 25 import java.lang.annotation.Retention; 26 import java.lang.annotation.RetentionPolicy; 27 import java.io.IOException; 28 29 /** 30 * A class that represents the metrics of a password that are used to decide whether or not a 31 * password meets the requirements. 32 * 33 * {@hide} 34 */ 35 public class PasswordMetrics implements Parcelable { 36 // Maximum allowed number of repeated or ordered characters in a sequence before we'll 37 // consider it a complex PIN/password. 38 public static final int MAX_ALLOWED_SEQUENCE = 3; 39 40 public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 41 public int length = 0; 42 public int letters = 0; 43 public int upperCase = 0; 44 public int lowerCase = 0; 45 public int numeric = 0; 46 public int symbols = 0; 47 public int nonLetter = 0; 48 PasswordMetrics()49 public PasswordMetrics() {} 50 PasswordMetrics(int quality, int length)51 public PasswordMetrics(int quality, int length) { 52 this.quality = quality; 53 this.length = length; 54 } 55 PasswordMetrics(int quality, int length, int letters, int upperCase, int lowerCase, int numeric, int symbols, int nonLetter)56 public PasswordMetrics(int quality, int length, int letters, int upperCase, int lowerCase, 57 int numeric, int symbols, int nonLetter) { 58 this(quality, length); 59 this.letters = letters; 60 this.upperCase = upperCase; 61 this.lowerCase = lowerCase; 62 this.numeric = numeric; 63 this.symbols = symbols; 64 this.nonLetter = nonLetter; 65 } 66 PasswordMetrics(Parcel in)67 private PasswordMetrics(Parcel in) { 68 quality = in.readInt(); 69 length = in.readInt(); 70 letters = in.readInt(); 71 upperCase = in.readInt(); 72 lowerCase = in.readInt(); 73 numeric = in.readInt(); 74 symbols = in.readInt(); 75 nonLetter = in.readInt(); 76 } 77 isDefault()78 public boolean isDefault() { 79 return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED 80 && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0 81 && numeric == 0 && symbols == 0 && nonLetter == 0; 82 } 83 84 @Override describeContents()85 public int describeContents() { 86 return 0; 87 } 88 89 @Override writeToParcel(Parcel dest, int flags)90 public void writeToParcel(Parcel dest, int flags) { 91 dest.writeInt(quality); 92 dest.writeInt(length); 93 dest.writeInt(letters); 94 dest.writeInt(upperCase); 95 dest.writeInt(lowerCase); 96 dest.writeInt(numeric); 97 dest.writeInt(symbols); 98 dest.writeInt(nonLetter); 99 } 100 101 public static final Parcelable.Creator<PasswordMetrics> CREATOR 102 = new Parcelable.Creator<PasswordMetrics>() { 103 public PasswordMetrics createFromParcel(Parcel in) { 104 return new PasswordMetrics(in); 105 } 106 107 public PasswordMetrics[] newArray(int size) { 108 return new PasswordMetrics[size]; 109 } 110 }; 111 computeForPassword(@onNull String password)112 public static PasswordMetrics computeForPassword(@NonNull String password) { 113 // Analyse the characters used 114 int letters = 0; 115 int upperCase = 0; 116 int lowerCase = 0; 117 int numeric = 0; 118 int symbols = 0; 119 int nonLetter = 0; 120 final int length = password.length(); 121 for (int i = 0; i < length; i++) { 122 switch (categoryChar(password.charAt(i))) { 123 case CHAR_LOWER_CASE: 124 letters++; 125 lowerCase++; 126 break; 127 case CHAR_UPPER_CASE: 128 letters++; 129 upperCase++; 130 break; 131 case CHAR_DIGIT: 132 numeric++; 133 nonLetter++; 134 break; 135 case CHAR_SYMBOL: 136 symbols++; 137 nonLetter++; 138 break; 139 } 140 } 141 142 // Determine the quality of the password 143 final boolean hasNumeric = numeric > 0; 144 final boolean hasNonNumeric = (letters + symbols) > 0; 145 final int quality; 146 if (hasNonNumeric && hasNumeric) { 147 quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 148 } else if (hasNonNumeric) { 149 quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; 150 } else if (hasNumeric) { 151 quality = maxLengthSequence(password) > MAX_ALLOWED_SEQUENCE 152 ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC 153 : DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; 154 } else { 155 quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 156 } 157 158 return new PasswordMetrics( 159 quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter); 160 } 161 162 /* 163 * Returns the maximum length of a sequential characters. A sequence is defined as 164 * monotonically increasing characters with a constant interval or the same character repeated. 165 * 166 * For example: 167 * maxLengthSequence("1234") == 4 168 * maxLengthSequence("13579") == 5 169 * maxLengthSequence("1234abc") == 4 170 * maxLengthSequence("aabc") == 3 171 * maxLengthSequence("qwertyuio") == 1 172 * maxLengthSequence("@ABC") == 3 173 * maxLengthSequence(";;;;") == 4 (anything that repeats) 174 * maxLengthSequence(":;<=>") == 1 (ordered, but not composed of alphas or digits) 175 * 176 * @param string the pass 177 * @return the number of sequential letters or digits 178 */ maxLengthSequence(@onNull String string)179 public static int maxLengthSequence(@NonNull String string) { 180 if (string.length() == 0) return 0; 181 char previousChar = string.charAt(0); 182 @CharacterCatagory int category = categoryChar(previousChar); //current sequence category 183 int diff = 0; //difference between two consecutive characters 184 boolean hasDiff = false; //if we are currently targeting a sequence 185 int maxLength = 0; //maximum length of a sequence already found 186 int startSequence = 0; //where the current sequence started 187 for (int current = 1; current < string.length(); current++) { 188 char currentChar = string.charAt(current); 189 @CharacterCatagory int categoryCurrent = categoryChar(currentChar); 190 int currentDiff = (int) currentChar - (int) previousChar; 191 if (categoryCurrent != category || Math.abs(currentDiff) > maxDiffCategory(category)) { 192 maxLength = Math.max(maxLength, current - startSequence); 193 startSequence = current; 194 hasDiff = false; 195 category = categoryCurrent; 196 } 197 else { 198 if(hasDiff && currentDiff != diff) { 199 maxLength = Math.max(maxLength, current - startSequence); 200 startSequence = current - 1; 201 } 202 diff = currentDiff; 203 hasDiff = true; 204 } 205 previousChar = currentChar; 206 } 207 maxLength = Math.max(maxLength, string.length() - startSequence); 208 return maxLength; 209 } 210 211 @Retention(RetentionPolicy.SOURCE) 212 @IntDef({CHAR_UPPER_CASE, CHAR_LOWER_CASE, CHAR_DIGIT, CHAR_SYMBOL}) 213 private @interface CharacterCatagory {} 214 private static final int CHAR_LOWER_CASE = 0; 215 private static final int CHAR_UPPER_CASE = 1; 216 private static final int CHAR_DIGIT = 2; 217 private static final int CHAR_SYMBOL = 3; 218 219 @CharacterCatagory categoryChar(char c)220 private static int categoryChar(char c) { 221 if ('a' <= c && c <= 'z') return CHAR_LOWER_CASE; 222 if ('A' <= c && c <= 'Z') return CHAR_UPPER_CASE; 223 if ('0' <= c && c <= '9') return CHAR_DIGIT; 224 return CHAR_SYMBOL; 225 } 226 maxDiffCategory(@haracterCatagory int category)227 private static int maxDiffCategory(@CharacterCatagory int category) { 228 switch (category) { 229 case CHAR_LOWER_CASE: 230 case CHAR_UPPER_CASE: 231 return 1; 232 case CHAR_DIGIT: 233 return 10; 234 default: 235 return 0; 236 } 237 } 238 } 239