1   /*
2    * Copyright 2003 - 2013 The eFaps Team
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Revision:        $Rev$
17   * Last Changed:    $Date$
18   * Last Changed By: $Author$
19   */
20  
21  package org.efaps.admin.datamodel.attributevalue;
22  
23  import java.io.IOException;
24  import java.io.StringReader;
25  import java.io.StringWriter;
26  import java.util.Properties;
27  
28  import org.efaps.admin.EFapsSystemConfiguration;
29  import org.efaps.admin.KernelSettings;
30  import org.efaps.admin.common.SystemConfiguration;
31  import org.efaps.db.Context;
32  import org.efaps.util.EFapsException;
33  import org.jasypt.digest.config.SimpleDigesterConfig;
34  import org.jasypt.util.password.ConfigurablePasswordEncryptor;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  /**
39   * TODO comment!
40   *
41   * @author The eFaps Team
42   * @version $Id$
43   */
44  public class PasswordStore
45  {
46  
47      /**
48       * Logging instance used to give logging information of this class.
49       */
50      private static final Logger LOG = LoggerFactory.getLogger(PasswordStore.class);
51  
52      /**
53       * Prefix for the PropertyKey of the Digest.
54       */
55      private static final String DIGEST = "Digest";
56  
57      /**
58       * Prefix for the PropertyKey of the Algorithm.
59       */
60      private static final String ALGORITHM = "Algorithm";
61  
62      /**
63       * Prefix for the PropertyKey of the Iterations.
64       */
65      private static final String ITERATIONS = "Iterations";
66  
67      /**
68       * Prefix for the PropertyKey of the Saltsize.
69       */
70      private static final String SALTSIZE = "Saltsize";
71  
72      /**
73       * Internal Properties used to store the information.
74       */
75      private final Properties props = new Properties();
76  
77      /**
78       * The configuration object for the digester.
79       */
80      private final SimpleDigesterConfig digesterConfig = new SimpleDigesterConfig();
81  
82      /**
83       * The threshold for password uniqueness.
84       */
85      private int threshold = 5;
86  
87      /**
88       * Constructor. To ensure the the Digester Configuration is valid some
89       * default values are set.
90       */
91      public PasswordStore()
92      {
93          this.digesterConfig.setAlgorithm("SHA-256");
94          this.digesterConfig.setIterations(100000);
95          this.digesterConfig.setSaltSizeBytes(16);
96      }
97  
98      /**
99       * Initialize the digester configuration by reading values from the kernel
100      * SystemConfiguration.
101      */
102     private void initConfig()
103     {
104         SystemConfiguration config = null;
105         try {
106             config = EFapsSystemConfiguration.get();
107             if (config != null) {
108                 final Properties confProps = config.getAttributeValueAsProperties(KernelSettings.PWDSTORE);
109                 this.digesterConfig.setAlgorithm(confProps.getProperty(PasswordStore.ALGORITHM,
110                                 this.digesterConfig.getAlgorithm()));
111                 this.digesterConfig.setIterations(confProps.getProperty(PasswordStore.ITERATIONS,
112                                 this.digesterConfig.getIterations().toString()));
113                 this.digesterConfig.setSaltSizeBytes(confProps.getProperty(PasswordStore.SALTSIZE,
114                                 this.digesterConfig.getSaltSizeBytes().toString()));
115                 this.threshold = config.getAttributeValueAsInteger(KernelSettings.PWDTH);
116             }
117         } catch (final EFapsException e) {
118             PasswordStore.LOG.error("Error on reading SystemConfiguration for PasswordStore", e);
119         }
120 
121     }
122 
123     /**
124      * Read a PasswordStore from a String.
125      *
126      * @param _readValue String to be read
127      * @throws EFapsException on error
128      */
129     public void read(final String _readValue)
130         throws EFapsException
131     {
132         if (_readValue != null) {
133             try {
134                 this.props.load(new StringReader(_readValue));
135             } catch (final IOException e) {
136                 throw new EFapsException(PasswordStore.class.getName(), e);
137             }
138         }
139     }
140 
141     /**
142      * Check the given Plain Text Password for equal on the current Hash by
143      * applying the algorithm salt etc.
144      *
145      * @param _plainPassword plain text password
146      * @return true if equal, else false
147      */
148     public boolean checkCurrent(final String _plainPassword)
149     {
150         return check(_plainPassword, 0);
151     }
152 
153     /**
154      * Check the given Plain Text Password for equal on the Hash by applying the
155      * algorithm salt etc.
156      *
157      * @param _plainPassword plain text password
158      * @param _pos position of the password to be checked
159      * @return true if equal, else false
160      */
161     private boolean check(final String _plainPassword,
162                           final int _pos)
163     {
164         boolean ret = false;
165         if (this.props.containsKey(PasswordStore.DIGEST + _pos)) {
166             final ConfigurablePasswordEncryptor passwordEncryptor = new ConfigurablePasswordEncryptor();
167             this.digesterConfig.setAlgorithm(this.props.getProperty(PasswordStore.ALGORITHM + _pos));
168             this.digesterConfig.setIterations(this.props.getProperty(PasswordStore.ITERATIONS + _pos));
169             this.digesterConfig.setSaltSizeBytes(this.props.getProperty(PasswordStore.SALTSIZE + _pos));
170             passwordEncryptor.setConfig(this.digesterConfig);
171             ret = passwordEncryptor.checkPassword(_plainPassword, this.props.getProperty(PasswordStore.DIGEST + _pos));
172         }
173         return ret;
174     }
175 
176     /**
177      * Is the given plain password repeated. It is checked against the existing
178      * previous passwords.
179      *
180      * @param _plainPassword plain text password
181      * @return true if repeated, else false
182      */
183     public boolean isRepeated(final String _plainPassword)
184     {
185         boolean ret = false;
186         for (int i = 1; i < this.threshold + 1; i++) {
187             ret = check(_plainPassword, i);
188             if (ret) {
189                 break;
190             }
191         }
192         return ret;
193     }
194 
195     /**
196      * Set the given given Plain Password as the new current Password by
197      * encrypting it.
198      *
199      * @param _plainPassword plain password to be used
200      * @param _currentValue current value of the Store
201      * @param _currentValue
202      * @throws EFapsException on error
203      */
204     public void setNew(final String _plainPassword,
205                        final String _currentValue)
206         throws EFapsException
207     {
208         initConfig();
209         read(_currentValue);
210         final ConfigurablePasswordEncryptor passwordEncryptor = new ConfigurablePasswordEncryptor();
211         passwordEncryptor.setConfig(this.digesterConfig);
212         final String encrypted = passwordEncryptor.encryptPassword(_plainPassword);
213         shiftAll();
214         this.props.setProperty(PasswordStore.DIGEST + 0, encrypted);
215         this.props.setProperty(PasswordStore.ALGORITHM + 0, this.digesterConfig.getAlgorithm());
216         this.props.setProperty(PasswordStore.ITERATIONS + 0, this.digesterConfig.getIterations().toString());
217         this.props.setProperty(PasswordStore.SALTSIZE + 0, this.digesterConfig.getSaltSizeBytes().toString());
218     }
219 
220     /**
221      * Shift all Properties.
222      */
223     private void shiftAll()
224     {
225         shift(PasswordStore.DIGEST);
226         shift(PasswordStore.ALGORITHM);
227         shift(PasswordStore.ITERATIONS);
228         shift(PasswordStore.SALTSIZE);
229     }
230 
231     /**
232      * Shift a property.
233      *
234      * @param _key key the property must be shifted for
235      */
236     private void shift(final String _key)
237     {
238         for (int i = this.threshold; i > 0; i--) {
239             this.props.setProperty(_key + i, this.props.getProperty(_key + (i - 1), "").trim());
240         }
241         int i = this.threshold + 1;
242         while (this.props.contains(_key + i)) {
243             this.props.remove(_key + i);
244             i++;
245         }
246     }
247 
248     @Override
249     public String toString()
250     {
251         final StringWriter writer = new StringWriter();
252         try {
253             this.props.store(writer, "Stored by User: " + Context.getThreadContext().getPersonId());
254         } catch (final IOException e) {
255             PasswordStore.LOG.error("Could not write to Writer", e);
256         } catch (final EFapsException e) {
257             PasswordStore.LOG.error("Could not read Context", e);
258         }
259         return writer.toString();
260     }
261 }