/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.domain.management.security.password.simple; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jboss.as.domain.management.logging.DomainManagementLogger; import org.jboss.as.domain.management.security.password.CompoundRestriction; import org.jboss.as.domain.management.security.password.Dictionary; import org.jboss.as.domain.management.security.password.Keyboard; import org.jboss.as.domain.management.security.password.PasswordRestriction; import org.jboss.as.domain.management.security.password.PasswordStrengthCheckResult; import org.jboss.as.domain.management.security.password.PasswordStrengthChecker; import org.jboss.as.domain.management.security.password.PasswordValidationException; import org.jboss.as.domain.management.security.password.RegexRestriction; /** * @author baranowb * */ public class SimplePasswordStrengthChecker implements PasswordStrengthChecker { public static final String REGEX_DIGITS = "[0-9]"; public static final String REGEX_SYMBOLS = "[^0-9a-zA-Z]"; public static final String REGEX_ALPHA_UC = "[A-Z]"; public static final String REGEX_ALPHA_LC = "[a-z]"; public static final String REGEX_ALPHA = "[a-zA-Z]"; protected static final int PWD_LEN_WEIGHT = 2; protected static final int REQUIREMENTS_WEIGHT = 10; protected static final int SYMBOLS_WEIGHT = 6; protected static final int DIGITS_WEIGHT = 4; protected static final int MIDDLE_NONCHAR_WEIGHT = 2; protected static final int ALPHA_WEIGHT = 2; protected static final int ALPHA_ONLY_WEIGHT = 2; protected static final int DIGITS_ONLY_WEIGHT = 4; protected static final int CONSECUTIVE_ALPHA_WEIGHT = 2; protected static final int CONSECUTIVE_DIGITS_WEIGHT = 2; protected static final int CONSECUTIVE_SYMBOLS_WEIGHT = 2; protected static final int DICTIONARY_WORD_WEIGHT = 1; protected static final int SEQUENTIAL_WEIGHT = 3; // this is not thread safe. private String userName; private String password; private int passwordLength; private final List<PasswordRestriction> restrictionsInPlace; private final Dictionary dictionary; private final Keyboard keyboard; private List<PasswordRestriction> adHocRestrictions; private SimplePasswordStrengthCheckResult result; public SimplePasswordStrengthChecker() { this.restrictionsInPlace = new ArrayList<PasswordRestriction>(); this.dictionary = new SimpleDictionary(); this.keyboard = new SimpleKeyboard(); } public SimplePasswordStrengthChecker(final List<PasswordRestriction> initRestrictions, final Dictionary dictionary, final Keyboard keyboard) { if (initRestrictions == null) { throw new IllegalArgumentException("Initial restrictions must not be null."); } this.restrictionsInPlace = Collections.unmodifiableList(initRestrictions); this.dictionary = dictionary; this.keyboard = keyboard; } /* * (non-Javadoc) * * @see org.jboss.as.domain.management.security.password.PasswordStrengthChecker#check(java.lang.String, java.util.List) */ @Override public PasswordStrengthCheckResult check(String userName, String password, List<PasswordRestriction> restictions) { try { this.userName = userName; this.password = password; this.passwordLength = this.password.length(); this.adHocRestrictions = restictions; this.result = new SimplePasswordStrengthCheckResult(); this.checkRestrictions(); // positive checks result.positive(password.length() * PWD_LEN_WEIGHT); this.checkSymbols(); this.checkDigits(); this.checkMiddleNonChar(); this.checkAlpha(); // negatives checks this.checkAlphaOnly(); this.checkNumbersOnly(); this.checkConsecutiveAlpha(); this.checkConsecutiveNumbers(); this.checkConsecutiveSymbols(); this.checkSequential(); // now evaluate. this.result.calculateStrength(); } finally { this.password = null; this.passwordLength = 0; this.adHocRestrictions = null; } return result; } protected void checkRestrictions() { int met = 0; // check addhoc first, those may be more important if (this.adHocRestrictions != null) { for (PasswordRestriction pr : this.adHocRestrictions) { if (pr instanceof CompoundRestriction) { for (PasswordRestriction wrapped : ((CompoundRestriction) pr).getRestrictions()) { try { wrapped.validate(userName, password); result.addPassedRestriction(wrapped); met++; } catch (PasswordValidationException pve) { result.addRestrictionFailure(pve); } } } else { try { pr.validate(userName, password); result.addPassedRestriction(pr); met++; } catch (PasswordValidationException pve) { result.addRestrictionFailure(pve); } } } } for (PasswordRestriction pr : this.restrictionsInPlace) { if (pr instanceof CompoundRestriction) { for (PasswordRestriction wrapped : ((CompoundRestriction) pr).getRestrictions()) { try { wrapped.validate(userName, password); result.addPassedRestriction(wrapped); met++; } catch (PasswordValidationException pve) { result.addRestrictionFailure(pve); } } } else { try { pr.validate(userName, password); result.addPassedRestriction(pr); met++; } catch (PasswordValidationException pve) { result.addRestrictionFailure(pve); } } } this.result.positive(met * REQUIREMENTS_WEIGHT); } protected void checkSymbols() { int met = 0; Pattern symbolsPatter = Pattern.compile(REGEX_SYMBOLS + "?"); Matcher matcher = symbolsPatter.matcher(this.password); while (matcher.find()) { int start = matcher.start(); int end = matcher.end(); if (start == end) { continue; } met++; } this.result.positive(met * REQUIREMENTS_WEIGHT); } protected void checkDigits() { int met = 0; Pattern symbolsPatter = Pattern.compile(REGEX_DIGITS + "?"); Matcher matcher = symbolsPatter.matcher(this.password); while (matcher.find()) { int start = matcher.start(); int end = matcher.end(); if (start == end) { continue; } met++; } this.result.positive(met * DIGITS_WEIGHT); } protected void checkMiddleNonChar() { int met = 0; Pattern symbolsPatter = Pattern.compile(REGEX_SYMBOLS + "?"); Matcher matcher = symbolsPatter.matcher(this.password); while (matcher.find()) { int start = matcher.start(); int end = matcher.end(); if (start == end && start != 0 && end != this.passwordLength) { continue; } met++; } this.result.positive(met * MIDDLE_NONCHAR_WEIGHT); } protected void checkAlpha() { int met = 0; Pattern symbolsPatter = Pattern.compile(REGEX_ALPHA_UC + "?"); Matcher matcher = symbolsPatter.matcher(this.password); while (matcher.find()) { int start = matcher.start(); int end = matcher.end(); if (start == end) { continue; } met++; } met = 0; symbolsPatter = Pattern.compile(REGEX_ALPHA_LC + "?"); matcher = symbolsPatter.matcher(this.password); while (matcher.find()) { int start = matcher.start(); int end = matcher.end(); if (start == end) { continue; } met++; } this.result.positive((this.passwordLength - met) * ALPHA_WEIGHT); } protected void checkAlphaOnly() { Pattern symbolsPatter = Pattern.compile(REGEX_ALPHA + "*"); Matcher matcher = symbolsPatter.matcher(this.password); if (matcher.find()) { if (matcher.end() == this.passwordLength) { // negative. this.result.negative(this.passwordLength * ALPHA_ONLY_WEIGHT); } } } protected void checkNumbersOnly() { Pattern symbolsPatter = Pattern.compile(REGEX_DIGITS + "*"); Matcher matcher = symbolsPatter.matcher(this.password); if (matcher.find()) { if (matcher.end() == this.passwordLength) { // negative. this.result.negative(this.passwordLength * DIGITS_ONLY_WEIGHT); } } } // those could be incorporated with above, but that would blurry everything. protected void checkConsecutiveAlpha() { Pattern symbolsPatter = Pattern.compile(REGEX_ALPHA_UC + "+"); Matcher matcher = symbolsPatter.matcher(this.password); int met = 0; while (matcher.find()) { int start = matcher.start(); int end = matcher.end(); if (start == end) { continue; } int diff = end - start; if (diff >= 3) { met += diff; } } this.result.negative(met * CONSECUTIVE_ALPHA_WEIGHT); // alpha lower case symbolsPatter = Pattern.compile(REGEX_ALPHA_LC + "+"); matcher = symbolsPatter.matcher(this.password); met = 0; while (matcher.find()) { int start = matcher.start(); int end = matcher.end(); if (start == end) { continue; } int diff = end - start; if (diff >= 3) { met += diff; } } this.result.negative(met * CONSECUTIVE_ALPHA_WEIGHT); } protected void checkConsecutiveNumbers() { Pattern symbolsPatter = Pattern.compile(REGEX_DIGITS + "+"); Matcher matcher = symbolsPatter.matcher(this.password); int met = 0; while (matcher.find()) { int start = matcher.start(); int end = matcher.end(); if (start == end) { continue; } int diff = end - start; if (diff >= 3) { met += diff; } } this.result.negative(met * CONSECUTIVE_DIGITS_WEIGHT); } protected void checkConsecutiveSymbols() { Pattern symbolsPatter = Pattern.compile(REGEX_SYMBOLS + "+"); Matcher matcher = symbolsPatter.matcher(this.password); int met = 0; while (matcher.find()) { int start = matcher.start(); int end = matcher.end(); if (start == end) { continue; } int diff = end - start; if (diff >= 3) { met += diff; } } this.result.negative(met * CONSECUTIVE_SYMBOLS_WEIGHT); } protected void checkSequential() { // iterate over chars and check if siblings, if so, check how long is chain int chainSize = 0; for (int index = 0; index < this.passwordLength - 1; index++) { if (this.keyboard.siblings(this.password, index)) { chainSize += this.keyboard.sequence(this.password, index); } else { // nop } } if (chainSize > 0) { this.result.negative(chainSize * SEQUENTIAL_WEIGHT); } } protected void checkDictionary() { if (dictionary != null) { int score = dictionary.dictionarySequence(password); if (score > 0) { this.result.negative(score * DICTIONARY_WORD_WEIGHT); } } } public static PasswordRestriction getRestrictionAlpha(int minAlpha) { return createRegExRestriction(minAlpha, REGEX_ALPHA, DomainManagementLogger.ROOT_LOGGER.passwordMustHaveAlphaInfo(minAlpha), DomainManagementLogger.ROOT_LOGGER.passwordMustHaveAlpha(minAlpha)); } public static PasswordRestriction getRestrictionDigit(int minDigit) { return createRegExRestriction(minDigit, REGEX_DIGITS, DomainManagementLogger.ROOT_LOGGER.passwordMustHaveDigitInfo(minDigit), DomainManagementLogger.ROOT_LOGGER.passwordMustHaveDigit(minDigit)); } public static PasswordRestriction getRestrictionSymbol(int minSymbol) { return createRegExRestriction(minSymbol, REGEX_SYMBOLS, DomainManagementLogger.ROOT_LOGGER.passwordMustHaveSymbolInfo(minSymbol), DomainManagementLogger.ROOT_LOGGER.passwordMustHaveSymbol(minSymbol)); } private static PasswordRestriction createRegExRestriction(int minChar, String regex, String requirementsMessage, String failureMessage) { if (minChar > 0) { return new RegexRestriction(String.format("(.*%s.*){%d}", REGEX_ALPHA, minChar), requirementsMessage, failureMessage); } else { return new PasswordRestriction() { @Override public void validate(String userName, String password) throws PasswordValidationException { } @Override public String getRequirementMessage() { return ""; } }; } } }