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