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.os.Parcel; 22 import android.os.Parcelable; 23 24 import java.lang.annotation.Retention; 25 import java.lang.annotation.RetentionPolicy; 26 27 /** 28 * A class that represents the metrics of a password that are used to decide whether or not a 29 * password meets the requirements. 30 * 31 * {@hide} 32 */ 33 public class PasswordMetrics implements Parcelable { 34 // Maximum allowed number of repeated or ordered characters in a sequence before we'll 35 // consider it a complex PIN/password. 36 public static final int MAX_ALLOWED_SEQUENCE = 3; 37 38 public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 39 public int length = 0; 40 public int letters = 0; 41 public int upperCase = 0; 42 public int lowerCase = 0; 43 public int numeric = 0; 44 public int symbols = 0; 45 public int nonLetter = 0; 46 PasswordMetrics()47 public PasswordMetrics() {} 48 PasswordMetrics(int quality, int length)49 public PasswordMetrics(int quality, int length) { 50 this.quality = quality; 51 this.length = length; 52 } 53 PasswordMetrics(int quality, int length, int letters, int upperCase, int lowerCase, int numeric, int symbols, int nonLetter)54 public PasswordMetrics(int quality, int length, int letters, int upperCase, int lowerCase, 55 int numeric, int symbols, int nonLetter) { 56 this(quality, length); 57 this.letters = letters; 58 this.upperCase = upperCase; 59 this.lowerCase = lowerCase; 60 this.numeric = numeric; 61 this.symbols = symbols; 62 this.nonLetter = nonLetter; 63 } 64 PasswordMetrics(Parcel in)65 private PasswordMetrics(Parcel in) { 66 quality = in.readInt(); 67 length = in.readInt(); 68 letters = in.readInt(); 69 upperCase = in.readInt(); 70 lowerCase = in.readInt(); 71 numeric = in.readInt(); 72 symbols = in.readInt(); 73 nonLetter = in.readInt(); 74 } 75 isDefault()76 public boolean isDefault() { 77 return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED 78 && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0 79 && numeric == 0 && symbols == 0 && nonLetter == 0; 80 } 81 82 @Override describeContents()83 public int describeContents() { 84 return 0; 85 } 86 87 @Override writeToParcel(Parcel dest, int flags)88 public void writeToParcel(Parcel dest, int flags) { 89 dest.writeInt(quality); 90 dest.writeInt(length); 91 dest.writeInt(letters); 92 dest.writeInt(upperCase); 93 dest.writeInt(lowerCase); 94 dest.writeInt(numeric); 95 dest.writeInt(symbols); 96 dest.writeInt(nonLetter); 97 } 98 99 public static final Parcelable.Creator<PasswordMetrics> CREATOR 100 = new Parcelable.Creator<PasswordMetrics>() { 101 public PasswordMetrics createFromParcel(Parcel in) { 102 return new PasswordMetrics(in); 103 } 104 105 public PasswordMetrics[] newArray(int size) { 106 return new PasswordMetrics[size]; 107 } 108 }; 109 computeForPassword(@onNull String password)110 public static PasswordMetrics computeForPassword(@NonNull String password) { 111 // Analyse the characters used 112 int letters = 0; 113 int upperCase = 0; 114 int lowerCase = 0; 115 int numeric = 0; 116 int symbols = 0; 117 int nonLetter = 0; 118 final int length = password.length(); 119 for (int i = 0; i < length; i++) { 120 switch (categoryChar(password.charAt(i))) { 121 case CHAR_LOWER_CASE: 122 letters++; 123 lowerCase++; 124 break; 125 case CHAR_UPPER_CASE: 126 letters++; 127 upperCase++; 128 break; 129 case CHAR_DIGIT: 130 numeric++; 131 nonLetter++; 132 break; 133 case CHAR_SYMBOL: 134 symbols++; 135 nonLetter++; 136 break; 137 } 138 } 139 140 // Determine the quality of the password 141 final boolean hasNumeric = numeric > 0; 142 final boolean hasNonNumeric = (letters + symbols) > 0; 143 final int quality; 144 if (hasNonNumeric && hasNumeric) { 145 quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 146 } else if (hasNonNumeric) { 147 quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; 148 } else if (hasNumeric) { 149 quality = maxLengthSequence(password) > MAX_ALLOWED_SEQUENCE 150 ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC 151 : DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; 152 } else { 153 quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 154 } 155 156 return new PasswordMetrics( 157 quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter); 158 } 159 160 @Override equals(Object other)161 public boolean equals(Object other) { 162 if (!(other instanceof PasswordMetrics)) { 163 return false; 164 } 165 PasswordMetrics o = (PasswordMetrics) other; 166 return this.quality == o.quality 167 && this.length == o.length 168 && this.letters == o.letters 169 && this.upperCase == o.upperCase 170 && this.lowerCase == o.lowerCase 171 && this.numeric == o.numeric 172 && this.symbols == o.symbols 173 && this.nonLetter == o.nonLetter; 174 } 175 176 /* 177 * Returns the maximum length of a sequential characters. A sequence is defined as 178 * monotonically increasing characters with a constant interval or the same character repeated. 179 * 180 * For example: 181 * maxLengthSequence("1234") == 4 182 * maxLengthSequence("13579") == 5 183 * maxLengthSequence("1234abc") == 4 184 * maxLengthSequence("aabc") == 3 185 * maxLengthSequence("qwertyuio") == 1 186 * maxLengthSequence("@ABC") == 3 187 * maxLengthSequence(";;;;") == 4 (anything that repeats) 188 * maxLengthSequence(":;<=>") == 1 (ordered, but not composed of alphas or digits) 189 * 190 * @param string the pass 191 * @return the number of sequential letters or digits 192 */ maxLengthSequence(@onNull String string)193 public static int maxLengthSequence(@NonNull String string) { 194 if (string.length() == 0) return 0; 195 char previousChar = string.charAt(0); 196 @CharacterCatagory int category = categoryChar(previousChar); //current sequence category 197 int diff = 0; //difference between two consecutive characters 198 boolean hasDiff = false; //if we are currently targeting a sequence 199 int maxLength = 0; //maximum length of a sequence already found 200 int startSequence = 0; //where the current sequence started 201 for (int current = 1; current < string.length(); current++) { 202 char currentChar = string.charAt(current); 203 @CharacterCatagory int categoryCurrent = categoryChar(currentChar); 204 int currentDiff = (int) currentChar - (int) previousChar; 205 if (categoryCurrent != category || Math.abs(currentDiff) > maxDiffCategory(category)) { 206 maxLength = Math.max(maxLength, current - startSequence); 207 startSequence = current; 208 hasDiff = false; 209 category = categoryCurrent; 210 } 211 else { 212 if(hasDiff && currentDiff != diff) { 213 maxLength = Math.max(maxLength, current - startSequence); 214 startSequence = current - 1; 215 } 216 diff = currentDiff; 217 hasDiff = true; 218 } 219 previousChar = currentChar; 220 } 221 maxLength = Math.max(maxLength, string.length() - startSequence); 222 return maxLength; 223 } 224 225 @Retention(RetentionPolicy.SOURCE) 226 @IntDef(prefix = { "CHAR_" }, value = { 227 CHAR_UPPER_CASE, 228 CHAR_LOWER_CASE, 229 CHAR_DIGIT, 230 CHAR_SYMBOL 231 }) 232 private @interface CharacterCatagory {} 233 private static final int CHAR_LOWER_CASE = 0; 234 private static final int CHAR_UPPER_CASE = 1; 235 private static final int CHAR_DIGIT = 2; 236 private static final int CHAR_SYMBOL = 3; 237 238 @CharacterCatagory categoryChar(char c)239 private static int categoryChar(char c) { 240 if ('a' <= c && c <= 'z') return CHAR_LOWER_CASE; 241 if ('A' <= c && c <= 'Z') return CHAR_UPPER_CASE; 242 if ('0' <= c && c <= '9') return CHAR_DIGIT; 243 return CHAR_SYMBOL; 244 } 245 maxDiffCategory(@haracterCatagory int category)246 private static int maxDiffCategory(@CharacterCatagory int category) { 247 switch (category) { 248 case CHAR_LOWER_CASE: 249 case CHAR_UPPER_CASE: 250 return 1; 251 case CHAR_DIGIT: 252 return 10; 253 default: 254 return 0; 255 } 256 } 257 } 258