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}