/* * Password Management Servlets (PWM) * http://www.pwm-project.org * * Copyright (c) 2006-2009 Novell, Inc. * Copyright (c) 2009-2017 The PWM Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package password.pwm.util.operations.otp; import password.pwm.util.logging.PwmLogger; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; /** * * @author Menno Pieters */ public class OTPPamUtil { private static final PwmLogger LOGGER = PwmLogger.forClass(OTPPamUtil.class); /** * Split the string in lines; separate by CR, LF or CRLF. * * @param text * @return list of Strings */ public static List<String> splitLines(final String text) { final List<String> list = new ArrayList<>(); if (text != null) { final String[] lines = text.split("\r?\n|\r"); list.addAll(Arrays.asList(lines)); } return list; } /** * * @param otpInfo * @return */ public static OTPUserRecord decomposePamData(final String otpInfo) { final List<String> lines = splitLines(otpInfo); if (lines.size() >= 2) { final Iterator<String> iterator = lines.iterator(); String line = iterator.next(); if (line.matches("^[A-Z2-7\\=]{16}$")) { final OTPUserRecord otp = new OTPUserRecord(); // default identifier otp.setSecret(line); final List<OTPUserRecord.RecoveryCode> recoveryCodes = new ArrayList<>(); while (iterator.hasNext()) { line = iterator.next(); if (line.startsWith("\" ")) { final String option = line.substring(2).trim(); if ("TOTP_AUTH".equals(option)) { otp.setType(OTPUserRecord.Type.TOTP); } else if (option.matches("^HOTP_COUNTER\\s+\\d+$")) { final String countStr = option.substring(option.indexOf(" ") + 1); otp.setType(OTPUserRecord.Type.HOTP); otp.setAttemptCount(Long.parseLong(countStr)); } } else if(line.matches("^\\d{8}$")) { final OTPUserRecord.RecoveryCode code = new OTPUserRecord.RecoveryCode(); code.setUsed(false); code.setHashCode(line); recoveryCodes.add(code); } else { LOGGER.trace(String.format("Unrecognized line: \"%s\"", line)); } } if (recoveryCodes.isEmpty()) { LOGGER.debug("No recovery codes read."); otp.setRecoveryCodes(null); otp.setRecoveryInfo(null); } else { LOGGER.debug(String.format("%d recovery codes read.", recoveryCodes.size())); final OTPUserRecord.RecoveryInfo recoveryInfo = new OTPUserRecord.RecoveryInfo(); recoveryInfo.setHashCount(0); recoveryInfo.setSalt(null); recoveryInfo.setHashMethod(null); otp.setRecoveryInfo(recoveryInfo); otp.setRecoveryCodes(recoveryCodes); } return otp; } } return null; } /** * Create a string representation to be stored by the operator. * * @param otp the record with OTP configuration * @return the string representation of the OTP record */ public static String composePamData(final OTPUserRecord otp) { if (otp == null) { return ""; } final String secret = otp.getSecret(); final OTPUserRecord.Type type = otp.getType(); final List<OTPUserRecord.RecoveryCode> recoveryCodes = otp.getRecoveryCodes(); String pamData = secret + "\n"; if (OTPUserRecord.Type.HOTP.equals(type)) { pamData += String.format("\" HOTP_COUNTER %d\n", otp.getAttemptCount()); } else { pamData += "\" TOTP_AUTH\n"; } if (recoveryCodes != null && recoveryCodes.size() > 0) { // The codes are assumed to be non-hashed for (final OTPUserRecord.RecoveryCode code : recoveryCodes) { if (!code.isUsed()) { pamData += code.getHashCode() + "\n"; } } } return pamData; } }