/** * Copyright (c) 2015, Lucee Assosication Switzerland. All rights reserved. * * This library 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 library 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 library. If not, see <http://www.gnu.org/licenses/>. * */ package lucee.runtime.config; import java.io.IOException; import java.security.NoSuchAlgorithmException; import lucee.commons.digest.Hash; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.StringUtil; import lucee.runtime.crypt.BlowfishEasy; import lucee.runtime.exp.PageException; import org.osgi.framework.BundleException; import org.w3c.dom.Element; import org.xml.sax.SAXException; public class PasswordImpl implements Password { private final String rawPassword; private final String password; private final String salt; private final int type; private final int origin; private PasswordImpl(int origin,String password, String salt, int type) { this.rawPassword=null; this.password=password; this.salt=salt; this.type=type; this.origin=origin; } private PasswordImpl(int origin,String rawPassword, String salt) { this.rawPassword=rawPassword; this.password=hash(rawPassword, salt); this.salt=salt; this.type=StringUtil.isEmpty(salt)?HASHED:HASHED_SALTED; this.origin=origin; } @Override public String getPassword() {return password;} @Override public String getSalt() {return salt;} @Override public int getType() {return type;} @Override public int getOrigin() {return origin;} @Override public Password isEqual(Config config,String other) { // a already hashed password that matches if(password.equals(other)) return this; // current password is only hashed if(type==HASHED) return this.password.equals(hash(other,null))?this:null; // current password is hashed and salted return this.password.equals(hash(other,salt))?this:null; } @Override public boolean equals(Object obj) { if(this == obj) return true; if(obj instanceof Password) { Password opw=(Password) obj; if(password.equals(opw.getPassword())) return true; if(obj instanceof PasswordImpl) { PasswordImpl pi=(PasswordImpl)obj; if(pi.rawPassword!=null) { if(type==HASHED) return hash(pi.rawPassword, null).equals(password); return hash(pi.rawPassword, salt).equals(password); } } } if(obj instanceof CharSequence) { String str=obj.toString(); if(password.equals(str)) return true; if(type==HASHED) return hash(str, null).equals(password); return hash(str, salt).equals(password); } return false; } private static String hash(String str, String salt) { try { return Hash.hash(StringUtil.isEmpty(salt,true)?str:str+":"+salt,Hash.ALGORITHM_SHA_256,5,Hash.ENCODING_HEX); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } /** * reads the password defined in the Lucee configuration, this can also in older formats (only hashed or encrypted) * @param el * @param salt * @param isDefault * @return */ public static Password readFromXML(Element el, String salt, boolean isDefault) { String prefix=isDefault?"default-":""; // first we look for the hashed and salted password String pw=el.getAttribute(prefix+"hspw"); if(!StringUtil.isEmpty(pw,true)) { // password is only of use when there is a salt as well if(salt==null) return null; return new PasswordImpl(ORIGIN_HASHED_SALTED,pw,salt,HASHED_SALTED); } // fall back to password that is hashed but not salted pw=el.getAttribute(prefix+"pw"); if(!StringUtil.isEmpty(pw,true)) { return new PasswordImpl(ORIGIN_HASHED,pw,null,HASHED); } // fall back to encrypted password String pwEnc = el.getAttribute(prefix+"password"); if (!StringUtil.isEmpty(pwEnc,true)) { String rawPassword = new BlowfishEasy("tpwisgh").decryptString(pwEnc); return new PasswordImpl(ORIGIN_ENCRYPTED,rawPassword,salt); } return null; } public static Password writeToXML(Element el,String passwordRaw, boolean isDefault) { // salt String salt=getSalt(el); Password pw = new PasswordImpl(ORIGIN_UNKNOW,passwordRaw, salt); writeToXML(el,pw,isDefault); return pw; } private static String getSalt(Element el) { String salt=el.getAttribute("salt"); if(StringUtil.isEmpty(salt,true)) throw new RuntimeException("missing salt!");// this should never happen return salt.trim(); } public static void writeToXML(Element el,Password pw, boolean isDefault) { String prefix=isDefault?"default-":""; if(pw==null) { if(el.hasAttribute(prefix+"hspw")) el.removeAttribute(prefix+"hspw"); if(el.hasAttribute(prefix+"pw")) el.removeAttribute(prefix+"pw"); if(el.hasAttribute(prefix+"password")) el.removeAttribute(prefix+"password"); } else { // remove backward compatibility if(el.hasAttribute(prefix+"pw")) el.removeAttribute(prefix+"pw"); if(el.hasAttribute(prefix+"password")) el.removeAttribute(prefix+"password"); if(pw.getType()==HASHED_SALTED) el.setAttribute(prefix+"hspw",pw.getPassword()); // password is not hashed and salted else { PasswordImpl pwi; if(pw instanceof PasswordImpl && (pwi=((PasswordImpl)pw)).rawPassword!=null) { el.setAttribute(prefix+"hspw",hash(pwi.rawPassword, getSalt(el))); } else { el.setAttribute(prefix+"pw",pw.getPassword());// this should never happen } } } } public static void removeFromXML(Element root, boolean isDefault) { writeToXML(root, (Password)null, isDefault); } public static Password updatePasswordIfNecessary(ConfigImpl config, Password passwordOld, String strPasswordNew) { try { // is the server context default password used boolean defPass=false; if(config instanceof ConfigWebImpl) defPass=((ConfigWebImpl)config).isDefaultPassword(); int origin=config.getPasswordOrigin(); // current is old style password and not a default password! if((origin==Password.ORIGIN_HASHED || origin==Password.ORIGIN_ENCRYPTED) && !defPass) { // is passord valid! if(config.isPasswordEqual(strPasswordNew)!=null) { // new salt String saltn=config.getSalt(); // get salt from context, not from old password that can be different when default password // new password Password passwordNew=null; if(!StringUtil.isEmpty(strPasswordNew,true)) passwordNew = new PasswordImpl(ORIGIN_UNKNOW,strPasswordNew,saltn); updatePassword(config, passwordOld, passwordNew); return passwordNew; } } } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} return null; } /** * * @param config Config of the context (ConfigServer to set a server level password) * @param strPasswordOld the old password to replace or null if there is no password set yet * @param strPasswordNew the new password * @throws IOException * @throws SAXException * @throws PageException * @throws BundleException */ public static void updatePassword(ConfigImpl config, String strPasswordOld, String strPasswordNew) throws SAXException, IOException, PageException, BundleException { // old salt int pwType=config.getPasswordType(); // get type from password String salto=config.getPasswordSalt(); // get salt from password if(pwType==Password.HASHED)salto=null; // if old password does not use a salt, we do not use a salt to hash // new salt String saltn=config.getSalt(); // get salt from context, not from old password that can be different when default password // old password Password passwordOld=null; if(!StringUtil.isEmpty(strPasswordOld,true)) passwordOld=new PasswordImpl(ORIGIN_UNKNOW,strPasswordOld,salto); // new password Password passwordNew=null; if(!StringUtil.isEmpty(strPasswordNew,true)) passwordNew = new PasswordImpl(ORIGIN_UNKNOW,strPasswordNew,saltn); updatePassword(config, passwordOld, passwordNew); } public static void updatePassword(ConfigImpl config, Password passwordOld, Password passwordNew) throws SAXException, IOException, PageException, BundleException { if(!config.hasPassword()) { config.setPassword(passwordNew); XMLConfigAdmin admin = XMLConfigAdmin.newInstance(config,passwordNew); admin.setPassword(passwordNew); admin.storeAndReload(); } else { ConfigWebUtil.checkPassword(config,"write",passwordOld); ConfigWebUtil.checkGeneralWriteAccess(config,passwordOld); XMLConfigAdmin admin = XMLConfigAdmin.newInstance(config,passwordOld); admin.setPassword(passwordNew); admin.storeAndReload(); } } public static Password passwordToCompare(ConfigWeb cw, boolean server, String rawPassword) { if(StringUtil.isEmpty(rawPassword,true)) return null; ConfigWebImpl cwi=(ConfigWebImpl) cw; int pwType; String pwSalt; if(server) { pwType=cwi.getServerPasswordType(); pwSalt=cwi.getServerPasswordSalt(); } else { pwType=cwi.getPasswordType(); pwSalt=cwi.getPasswordSalt(); } // if the internal password is not using the salt yet, this hash should eigther String salt=pwType==Password.HASHED?null:pwSalt; return new PasswordImpl(ORIGIN_UNKNOW,rawPassword,salt); } }