View Javadoc
1   /*
2    * Prometheus: Application Framework
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.prometheus.preference;
18  
19  import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
20  import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
21  import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactory;
22  import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactoryType;
23  import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySet;
24  import io.github.tonywasher.joceanus.gordianknot.api.keyset.spec.GordianKeySetSpec;
25  import io.github.tonywasher.joceanus.gordianknot.api.keyset.spec.GordianKeySetSpecBuilder;
26  import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianKeySetLock;
27  import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianLockFactory;
28  import io.github.tonywasher.joceanus.gordianknot.api.lock.spec.GordianPasswordLockSpec;
29  import io.github.tonywasher.joceanus.gordianknot.util.GordianGenerator;
30  import io.github.tonywasher.joceanus.gordianknot.util.GordianUtilities;
31  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceKey;
32  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceManager;
33  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceParams;
34  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceResource;
35  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
36  import io.github.tonywasher.joceanus.oceanus.convert.OceanusDataConverter;
37  import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogManager;
38  import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogger;
39  import io.github.tonywasher.joceanus.prometheus.exc.PrometheusSecurityException;
40  
41  import java.net.InetAddress;
42  import java.net.UnknownHostException;
43  import java.util.EnumSet;
44  import java.util.Set;
45  
46  /**
47   * Security for Preferences.
48   */
49  public class PrometheusPreferenceSecurity
50          implements PrometheusPreferenceEncryptor {
51      /**
52       * Logger.
53       */
54      private static final OceanusLogger LOGGER = OceanusLogManager.getLogger(PrometheusPreferenceSecurity.class);
55  
56      /**
57       * Default KeyLength.
58       */
59      private static final GordianLength DEFAULT_KEYLEN = GordianLength.LEN_256;
60  
61      /**
62       * The KeySet.
63       */
64      private final GordianKeySet theKeySet;
65  
66      /**
67       * Constructor.
68       *
69       * @param pManager the preference manager
70       * @throws OceanusException on error
71       */
72      PrometheusPreferenceSecurity(final MetisPreferenceManager pManager) throws OceanusException {
73          /* Protect against exceptions */
74          try {
75              /* Create a Security Factory */
76              final GordianFactory myFactory = GordianGenerator.createFactory(GordianFactoryType.BC);
77              final GordianLockFactory myLocks = myFactory.getLockFactory();
78  
79              /* Obtain the hash as a preference */
80              final PrometheusBaseSecurityPreferences myPrefs = pManager.getPreferenceSet(PrometheusBaseSecurityPreferences.class);
81              final byte[] myLock = myPrefs.getByteArrayValue(PrometheusSecurityPreferenceKey.LOCK);
82  
83              /* Derive the password */
84              final char[] myHost = getHostName();
85              final char[] myUser = System.getProperty("user.name").toCharArray();
86              final char[] myPassword = new char[myHost.length + myUser.length];
87              System.arraycopy(myHost, 0, myPassword, 0, myHost.length);
88              System.arraycopy(myUser, 0, myPassword, myHost.length, myUser.length);
89  
90              /* Derive or create the lock */
91              final GordianKeySetLock myKeySetLock = myLock == null
92                      ? myLocks.newKeySetLock(GordianUtilities.newPasswordLockSpecBuilder().passwordLock(), myPassword)
93                      : myLocks.resolveKeySetLock(myLock, myPassword);
94  
95              /* record the KeySet */
96              theKeySet = myKeySetLock.getKeySet();
97  
98              /* If we have created a new lock */
99              if (myLock == null) {
100                 /* Record the lock */
101                 myPrefs.setHash(myKeySetLock.getLockBytes());
102                 myPrefs.storeChanges();
103             }
104         } catch (GordianException e) {
105             throw new PrometheusSecurityException(e);
106         }
107     }
108 
109     @Override
110     public byte[] encryptValue(final char[] pValue) throws OceanusException {
111         /* Protect against exceptions */
112         try {
113             final byte[] myBytes = OceanusDataConverter.charsToByteArray(pValue);
114             return theKeySet.encryptBytes(myBytes);
115         } catch (GordianException e) {
116             throw new PrometheusSecurityException(e);
117         }
118     }
119 
120     @Override
121     public char[] decryptValue(final byte[] pValue) throws OceanusException {
122         /* Protect against exceptions */
123         try {
124             final byte[] myBytes = theKeySet.decryptBytes(pValue);
125             return OceanusDataConverter.bytesToCharArray(myBytes);
126         } catch (GordianException e) {
127             throw new PrometheusSecurityException(e);
128         }
129     }
130 
131     /**
132      * determine hostName.
133      *
134      * @return the hostName
135      */
136     private static char[] getHostName() {
137         /* Protect against exceptions */
138         try {
139             final InetAddress myAddr = InetAddress.getLocalHost();
140             return myAddr.getHostName().toCharArray();
141 
142         } catch (UnknownHostException e) {
143             LOGGER.error("Hostname can not be resolved", e);
144             return "localhost".toCharArray();
145         }
146     }
147 
148     /**
149      * SecurityPreferenceKey.
150      */
151     public enum PrometheusSecurityPreferenceKey implements MetisPreferenceKey {
152         /**
153          * Lock.
154          */
155         LOCK("Lock", null),
156 
157         /**
158          * Factory.
159          */
160         FACTORY("FactoryType", MetisPreferenceResource.SECPREF_FACTORY),
161 
162         /**
163          * KeyLength.
164          */
165         KEYLENGTH("KeyLength", MetisPreferenceResource.SECPREF_KEYLEN),
166 
167         /**
168          * Cipher Steps.
169          */
170         CIPHERSTEPS("CipherSteps", MetisPreferenceResource.SECPREF_CIPHERSTEPS),
171 
172         /**
173          * Hash Iterations.
174          */
175         HASHITERATIONS("HashIterations", MetisPreferenceResource.SECPREF_ITERATIONS),
176 
177         /**
178          * ActiveKeySets.
179          */
180         ACTIVEKEYSETS("NumActiveKeySets", MetisPreferenceResource.SECPREF_KEYSETS);
181 
182         /**
183          * The name of the Preference.
184          */
185         private final String theName;
186 
187         /**
188          * The display string.
189          */
190         private final String theDisplay;
191 
192         /**
193          * Constructor.
194          *
195          * @param pName    the name
196          * @param pDisplay the display resource
197          */
198         PrometheusSecurityPreferenceKey(final String pName,
199                                         final MetisPreferenceResource pDisplay) {
200             theName = pName;
201             theDisplay = pDisplay != null
202                     ? pDisplay.getValue()
203                     : null;
204         }
205 
206         @Override
207         public String getName() {
208             return theName;
209         }
210 
211         @Override
212         public String getDisplay() {
213             return theDisplay;
214         }
215     }
216 
217     /**
218      * PrefSecurityPreferences.
219      */
220     public static class PrometheusBaseSecurityPreferences
221             extends PrometheusPreferenceSet {
222         /**
223          * Constructor.
224          *
225          * @param pParams the parameters
226          * @throws OceanusException on error
227          */
228         public PrometheusBaseSecurityPreferences(final MetisPreferenceParams pParams) throws OceanusException {
229             super(pParams, MetisPreferenceResource.SECPREF_BASEPREFNAME);
230             setHidden();
231         }
232 
233         /**
234          * Set lock.
235          *
236          * @param pHash the lock
237          */
238         protected void setHash(final byte[] pHash) {
239             getByteArrayPreference(PrometheusSecurityPreferenceKey.LOCK).setValue(pHash);
240         }
241 
242         @Override
243         protected void definePreferences() {
244             defineByteArrayPreference(PrometheusSecurityPreferenceKey.LOCK);
245         }
246 
247         @Override
248         public void autoCorrectPreferences() {
249             /* No-OP */
250         }
251     }
252 
253     /**
254      * PrefSecurityPreferences.
255      */
256     public static class PrometheusSecurityPreferences
257             extends PrometheusPreferenceSet {
258         /**
259          * Valid lengths.
260          */
261         private static final Set<GordianLength> VALID_LENGTHS = EnumSet.of(GordianLength.LEN_128, GordianLength.LEN_192, GordianLength.LEN_256);
262 
263         /**
264          * Minimum Number of Active KeySets.
265          */
266         private static final int MINIMUM_ACTIVE_KEYSETS = 4;
267 
268         /**
269          * Maximum Number of Active KeySets.
270          */
271         private static final int MAXIMUM_ACTIVE_KEYSETS = 64;
272 
273         /**
274          * Default Number of Active KeySets.
275          */
276         private static final int DEFAULT_ACTIVE_KEYSETS = 8;
277 
278         /**
279          * Constructor.
280          *
281          * @param pParams the viewer manager
282          * @throws OceanusException on error
283          */
284         public PrometheusSecurityPreferences(final MetisPreferenceParams pParams) throws OceanusException {
285             super(pParams, MetisPreferenceResource.SECPREF_PREFNAME);
286         }
287 
288         /**
289          * Get FactoryType.
290          *
291          * @return the factoryType
292          */
293         public GordianFactoryType getFactoryType() {
294             return getEnumValue(PrometheusSecurityPreferenceKey.FACTORY, GordianFactoryType.class);
295         }
296 
297         /**
298          * Get KeySetSpec.
299          *
300          * @return the spec
301          */
302         public GordianKeySetSpec getKeySetSpec() {
303             /* Build and return keySetSpec */
304             final GordianLength myKeyLen = getEnumValue(PrometheusSecurityPreferenceKey.KEYLENGTH, GordianLength.class);
305             final int mySteps = getIntegerValue(PrometheusSecurityPreferenceKey.CIPHERSTEPS);
306             final GordianKeySetSpecBuilder myBuilder = GordianUtilities.newKeySetSpecBuilder();
307             return myBuilder.keySet(myKeyLen, mySteps);
308         }
309 
310         /**
311          * Get PasswordLockSpec.
312          *
313          * @return the spec
314          */
315         public GordianPasswordLockSpec getPasswordLockSpec() {
316             /* Build and return keySetSpec */
317             final int myIterations = getIntegerValue(PrometheusSecurityPreferenceKey.HASHITERATIONS);
318             return GordianUtilities.newPasswordLockSpecBuilder().passwordLock(myIterations, getKeySetSpec());
319         }
320 
321         @Override
322         protected void definePreferences() throws OceanusException {
323             defineEnumPreference(PrometheusSecurityPreferenceKey.FACTORY, GordianFactoryType.class);
324             defineEnumPreference(PrometheusSecurityPreferenceKey.KEYLENGTH, GordianLength.class);
325             defineIntegerPreference(PrometheusSecurityPreferenceKey.CIPHERSTEPS);
326             defineIntegerPreference(PrometheusSecurityPreferenceKey.HASHITERATIONS);
327             defineIntegerPreference(PrometheusSecurityPreferenceKey.ACTIVEKEYSETS);
328         }
329 
330         @Override
331         public void autoCorrectPreferences() {
332             /* Make sure that the factory is specified */
333             final MetisEnumPreference<GordianFactoryType> myFactPref
334                     = getEnumPreference(PrometheusSecurityPreferenceKey.FACTORY, GordianFactoryType.class);
335             if (!myFactPref.isAvailable()) {
336                 myFactPref.setValue(GordianFactoryType.BC);
337             }
338 
339             /* Make sure that the restricted state is specified */
340             final MetisEnumPreference<GordianLength> myLengthPref
341                     = getEnumPreference(PrometheusSecurityPreferenceKey.KEYLENGTH, GordianLength.class);
342             if (!myLengthPref.isAvailable()) {
343                 myLengthPref.setValue(DEFAULT_KEYLEN);
344             }
345 
346             /* Make sure that the length is restricted */
347             myLengthPref.setFilter(VALID_LENGTHS::contains);
348 
349             /* Make sure that the cipherSteps is specified */
350             MetisIntegerPreference myPref = getIntegerPreference(PrometheusSecurityPreferenceKey.CIPHERSTEPS);
351             if (!myPref.isAvailable()) {
352                 myPref.setValue(GordianKeySetSpec.DEFAULT_CIPHER_STEPS);
353             }
354 
355             /* Define the range */
356             myPref.setRange(GordianKeySetSpec.MINIMUM_CIPHER_STEPS, GordianKeySetSpec.MAXIMUM_CIPHER_STEPS);
357             if (!myPref.validate()) {
358                 myPref.setValue(GordianKeySetSpec.DEFAULT_CIPHER_STEPS);
359             }
360 
361             /* Make sure that the hashIterations is specified */
362             myPref = getIntegerPreference(PrometheusSecurityPreferenceKey.HASHITERATIONS);
363             if (!myPref.isAvailable()) {
364                 myPref.setValue(GordianPasswordLockSpec.DEFAULT_ITERATIONS);
365             }
366 
367             /* Define the range */
368             myPref.setRange(GordianPasswordLockSpec.MINIMUM_ITERATIONS, GordianPasswordLockSpec.MAXIMUM_ITERATIONS);
369             if (!myPref.validate()) {
370                 myPref.setValue(GordianPasswordLockSpec.DEFAULT_ITERATIONS);
371             }
372 
373             /* Make sure that the activeKeySets is specified */
374             myPref = getIntegerPreference(PrometheusSecurityPreferenceKey.ACTIVEKEYSETS);
375             if (!myPref.isAvailable()) {
376                 myPref.setValue(DEFAULT_ACTIVE_KEYSETS);
377             }
378 
379             /* Define the range */
380             myPref.setRange(MINIMUM_ACTIVE_KEYSETS, MAXIMUM_ACTIVE_KEYSETS);
381             if (!myPref.validate()) {
382                 myPref.setValue(DEFAULT_ACTIVE_KEYSETS);
383             }
384         }
385     }
386 }