PrometheusSecurityPasswordManager.java
/*
* Prometheus: Application Framework
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.prometheus.security;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactory;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactory.GordianFactoryLock;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactoryType;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianBadCredentialsException;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySet;
import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianKeyPairLock;
import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianKeySetLock;
import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianLockFactory;
import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianPasswordLockSpec;
import io.github.tonywasher.joceanus.gordianknot.api.zip.GordianZipLock;
import io.github.tonywasher.joceanus.gordianknot.util.GordianGenerator;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.prometheus.exc.PrometheusDataException;
import io.github.tonywasher.joceanus.prometheus.exc.PrometheusLogicException;
import io.github.tonywasher.joceanus.prometheus.exc.PrometheusSecurityException;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* Security Manager class which holds a cache of all resolved password hashes. For password
* hashes that were not previously resolved, previously used passwords will be attempted. If no
* match is found, then the user will be prompted for the password.
*/
public class PrometheusSecurityPasswordManager {
/**
* Text for Bad Password Error.
*/
private static final String NLS_ERRORPASS = PrometheusSecurityResource.SECURITY_BAD_PASSWORD.getValue();
/**
* Security factory.
*/
private final GordianFactory theFactory;
/**
* Lock factory.
*/
private final GordianLockFactory theLockFactory;
/**
* PasswordLockSpec.
*/
private final GordianPasswordLockSpec theLockSpec;
/**
* The Cache.
*/
private final PrometheusSecurityPasswordCache theCache;
/**
* Dialog controller.
*/
private PrometheusSecurityDialogController theDialog;
/**
* Constructor.
*
* @param pFactory the factory
* @param pDialog the dialog controller
* @throws OceanusException on error
*/
public PrometheusSecurityPasswordManager(final GordianFactory pFactory,
final PrometheusSecurityDialogController pDialog) throws OceanusException {
this(pFactory, new GordianPasswordLockSpec(), pDialog);
}
/**
* Constructor.
*
* @param pFactory the factory
* @param pLockSpec the lockSpec
* @param pDialog the dialog controller
* @throws OceanusException on error
*/
public PrometheusSecurityPasswordManager(final GordianFactory pFactory,
final GordianPasswordLockSpec pLockSpec,
final PrometheusSecurityDialogController pDialog) throws OceanusException {
/* Allocate the factory */
theFactory = pFactory;
theLockFactory = theFactory.getLockFactory();
theDialog = pDialog;
theLockSpec = pLockSpec;
/* Allocate a new cache */
theCache = new PrometheusSecurityPasswordCache(this, theLockSpec);
}
/**
* Obtain the security factory.
*
* @return the factory
*/
public GordianFactory getSecurityFactory() {
return theFactory;
}
/**
* Obtain the lockSpec.
*
* @return the lockSpec
*/
public GordianPasswordLockSpec getLockSpec() {
return theLockSpec;
}
/**
* Set the dialog controller.
*
* @param pDialog the controller
*/
public void setDialogController(final PrometheusSecurityDialogController pDialog) {
theDialog = pDialog;
}
/**
* Create a new factoryLock.
*
* @param pSource the description of the secured resource
* @return the factoryLock
* @throws OceanusException on error
*/
public GordianFactoryLock newFactoryLock(final String pSource) throws OceanusException {
/* Protect against exceptions */
try {
final GordianFactory myFactory = GordianGenerator.createRandomFactory(GordianFactoryType.BC);
return (GordianFactoryLock) requestPassword(pSource, true, p -> createFactoryLock(myFactory, p));
} catch (GordianException e) {
throw new PrometheusSecurityException(e);
}
}
/**
* Create a new factoryLock.
*
* @param pFactory the factory to lock
* @param pSource the description of the secured resource
* @return the factoryLock
* @throws OceanusException on error
*/
public GordianFactoryLock newFactoryLock(final GordianFactory pFactory,
final String pSource) throws OceanusException {
return (GordianFactoryLock) requestPassword(pSource, true, p -> createFactoryLock(pFactory, p));
}
/**
* Resolve the factoryLock bytes.
*
* @param pLockBytes the lock bytes to resolve
* @param pSource the description of the secured resource
* @return the factoryLock
* @throws OceanusException on error
*/
public GordianFactoryLock resolveFactoryLock(final byte[] pLockBytes,
final String pSource) throws OceanusException {
/* Look up resolved factory */
GordianFactoryLock myFactory = theCache.lookUpResolvedFactoryLock(pLockBytes);
/* If we have not seen the lock then attempt known passwords */
if (myFactory == null) {
myFactory = theCache.attemptKnownPasswordsForFactoryLock(pLockBytes);
}
/* If we have not resolved the lock */
if (myFactory == null) {
myFactory = (GordianFactoryLock) requestPassword(pSource, false, p -> resolveFactoryLock(pLockBytes, p));
}
/* Return the resolved factoryLock */
return myFactory;
}
/**
* obtain new locked factory (same password).
*
* @param pReference the reference to clone password from
* @return the similar factoryLock
* @throws OceanusException on error
*/
public GordianFactoryLock similarFactoryLock(final Object pReference) throws OceanusException {
/* Protect against exceptions */
try {
/* Create a new random factory */
final GordianFactory myFactory = GordianGenerator.createRandomFactory(GordianFactoryType.BC);
/* LookUp the password */
final ByteBuffer myPassword = theCache.lookUpResolvedPassword(pReference);
/* Create a similar factoryLock */
return theCache.createSimilarFactoryLock(myFactory, myPassword);
} catch (GordianException e) {
throw new PrometheusSecurityException(e);
}
}
/**
* Create a new keySetLock.
*
* @param pSource the description of the secured resource
* @return the keySetLock
* @throws OceanusException on error
*/
public GordianKeySetLock newKeySetLock(final String pSource) throws OceanusException {
/* Protect against exceptions */
try {
final GordianKeySet myKeySet = theFactory.getKeySetFactory().generateKeySet(theLockSpec.getKeySetSpec());
return (GordianKeySetLock) requestPassword(pSource, true, p -> createKeySetLock(myKeySet, p));
} catch (GordianException e) {
throw new PrometheusSecurityException(e);
}
}
/**
* Create a new keySetLock.
*
* @param pKeySet the keySet to lock
* @param pSource the description of the secured resource
* @return the keySetLock
* @throws OceanusException on error
*/
public GordianKeySetLock newKeySetLock(final GordianKeySet pKeySet,
final String pSource) throws OceanusException {
return (GordianKeySetLock) requestPassword(pSource, true, p -> createKeySetLock(pKeySet, p));
}
/**
* Resolve the keySetLock bytes.
*
* @param pLockBytes the lock bytes to resolve
* @param pSource the description of the secured resource
* @return the keySetLock
* @throws OceanusException on error
*/
public GordianKeySetLock resolveKeySetLock(final byte[] pLockBytes,
final String pSource) throws OceanusException {
/* Look up resolved keySet */
GordianKeySetLock myKeySet = theCache.lookUpResolvedKeySetLock(pLockBytes);
/* If we have not seen the lock then attempt known passwords */
if (myKeySet == null) {
myKeySet = theCache.attemptKnownPasswordsForKeySetLock(pLockBytes);
}
/* If we have not resolved the lock */
if (myKeySet == null) {
myKeySet = (GordianKeySetLock) requestPassword(pSource, false, p -> resolveKeySetLock(pLockBytes, p));
}
/* Return the resolved keySetLock */
return myKeySet;
}
/**
* obtain new locked keySet (same password).
*
* @param pReference the reference to clone password from
* @return the similar keySetLock
* @throws OceanusException on error
*/
public GordianKeySetLock similarKeySetLock(final Object pReference) throws OceanusException {
/* Protect against exceptions */
try {
/* Create a new random keySet */
final GordianKeySet myKeySet = theFactory.getKeySetFactory().generateKeySet(theLockSpec.getKeySetSpec());
/* LookUp the password */
final ByteBuffer myPassword = theCache.lookUpResolvedPassword(pReference);
/* Create a similar keySetLock */
return theCache.createSimilarKeySetLock(myKeySet, myPassword);
} catch (GordianException e) {
throw new PrometheusSecurityException(e);
}
}
/**
* Create a new keyPairLock.
*
* @param pKeyPair the keyPair
* @param pSource the description of the secured resource
* @return the keyPairLock
* @throws OceanusException on error
*/
public GordianKeyPairLock newKeyPairLock(final GordianKeyPair pKeyPair,
final String pSource) throws OceanusException {
return (GordianKeyPairLock) requestPassword(pSource, true, p -> createKeyPairLock(pKeyPair, p));
}
/**
* Resolve the keyPairLock.
*
* @param pLockBytes the LockBytes to resolve
* @param pKeyPair the keyPair
* @param pSource the description of the secured resource
* @return the keyPairLock
* @throws OceanusException on error
*/
public GordianKeyPairLock resolveKeyPairLock(final byte[] pLockBytes,
final GordianKeyPair pKeyPair,
final String pSource) throws OceanusException {
/* Look up resolved keySet */
GordianKeyPairLock myKeyPair = theCache.lookUpResolvedKeyPairLock(pLockBytes, pKeyPair);
/* If we have not seen the lock then attempt known passwords */
if (myKeyPair == null) {
myKeyPair = theCache.attemptKnownPasswordsForKeyPairLock(pLockBytes, pKeyPair);
}
/* If we have not resolved the lock */
if (myKeyPair == null) {
myKeyPair = (GordianKeyPairLock) requestPassword(pSource, false, p -> resolveKeyPairLock(pLockBytes, pKeyPair, p));
}
/* Return the resolved keyPairLock */
return myKeyPair;
}
/**
* obtain similar (same password) zipLock.
*
* @param pKeyPair the keyPair
* @param pReference the reference to clone password from
* @return the similar keyPairLock
* @throws OceanusException on error
*/
public GordianKeyPairLock similarKeyPairLock(final GordianKeyPair pKeyPair,
final Object pReference) throws OceanusException {
/* LookUp the password */
final ByteBuffer myPassword = theCache.lookUpResolvedPassword(pReference);
/* Create a similar keyPairLock */
return theCache.createSimilarKeyPairLock(pKeyPair, myPassword);
}
/**
* Resolve the zipLock.
*
* @param pZipLock the hash bytes to resolve
* @param pSource the description of the secured resource
* @throws OceanusException on error
*/
public void resolveZipLock(final GordianZipLock pZipLock,
final String pSource) throws OceanusException {
switch (pZipLock.getLockType()) {
case KEYSET_PASSWORD:
resolveKeySetZipLock(pZipLock, pSource);
break;
case FACTORY_PASSWORD:
resolveFactoryZipLock(pZipLock, pSource);
break;
case KEYPAIR_PASSWORD:
default:
throw new PrometheusLogicException("KeyPair zipLock not supported yet");
}
}
/**
* Resolve a keySet ZipLock.
*
* @param pZipLock the zipLock
* @param pSource the description of the secured resource
* @throws OceanusException on error
*/
private void resolveKeySetZipLock(final GordianZipLock pZipLock,
final String pSource) throws OceanusException {
/* Protect against exceptions */
try {
/* Access lockBytes */
final byte[] myLockBytes = pZipLock.getLockBytes();
/* Look up resolved keySet */
final GordianKeySetLock myLock = resolveKeySetLock(myLockBytes, pSource);
/* If we resolved the lock */
if (myLock != null) {
pZipLock.unlock(myLock);
}
} catch (GordianException e) {
throw new PrometheusSecurityException(e);
}
}
/**
* Resolve a factory ZipLock.
*
* @param pZipLock the zipLock
* @param pSource the description of the secured resource
* @throws OceanusException on error
*/
private void resolveFactoryZipLock(final GordianZipLock pZipLock,
final String pSource) throws OceanusException {
/* Protect against exceptions */
try {
/* Access lockBytes */
final byte[] myLockBytes = pZipLock.getLockBytes();
/* Look up resolved keySet */
final GordianFactoryLock myLock = resolveFactoryLock(myLockBytes, pSource);
/* If we resolved the lock */
if (myLock != null) {
pZipLock.unlock(myLock);
}
} catch (GordianException e) {
throw new PrometheusSecurityException(e);
}
}
/**
* Request password.
*
* @param pSource the description of the secured resource
* @param pNeedConfirm do we need confirmation
* @param pProcessor the password processor
* @return the keySetHash
* @throws OceanusException on error
*/
public Object requestPassword(final String pSource,
final boolean pNeedConfirm,
final PrometheusProcessPassword pProcessor) throws OceanusException {
/* Allocate variables */
Object myResult = null;
/* Create a new password dialog */
theDialog.createTheDialog(pSource, pNeedConfirm);
/* Prompt for the password */
boolean isPasswordOk = false;
char[] myPassword = null;
while (theDialog.showTheDialog()) {
try {
/* Access the password */
myPassword = theDialog.getPassword();
/* Validate the password */
final String myError = PrometheusPassCheck.validatePassword(myPassword);
if (myError != null) {
theDialog.reportBadPassword(myError);
continue;
}
/* Process the password */
theDialog.showTheSpinner(true);
myResult = pProcessor.processPassword(myPassword);
/* No exception so we are good to go */
isPasswordOk = true;
break;
} catch (GordianBadCredentialsException e) {
theDialog.reportBadPassword(NLS_ERRORPASS);
if (myPassword != null) {
Arrays.fill(myPassword, (char) 0);
}
} finally {
if (myPassword != null) {
Arrays.fill(myPassword, (char) 0);
myPassword = null;
}
}
}
/* release password resources */
theDialog.releaseDialog();
/* If we did not get a password */
if (!isPasswordOk) {
/* Throw an exception */
throw new PrometheusDataException(NLS_ERRORPASS);
}
/* Return the result */
return myResult;
}
/**
* Process password interface.
*/
@FunctionalInterface
public interface PrometheusProcessPassword {
/**
* Process password.
*
* @param pPassword the password
* @return the result
* @throws OceanusException on error
* @throws GordianBadCredentialsException if password does not match
*/
Object processPassword(char[] pPassword) throws OceanusException;
}
/**
* Create new factoryLock.
*
* @param pFactory the factory
* @param pPassword the password
* @return the new lock
* @throws OceanusException on error
*/
private GordianFactoryLock createFactoryLock(final GordianFactory pFactory,
final char[] pPassword) throws OceanusException {
/* Protect against exceptions */
try {
final GordianFactoryLock myLock = theFactory.newFactoryLock(pFactory, theLockSpec, pPassword);
theCache.addResolvedFactory(myLock, pPassword);
return myLock;
} catch (GordianException e) {
throw new PrometheusSecurityException(e);
}
}
/**
* Resolve password for factoryLock.
*
* @param pLockBytes the lock bytes
* @param pPassword the password
* @return the resolved lock
* @throws OceanusException on error
*/
private GordianFactoryLock resolveFactoryLock(final byte[] pLockBytes,
final char[] pPassword) throws OceanusException {
/* Protect against exceptions */
try {
final GordianFactoryLock myFactory = theFactory.resolveFactoryLock(pLockBytes, pPassword);
theCache.addResolvedFactory(myFactory, pPassword);
return myFactory;
} catch (GordianException e) {
throw new PrometheusSecurityException(e);
}
}
/**
* Create new keySetLock.
*
* @param pKeySet the keySet
* @param pPassword the password
* @return the new lock
* @throws OceanusException on error
*/
private GordianKeySetLock createKeySetLock(final GordianKeySet pKeySet,
final char[] pPassword) throws OceanusException {
/* Protect against exceptions */
try {
final GordianKeySetLock myLock = theLockFactory.newKeySetLock(pKeySet, theLockSpec, pPassword);
theCache.addResolvedKeySet(myLock, pPassword);
return myLock;
} catch (GordianException e) {
throw new PrometheusSecurityException(e);
}
}
/**
* Resolve password for keySetLock.
*
* @param pLockBytes the lock bytes
* @param pPassword the password
* @return the resolved lock
* @throws OceanusException on error
*/
private GordianKeySetLock resolveKeySetLock(final byte[] pLockBytes,
final char[] pPassword) throws OceanusException {
/* Protect against exceptions */
try {
final GordianKeySetLock myKeySet = theLockFactory.resolveKeySetLock(pLockBytes, pPassword);
theCache.addResolvedKeySet(myKeySet, pPassword);
return myKeySet;
} catch (GordianException e) {
throw new PrometheusSecurityException(e);
}
}
/**
* Create new keyPairLock.
*
* @param pKeyPair the keyPair
* @param pPassword the password
* @return the new lock
* @throws OceanusException on error
*/
private GordianKeyPairLock createKeyPairLock(final GordianKeyPair pKeyPair,
final char[] pPassword) throws OceanusException {
/* Protect against exceptions */
try {
final GordianKeyPairLock myLock = theLockFactory.newKeyPairLock(theLockSpec, pKeyPair, pPassword);
theCache.addResolvedKeyPair(myLock, pPassword);
return myLock;
} catch (GordianException e) {
throw new PrometheusSecurityException(e);
}
}
/**
* Resolve password for keyPairLock.
*
* @param pLockBytes the lock bytes
* @param pKeyPair the keyPair
* @param pPassword the password
* @return the resolved lock
* @throws OceanusException on error
*/
private GordianKeyPairLock resolveKeyPairLock(final byte[] pLockBytes,
final GordianKeyPair pKeyPair,
final char[] pPassword) throws OceanusException {
/* Protect against exceptions */
try {
final GordianKeyPairLock myKeyPair = theLockFactory.resolveKeyPairLock(pLockBytes, pKeyPair, pPassword);
theCache.addResolvedKeyPair(myKeyPair, pPassword);
return myKeyPair;
} catch (GordianException e) {
throw new PrometheusSecurityException(e);
}
}
/**
* Password Check enum.
*/
private enum PrometheusPassCheck {
/**
* Numeric.
*/
NUMERIC(1),
/**
* Lowercase.
*/
LOWERCASE(2),
/**
* Numeric.
*/
UPPERCASE(4),
/**
* Special.
*/
SPECIAL(8);
/**
* Text for Bad Length Error.
*/
private static final String NLS_BADLENGTH = PrometheusSecurityResource.SECURITY_BAD_PASSLEN.getValue();
/**
* Text for Invalid characters Error.
*/
private static final String NLS_BADCHAR = PrometheusSecurityResource.SECURITY_INVALID_CHARS.getValue();
/**
* Special characters.
*/
private static final String SPECIAL_CHARS = "%$^!@-_+~#&*";
/**
* Minimum password length.
*/
private static final int MINPASSLEN = 8;
/**
* The flag.
*/
private final int theFlag;
/**
* Constructor.
*
* @param pFlag the flag
*/
PrometheusPassCheck(final int pFlag) {
theFlag = pFlag;
}
/**
* Obtain the flag.
*
* @return the flag
*/
private int getFlag() {
return theFlag;
}
/**
* Check password.
*
* @param pPassword the password
* @return the error message (or null)
*/
static String validatePassword(final char[] pPassword) {
/* Password must be at least 8 characters in length */
if (pPassword.length < MINPASSLEN) {
return NLS_BADLENGTH;
}
/* Loop through the password ensuring that it has at least one of each type */
int myResult = 0;
for (char c : pPassword) {
if (Character.isDigit(c)) {
myResult |= NUMERIC.getFlag();
} else if (Character.isLowerCase(c)) {
myResult |= LOWERCASE.getFlag();
} else if (Character.isUpperCase(c)) {
myResult |= UPPERCASE.getFlag();
} else if (SPECIAL_CHARS.indexOf(c) != -1) {
myResult |= SPECIAL.getFlag();
}
}
/* If we do not have at least one of each */
if (myResult != getExpectedResult()) {
return NLS_BADCHAR;
}
return null;
}
/**
* Obtain expected result.
*
* @return the expected result
*/
private static int getExpectedResult() {
int myResult = 0;
for (PrometheusPassCheck myCheck : values()) {
myResult |= myCheck.getFlag();
}
return myResult;
}
}
}