001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.config; 020 021import java.io.IOException; 022import java.security.NoSuchAlgorithmException; 023 024import lucee.commons.digest.Hash; 025import lucee.commons.lang.ExceptionUtil; 026import lucee.commons.lang.StringUtil; 027import lucee.runtime.crypt.BlowfishEasy; 028import lucee.runtime.exp.PageException; 029 030import org.w3c.dom.Element; 031import org.xml.sax.SAXException; 032 033public class Password { 034 035 public static final int HASHED=1; 036 public static final int HASHED_SALTED=2; 037 038 public static final int ORIGIN_ENCRYPTED=3; 039 public static final int ORIGIN_HASHED=4; 040 public static final int ORIGIN_HASHED_SALTED=5; 041 public static final int ORIGIN_UNKNOW=6; 042 043 private final String rawPassword; 044 public final String password; 045 public final String salt; 046 public final int type; 047 public final int origin; 048 049 050 private Password(int origin,String password, String salt, int type) { 051 this.rawPassword=null; 052 this.password=password; 053 this.salt=salt; 054 this.type=type; 055 this.origin=origin; 056 } 057 058 private Password(int origin,String rawPassword, String salt) { 059 this.rawPassword=rawPassword; 060 this.password=hash(rawPassword, salt); 061 this.salt=salt; 062 this.type=StringUtil.isEmpty(salt)?HASHED:HASHED_SALTED; 063 this.origin=origin; 064 } 065 066 067 private static String hash(String str, String salt) { 068 try { 069 return Hash.hash(StringUtil.isEmpty(salt,true)?str:str+":"+salt,Hash.ALGORITHM_SHA_256,5,Hash.ENCODING_HEX); 070 } 071 catch (NoSuchAlgorithmException e) { 072 throw new RuntimeException(e); 073 } 074 } 075 /*public Password isEqual(Config config,String other, boolean hashIfNecessary) { 076 Password b = _isEqual(config, other, hashIfNecessary); 077 if(b==null) { 078 print.e("+++++++++++++++++++++++++"); 079 print.e(type); 080 print.e(salt); 081 print.e(other); 082 print.e(this.password); 083 print.e((hash(other,null))); 084 print.e((hash(other,salt))); 085 print.ds(); 086 } 087 return b; 088 }*/ 089 public Password isEqual(Config config,String other, boolean hashIfNecessary) { 090 if(password.equals(other)) return this; 091 092 //String salt=((ConfigImpl)config).getSalt(); // this hash is used! 093 if(!hashIfNecessary) return null; 094 095 096 // current password is only hashed 097 if(type==HASHED) return this.password.equals(hash(other,null))?this:null; 098 // current password is hashed and salted 099 100 101 return this.password.equals(hash(other,salt))?this:null; 102 } 103 104 public static Password getInstance(Element el, String salt, boolean isDefault) { 105 String prefix=isDefault?"default-":""; 106 107 // first we look for the hashed and salted password 108 String pw=el.getAttribute(prefix+"hspw"); 109 if(!StringUtil.isEmpty(pw,true)) { 110 // password is only of use when there is a salt as well 111 if(salt==null) return null; 112 return new Password(ORIGIN_HASHED_SALTED,pw,salt,HASHED_SALTED); 113 } 114 115 // fall back to password that is hashed but not salted 116 pw=el.getAttribute(prefix+"pw"); 117 if(!StringUtil.isEmpty(pw,true)) { 118 return new Password(ORIGIN_HASHED,pw,null,HASHED); 119 } 120 121 // fall back to encrypted password 122 String pwEnc = el.getAttribute(prefix+"password"); 123 if (!StringUtil.isEmpty(pwEnc,true)) { 124 String rawPassword = new BlowfishEasy("tpwisgh").decryptString(pwEnc); 125 return new Password(ORIGIN_ENCRYPTED,rawPassword,salt); 126 } 127 return null; 128 } 129 130 131 public static Password getInstanceFromRawPassword(String rawPassword, String salt) { 132 return new Password(ORIGIN_UNKNOW,rawPassword,salt); 133 } 134 135 public static Password hashAndStore(Element el,String password, boolean isDefault) { 136 // salt 137 String salt=el.getAttribute("salt"); 138 if(StringUtil.isEmpty(salt,true)) throw new RuntimeException("missing salt!");// this should never happen 139 salt=salt.trim(); 140 141 Password pw = new Password(ORIGIN_UNKNOW,hash(password, salt), salt, StringUtil.isEmpty(salt)?HASHED:HASHED_SALTED); 142 store(el,pw,isDefault); 143 return pw; 144 } 145 146 147 public static void store(Element el,Password pw, boolean isDefault) { 148 String prefix=isDefault?"default-":""; 149 if(pw==null) { 150 if(el.hasAttribute(prefix+"hspw")) el.removeAttribute(prefix+"hspw"); 151 if(el.hasAttribute(prefix+"pw")) el.removeAttribute(prefix+"pw"); 152 if(el.hasAttribute(prefix+"password")) el.removeAttribute(prefix+"password"); 153 } 154 else { 155 // also set older password type, needed when someone downgrade Lucee 156 if(pw.rawPassword!=null) { 157 if(el.hasAttribute(prefix+"pw")) el.setAttribute(prefix+"pw",hash(pw.rawPassword, null)); 158 String encoded = new BlowfishEasy("tpwisgh").encryptString(pw.rawPassword); 159 if(el.hasAttribute(prefix+"password")) el.setAttribute(prefix+"password",encoded); 160 } 161 162 el.setAttribute(prefix+"hspw",pw.password); 163 } 164 165 // remove older passwords 166 // xxx when update the older needed to be updated as well or removed 167 } 168 169 170 public static void remove(Element root, boolean isDefault) { 171 store(root, null, isDefault); 172 } 173 174 175 public static Password updatePasswordIfNecessary(ConfigImpl config, Password passwordOld, String strPasswordNew) { 176 177 try { 178 // is the server context default password used 179 boolean defPass=false; 180 if(config instanceof ConfigWebImpl) 181 defPass=((ConfigWebImpl)config).isDefaultPassword(); 182 183 184 int origin=config.getPasswordOrigin(); 185 186 // is old style password! 187 if((origin==Password.ORIGIN_HASHED || origin==Password.ORIGIN_ENCRYPTED) && !defPass) { 188 // is passord valid! 189 if(config.isPasswordEqual(strPasswordNew, true)!=null) { 190 // new salt 191 String saltn=config.getSalt(); // get salt from context, not from old password that can be different when default password 192 193 // new password 194 Password passwordNew=null; 195 if(!StringUtil.isEmpty(strPasswordNew,true)) 196 passwordNew = Password.getInstanceFromRawPassword(strPasswordNew, saltn); 197 198 updatePassword(config, passwordOld, passwordNew); 199 return passwordNew; 200 } 201 } 202 } 203 catch (Throwable t) { 204 ExceptionUtil.rethrowIfNecessary(t); 205 // Optinal functionality, ignore failures 206 t.printStackTrace(); 207 } 208 return null; 209 } 210 /** 211 * 212 * @param config Config of the context (ConfigServer to set a server level password) 213 * @param strPasswordOld the old password to replace or null if there is no password set yet 214 * @param strPasswordNew the new password 215 * @throws IOException 216 * @throws SAXException 217 * @throws PageException 218 */ 219 public static void updatePassword(ConfigImpl config, String strPasswordOld, String strPasswordNew) throws SAXException, IOException, PageException { 220 221 // old salt 222 int pwType=config.getPasswordType(); // get type from password 223 String salto=config.getPasswordSalt(); // get salt from password 224 if(pwType==Password.HASHED)salto=null; // if old password does not use a salt, we do not use a salt to hash 225 226 // new salt 227 String saltn=config.getSalt(); // get salt from context, not from old password that can be different when default password 228 229 230 // old password 231 Password passwordOld=null; 232 if(!StringUtil.isEmpty(strPasswordOld,true)) 233 passwordOld=Password.getInstanceFromRawPassword(strPasswordOld, salto); 234 235 // new password 236 Password passwordNew=null; 237 if(!StringUtil.isEmpty(strPasswordNew,true)) 238 passwordNew = Password.getInstanceFromRawPassword(strPasswordNew, saltn); 239 240 updatePassword(config, passwordOld, passwordNew); 241 242 243 244 } 245 246 public static void updatePassword(ConfigImpl config, Password passwordOld, Password passwordNew) throws SAXException, IOException, PageException { 247 if(!config.hasPassword()) { 248 config.setPassword(passwordNew); 249 ConfigWebAdmin admin = ConfigWebAdmin.newInstance(config,passwordNew.password); 250 admin.setPassword(passwordNew); 251 admin.store(); 252 } 253 else { 254 ConfigWebUtil.checkPassword(config,"write",passwordOld.password); 255 ConfigWebUtil.checkGeneralWriteAccess(config,passwordOld.password); 256 ConfigWebAdmin admin = ConfigWebAdmin.newInstance(config,passwordOld.password); 257 admin.setPassword(passwordNew); 258 admin.store(); 259 } 260 } 261 262 public static Password hashPassword(ConfigWeb cw, boolean server, String rawPassword) { 263 ConfigWebImpl cwi=(ConfigWebImpl) cw; 264 int pwType; 265 String pwSalt; 266 if(server) { 267 pwType=cwi.getServerPasswordType(); 268 pwSalt=cwi.getServerPasswordSalt(); 269 } 270 else { 271 pwType=cwi.getPasswordType(); 272 pwSalt=cwi.getPasswordSalt(); 273 } 274 275 276 // if the internal password is not using the salt yet, this hash should eigther 277 String salt=pwType==Password.HASHED?null:pwSalt; 278 279 return Password.getInstanceFromRawPassword(rawPassword,salt); 280 } 281 282 283 284}