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