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.security;
18  
19  import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
20  import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactory;
21  import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactory.GordianFactoryLock;
22  import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
23  import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianBadCredentialsException;
24  import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySet;
25  import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySetFactory;
26  import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianKeyPairLock;
27  import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianKeySetLock;
28  import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianLock;
29  import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianLockFactory;
30  import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianPasswordLockSpec;
31  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
32  import io.github.tonywasher.joceanus.oceanus.convert.OceanusDataConverter;
33  import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogManager;
34  import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogger;
35  import io.github.tonywasher.joceanus.prometheus.exc.PrometheusDataException;
36  import io.github.tonywasher.joceanus.prometheus.exc.PrometheusSecurityException;
37  
38  import java.nio.ByteBuffer;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.List;
42  import java.util.Objects;
43  
44  /**
45   * Password Cache.
46   */
47  public class PrometheusSecurityPasswordCache {
48      /**
49       * Logger.
50       */
51      private static final OceanusLogger LOGGER = OceanusLogManager.getLogger(PrometheusSecurityPasswordCache.class);
52  
53      /**
54       * Password failed message.
55       */
56      private static final String PASSWORD_FAIL = "Password attempt failed";
57  
58      /**
59       * List of resolved Locks.
60       */
61      private final List<PrometheusLockCache<?>> theLocks;
62  
63      /**
64       * List of successful passwords.
65       */
66      private final List<ByteBuffer> thePasswords;
67  
68      /**
69       * The Factory.
70       */
71      private final GordianFactory theFactory;
72  
73      /**
74       * The KeySet Factory.
75       */
76      private final GordianKeySetFactory theKeySetFactory;
77  
78      /**
79       * The lockFactory.
80       */
81      private final GordianLockFactory theLockFactory;
82  
83      /**
84       * PasswordLockSpec.
85       */
86      private final GordianPasswordLockSpec theLockSpec;
87  
88      /**
89       * Local keySet.
90       */
91      private final GordianKeySet theKeySet;
92  
93      /**
94       * Constructor.
95       *
96       * @param pManager  the password manager
97       * @param pLockSpec the passwordLockSpec
98       * @throws OceanusException on error
99       */
100     PrometheusSecurityPasswordCache(final PrometheusSecurityPasswordManager pManager,
101                                     final GordianPasswordLockSpec pLockSpec) throws OceanusException {
102         /* Protect against exceptions */
103         try {
104             /* Store factory and lockSpec*/
105             theFactory = pManager.getSecurityFactory();
106             theKeySetFactory = theFactory.getKeySetFactory();
107             theLockFactory = theFactory.getLockFactory();
108             theLockSpec = pLockSpec;
109 
110             /* Create a keySet */
111             theKeySet = theKeySetFactory.generateKeySet(pLockSpec.getKeySetSpec());
112 
113             /* Create the lists */
114             theLocks = new ArrayList<>();
115             thePasswords = new ArrayList<>();
116 
117         } catch (GordianException e) {
118             throw new PrometheusSecurityException(e);
119         }
120     }
121 
122     /**
123      * Add resolved factoryLock to cache.
124      *
125      * @param pFactory  the resolved FactoryLock
126      * @param pPassword the password
127      * @throws OceanusException on error
128      */
129     void addResolvedFactory(final GordianFactoryLock pFactory,
130                             final char[] pPassword) throws OceanusException {
131         byte[] myPasswordBytes = null;
132         try {
133             /* Encrypt the password */
134             myPasswordBytes = OceanusDataConverter.charsToByteArray(pPassword);
135             final byte[] myEncrypted = theKeySet.encryptBytes(myPasswordBytes);
136 
137             /* Add the entry to the lists */
138             final ByteBuffer myBuffer = ByteBuffer.wrap(myEncrypted);
139             theLocks.add(new PrometheusLockCache<>(pFactory, myBuffer));
140             thePasswords.add(myBuffer);
141 
142 
143         } catch (GordianException e) {
144             throw new PrometheusSecurityException(e);
145 
146         } finally {
147             /* Clear out password */
148             if (myPasswordBytes != null) {
149                 Arrays.fill(myPasswordBytes, (byte) 0);
150             }
151         }
152     }
153 
154     /**
155      * Add resolved keySetLock to cache.
156      *
157      * @param pKeySet   the resolved keySetLock
158      * @param pPassword the password
159      * @throws OceanusException on error
160      */
161     void addResolvedKeySet(final GordianKeySetLock pKeySet,
162                            final char[] pPassword) throws OceanusException {
163         byte[] myPasswordBytes = null;
164         try {
165             /* Encrypt the password */
166             myPasswordBytes = OceanusDataConverter.charsToByteArray(pPassword);
167             final byte[] myEncrypted = theKeySet.encryptBytes(myPasswordBytes);
168 
169             /* Add the entry to the lists */
170             final ByteBuffer myBuffer = ByteBuffer.wrap(myEncrypted);
171             theLocks.add(new PrometheusLockCache<>(pKeySet, myBuffer));
172             thePasswords.add(myBuffer);
173 
174         } catch (GordianException e) {
175             throw new PrometheusSecurityException(e);
176 
177         } finally {
178             /* Clear out password */
179             if (myPasswordBytes != null) {
180                 Arrays.fill(myPasswordBytes, (byte) 0);
181             }
182         }
183     }
184 
185     /**
186      * Add resolved keyPairLock to cache.
187      *
188      * @param pKeyPair  the resolved keyPairLock
189      * @param pPassword the password
190      * @throws OceanusException on error
191      */
192     void addResolvedKeyPair(final GordianKeyPairLock pKeyPair,
193                             final char[] pPassword) throws OceanusException {
194         byte[] myPasswordBytes = null;
195         try {
196             /* Encrypt the password */
197             myPasswordBytes = OceanusDataConverter.charsToByteArray(pPassword);
198             final byte[] myEncrypted = theKeySet.encryptBytes(myPasswordBytes);
199 
200             /* Add the entry to the lists */
201             final ByteBuffer myBuffer = ByteBuffer.wrap(myEncrypted);
202             theLocks.add(new PrometheusLockCache<>(pKeyPair, myBuffer));
203             thePasswords.add(myBuffer);
204 
205         } catch (GordianException e) {
206             throw new PrometheusSecurityException(e);
207 
208         } finally {
209             /* Clear out password */
210             if (myPasswordBytes != null) {
211                 Arrays.fill(myPasswordBytes, (byte) 0);
212             }
213         }
214     }
215 
216     /**
217      * LookUp previously resolved Factory.
218      *
219      * @param pLockBytes the LockBytes to search for
220      * @return the previous factoryLock if found, otherwise null
221      */
222     GordianFactoryLock lookUpResolvedFactoryLock(final byte[] pLockBytes) {
223         /* Look for the factory in the list */
224         for (PrometheusLockCache<?> myCurr : theLocks) {
225             /* If this is the factoryLock we are looking for, return it */
226             if (myCurr.getLock() instanceof GordianFactoryLock
227                     && Arrays.equals(pLockBytes, myCurr.getLock().getLockBytes())) {
228                 return (GordianFactoryLock) myCurr.getLock();
229             }
230         }
231 
232         /* Return not found */
233         return null;
234     }
235 
236     /**
237      * LookUp previously resolved keySet.
238      *
239      * @param pLockBytes the LockBytes to search for
240      * @return the previous keySetLock if found, otherwise null
241      */
242     GordianKeySetLock lookUpResolvedKeySetLock(final byte[] pLockBytes) {
243         /* Look for the keySet in the list */
244         for (PrometheusLockCache<?> myCurr : theLocks) {
245             /* If this is the keySetLock we are looking for, return it */
246             if (myCurr.getLock() instanceof GordianKeySetLock
247                     && Arrays.equals(pLockBytes, myCurr.getLock().getLockBytes())) {
248                 return (GordianKeySetLock) myCurr.getLock();
249             }
250         }
251 
252         /* Return not found */
253         return null;
254     }
255 
256     /**
257      * LookUp previously resolved keyPair.
258      *
259      * @param pLockBytes the LockBytes to search for
260      * @param pKeyPair   the keyPair
261      * @return the previous keySetLock if found, otherwise null
262      */
263     GordianKeyPairLock lookUpResolvedKeyPairLock(final byte[] pLockBytes,
264                                                  final GordianKeyPair pKeyPair) {
265         /* Look for the keyPair in the list */
266         for (PrometheusLockCache<?> myCurr : theLocks) {
267             /* If this is the keyPairLock we are looking for, return it */
268             if (myCurr.getLock() instanceof GordianKeyPairLock
269                     && Arrays.equals(pLockBytes, myCurr.getLock().getLockBytes())
270                     && pKeyPair.equals(((GordianKeyPairLock) myCurr.getLock()).getKeyPair())) {
271                 return (GordianKeyPairLock) myCurr.getLock();
272             }
273         }
274 
275         /* Return not found */
276         return null;
277     }
278 
279     /**
280      * LookUp previously resolved Password.
281      *
282      * @param pReference the Reference to search for
283      * @return the encrypted password
284      * @throws OceanusException on error
285      */
286     ByteBuffer lookUpResolvedPassword(final Object pReference) throws OceanusException {
287         /* If the reference is a lock */
288         if (pReference instanceof GordianLock) {
289             /* Look for the lock in the list */
290             final GordianLock<?> myReference = (GordianLock<?>) pReference;
291             for (PrometheusLockCache<?> myCurr : theLocks) {
292                 /* If this is the lock are looking for, return it */
293                 if (Objects.equals(myReference, myCurr.getLock())) {
294                     return myCurr.getPassword();
295                 }
296             }
297         }
298 
299         /* Throw error */
300         throw new PrometheusDataException("Referenced Object not known");
301     }
302 
303     /**
304      * Attempt known passwords for factory lock.
305      *
306      * @param pLockBytes the lockBytes to attempt passwords for
307      * @return the new FactoryLock if successful, otherwise null
308      */
309     GordianFactoryLock attemptKnownPasswordsForFactoryLock(final byte[] pLockBytes) {
310         /* Loop through the passwords */
311         for (ByteBuffer myCurr : thePasswords) {
312             /* Attempt the password */
313             final GordianFactoryLock myFactory = attemptPasswordForFactoryLock(pLockBytes, myCurr.array());
314 
315             /* If we succeeded */
316             if (myFactory != null) {
317                 /* Add the factory to the list and return it */
318                 theLocks.add(new PrometheusLockCache<>(myFactory, myCurr));
319                 return myFactory;
320             }
321         }
322 
323         /* Return null */
324         return null;
325     }
326 
327     /**
328      * Attempt the cached password against the passed lock.
329      *
330      * @param pLockBytes the Lock to test against
331      * @param pPassword  the encrypted password
332      * @return the new FactoryLock if successful, otherwise null
333      */
334     private GordianFactoryLock attemptPasswordForFactoryLock(final byte[] pLockBytes,
335                                                              final byte[] pPassword) {
336         /* Protect against exceptions */
337         byte[] myPasswordBytes = null;
338         char[] myPasswordChars = null;
339         try {
340             /* Access the original password */
341             myPasswordBytes = theKeySet.decryptBytes(pPassword);
342             myPasswordChars = OceanusDataConverter.bytesToCharArray(myPasswordBytes);
343 
344             /* Try to resolve the lock and return it */
345             return theFactory.resolveFactoryLock(pLockBytes, myPasswordChars);
346 
347             /* Catch Exceptions */
348         } catch (GordianException
349                  | OceanusException e) {
350             LOGGER.error(PASSWORD_FAIL, e);
351             return null;
352 
353         } catch (GordianBadCredentialsException e) {
354             return null;
355 
356         } finally {
357             /* Clear out password */
358             if (myPasswordBytes != null) {
359                 Arrays.fill(myPasswordBytes, (byte) 0);
360             }
361             if (myPasswordChars != null) {
362                 Arrays.fill(myPasswordChars, (char) 0);
363             }
364         }
365     }
366 
367     /**
368      * Attempt known passwords for keySet lock.
369      *
370      * @param pLockBytes the lockBytes to attempt passwords for
371      * @return the new keySetLock if successful, otherwise null
372      */
373     GordianKeySetLock attemptKnownPasswordsForKeySetLock(final byte[] pLockBytes) {
374         /* Loop through the passwords */
375         for (ByteBuffer myCurr : thePasswords) {
376             /* Attempt the password */
377             final GordianKeySetLock myKeySet = attemptPasswordForKeySetLock(pLockBytes, myCurr.array());
378 
379             /* If we succeeded */
380             if (myKeySet != null) {
381                 /* Add the factory to the list and return it */
382                 theLocks.add(new PrometheusLockCache<>(myKeySet, myCurr));
383                 return myKeySet;
384             }
385         }
386 
387         /* Return null */
388         return null;
389     }
390 
391     /**
392      * Attempt the cached password against the passed lock.
393      *
394      * @param pLockBytes the Lock to test against
395      * @param pPassword  the encrypted password
396      * @return the new keySetLock if successful, otherwise null
397      */
398     private GordianKeySetLock attemptPasswordForKeySetLock(final byte[] pLockBytes,
399                                                            final byte[] pPassword) {
400         /* Protect against exceptions */
401         byte[] myPasswordBytes = null;
402         char[] myPasswordChars = null;
403         try {
404             /* Access the original password */
405             myPasswordBytes = theKeySet.decryptBytes(pPassword);
406             myPasswordChars = OceanusDataConverter.bytesToCharArray(myPasswordBytes);
407 
408             /* Try to resolve the lock and return it */
409             return theLockFactory.resolveKeySetLock(pLockBytes, myPasswordChars);
410 
411             /* Catch Exceptions */
412         } catch (GordianException
413                  | OceanusException e) {
414             LOGGER.error(PASSWORD_FAIL, e);
415             return null;
416 
417         } catch (GordianBadCredentialsException e) {
418             return null;
419 
420         } finally {
421             /* Clear out password */
422             if (myPasswordBytes != null) {
423                 Arrays.fill(myPasswordBytes, (byte) 0);
424             }
425             if (myPasswordChars != null) {
426                 Arrays.fill(myPasswordChars, (char) 0);
427             }
428         }
429     }
430 
431     /**
432      * Attempt known passwords for keyPair lock.
433      *
434      * @param pLockBytes the lockBytes to attempt passwords for
435      * @param pKeyPair   the keyPair
436      * @return the new keyPairLock if successful, otherwise null
437      */
438     GordianKeyPairLock attemptKnownPasswordsForKeyPairLock(final byte[] pLockBytes,
439                                                            final GordianKeyPair pKeyPair) {
440         /* Loop through the passwords */
441         for (ByteBuffer myCurr : thePasswords) {
442             /* Attempt the password */
443             final GordianKeyPairLock myKeyPair = attemptPasswordForKeyPairLock(pLockBytes, pKeyPair, myCurr.array());
444 
445             /* If we succeeded */
446             if (myKeyPair != null) {
447                 /* Add the factory to the list and return it */
448                 theLocks.add(new PrometheusLockCache<>(myKeyPair, myCurr));
449                 return myKeyPair;
450             }
451         }
452 
453         /* Return null */
454         return null;
455     }
456 
457     /**
458      * Attempt the cached password against the passed lock.
459      *
460      * @param pLockBytes the Lock to test against
461      * @param pKeyPair   the keyPair
462      * @param pPassword  the encrypted password
463      * @return the new keyPairLock if successful, otherwise null
464      */
465     private GordianKeyPairLock attemptPasswordForKeyPairLock(final byte[] pLockBytes,
466                                                              final GordianKeyPair pKeyPair,
467                                                              final byte[] pPassword) {
468         /* Protect against exceptions */
469         byte[] myPasswordBytes = null;
470         char[] myPasswordChars = null;
471         try {
472             /* Access the original password */
473             myPasswordBytes = theKeySet.decryptBytes(pPassword);
474             myPasswordChars = OceanusDataConverter.bytesToCharArray(myPasswordBytes);
475 
476             /* Try to resolve the lock and return it */
477             return theLockFactory.resolveKeyPairLock(pLockBytes, pKeyPair, myPasswordChars);
478 
479             /* Catch Exceptions */
480         } catch (GordianException
481                  | OceanusException e) {
482             LOGGER.error(PASSWORD_FAIL, e);
483             return null;
484 
485         } catch (GordianBadCredentialsException e) {
486             return null;
487 
488         } finally {
489             /* Clear out password */
490             if (myPasswordBytes != null) {
491                 Arrays.fill(myPasswordBytes, (byte) 0);
492             }
493             if (myPasswordChars != null) {
494                 Arrays.fill(myPasswordChars, (char) 0);
495             }
496         }
497     }
498 
499     /**
500      * Create a factoryLock with a previously used password.
501      *
502      * @param pFactory  the new factory
503      * @param pPassword the encrypted password
504      * @return the new factoryLock
505      * @throws OceanusException on error
506      */
507     GordianFactoryLock createSimilarFactoryLock(final GordianFactory pFactory,
508                                                 final ByteBuffer pPassword) throws OceanusException {
509         /* Protect against exceptions */
510         byte[] myPasswordBytes = null;
511         char[] myPasswordChars = null;
512         try {
513             /* Access the original password */
514             myPasswordBytes = theKeySet.decryptBytes(pPassword.array());
515             myPasswordChars = OceanusDataConverter.bytesToCharArray(myPasswordBytes);
516 
517             /* Create the new lock */
518             final GordianFactoryLock myLock = theFactory.newFactoryLock(pFactory, theLockSpec, myPasswordChars);
519 
520             /* Add the entry to the list and return the hash */
521             theLocks.add(new PrometheusLockCache<>(myLock, pPassword));
522             return myLock;
523 
524         } catch (GordianException e) {
525             throw new PrometheusSecurityException(e);
526 
527         } finally {
528             /* Clear out password */
529             if (myPasswordBytes != null) {
530                 Arrays.fill(myPasswordBytes, (byte) 0);
531             }
532             if (myPasswordChars != null) {
533                 Arrays.fill(myPasswordChars, (char) 0);
534             }
535         }
536     }
537 
538     /**
539      * Create a keySetLock with a previously used password.
540      *
541      * @param pKeySet   the new keySet
542      * @param pPassword the encrypted password
543      * @return the new factoryLock
544      * @throws OceanusException on error
545      */
546     GordianKeySetLock createSimilarKeySetLock(final GordianKeySet pKeySet,
547                                               final ByteBuffer pPassword) throws OceanusException {
548         /* Protect against exceptions */
549         byte[] myPasswordBytes = null;
550         char[] myPasswordChars = null;
551         try {
552             /* Access the original password */
553             myPasswordBytes = theKeySet.decryptBytes(pPassword.array());
554             myPasswordChars = OceanusDataConverter.bytesToCharArray(myPasswordBytes);
555 
556             /* Create the new lock */
557             final GordianKeySetLock myLock = theLockFactory.newKeySetLock(pKeySet, theLockSpec, myPasswordChars);
558 
559             /* Add the entry to the list and return the hash */
560             theLocks.add(new PrometheusLockCache<>(myLock, pPassword));
561             return myLock;
562 
563         } catch (GordianException e) {
564             throw new PrometheusSecurityException(e);
565 
566         } finally {
567             /* Clear out password */
568             if (myPasswordBytes != null) {
569                 Arrays.fill(myPasswordBytes, (byte) 0);
570             }
571             if (myPasswordChars != null) {
572                 Arrays.fill(myPasswordChars, (char) 0);
573             }
574         }
575     }
576 
577     /**
578      * Create a zipLock with a previously used password.
579      *
580      * @param pKeyPair  the keyPair
581      * @param pPassword the encrypted password
582      * @return the new PasswordHash
583      * @throws OceanusException on error
584      */
585     GordianKeyPairLock createSimilarKeyPairLock(final GordianKeyPair pKeyPair,
586                                                 final ByteBuffer pPassword) throws OceanusException {
587         /* Protect against exceptions */
588         byte[] myPasswordBytes = null;
589         char[] myPasswordChars = null;
590         try {
591             /* Access the original password */
592             myPasswordBytes = theKeySet.decryptBytes(pPassword.array());
593             myPasswordChars = OceanusDataConverter.bytesToCharArray(myPasswordBytes);
594 
595             /* Create the similar passwordLock and return it */
596             return theLockFactory.newKeyPairLock(theLockSpec, pKeyPair, myPasswordChars);
597 
598         } catch (GordianException e) {
599             throw new PrometheusSecurityException(e);
600 
601         } finally {
602             /* Clear out password */
603             if (myPasswordBytes != null) {
604                 Arrays.fill(myPasswordBytes, (byte) 0);
605             }
606             if (myPasswordChars != null) {
607                 Arrays.fill(myPasswordChars, (char) 0);
608             }
609         }
610     }
611 
612     /**
613      * The lockCache.
614      *
615      * @param <T> the locked object
616      *
617      */
618     static class PrometheusLockCache<T> {
619         /**
620          * The FactoryLock.
621          */
622         private final GordianLock<T> theLock;
623 
624         /**
625          * The Encrypted password.
626          */
627         private final ByteBuffer thePassword;
628 
629         /**
630          * Constructor.
631          *
632          * @param pLock     the Lock
633          * @param pPassword the encrypted password
634          */
635         PrometheusLockCache(final GordianLock<T> pLock,
636                             final ByteBuffer pPassword) {
637             theLock = pLock;
638             thePassword = pPassword;
639         }
640 
641         /**
642          * Obtain the lock.
643          *
644          * @return the Lock
645          */
646         GordianLock<T> getLock() {
647             return theLock;
648         }
649 
650         /**
651          * Obtain the encrypted password.
652          *
653          * @return the password
654          */
655         ByteBuffer getPassword() {
656             return thePassword;
657         }
658     }
659 }