diff --git a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/ElytronMessages.java b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/ElytronMessages.java index 38fd26b646e..9e2e1a48882 100644 --- a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/ElytronMessages.java +++ b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/ElytronMessages.java @@ -153,6 +153,19 @@ interface ElytronMessages extends BasicLogger { @Message(id = 13006, value = "Filesystem-backed realm unable to encrypt identity") RealmUnavailableException fileSystemRealmEncryptionFailed(@Cause Throwable cause); - @Message(id = 13007, value = "Filesystem-backed realm found an incompatible identity version. Requires at least version: %s") - RealmUnavailableException fileSystemRealmIncompatibleIdentityVersion(String expectedVersion); + @Message(id = 13007, value = "Signature for the following identity is invalid: %s.") + IntegrityException invalidIdentitySignature(String s); + + @Message(id = 13008, value = "Unable to create a signature for the file: %s.") + RealmUnavailableException unableToGenerateSignature(String s); + + @Message(id = 13009, value = "Unable to locate the signature element for the file: %s") + RealmUnavailableException cannotFindSignature(String s); + + @Message(id = 13010, value = "Both PrivateKey and PublicKey must be defined for realm at: %s") + IllegalArgumentException invalidKeyPairArgument(String s); + + @Message(id = 13011, value = "Integrity has not been enabled for the realm at: %s") + IllegalArgumentException integrityNotEnabled(String s); + } diff --git a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FileSystemSecurityRealm.java b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FileSystemSecurityRealm.java index 16e55db8ad7..b5f0b9486bd 100644 --- a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FileSystemSecurityRealm.java +++ b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FileSystemSecurityRealm.java @@ -29,6 +29,7 @@ import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -41,13 +42,17 @@ import java.nio.file.Path; import java.security.AccessController; import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.Principal; +import java.security.PrivateKey; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.Provider; +import java.security.PublicKey; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; @@ -65,16 +70,45 @@ import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; import java.util.function.Consumer; import java.util.function.Supplier; import javax.crypto.SecretKey; +import javax.xml.crypto.MarshalException; +import javax.xml.crypto.dsig.CanonicalizationMethod; +import javax.xml.crypto.dsig.DigestMethod; +import javax.xml.crypto.dsig.Reference; +import javax.xml.crypto.dsig.SignedInfo; +import javax.xml.crypto.dsig.Transform; +import javax.xml.crypto.dsig.XMLSignature; +import javax.xml.crypto.dsig.XMLSignatureException; +import javax.xml.crypto.dsig.XMLSignatureFactory; +import javax.xml.crypto.dsig.dom.DOMSignContext; +import javax.xml.crypto.dsig.dom.DOMValidateContext; +import javax.xml.crypto.dsig.keyinfo.KeyInfo; +import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; +import javax.xml.crypto.dsig.keyinfo.KeyValue; +import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; +import javax.xml.crypto.dsig.spec.TransformParameterSpec; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.wildfly.common.Assert; import org.wildfly.common.bytes.ByteStringBuilder; import org.wildfly.common.codec.Base32Alphabet; @@ -108,6 +142,7 @@ import org.wildfly.security.password.spec.PasswordSpec; import org.wildfly.security.password.util.ModularCrypt; import org.wildfly.security.permission.ElytronPermission; +import org.xml.sax.SAXException; /** * A simple filesystem-backed security realm. @@ -125,7 +160,8 @@ private enum Version { VERSION_1_0("urn:elytron:1.0", null), VERSION_1_0_1("urn:elytron:1.0.1", VERSION_1_0), - VERSION_1_1("urn:elytron:identity:1.1", VERSION_1_0_1); + VERSION_1_1("urn:elytron:identity:1.1", VERSION_1_0_1), + VERSION_1_2("urn:elytron:identity:1.2", VERSION_1_1); final String namespace; @@ -164,7 +200,8 @@ boolean isAtLeast(Version version) { private final Charset hashCharset; private final Encoding hashEncoding; private final SecretKey secretKey; - + private final PrivateKey privateKey; + private final PublicKey publicKey; private final ConcurrentHashMap realmIdentityLocks = new ConcurrentHashMap<>(); /** @@ -188,8 +225,11 @@ public static FileSystemSecurityRealmBuilder builder() { * @param hashEncoding the string format for the hashed passwords. Uses Base64 by default. * @param providers The providers supplier * @param secretKey the SecretKey used to encrypt and decrypt the security realm (if {@code null}, the security realm will be unencrypted) + * @param privateKey the PrivateKey used to verify the integrity of the security realm (if {@code null}, the security realm will not verify integrity) + * @param publicKey the PublicKey used to verify the integrity of the security realm (if {@code null}, the security realm will not verify integrity) + * */ - public FileSystemSecurityRealm(final Path root, final NameRewriter nameRewriter, final int levels, final boolean encoded, final Encoding hashEncoding, final Charset hashCharset, final Supplier providers, final SecretKey secretKey) { + public FileSystemSecurityRealm(final Path root, final NameRewriter nameRewriter, final int levels, final boolean encoded, final Encoding hashEncoding, final Charset hashCharset, final Supplier providers, final SecretKey secretKey, final PrivateKey privateKey, final PublicKey publicKey) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(CREATE_SECURITY_REALM); @@ -202,6 +242,26 @@ public FileSystemSecurityRealm(final Path root, final NameRewriter nameRewriter, this.hashEncoding = hashEncoding != null ? hashEncoding : Encoding.BASE64; this.providers = providers != null ? providers : INSTALLED_PROVIDERS; this.secretKey = secretKey; + this.privateKey = privateKey; + this.publicKey = publicKey; + + } + + /** + * Construct a new instance. + * + * Construction with enabled security manager requires {@code createSecurityRealm} {@link ElytronPermission}. + * + * @param root the root path of the identity store + * @param nameRewriter the name rewriter to apply to looked up names + * @param levels the number of levels of directory hashing to apply + * @param encoded whether identity names should be BASE32 encoded before using as filename + * @param hashCharset the character set to use when converting password strings to a byte array. Uses UTF-8 by default. + * @param hashEncoding the string format for the hashed passwords. Uses Base64 by default. + * @param secretKey the SecretKey used to encrypt and decrypt the security realm (if {@code null}, the security realm will be unencrypted) + */ + public FileSystemSecurityRealm(final Path root, final NameRewriter nameRewriter, final int levels, final boolean encoded, final Encoding hashEncoding, final Charset hashCharset, final SecretKey secretKey) { + this(root, nameRewriter, levels, encoded, hashEncoding, hashCharset, INSTALLED_PROVIDERS, secretKey, null, null); } /** @@ -217,7 +277,7 @@ public FileSystemSecurityRealm(final Path root, final NameRewriter nameRewriter, * @param hashEncoding the string format for the hashed passwords. Uses Base64 by default. */ public FileSystemSecurityRealm(final Path root, final NameRewriter nameRewriter, final int levels, final boolean encoded, final Encoding hashEncoding, final Charset hashCharset) { - this(root, nameRewriter, levels, encoded, hashEncoding, hashCharset, INSTALLED_PROVIDERS, null); + this(root, nameRewriter, levels, encoded, hashEncoding, hashCharset, INSTALLED_PROVIDERS, null, null, null); } /** @@ -231,7 +291,7 @@ public FileSystemSecurityRealm(final Path root, final NameRewriter nameRewriter, * @param encoded whether identity names should by BASE32 encoded before using as filename */ public FileSystemSecurityRealm(final Path root, final NameRewriter nameRewriter, final int levels, final boolean encoded) { - this(root, nameRewriter, levels, encoded, Encoding.BASE64, StandardCharsets.UTF_8, INSTALLED_PROVIDERS, null); + this(root, nameRewriter, levels, encoded, Encoding.BASE64, StandardCharsets.UTF_8, INSTALLED_PROVIDERS, null, null, null); } /** @@ -255,7 +315,7 @@ public FileSystemSecurityRealm(final Path root, final NameRewriter nameRewriter, * @param hashCharset the character set to use when converting password strings to a byte array. Uses UTF-8 by default and must not be {@code null}. */ public FileSystemSecurityRealm(final Path root, final NameRewriter nameRewriter, final int levels, final Encoding hashEncoding, final Charset hashCharset) { - this(root, nameRewriter, levels, true, hashEncoding, hashCharset, INSTALLED_PROVIDERS, null); + this(root, nameRewriter, levels, true, hashEncoding, hashCharset, INSTALLED_PROVIDERS, null, null, null); } @@ -280,7 +340,7 @@ public FileSystemSecurityRealm(final Path root, final int levels) { * @param hashCharset the character set to use when converting password strings to a byte array. Uses UTF-8 by default and must not be {@code null}. */ public FileSystemSecurityRealm(final Path root, final int levels, final Encoding hashEncoding, final Charset hashCharset) { - this(root, NameRewriter.IDENTITY_REWRITER, levels, true, hashEncoding, hashCharset, INSTALLED_PROVIDERS, null); + this(root, NameRewriter.IDENTITY_REWRITER, levels, true, hashEncoding, hashCharset, INSTALLED_PROVIDERS, null, null, null); } /** @@ -300,13 +360,16 @@ public FileSystemSecurityRealm(final Path root) { * @param hashCharset the character set to use when converting password strings to a byte array. Uses UTF-8 by default and must not be {@code null} */ public FileSystemSecurityRealm(final Path root, final Encoding hashEncoding, final Charset hashCharset) { - this(root, NameRewriter.IDENTITY_REWRITER, 2, true, hashEncoding, hashCharset, INSTALLED_PROVIDERS, null); + this(root, NameRewriter.IDENTITY_REWRITER, 2, true, hashEncoding, hashCharset, INSTALLED_PROVIDERS, null, null, null); } public FileSystemSecurityRealm(Path root, int levels, Supplier providers) { - this(root, NameRewriter.IDENTITY_REWRITER, levels, true, Encoding.BASE64, StandardCharsets.UTF_8, providers, null); + this(root, NameRewriter.IDENTITY_REWRITER, levels, true, Encoding.BASE64, StandardCharsets.UTF_8, providers, null, null, null); } + public boolean hasIntegrityEnabled() { + return privateKey != null && publicKey != null; + } private Path pathFor(String name) { assert name.codePointCount(0, name.length()) > 0; String normalizedName = name; @@ -385,7 +448,7 @@ private ModifiableRealmIdentity getRealmIdentity(final String name, final boolea } else { lock = realmIdentityLock.lockShared(); } - return new Identity(finalName, pathFor(finalName), lock, hashCharset, hashEncoding, providers, secretKey); + return new Identity(finalName, pathFor(finalName), lock, hashCharset, hashEncoding, providers, secretKey, privateKey, publicKey, hasIntegrityEnabled()); } public ModifiableRealmIdentityIterator getRealmIdentityIterator() throws RealmUnavailableException { @@ -512,6 +575,50 @@ interface CredentialParseFunction { void parseCredential(String algorithm, String format, String body) throws RealmUnavailableException, XMLStreamException; } + /** + * Re-generate the signatures for all the identities in this realm. + * This method is intended to be called after updating the key pair used by this realm. + * + * @throws RealmUnavailableException if the realm is not able to handle requests for any reason + */ + public void updateRealmKeyPair() throws RealmUnavailableException { + if (! hasIntegrityEnabled()) { + throw ElytronMessages.log.integrityNotEnabled(root.toString()); + } + ModifiableRealmIdentityIterator realmIterator = this.getRealmIdentityIterator(); + while (realmIterator.hasNext()) { + Identity identity = (Identity) realmIterator.next(); + try{ + identity.writeDigitalSignature(identity.path, identity.name); + } finally { + identity.dispose(); + } + } + realmIterator.close(); + } + + /** + * Verify the integrity of each identity file in this realm. + * @return {@code true} if the integrity of all the identity files in the realm is successfully verified and {@code false} otherwise + * + */ + public boolean verifyRealmIntegrity() throws RealmUnavailableException { + if (! hasIntegrityEnabled()) { + throw ElytronMessages.log.integrityNotEnabled(root.toString()); + } + ModifiableRealmIdentityIterator realmIterator = this.getRealmIdentityIterator(); + while (realmIterator.hasNext()) { + Identity identity = (Identity) realmIterator.next(); + if(! identity.verifyIntegrity()) { + identity.dispose(); + return false; + } + identity.dispose(); + } + realmIterator.close(); + return true; + } + static class Identity implements ModifiableRealmIdentity { private static final String ENCRYPTION_FORMAT = "enc_base64"; @@ -522,13 +629,16 @@ static class Identity implements ModifiableRealmIdentity { private final String name; private final Path path; - private Supplier providers; + private final Supplier providers; private IdentityLock lock; private final Charset hashCharset; private final Encoding hashEncoding; private final SecretKey secretKey; + private final PrivateKey privateKey; + private final PublicKey publicKey; + private final boolean integrityEnabled; - Identity(final String name, final Path path, final IdentityLock lock, final Charset hashCharset, final Encoding hashEncoding, Supplier providers, final SecretKey secretKey) { + Identity(final String name, final Path path, final IdentityLock lock, final Charset hashCharset, final Encoding hashEncoding, Supplier providers, final SecretKey secretKey, final PrivateKey privateKey, final PublicKey publicKey, final boolean integrityEnabled) { this.name = name; this.path = path; this.lock = lock; @@ -536,6 +646,9 @@ static class Identity implements ModifiableRealmIdentity { this.hashEncoding = hashEncoding; this.providers = providers; this.secretKey = secretKey; + this.privateKey = privateKey; + this.publicKey = publicKey; + this.integrityEnabled = integrityEnabled; } public Principal getRealmIdentityPrincipal() { @@ -589,12 +702,25 @@ public boolean verifyEvidence(final Evidence evidence) throws RealmUnavailableEx Assert.checkNotNullParam("evidence", evidence); if (ElytronMessages.log.isTraceEnabled()) { - final LoadedIdentity loadedIdentity = loadIdentity(false, true); - ElytronMessages.log.tracef("Trying to authenticate identity %s using FileSystemSecurityRealm", - (loadedIdentity != null) ? loadedIdentity.getName() : "null"); + try { + final LoadedIdentity loadedIdentity = loadIdentity(false, true); + ElytronMessages.log.tracef("Trying to authenticate identity %s using FileSystemSecurityRealm", (loadedIdentity != null) ? loadedIdentity.getName() : "null"); + } catch (RealmUnavailableException e) { + if (e.getCause() instanceof IntegrityException) { + return false; + } + throw e; + } + } + List credentials = null; + try { + credentials = loadCredentials(); + } catch (RealmUnavailableException e) { + if (e.getCause() instanceof IntegrityException) { + return false; + } + throw e; } - - List credentials = loadCredentials(); ElytronMessages.log.tracef("FileSystemSecurityRealm - verification evidence [%s] against [%d] credentials...", evidence, credentials.size()); for (Credential credential : credentials) { if (credential.canVerify(evidence)) { @@ -694,17 +820,39 @@ private Void createPrivileged() throws RealmUnavailableException { final XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory(); try (OutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(tempPath, WRITE, CREATE_NEW, DSYNC))) { try (AutoCloseableXMLStreamWriterHolder holder = new AutoCloseableXMLStreamWriterHolder(xmlOutputFactory.createXMLStreamWriter(outputStream))) { + String namespace = ""; + if (integrityEnabled) { + namespace = Version.VERSION_1_2.getNamespace(); + } else if (secretKey != null) { + namespace = Version.VERSION_1_1.getNamespace(); + } else { + namespace = Version.VERSION_1_0.getNamespace(); + } final XMLStreamWriter streamWriter = holder.getXmlStreamWriter(); // create empty identity streamWriter.writeStartDocument(); streamWriter.writeCharacters("\n"); streamWriter.writeStartElement("identity"); - streamWriter.writeDefaultNamespace(secretKey != null ? Version.VERSION_1_1.getNamespace() : Version.VERSION_1_0.getNamespace()); + streamWriter.writeDefaultNamespace(namespace); + if (integrityEnabled) { + streamWriter.writeCharacters("\n "); + streamWriter.writeStartElement("principal"); + streamWriter.writeAttribute("name", secretKey != null ? CipherUtil.encrypt(name, secretKey) : name); + streamWriter.writeEndElement(); + streamWriter.writeCharacters("\n "); + } streamWriter.writeEndElement(); streamWriter.writeEndDocument(); - } catch (XMLStreamException e) { + } catch (XMLStreamException | GeneralSecurityException e) { throw ElytronMessages.log.fileSystemRealmFailedToWrite(tempPath, name, e); } + if(integrityEnabled) { + try { + writeDigitalSignature(tempPath, this.name); + } catch (RealmUnavailableException e) { + throw ElytronMessages.log.unableToGenerateSignature(path.toString()); + } + } } catch (FileAlreadyExistsException ignored) { // try a new name continue; @@ -778,6 +926,9 @@ private void replaceIdentity(final LoadedIdentity newIdentity) throws RealmUnava } private Void replaceIdentityPrivileged(final LoadedIdentity newIdentity) throws RealmUnavailableException { + if (!verifyIntegrity()) { + throw new RealmUnavailableException(ElytronMessages.log.invalidIdentitySignature(name)); + } for (;;) { final Path tempPath = tempPath(); try { @@ -790,6 +941,13 @@ private Void replaceIdentityPrivileged(final LoadedIdentity newIdentity) throws } catch (GeneralSecurityException e) { throw ElytronMessages.log.fileSystemRealmEncryptionFailed(e); } + if (integrityEnabled) { + try { + writeDigitalSignature(tempPath, name); + } catch (RealmUnavailableException e) { + throw ElytronMessages.log.unableToGenerateSignature(path.toString()); + } + } } catch (FileAlreadyExistsException ignored) { // try a new name continue; @@ -840,22 +998,28 @@ private Version requiredVersion(final LoadedIdentity identityToWrite) { // if new functionality is used then use the required schema version otherwise fallback // to an older version. - if (secretKey != null) { + if (integrityEnabled) { + return Version.VERSION_1_2; + } else if (secretKey != null) { return Version.VERSION_1_1; + } else { + return Version.VERSION_1_0; } - // We would not require 1.0.1 as no realm specific changed were made. - //return Version.VERSION_1_0_1; - - return Version.VERSION_1_0; } private void writeIdentity(final XMLStreamWriter streamWriter, final LoadedIdentity newIdentity) throws XMLStreamException, InvalidKeySpecException, NoSuchAlgorithmException, GeneralSecurityException { streamWriter.writeStartDocument(); streamWriter.writeCharacters("\n"); streamWriter.writeStartElement("identity"); - streamWriter.writeDefaultNamespace(requiredVersion(newIdentity).getNamespace()); + if (integrityEnabled) { + streamWriter.writeCharacters("\n "); + streamWriter.writeStartElement("principal"); + streamWriter.writeAttribute("name", secretKey != null ? CipherUtil.encrypt(name, secretKey) : name); + streamWriter.writeEndElement(); + } + if (newIdentity.getCredentials().size() > 0) { streamWriter.writeCharacters("\n "); streamWriter.writeStartElement("credentials"); @@ -921,7 +1085,9 @@ private void writeIdentity(final XMLStreamWriter streamWriter, final LoadedIdent } while (entryIter.hasNext()); streamWriter.writeCharacters("\n "); streamWriter.writeEndElement(); + streamWriter.writeCharacters("\n"); } + streamWriter.writeCharacters("\n "); streamWriter.writeEndElement(); streamWriter.writeEndDocument(); } @@ -955,6 +1121,9 @@ private LoadedIdentity loadIdentity(final boolean skipCredentials, final boolean } protected LoadedIdentity loadIdentityPrivileged(final boolean skipCredentials, final boolean skipAttributes) throws RealmUnavailableException { + if (!verifyIntegrity()) { + throw new RealmUnavailableException(ElytronMessages.log.invalidIdentitySignature(name)); + } try (InputStream inputStream = Files.newInputStream(path, READ)) { final XMLInputFactory inputFactory = XMLInputFactory.newFactory(); inputFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE); @@ -999,16 +1168,25 @@ private LoadedIdentity parseIdentityContents(final XMLStreamReader streamReader, for (;;) { if (streamReader.isEndElement()) { if (attributes == Attributes.EMPTY && !skipAttributes) { - //Since this could be a use-case wanting to modify the attributes, make sure that we have a - //modifiable version of Attributes; + // Since this could be a use-case wanting to modify the attributes, make sure that we have a + // modifiable version of Attributes; attributes = new MapAttributes(); } return new LoadedIdentity(name, credentials, attributes, hashEncoding); } - if (! version.getNamespace().equals(streamReader.getNamespaceURI())) { + if (!(version.getNamespace().equals(streamReader.getNamespaceURI())) && !(XMLSignature.XMLNS.equals(streamReader.getNamespaceURI()))) { // Mixed versions unsupported. throw ElytronMessages.log.fileSystemRealmInvalidContent(path, streamReader.getLocation().getLineNumber(), name); } + + if ("principal".equals(streamReader.getLocalName())) { + if (version.isAtLeast(Version.VERSION_1_2)) { + consumeContent(streamReader); + } else { + throw ElytronMessages.log.fileSystemRealmInvalidContent(path, streamReader.getLocation().getLineNumber(), name); + } + } + if (! gotCredentials && "credentials".equals(streamReader.getLocalName())) { gotCredentials = true; if (skipCredentials) { @@ -1294,6 +1472,124 @@ private void consumeContent(final XMLStreamReader reader) throws XMLStreamExcept } } + private boolean verifyIntegrity() { + if (this.publicKey != null) { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + Document doc; + try { + doc = dbf.newDocumentBuilder().parse(path.toString()); + } catch (SAXException | IOException | ParserConfigurationException e) { + return false; + } + return (validatePrincipalName(doc) && validateDigitalSignature(doc)); + } + return true; + } + + // Process for updating identity: + // 1. Validate current identity digital signature + // 2. Update identity with new data + // 3. Create new digital signature + private boolean validateDigitalSignature(Document doc) { + try { + NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); + if (nl.getLength() == 0) { + throw ElytronMessages.log.cannotFindSignature(path.toString()); + } + XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); + DOMValidateContext valContext = new DOMValidateContext(publicKey, nl.item(0)); + XMLSignature signature = fac.unmarshalXMLSignature(valContext); + boolean coreValidity = signature.validate(valContext); + ElytronMessages.log.tracef("FileSystemSecurityRealm - verification against signature for credential [%s] = %b", name, coreValidity); + return coreValidity; + } catch (IOException | MarshalException | XMLSignatureException e) { + ElytronMessages.log.tracef("FileSystemSecurityRealm - Error during verification. Signature for credential [%s] failed", name); + return false; + } + } + + private boolean validatePrincipalName(Document doc) { + NodeList nl = doc.getElementsByTagName("principal"); + if (nl.getLength() == 0) { + ElytronMessages.log.tracef("FileSystemSecurityRealm - verification against principal for credential [%s] = %b", name, false); + return false; + } + String principalName = nl.item(0).getAttributes().getNamedItem("name").getNodeValue(); + if (secretKey != null) { + try { + principalName = CipherUtil.decrypt(principalName, secretKey); + } catch (GeneralSecurityException e) { + ElytronMessages.log.tracef("FileSystemSecurityRealm - verification against principal for credential [%s] = %b", name, false); + return false; + } + } + boolean validity = Objects.equals(principalName, name); + ElytronMessages.log.tracef("FileSystemSecurityRealm - verification against principal for credential [%s] = %b", name, validity); + return validity; + } + + private void writeDigitalSignature(Path path, String name) throws RealmUnavailableException { + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + DocumentBuilder builder = dbf.newDocumentBuilder(); + Document doc = builder.parse(Files.newInputStream(path)); + Element elem = doc.getDocumentElement(); + NodeList signatureNode = doc.getElementsByTagName("Signature"); + if (signatureNode.getLength() > 0) { + Node sig = signatureNode.item(0); + elem.removeChild(sig); + } + DOMSignContext dsc = new DOMSignContext(this.privateKey, elem); + XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); + Reference ref = fac.newReference + ("", fac.newDigestMethod(DigestMethod.SHA256, null), + Collections.singletonList + (fac.newTransform(Transform.ENVELOPED, + (TransformParameterSpec) null)), null, null); + String signatureMethod = ""; + // https://issues.redhat.com/browse/ELY-2346 + // Once JDK 8 support is removed use the javax.xml.crypto.dsig.SignatureMethod to set these signatureMethods + switch (this.publicKey.getAlgorithm()) { + case "DSA": + signatureMethod = "http://www.w3.org/2009/xmldsig11#dsa-sha256"; + break; + case "RSA": + signatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"; + break; + case "HMAC": + signatureMethod = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"; + break; + case "EC": + signatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"; + break; + } + SignedInfo si = fac.newSignedInfo + (fac.newCanonicalizationMethod + (CanonicalizationMethod.INCLUSIVE, + (C14NMethodParameterSpec) null), + fac.newSignatureMethod(signatureMethod, null), + Collections.singletonList(ref)); + KeyInfoFactory kif = fac.getKeyInfoFactory(); + KeyValue kv = kif.newKeyValue(this.publicKey); + KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv)); + XMLSignature signature = fac.newXMLSignature(si, ki); + signature.sign(dsc); + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + DOMSource source = new DOMSource(doc); + FileWriter writer = new FileWriter(String.valueOf(path)); + StreamResult result = new StreamResult(writer); + transformer.transform(source, result); + ElytronMessages.log.tracef("FileSystemSecurityRealm - signature against file updated [%s]", name); + writer.close(); + } catch (ParserConfigurationException | IOException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | + KeyException | XMLSignatureException | MarshalException | TransformerException | SAXException e) { + ElytronMessages.log.tracef("FileSystemSecurityRealm - Error during signature generation against identity [%s]", name); + throw ElytronMessages.log.unableToGenerateSignature(String.valueOf(this.path)); + } + } } protected static final class LoadedIdentity { diff --git a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FileSystemSecurityRealmBuilder.java b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FileSystemSecurityRealmBuilder.java index b0839aad96e..1694a67ba4c 100644 --- a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FileSystemSecurityRealmBuilder.java +++ b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FileSystemSecurityRealmBuilder.java @@ -20,7 +20,9 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.security.PrivateKey; import java.security.Provider; +import java.security.PublicKey; import java.util.function.Supplier; import javax.crypto.SecretKey; @@ -44,6 +46,8 @@ public class FileSystemSecurityRealmBuilder { private Charset hashCharset; private Encoding hashEncoding; private SecretKey secretKey; + private PrivateKey privateKey; + private PublicKey publicKey; private Supplier providers; FileSystemSecurityRealmBuilder() { @@ -132,12 +136,42 @@ public FileSystemSecurityRealmBuilder setSecretKey(final SecretKey secretKey) { return this; } + /** + * Set the providers to be used by the realm. + * + * @param providers the provider to be used (must not be {@code null}) + * @return this builder. + */ public FileSystemSecurityRealmBuilder setProviders(final Supplier providers) { Assert.checkNotNullParam("providers", providers); this.providers = providers; return this; } + /** + * Set the PrivateKey to be used by the realm. + * + * @param privateKey the asymmetric PrivateKey used to sign the identity files used for file integrity (must not be {@code null}) + * @return this builder. + */ + public FileSystemSecurityRealmBuilder setPrivateKey(final PrivateKey privateKey) { + Assert.checkNotNullParam("privateKey", privateKey); + this.privateKey = privateKey; + return this; + } + + /** + * Set the PublicKey to be used by the realm. + * + * @param publicKey the asymmetric PublicKey used to verify the identity files used for file integrity (must not be {@code null}) + * @return this builder. + */ + public FileSystemSecurityRealmBuilder setPublicKey(final PublicKey publicKey) { + Assert.checkNotNullParam("publicKey", publicKey); + this.publicKey = publicKey; + return this; + } + /** * Builds a new {@link FileSystemSecurityRealm} instance based on configuration defined for this {@link FileSystemSecurityRealmBuilder} instance. * @@ -154,6 +188,10 @@ public FileSystemSecurityRealm build() { if (hashCharset == null) { hashCharset = StandardCharsets.UTF_8; } - return new FileSystemSecurityRealm(root, nameRewriter, levels, encoded, hashEncoding, hashCharset, providers, secretKey); + if (privateKey == null ^ publicKey == null) { + throw ElytronMessages.log.invalidKeyPairArgument(root.toString()); + } + + return new FileSystemSecurityRealm(root, nameRewriter, levels, encoded, hashEncoding, hashCharset, providers, secretKey, privateKey, publicKey); } } diff --git a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/IntegrityException.java b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/IntegrityException.java new file mode 100644 index 00000000000..4548ebf43b5 --- /dev/null +++ b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/IntegrityException.java @@ -0,0 +1,71 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2022 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * 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 org.wildfly.security.auth.realm; + +import java.io.IOException; + +/** + * Exception to indicate a general failure related to the Integrity Verification of the Filesystem Realm. + * + * @author Ashpan Raskar + */ +public class IntegrityException extends IOException { + + + private static final long serialVersionUID = 8889252552074803941L; + + /** + * Constructs a new {@code IntegrityException} instance. The message is left blank ({@code null}), and no + * cause is specified. + */ + public IntegrityException() { + } + + /** + * Constructs a new {@code IntegrityException} instance with an initial message. No cause is specified. + * + * @param msg the message + */ + public IntegrityException(final String msg) { + super(msg); + } + + /** + * Constructs a new {@code IntegrityException} instance with an initial cause. If a non-{@code null} cause + * is specified, its message is used to initialize the message of this {@code IntegrityException}; otherwise + * the message is left blank ({@code null}). + * + * @param cause the cause + */ + public IntegrityException(final Throwable cause) { + super(cause); + } + + /** + * Constructs a new {@code IntegrityException} instance with an initial message and cause. + * + * @param msg the message + * @param cause the cause + */ + public IntegrityException(final String msg, final Throwable cause) { + super(msg, cause); + } + +} + diff --git a/auth/realm/base/src/main/resources/schema/elytron-identity-1_2.xsd b/auth/realm/base/src/main/resources/schema/elytron-identity-1_2.xsd index df7dba36f22..e2cfd61649e 100644 --- a/auth/realm/base/src/main/resources/schema/elytron-identity-1_2.xsd +++ b/auth/realm/base/src/main/resources/schema/elytron-identity-1_2.xsd @@ -21,6 +21,7 @@ @@ -28,11 +29,14 @@ + + + @@ -144,4 +148,5 @@ + diff --git a/pom.xml b/pom.xml index 215288b514b..e750d5ada3b 100644 --- a/pom.xml +++ b/pom.xml @@ -103,6 +103,7 @@ 17.0.0 4.3.3 2.40.0 + 2.3.0 INFO @@ -1028,7 +1029,6 @@ ${version.org.bitbucket.b_c.jose4j} - diff --git a/tests/base/src/test/java/org/wildfly/security/auth/FileSystemSecurityRealmTest.java b/tests/base/src/test/java/org/wildfly/security/auth/FileSystemSecurityRealmTest.java index 067d3aeab7c..a4efb640a33 100644 --- a/tests/base/src/test/java/org/wildfly/security/auth/FileSystemSecurityRealmTest.java +++ b/tests/base/src/test/java/org/wildfly/security/auth/FileSystemSecurityRealmTest.java @@ -21,12 +21,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.wildfly.security.auth.server.ServerUtils.ELYTRON_PASSWORD_PROVIDERS; import static org.wildfly.security.password.interfaces.BCryptPassword.BCRYPT_SALT_SIZE; import java.io.File; import java.io.IOException; +import java.io.PrintWriter; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; @@ -36,6 +38,10 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -44,14 +50,25 @@ import java.util.concurrent.ThreadLocalRandom; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import javax.xml.crypto.MarshalException; +import javax.xml.crypto.dsig.XMLSignature; +import javax.xml.crypto.dsig.XMLSignatureException; +import javax.xml.crypto.dsig.XMLSignatureFactory; +import javax.xml.crypto.dsig.dom.DOMValidateContext; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import org.junit.Assert; import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; import org.wildfly.common.iteration.CodePointIterator; import org.wildfly.security.auth.principal.NamePrincipal; import org.wildfly.security.auth.realm.FileSystemSecurityRealm; +import org.wildfly.security.auth.realm.FileSystemSecurityRealmBuilder; import org.wildfly.security.auth.server.ModifiableRealmIdentity; import org.wildfly.security.auth.server.ModifiableRealmIdentityIterator; import org.wildfly.security.auth.server.NameRewriter; +import org.wildfly.security.auth.server.RealmUnavailableException; import org.wildfly.security.authz.Attributes; import org.wildfly.security.authz.AuthorizationIdentity; import org.wildfly.security.authz.MapAttributes; @@ -83,12 +100,14 @@ // has dependency on wildfly-elytron-realm, wildfly-elytron-auth-server, wildfly-elytron-credential public class FileSystemSecurityRealmTest { -// private static final Provider provider = WildFlyElytronPasswordProvider.getInstance(); - public FileSystemSecurityRealmTest() throws GeneralSecurityException { } - private final SecretKey key = SecretKeyUtil.generateSecretKey(128); + SecretKey secretKey = SecretKeyUtil.generateSecretKey(128); + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + KeyPair pair = keyPairGen.generateKeyPair(); + PrivateKey privateKey = pair.getPrivate(); + PublicKey publicKey = pair.getPublic(); @Test public void testCreateIdentityWithNoLevels() throws Exception { @@ -102,7 +121,6 @@ public void testCreateIdentityWithNoLevels() throws Exception { assertTrue(identity.exists()); } - @Test public void testCreateIdentityWithLevels() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 3, ELYTRON_PASSWORD_PROVIDERS); @@ -119,8 +137,25 @@ public void testCreateIdentityWithLevelsEncryption() throws Exception { FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() .setRoot(getRootPath()) .setLevels(3) - .setSecretKey(key) .setProviders(ELYTRON_PASSWORD_PROVIDERS) + .setSecretKey(secretKey) + .build(); + ModifiableRealmIdentity identity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + assertFalse(identity.exists()); + identity.create(); + + assertTrue(identity.exists()); + identity.dispose(); + } + + @Test + public void testCreateIdentityWithLevelsIntegrity() throws Exception { + FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() + .setRoot(getRootPath()) + .setLevels(3) + .setProviders(ELYTRON_PASSWORD_PROVIDERS) + .setPrivateKey(privateKey) + .setPublicKey(publicKey) .build(); ModifiableRealmIdentity identity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); assertFalse(identity.exists()); @@ -145,218 +180,142 @@ public void testCreateAndLoadIdentity() throws Exception { @Test public void testCreateAndLoadIdentityEncryption() throws Exception { - FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath()) - .setLevels(3) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); + FileSystemSecurityRealm securityRealm = getBuilder(secretKey).build(); ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); newIdentity.create(); newIdentity.dispose(); - securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath(false)) - .setLevels(3) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); assertTrue(existingIdentity.exists()); existingIdentity.dispose(); } @Test - public void testShortUsername() throws Exception { - FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 3, ELYTRON_PASSWORD_PROVIDERS); - ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("p")); + public void testCreateAndLoadIdentityIntegrity() throws Exception { + FileSystemSecurityRealm securityRealm = getBuilder(privateKey, publicKey).build(); + ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); newIdentity.create(); - newIdentity.dispose(); - ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("p")); + ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); assertTrue(existingIdentity.exists()); existingIdentity.dispose(); } @Test - public void testShortUsernameEncryption() throws Exception { + public void testInvalidSignature() throws Exception { FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath()) + .setRoot(Paths.get("./target/test-classes/filesystem-realm-exists/")) .setLevels(3) - .setSecretKey(key) + .setPublicKey(publicKey) + .setPrivateKey(privateKey) .setProviders(ELYTRON_PASSWORD_PROVIDERS) .build(); - ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("p")); - newIdentity.create(); - - newIdentity.dispose(); + ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("user")); + char[] actualPassword = "secretPassword".toCharArray(); + assertFalse(existingIdentity.verifyEvidence(new PasswordGuessEvidence(actualPassword))); + existingIdentity.dispose(); + } - ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("p")); - assertTrue(existingIdentity.exists()); + @Test + public void testInvalidIdentityVersion() throws Exception { + FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() + .setRoot(Paths.get("./target/test-classes/filesystem-realm-exists/")) + .setLevels(3) + .setProviders(ELYTRON_PASSWORD_PROVIDERS) + .build(); + ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("user2")); + MapAttributes newAttributes = new MapAttributes(); + newAttributes.addFirst("name", "plainUser"); + newAttributes.addAll("roles", Arrays.asList("Employee", "Manager", "Admin")); + assertThrows(RealmUnavailableException.class, () -> { + existingIdentity.setAttributes(newAttributes); + }); existingIdentity.dispose(); } + @Test + public void testShortUsername() throws Exception { + FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 3, ELYTRON_PASSWORD_PROVIDERS); + shortUsername(securityRealm); + } + + @Test + public void testShortUsernameEncryption() throws Exception { + FileSystemSecurityRealm securityRealm = getBuilder(secretKey).build(); + shortUsername(securityRealm); + } + + @Test + public void testShortUsernameIntegrity() throws Exception { + FileSystemSecurityRealm securityRealm = getBuilder(privateKey, publicKey).build(); + shortUsername(securityRealm); + } + @Test public void testSpecialCharacters() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 3, ELYTRON_PASSWORD_PROVIDERS); - ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("special*.\"/\\[]:;|=,用戶 ")); - newIdentity.create(); - newIdentity.dispose(); + specialCharacters(securityRealm); + } - ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("special*.\"/\\[]:;|=,用戶 ")); - assertTrue(existingIdentity.exists()); - existingIdentity.dispose(); + @Test + public void testSpecialCharactersEncryption() throws Exception { + FileSystemSecurityRealm securityRealm = getBuilder(secretKey).build(); + specialCharacters(securityRealm); + } + + @Test + public void testSpecialCharactersIntegrity() throws Exception { + FileSystemSecurityRealm securityRealm = getBuilder(privateKey, publicKey).build(); + specialCharacters(securityRealm); } @Test public void testCaseSensitive() throws Exception { FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 3, ELYTRON_PASSWORD_PROVIDERS); - ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - newIdentity.create(); - assertTrue(newIdentity.exists()); - newIdentity.dispose(); - - ModifiableRealmIdentity differentIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("PLAINUSER")); - assertFalse(differentIdentity.exists()); - differentIdentity.dispose(); + caseSensitive(securityRealm); } @Test public void testCaseSensitiveEncryption() throws Exception { - FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath()) - .setLevels(3) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); - ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - newIdentity.create(); - assertTrue(newIdentity.exists()); - newIdentity.dispose(); + FileSystemSecurityRealm securityRealm = getBuilder(privateKey, publicKey).build(); + caseSensitive(securityRealm); + } - ModifiableRealmIdentity differentIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("PLAINUSER")); - assertFalse(differentIdentity.exists()); - differentIdentity.dispose(); + @Test + public void testCaseSensitiveIntegrity() throws Exception { + FileSystemSecurityRealm securityRealm = getBuilder(privateKey, publicKey).build(); + caseSensitive(securityRealm); } @Test public void testCreateAndLoadAndDeleteIdentity() throws Exception { - FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 3, ELYTRON_PASSWORD_PROVIDERS); - ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - newIdentity.create(); - newIdentity.dispose(); - - securityRealm = new FileSystemSecurityRealm(getRootPath(false), 3, ELYTRON_PASSWORD_PROVIDERS); - ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - assertTrue(existingIdentity.exists()); - existingIdentity.delete(); - assertFalse(existingIdentity.exists()); - existingIdentity.dispose(); - - securityRealm = new FileSystemSecurityRealm(getRootPath(false), 3, ELYTRON_PASSWORD_PROVIDERS); - existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - assertFalse(existingIdentity.exists()); - existingIdentity.dispose(); + createAndLoadAndDeleteIdentity(null, null, null); } @Test public void testCreateAndLoadAndDeleteIdentityEncryption() throws Exception { - FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath()) - .setLevels(3) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); - ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - newIdentity.create(); - newIdentity.dispose(); - - securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath(false)) - .setLevels(3) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); - ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - assertTrue(existingIdentity.exists()); - existingIdentity.delete(); - assertFalse(existingIdentity.exists()); - existingIdentity.dispose(); + createAndLoadAndDeleteIdentity(secretKey, null, null); + } - securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath(false)) - .setLevels(3) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); - existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - assertFalse(existingIdentity.exists()); - existingIdentity.dispose(); + @Test + public void testCreateAndLoadAndDeleteIdentityIntegrity() throws Exception { + createAndLoadAndDeleteIdentity(null, privateKey, publicKey); } @Test public void testCreateIdentityWithAttributes() throws Exception { - FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 1, ELYTRON_PASSWORD_PROVIDERS); - ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - - newIdentity.create(); - - MapAttributes newAttributes = new MapAttributes(); - - newAttributes.addFirst("name", "plainUser"); - newAttributes.addAll("roles", Arrays.asList("Employee", "Manager", "Admin")); - - newIdentity.setAttributes(newAttributes); - newIdentity.dispose(); - - securityRealm = new FileSystemSecurityRealm(getRootPath(false), 1, ELYTRON_PASSWORD_PROVIDERS); - - ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - AuthorizationIdentity authorizationIdentity = existingIdentity.getAuthorizationIdentity(); - Attributes existingAttributes = authorizationIdentity.getAttributes(); - existingIdentity.dispose(); - - assertEquals(newAttributes.size(), existingAttributes.size()); - assertTrue(newAttributes.get("name").containsAll(existingAttributes.get("name"))); - assertTrue(newAttributes.get("roles").containsAll(existingAttributes.get("roles"))); + createIdentityWithAttributes(null, null, null); } @Test public void testCreateIdentityWithAttributesEncryption() throws Exception { - FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath()) - .setLevels(1) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); - ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - - newIdentity.create(); - - MapAttributes newAttributes = new MapAttributes(); - - newAttributes.addFirst("name", "plainUser"); - newAttributes.addAll("roles", Arrays.asList("Employee", "Manager", "Admin")); - - newIdentity.setAttributes(newAttributes); - newIdentity.dispose(); - - securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath(false)) - .setLevels(1) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); - - ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - AuthorizationIdentity authorizationIdentity = existingIdentity.getAuthorizationIdentity(); - Attributes existingAttributes = authorizationIdentity.getAttributes(); - existingIdentity.dispose(); + createIdentityWithAttributes(secretKey, null, null); + } - assertEquals(newAttributes.size(), existingAttributes.size()); - assertTrue(newAttributes.get("name").containsAll(existingAttributes.get("name"))); - assertTrue(newAttributes.get("roles").containsAll(existingAttributes.get("roles"))); + @Test + public void testCreateIdentityWithAttributesIntegrity() throws Exception { + createIdentityWithAttributes(null, privateKey, publicKey); } @Test @@ -374,7 +333,16 @@ public void testCreateIdentityWithClearPasswordEncryption() throws Exception { PasswordFactory factory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR, ELYTRON_PASSWORD_PROVIDERS); ClearPassword clearPassword = (ClearPassword) factory.generatePassword(new ClearPasswordSpec(actualPassword)); - assertCreateIdentityWithPassword(actualPassword, clearPassword, key); + assertCreateIdentityWithPassword(actualPassword, clearPassword, secretKey); + } + + @Test + public void testCreateIdentityWithClearPasswordIntegrity() throws Exception { + char[] actualPassword = "secretPassword".toCharArray(); + PasswordFactory factory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR, ELYTRON_PASSWORD_PROVIDERS); + ClearPassword clearPassword = (ClearPassword) factory.generatePassword(new ClearPasswordSpec(actualPassword)); + + assertCreateIdentityWithPassword(actualPassword, clearPassword, publicKey, privateKey); } @Test @@ -396,7 +364,18 @@ public void testCreateIdentityWithBcryptCredentialEncryption() throws Exception new EncryptablePasswordSpec(actualPassword, new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE))) ); - assertCreateIdentityWithPassword(actualPassword, bCryptPassword, key); + assertCreateIdentityWithPassword(actualPassword, bCryptPassword, secretKey); + } + + @Test + public void testCreateIdentityWithBcryptCredentialIntegrity() throws Exception { + PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PASSWORD_PROVIDERS); + char[] actualPassword = "secretPassword".toCharArray(); + BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( + new EncryptablePasswordSpec(actualPassword, new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE))) + ); + + assertCreateIdentityWithPassword(actualPassword, bCryptPassword, publicKey, privateKey); } @Test @@ -416,7 +395,17 @@ public void testCreateIdentityWithBcryptCredentialHexEncodedEncryption() throws BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( new EncryptablePasswordSpec(actualPassword, new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE)))); - assertCreateIdentityWithPassword(actualPassword, bCryptPassword, Encoding.HEX, StandardCharsets.UTF_8, key); + assertCreateIdentityWithPassword(actualPassword, bCryptPassword, Encoding.HEX, StandardCharsets.UTF_8, secretKey); + } + + @Test + public void testCreateIdentityWithBcryptCredentialHexEncodedIntegrity() throws Exception { + PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PASSWORD_PROVIDERS); + char[] actualPassword = "secretPassword".toCharArray(); + BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( + new EncryptablePasswordSpec(actualPassword, new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE)))); + + assertCreateIdentityWithPassword(actualPassword, bCryptPassword, Encoding.HEX, StandardCharsets.UTF_8, publicKey, privateKey); } @Test @@ -438,7 +427,18 @@ public void testCreateIdentityWithBcryptCredentialBase64AndCharsetEncryption() t new EncryptablePasswordSpec(actualPassword, new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE)), Charset.forName("gb2312"))); - assertCreateIdentityWithPassword(actualPassword, bCryptPassword, Encoding.BASE64, Charset.forName("gb2312"), key); + assertCreateIdentityWithPassword(actualPassword, bCryptPassword, Encoding.BASE64, Charset.forName("gb2312"), secretKey); + } + + @Test + public void testCreateIdentityWithBcryptCredentialBase64AndCharsetIntegrity() throws Exception { + PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PASSWORD_PROVIDERS); + char[] actualPassword = "password密码".toCharArray(); + BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( + new EncryptablePasswordSpec(actualPassword, new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE)), + Charset.forName("gb2312"))); + + assertCreateIdentityWithPassword(actualPassword, bCryptPassword, Encoding.BASE64, Charset.forName("gb2312"), publicKey, privateKey); } @Test @@ -471,7 +471,18 @@ public void testCreateIdentityWithScramCredentialEncryption() throws Exception { EncryptablePasswordSpec encSpec = new EncryptablePasswordSpec(actualPassword, new IteratedSaltedPasswordAlgorithmSpec(4096, salt)); ScramDigestPassword scramPassword = (ScramDigestPassword) factory.generatePassword(encSpec); - assertCreateIdentityWithPassword(actualPassword, scramPassword, key); + assertCreateIdentityWithPassword(actualPassword, scramPassword, secretKey); + } + + @Test + public void testCreateIdentityWithScramCredentialIntegrity() throws Exception { + char[] actualPassword = "secretPassword".toCharArray(); + byte[] salt = generateRandomSalt(BCRYPT_SALT_SIZE); + PasswordFactory factory = PasswordFactory.getInstance(ScramDigestPassword.ALGORITHM_SCRAM_SHA_256, ELYTRON_PASSWORD_PROVIDERS); + EncryptablePasswordSpec encSpec = new EncryptablePasswordSpec(actualPassword, new IteratedSaltedPasswordAlgorithmSpec(4096, salt)); + ScramDigestPassword scramPassword = (ScramDigestPassword) factory.generatePassword(encSpec); + + assertCreateIdentityWithPassword(actualPassword, scramPassword, secretKey); } @Test @@ -516,7 +527,18 @@ public void testCreateIdentityWithDigestEncryption() throws Exception { EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(actualPassword, dpas); DigestPassword digestPassword = (DigestPassword) factory.generatePassword(encryptableSpec); - assertCreateIdentityWithPassword(actualPassword, digestPassword, key); + assertCreateIdentityWithPassword(actualPassword, digestPassword, secretKey); + } + + @Test + public void testCreateIdentityWithDigestIntegrity() throws Exception { + char[] actualPassword = "secretPassword".toCharArray(); + PasswordFactory factory = PasswordFactory.getInstance(DigestPassword.ALGORITHM_DIGEST_SHA_512, ELYTRON_PASSWORD_PROVIDERS); + DigestPasswordAlgorithmSpec dpas = new DigestPasswordAlgorithmSpec("jsmith", "elytron"); + EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(actualPassword, dpas); + DigestPassword digestPassword = (DigestPassword) factory.generatePassword(encryptableSpec); + + assertCreateIdentityWithPassword(actualPassword, digestPassword, publicKey, privateKey); } @Test @@ -549,17 +571,28 @@ public void testCreateIdentityWithDigestHexEncodedAndCharsetEncryption() throws EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(actualPassword, dpas); DigestPassword digestPassword = (DigestPassword) factory.generatePassword(encryptableSpec); - assertCreateIdentityWithPassword(actualPassword, digestPassword, Encoding.HEX, Charset.forName("KOI8-R"), key); + assertCreateIdentityWithPassword(actualPassword, digestPassword, Encoding.HEX, Charset.forName("KOI8-R"), secretKey); } @Test - public void testCreateIdentityWithSimpleDigest() throws Exception { + public void testCreateIdentityWithDigestHexEncodedAndCharsetIntegrity() throws Exception { char[] actualPassword = "secretPassword".toCharArray(); - EncryptablePasswordSpec eps = new EncryptablePasswordSpec(actualPassword, null); - PasswordFactory passwordFactory = PasswordFactory.getInstance(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_512, ELYTRON_PASSWORD_PROVIDERS); - SimpleDigestPassword tsdp = (SimpleDigestPassword) passwordFactory.generatePassword(eps); - - assertCreateIdentityWithPassword(actualPassword, tsdp); + PasswordFactory factory = PasswordFactory.getInstance(DigestPassword.ALGORITHM_DIGEST_SHA_512, ELYTRON_PASSWORD_PROVIDERS); + DigestPasswordAlgorithmSpec dpas = new DigestPasswordAlgorithmSpec("jsmith", "elytron"); + EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(actualPassword, dpas); + DigestPassword digestPassword = (DigestPassword) factory.generatePassword(encryptableSpec); + + assertCreateIdentityWithPassword(actualPassword, digestPassword, Encoding.HEX, Charset.forName("KOI8-R"), publicKey, privateKey); + } + + @Test + public void testCreateIdentityWithSimpleDigest() throws Exception { + char[] actualPassword = "secretPassword".toCharArray(); + EncryptablePasswordSpec eps = new EncryptablePasswordSpec(actualPassword, null); + PasswordFactory passwordFactory = PasswordFactory.getInstance(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_512, ELYTRON_PASSWORD_PROVIDERS); + SimpleDigestPassword tsdp = (SimpleDigestPassword) passwordFactory.generatePassword(eps); + + assertCreateIdentityWithPassword(actualPassword, tsdp); } @Test @@ -627,64 +660,39 @@ public void testCreateIdentityWithSimpleSaltedDigestHexEncodedAndCharsetEncrypti PasswordFactory passwordFactory = PasswordFactory.getInstance(SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_512, ELYTRON_PASSWORD_PROVIDERS); SaltedSimpleDigestPassword tsdp = (SaltedSimpleDigestPassword) passwordFactory.generatePassword(eps); - assertCreateIdentityWithPassword(actualPassword, tsdp, Encoding.HEX, Charset.forName("gb2312"), key); + assertCreateIdentityWithPassword(actualPassword, tsdp, Encoding.HEX, Charset.forName("gb2312"), secretKey); } @Test - public void testCreateIdentityWithEverything() throws Exception { - FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 1, ELYTRON_PASSWORD_PROVIDERS); - ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - - newIdentity.create(); - - MapAttributes newAttributes = new MapAttributes(); - - newAttributes.addFirst("firstName", "John"); - newAttributes.addFirst("lastName", "Smith"); - newAttributes.addAll("roles", Arrays.asList("Employee", "Manager", "Admin")); - - newIdentity.setAttributes(newAttributes); - - List credentials = new ArrayList<>(); - - PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PASSWORD_PROVIDERS); - BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( - new EncryptablePasswordSpec("secretPassword".toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE))) - ); - - credentials.add(new PasswordCredential(bCryptPassword)); - - byte[] hash = CodePointIterator.ofString("505d889f90085847").hexDecode().drain(); - String seed = "ke1234"; - PasswordFactory otpFactory = PasswordFactory.getInstance(OneTimePassword.ALGORITHM_OTP_SHA1, ELYTRON_PASSWORD_PROVIDERS); - OneTimePassword otpPassword = (OneTimePassword) otpFactory.generatePassword( - new OneTimePasswordSpec(hash, seed, 500) - ); - credentials.add(new PasswordCredential(otpPassword)); + public void testCreateIdentityWithSimpleSaltedDigestHexEncodedAndCharsetIntegrity() throws Exception { + char[] actualPassword = "password密码".toCharArray(); + byte[] salt = generateRandomSalt(BCRYPT_SALT_SIZE); + SaltedPasswordAlgorithmSpec spac = new SaltedPasswordAlgorithmSpec(salt); + EncryptablePasswordSpec eps = new EncryptablePasswordSpec(actualPassword, spac, Charset.forName("gb2312")); + PasswordFactory passwordFactory = PasswordFactory.getInstance(SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_512, ELYTRON_PASSWORD_PROVIDERS); + SaltedSimpleDigestPassword tsdp = (SaltedSimpleDigestPassword) passwordFactory.generatePassword(eps); - newIdentity.setCredentials(credentials); - newIdentity.dispose(); + assertCreateIdentityWithPassword(actualPassword, tsdp, Encoding.HEX, Charset.forName("gb2312"), publicKey, privateKey); + } - securityRealm = new FileSystemSecurityRealm(getRootPath(false), 1, ELYTRON_PASSWORD_PROVIDERS); - ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - assertTrue(existingIdentity.exists()); - assertTrue(existingIdentity.verifyEvidence(new PasswordGuessEvidence("secretPassword".toCharArray()))); + @Test + public void testCreateIdentityWithEverything() throws Exception { + createIdentityWithEverything(null, null, null); + } - OneTimePassword otp = existingIdentity.getCredential(PasswordCredential.class, OneTimePassword.ALGORITHM_OTP_SHA1).getPassword(OneTimePassword.class); - assertNotNull(otp); - assertEquals(OneTimePassword.ALGORITHM_OTP_SHA1, otp.getAlgorithm()); - assertArrayEquals(hash, otp.getHash()); - assertEquals(seed, otp.getSeed()); - assertEquals(500, otp.getSequenceNumber()); + @Test + public void testCreateIdentityWithEverythingEncryption() throws Exception { + createIdentityWithEverything(secretKey, null, null); + } - AuthorizationIdentity authorizationIdentity = existingIdentity.getAuthorizationIdentity(); - Attributes existingAttributes = authorizationIdentity.getAttributes(); - existingIdentity.dispose(); + @Test + public void testCreateIdentityWithEverythingIntegrity() throws Exception { + createIdentityWithEverything(null, privateKey, publicKey); + } - assertEquals(newAttributes.size(), existingAttributes.size()); - assertTrue(newAttributes.get("firstName").containsAll(existingAttributes.get("firstName"))); - assertTrue(newAttributes.get("lastName").containsAll(existingAttributes.get("lastName"))); - assertTrue(newAttributes.get("roles").containsAll(existingAttributes.get("roles"))); + @Test + public void testCreateIdentityWithEverythingEncryptionAndIntegrity() throws Exception { + createIdentityWithEverything(secretKey, privateKey, publicKey); } @Test @@ -715,176 +723,19 @@ public void testVerifyCredentialsPreExistingIdentity() throws Exception { existingIdentity.dispose(); } - @Test - public void testCreateIdentityWithEverythingEncryption() throws Exception { - FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath()) - .setLevels(1) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); - ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - - newIdentity.create(); - - MapAttributes newAttributes = new MapAttributes(); - - newAttributes.addFirst("firstName", "John"); - newAttributes.addFirst("lastName", "Smith"); - newAttributes.addAll("roles", Arrays.asList("Employee", "Manager", "Admin")); - - newIdentity.setAttributes(newAttributes); - - List credentials = new ArrayList<>(); - - PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PASSWORD_PROVIDERS); - BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( - new EncryptablePasswordSpec("secretPassword".toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE))) - ); - - credentials.add(new PasswordCredential(bCryptPassword)); - - byte[] hash = CodePointIterator.ofString("505d889f90085847").hexDecode().drain(); - String seed = "ke1234"; - PasswordFactory otpFactory = PasswordFactory.getInstance(OneTimePassword.ALGORITHM_OTP_SHA1, ELYTRON_PASSWORD_PROVIDERS); - OneTimePassword otpPassword = (OneTimePassword) otpFactory.generatePassword( - new OneTimePasswordSpec(hash, seed, 500) - ); - credentials.add(new PasswordCredential(otpPassword)); - - newIdentity.setCredentials(credentials); - newIdentity.dispose(); - - securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath(false)) - .setLevels(1) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); - ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); - assertTrue(existingIdentity.exists()); - assertTrue(existingIdentity.verifyEvidence(new PasswordGuessEvidence("secretPassword".toCharArray()))); - - OneTimePassword otp = existingIdentity.getCredential(PasswordCredential.class, OneTimePassword.ALGORITHM_OTP_SHA1).getPassword(OneTimePassword.class); - assertNotNull(otp); - assertEquals(OneTimePassword.ALGORITHM_OTP_SHA1, otp.getAlgorithm()); - assertArrayEquals(hash, otp.getHash()); - assertEquals(seed, otp.getSeed()); - assertEquals(500, otp.getSequenceNumber()); - - AuthorizationIdentity authorizationIdentity = existingIdentity.getAuthorizationIdentity(); - Attributes existingAttributes = authorizationIdentity.getAttributes(); - existingIdentity.dispose(); - - assertEquals(newAttributes.size(), existingAttributes.size()); - assertTrue(newAttributes.get("firstName").containsAll(existingAttributes.get("firstName"))); - assertTrue(newAttributes.get("lastName").containsAll(existingAttributes.get("lastName"))); - assertTrue(newAttributes.get("roles").containsAll(existingAttributes.get("roles"))); - } - @Test public void testCredentialReplacing() throws Exception { - FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 1, ELYTRON_PASSWORD_PROVIDERS); - ModifiableRealmIdentity identity1 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("testingUser")); - identity1.create(); - - List credentials = new ArrayList<>(); - - PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PASSWORD_PROVIDERS); - BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( - new EncryptablePasswordSpec("secretPassword".toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE))) - ); - credentials.add(new PasswordCredential(bCryptPassword)); - - byte[] hash = CodePointIterator.ofString("505d889f90085847").hexDecode().drain(); - String seed = "ke1234"; - PasswordFactory otpFactory = PasswordFactory.getInstance(OneTimePassword.ALGORITHM_OTP_SHA1, ELYTRON_PASSWORD_PROVIDERS); - OneTimePassword otpPassword = (OneTimePassword) otpFactory.generatePassword( - new OneTimePasswordSpec(hash, seed, 500) - ); - credentials.add(new PasswordCredential(otpPassword)); - - identity1.setCredentials(credentials); - identity1.dispose(); - - // checking result - securityRealm = new FileSystemSecurityRealm(getRootPath(false), 1, ELYTRON_PASSWORD_PROVIDERS); - ModifiableRealmIdentity identity3 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("testingUser")); - - assertTrue(identity3.exists()); - assertTrue(identity3.verifyEvidence(new PasswordGuessEvidence("secretPassword".toCharArray()))); - identity3.dispose(); + credentialReplacing(null, null, null); } @Test public void testCredentialReplacingEncryption() throws Exception { - FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath()) - .setLevels(1) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); - ModifiableRealmIdentity identity1 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("testingUser")); - identity1.create(); - - List credentials = new ArrayList<>(); - - PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PASSWORD_PROVIDERS); - BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( - new EncryptablePasswordSpec("secretPassword".toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE))) - ); - credentials.add(new PasswordCredential(bCryptPassword)); - - byte[] hash = CodePointIterator.ofString("505d889f90085847").hexDecode().drain(); - String seed = "ke1234"; - PasswordFactory otpFactory = PasswordFactory.getInstance(OneTimePassword.ALGORITHM_OTP_SHA1, ELYTRON_PASSWORD_PROVIDERS); - OneTimePassword otpPassword = (OneTimePassword) otpFactory.generatePassword( - new OneTimePasswordSpec(hash, seed, 500) - ); - credentials.add(new PasswordCredential(otpPassword)); - - identity1.setCredentials(credentials); - identity1.dispose(); - - // checking result - securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath(false)) - .setLevels(1) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); - ModifiableRealmIdentity identity3 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("testingUser")); - - assertTrue(identity3.exists()); - assertTrue(identity3.verifyEvidence(new PasswordGuessEvidence("secretPassword".toCharArray()))); - identity3.dispose(); - } - - private FileSystemSecurityRealm createRealmWithTwoIdentities() throws Exception { - FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 1); - ModifiableRealmIdentity identity1 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("firstUser")); - identity1.create(); - identity1.dispose(); - ModifiableRealmIdentity identity2 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("secondUser")); - identity2.create(); - identity2.dispose(); - return securityRealm; + credentialReplacing(secretKey, null, null); } - private FileSystemSecurityRealm createRealmWithTwoIdentities(SecretKey secretKey) throws Exception { - FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath()) - .setLevels(1) - .setSecretKey(secretKey) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); - ModifiableRealmIdentity identity1 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("firstUser")); - identity1.create(); - identity1.dispose(); - ModifiableRealmIdentity identity2 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("secondUser")); - identity2.create(); - identity2.dispose(); - return securityRealm; + @Test + public void testCredentialReplacingIntegrity() throws Exception { + credentialReplacing(null, privateKey, publicKey); } @Test @@ -904,7 +755,22 @@ public void testIterating() throws Exception { @Test public void testIteratingEncryption() throws Exception { - FileSystemSecurityRealm securityRealm = createRealmWithTwoIdentities(key); + FileSystemSecurityRealm securityRealm = createRealmWithTwoIdentities(secretKey); + Iterator iterator = securityRealm.getRealmIdentityIterator(); + + int count = 0; + while(iterator.hasNext()){ + Assert.assertTrue(iterator.next().exists()); + count++; + } + + Assert.assertEquals(2, count); + getRootPath(); // will fail on windows if iterator not closed correctly + } + + @Test + public void testIteratingIntegrity() throws Exception { + FileSystemSecurityRealm securityRealm = createRealmWithTwoIdentities(publicKey, privateKey); Iterator iterator = securityRealm.getRealmIdentityIterator(); int count = 0; @@ -958,21 +824,11 @@ public void testMismatchSecretKey() throws Exception { char[] actualPassword = "secretPassword".toCharArray(); PasswordFactory factory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR, ELYTRON_PASSWORD_PROVIDERS); ClearPassword clearPassword = (ClearPassword) factory.generatePassword(new ClearPasswordSpec(actualPassword)); - FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath()) - .setLevels(2) - .setSecretKey(key) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); + FileSystemSecurityRealm securityRealm = getBuilder(secretKey).build(); ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); newIdentity.create(); newIdentity.setCredentials(Collections.singleton(new PasswordCredential(clearPassword))); - securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath(false)) - .setLevels(2) - .setSecretKey(SecretKeyUtil.generateSecretKey(192)) - .setProviders(ELYTRON_PASSWORD_PROVIDERS) - .build(); + securityRealm = getBuilder(SecretKeyUtil.generateSecretKey(192)).build(); ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); try { existingIdentity.verifyEvidence(new PasswordGuessEvidence(actualPassword)); @@ -983,29 +839,401 @@ public void testMismatchSecretKey() throws Exception { } @Test - public void encodedIfNotEncrypted() throws Exception { - File file = new File(getRootPath().toString() + "/plainuser-OBWGC2LOKVZWK4Q.xml"); - FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath()) - .setLevels(0) - .setEncoded(true) + public void testMismatchKeyPair() throws Exception { + PrivateKey falsePrivateKey = KeyPairGenerator.getInstance("RSA").generateKeyPair().getPrivate(); + PublicKey falsePublicKey = KeyPairGenerator.getInstance("RSA").generateKeyPair().getPublic(); + + char[] actualPassword = "secretPassword".toCharArray(); + PasswordFactory factory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR, ELYTRON_PASSWORD_PROVIDERS); + ClearPassword clearPassword = (ClearPassword) factory.generatePassword(new ClearPasswordSpec(actualPassword)); + FileSystemSecurityRealm securityRealm = getBuilder(privateKey, publicKey).build(); + ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + newIdentity.create(); + newIdentity.setCredentials(Collections.singleton(new PasswordCredential(clearPassword))); + securityRealm = getBuilder(falsePrivateKey, falsePublicKey).build(); + ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + assertFalse(existingIdentity.verifyEvidence(new PasswordGuessEvidence(actualPassword))); + existingIdentity.dispose(); + } + + @Test + public void encodedIfNotEncrypted() throws Exception { + File file = new File(getRootPath().toString() + "/p/l/plainuser-OBWGC2LOKVZWK4Q.xml"); + FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() + .setRoot(getRootPath(false)) .setProviders(ELYTRON_PASSWORD_PROVIDERS) + .setEncoded(true) .build(); ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); newIdentity.create(); assertTrue(file.isFile()); newIdentity.dispose(); securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath()) - .setLevels(0) - .setEncoded(true) - .setSecretKey(key) + .setRoot(getRootPath(true)) .setProviders(ELYTRON_PASSWORD_PROVIDERS) + .setSecretKey(secretKey) .build(); newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); newIdentity.create(); assertFalse(file.isFile()); + newIdentity.dispose(); + } + + @Test + public void testNewKeyPairRewrite() throws Exception { + FileSystemSecurityRealm securityRealm = getBuilder(privateKey, publicKey).build(); + ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + newIdentity.create(); + MapAttributes newAttributes = new MapAttributes(); + newAttributes.addFirst("name", "plainUser"); + newAttributes.addAll("roles", Arrays.asList("Employee", "Manager", "Admin")); + newIdentity.setAttributes(newAttributes); + newIdentity.dispose(); + String identityPath = getRootPath(false) + File.separator + "p" + File.separator + "l" + File.separator + "plainuser-OBWGC2LOKVZWK4Q.xml"; + assertTrue(validateDigitalSignature(identityPath, "plainUser", publicKey)); + + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + KeyPair pair = keyPairGen.generateKeyPair(); + PrivateKey newPrivateKey = pair.getPrivate(); + PublicKey newPublicKey = pair.getPublic(); + securityRealm = getBuilder(newPrivateKey, newPublicKey).build(); + assertFalse(validateDigitalSignature(identityPath, "plainUser", newPublicKey)); + securityRealm.updateRealmKeyPair(); + assertTrue(validateDigitalSignature(identityPath, "plainUser", newPublicKey)); + newIdentity.dispose(); + } + + @Test + public void testIdentityPrincipalTampered() throws Exception { + FileSystemSecurityRealm securityRealm = getBuilder(privateKey, publicKey).build(); + ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + newIdentity.create(); + newIdentity.dispose(); + Path identityFile = Paths.get(getRootPath(false).toString(), "p", "l", "plainuser-OBWGC2LOKVZWK4Q.xml"); + String identityFileString = new String(Files.readAllBytes(identityFile)); + identityFileString = identityFileString.replace("plainUser", "TAMPERED_PRINCIPAL"); + PrintWriter out = new PrintWriter(identityFile.toString()); + out.println(identityFileString); + out.close(); + assertFalse(securityRealm.verifyRealmIntegrity()); + } + + @Test + public void testIdentityCredentialsTampered() throws Exception { + FileSystemSecurityRealm securityRealm = getBuilder(privateKey, publicKey).build(); + + List credentials = new ArrayList<>(); + PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PASSWORD_PROVIDERS); + BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( + new EncryptablePasswordSpec("secretPassword".toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE))) + ); + credentials.add(new PasswordCredential(bCryptPassword)); + + ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + newIdentity.create(); + newIdentity.setCredentials(credentials); + newIdentity.dispose(); + + Path identityFile = Paths.get(getRootPath(false).toString(), "p", "l", "plainuser-OBWGC2LOKVZWK4Q.xml"); + String identityFileString = new String(Files.readAllBytes(identityFile)); + identityFileString = identityFileString.replace("plainUser", "TAMPERED_PRINCIPAL"); + String start = "\\"; + String end = "\\"; + identityFileString = identityFileString.replaceAll(start + ".*" + end, "FAKE_PASSWORD"); + PrintWriter out = new PrintWriter(identityFile.toString()); + out.println(identityFileString); + out.close(); + assertFalse(securityRealm.verifyRealmIntegrity()); + } + + private void credentialReplacing(SecretKey secretKey, PrivateKey privateKey, PublicKey publicKey) throws Exception { + assertFalse(privateKey != null ^ publicKey != null); + FileSystemSecurityRealmBuilder securityRealmBuilder = FileSystemSecurityRealm.builder() + .setRoot(getRootPath()) + .setLevels(1) + .setProviders(ELYTRON_PASSWORD_PROVIDERS); + if (secretKey != null) { securityRealmBuilder.setSecretKey(secretKey); } + else if (privateKey != null) { securityRealmBuilder.setPrivateKey(privateKey); securityRealmBuilder.setPublicKey(publicKey); } + FileSystemSecurityRealm securityRealm = securityRealmBuilder.build(); + ModifiableRealmIdentity identity1 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("testingUser")); + identity1.create(); + + List credentials = new ArrayList<>(); + + PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PASSWORD_PROVIDERS); + BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( + new EncryptablePasswordSpec("secretPassword".toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE))) + ); + credentials.add(new PasswordCredential(bCryptPassword)); + + byte[] hash = CodePointIterator.ofString("505d889f90085847").hexDecode().drain(); + String seed = "ke1234"; + PasswordFactory otpFactory = PasswordFactory.getInstance(OneTimePassword.ALGORITHM_OTP_SHA1, ELYTRON_PASSWORD_PROVIDERS); + OneTimePassword otpPassword = (OneTimePassword) otpFactory.generatePassword( + new OneTimePasswordSpec(hash, seed, 500) + ); + credentials.add(new PasswordCredential(otpPassword)); + + identity1.setCredentials(credentials); + identity1.dispose(); + + // checking result + securityRealmBuilder = FileSystemSecurityRealm.builder() + .setRoot(getRootPath(false)) + .setLevels(1) + .setProviders(ELYTRON_PASSWORD_PROVIDERS); + if (secretKey != null) { securityRealmBuilder.setSecretKey(secretKey); } + else if (privateKey != null) { securityRealmBuilder.setPrivateKey(privateKey); securityRealmBuilder.setPublicKey(publicKey); } + securityRealm = securityRealmBuilder.build(); + ModifiableRealmIdentity identity3 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("testingUser")); + assertTrue(identity3.exists()); + assertTrue(identity3.verifyEvidence(new PasswordGuessEvidence("secretPassword".toCharArray()))); + identity3.dispose(); + } + + private void createIdentityWithEverything(SecretKey secretKey, PrivateKey privateKey, PublicKey publicKey) throws Exception { + assertFalse(privateKey != null ^ publicKey != null); + FileSystemSecurityRealmBuilder securityRealmBuilder = FileSystemSecurityRealm.builder() + .setRoot(getRootPath()) + .setLevels(1) + .setProviders(ELYTRON_PASSWORD_PROVIDERS); + if (secretKey != null) { securityRealmBuilder.setSecretKey(secretKey); } + if (privateKey != null) { securityRealmBuilder.setPrivateKey(privateKey); securityRealmBuilder.setPublicKey(publicKey); } + FileSystemSecurityRealm securityRealm = securityRealmBuilder.build(); + ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + + newIdentity.create(); + + MapAttributes newAttributes = new MapAttributes(); + + newAttributes.addFirst("firstName", "John"); + newAttributes.addFirst("lastName", "Smith"); + newAttributes.addAll("roles", Arrays.asList("Employee", "Manager", "Admin")); + + newIdentity.setAttributes(newAttributes); + + List credentials = new ArrayList<>(); + + PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PASSWORD_PROVIDERS); + BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( + new EncryptablePasswordSpec("secretPassword".toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(10, generateRandomSalt(BCRYPT_SALT_SIZE))) + ); + + credentials.add(new PasswordCredential(bCryptPassword)); + + byte[] hash = CodePointIterator.ofString("505d889f90085847").hexDecode().drain(); + String seed = "ke1234"; + PasswordFactory otpFactory = PasswordFactory.getInstance(OneTimePassword.ALGORITHM_OTP_SHA1, ELYTRON_PASSWORD_PROVIDERS); + OneTimePassword otpPassword = (OneTimePassword) otpFactory.generatePassword( + new OneTimePasswordSpec(hash, seed, 500) + ); + credentials.add(new PasswordCredential(otpPassword)); + + newIdentity.setCredentials(credentials); + newIdentity.dispose(); + + securityRealmBuilder = FileSystemSecurityRealm.builder() + .setRoot(getRootPath(false)) + .setLevels(1) + .setProviders(ELYTRON_PASSWORD_PROVIDERS); + if (secretKey != null) { securityRealmBuilder.setSecretKey(secretKey); } + if (privateKey != null) { securityRealmBuilder.setPrivateKey(privateKey); securityRealmBuilder.setPublicKey(publicKey); } + securityRealm = securityRealmBuilder.build(); + ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + assertTrue(existingIdentity.exists()); + assertTrue(existingIdentity.verifyEvidence(new PasswordGuessEvidence("secretPassword".toCharArray()))); + + OneTimePassword otp = existingIdentity.getCredential(PasswordCredential.class, OneTimePassword.ALGORITHM_OTP_SHA1).getPassword(OneTimePassword.class); + assertNotNull(otp); + assertEquals(OneTimePassword.ALGORITHM_OTP_SHA1, otp.getAlgorithm()); + assertArrayEquals(hash, otp.getHash()); + assertEquals(seed, otp.getSeed()); + assertEquals(500, otp.getSequenceNumber()); + + AuthorizationIdentity authorizationIdentity = existingIdentity.getAuthorizationIdentity(); + Attributes existingAttributes = authorizationIdentity.getAttributes(); + existingIdentity.dispose(); + + assertEquals(newAttributes.size(), existingAttributes.size()); + assertTrue(newAttributes.get("firstName").containsAll(existingAttributes.get("firstName"))); + assertTrue(newAttributes.get("lastName").containsAll(existingAttributes.get("lastName"))); + assertTrue(newAttributes.get("roles").containsAll(existingAttributes.get("roles"))); + } + + private void createIdentityWithAttributes(SecretKey secretKey, PrivateKey privateKey, PublicKey publicKey) throws Exception { + assertFalse(privateKey != null ^ publicKey != null); + FileSystemSecurityRealmBuilder securityRealmBuilder = FileSystemSecurityRealm.builder() + .setRoot(getRootPath()) + .setLevels(1) + .setProviders(ELYTRON_PASSWORD_PROVIDERS); + if (secretKey != null) { securityRealmBuilder.setSecretKey(secretKey); } + if (privateKey != null) { securityRealmBuilder.setPrivateKey(privateKey); securityRealmBuilder.setPublicKey(publicKey); } + FileSystemSecurityRealm securityRealm = securityRealmBuilder.build(); + ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + + newIdentity.create(); + + MapAttributes newAttributes = new MapAttributes(); + + newAttributes.addFirst("name", "plainUser"); + newAttributes.addAll("roles", Arrays.asList("Employee", "Manager", "Admin")); + + newIdentity.setAttributes(newAttributes); + newIdentity.dispose(); + + securityRealmBuilder = FileSystemSecurityRealm.builder() + .setRoot(getRootPath(false)) + .setLevels(1) + .setProviders(ELYTRON_PASSWORD_PROVIDERS); + if (secretKey != null) { securityRealmBuilder.setSecretKey(secretKey); } + if (privateKey != null) { securityRealmBuilder.setPrivateKey(privateKey); securityRealmBuilder.setPublicKey(publicKey); } + securityRealm = securityRealmBuilder.build(); + + ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + AuthorizationIdentity authorizationIdentity = existingIdentity.getAuthorizationIdentity(); + Attributes existingAttributes = authorizationIdentity.getAttributes(); + existingIdentity.dispose(); + + assertEquals(newAttributes.size(), existingAttributes.size()); + assertTrue(newAttributes.get("name").containsAll(existingAttributes.get("name"))); + assertTrue(newAttributes.get("roles").containsAll(existingAttributes.get("roles"))); + } + + private void createAndLoadAndDeleteIdentity(SecretKey secretKey, PrivateKey privateKey, PublicKey publicKey) throws Exception { + assertFalse(privateKey != null ^ publicKey != null); + FileSystemSecurityRealmBuilder securityRealmBuilder = FileSystemSecurityRealm.builder() + .setRoot(getRootPath()) + .setLevels(3) + .setProviders(ELYTRON_PASSWORD_PROVIDERS); + if (secretKey != null) { securityRealmBuilder.setSecretKey(secretKey); } + if (privateKey != null) { securityRealmBuilder.setPrivateKey(privateKey); securityRealmBuilder.setPublicKey(publicKey); } + + FileSystemSecurityRealm securityRealm = securityRealmBuilder.build(); + ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + newIdentity.create(); + newIdentity.dispose(); + + securityRealmBuilder = FileSystemSecurityRealm.builder() + .setRoot(getRootPath(false)) + .setLevels(3) + .setProviders(ELYTRON_PASSWORD_PROVIDERS); + if (secretKey != null) { securityRealmBuilder.setSecretKey(secretKey); } + if (privateKey != null) { securityRealmBuilder.setPrivateKey(privateKey); securityRealmBuilder.setPublicKey(publicKey); } + securityRealm = securityRealmBuilder.build(); + + ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + assertTrue(existingIdentity.exists()); + existingIdentity.delete(); + assertFalse(existingIdentity.exists()); + existingIdentity.dispose(); + + securityRealmBuilder = FileSystemSecurityRealm.builder() + .setRoot(getRootPath(false)) + .setLevels(3) + .setProviders(ELYTRON_PASSWORD_PROVIDERS); + if (secretKey != null) { securityRealmBuilder.setSecretKey(secretKey); } + if (privateKey != null) { securityRealmBuilder.setPrivateKey(privateKey); securityRealmBuilder.setPublicKey(publicKey); } + securityRealm = securityRealmBuilder.build(); + existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + assertFalse(existingIdentity.exists()); + existingIdentity.dispose(); + } + + private void specialCharacters(FileSystemSecurityRealm securityRealm) throws Exception{ + ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("special*.\"/\\[]:;|=,用戶 ")); + newIdentity.create(); + newIdentity.dispose(); + + ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("special*.\"/\\[]:;|=,用戶 ")); + assertTrue(existingIdentity.exists()); + existingIdentity.dispose(); + } + + private void shortUsername(FileSystemSecurityRealm securityRealm) throws Exception { + ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("p")); + newIdentity.create(); + + newIdentity.dispose(); + + ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("p")); + assertTrue(existingIdentity.exists()); + existingIdentity.dispose(); + } + + private void caseSensitive(FileSystemSecurityRealm securityRealm) throws Exception { + ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + newIdentity.create(); + assertTrue(newIdentity.exists()); + newIdentity.dispose(); + + ModifiableRealmIdentity differentIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("PLAINUSER")); + assertFalse(differentIdentity.exists()); + differentIdentity.dispose(); + } + + private FileSystemSecurityRealm createRealmWithTwoIdentities() throws Exception { + FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), 1); + ModifiableRealmIdentity identity1 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("firstUser")); + identity1.create(); + identity1.dispose(); + ModifiableRealmIdentity identity2 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("secondUser")); + identity2.create(); + identity2.dispose(); + return securityRealm; + } + private FileSystemSecurityRealm createRealmWithTwoIdentities(SecretKey secretKey) throws Exception { + FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() + .setRoot(getRootPath()) + .setLevels(1) + .setSecretKey(secretKey) + .setProviders(ELYTRON_PASSWORD_PROVIDERS) + .build(); + ModifiableRealmIdentity identity1 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("firstUser")); + identity1.create(); + identity1.dispose(); + ModifiableRealmIdentity identity2 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("secondUser")); + identity2.create(); + identity2.dispose(); + return securityRealm; + } + private FileSystemSecurityRealm createRealmWithTwoIdentities(PublicKey publicKey, PrivateKey privateKey) throws Exception { + FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() + .setRoot(getRootPath()) + .setLevels(1) + .setPublicKey(publicKey) + .setPrivateKey(privateKey) + .setProviders(ELYTRON_PASSWORD_PROVIDERS) + .build(); + ModifiableRealmIdentity identity1 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("firstUser")); + identity1.create(); + identity1.dispose(); + ModifiableRealmIdentity identity2 = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("secondUser")); + identity2.create(); + identity2.dispose(); + return securityRealm; + } + + private FileSystemSecurityRealmBuilder getBuilder(SecretKey secretKey, PrivateKey privateKey, PublicKey publicKey) throws Exception { + FileSystemSecurityRealmBuilder securityRealmBuilder = FileSystemSecurityRealm.builder() + .setRoot(getRootPath(false)) + .setProviders(ELYTRON_PASSWORD_PROVIDERS); + if (secretKey != null) { + securityRealmBuilder.setSecretKey(secretKey); + } + if (privateKey != null && publicKey != null) { + securityRealmBuilder.setPrivateKey(privateKey).setPublicKey(publicKey); + } + return securityRealmBuilder; + } + private FileSystemSecurityRealmBuilder getBuilder(SecretKey secretKey) throws Exception { + return getBuilder(secretKey, null, null); + } + private FileSystemSecurityRealmBuilder getBuilder(PrivateKey privateKey, PublicKey publicKey) throws Exception { + return getBuilder(null, privateKey, publicKey); + } + private FileSystemSecurityRealmBuilder getBuilder() throws Exception { + return getBuilder(null, null, null); } private void assertCreateIdentityWithPassword(char[] actualPassword, Password credential) throws Exception { @@ -1014,15 +1242,18 @@ private void assertCreateIdentityWithPassword(char[] actualPassword, Password cr private void assertCreateIdentityWithPassword(char[] actualPassword, Password credential, SecretKey secretKey) throws Exception { assertCreateIdentityWithPassword(actualPassword, credential, Encoding.BASE64, StandardCharsets.UTF_8, secretKey); } + private void assertCreateIdentityWithPassword(char[] actualPassword, Password credential, PublicKey publicKey, PrivateKey privateKey) throws Exception { + assertCreateIdentityWithPassword(actualPassword, credential, Encoding.BASE64, StandardCharsets.UTF_8, publicKey, privateKey); + } private void assertCreateIdentityWithPassword(char[] actualPassword, Password credential, Encoding hashEncoding, Charset hashCharset) throws Exception { - FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), NameRewriter.IDENTITY_REWRITER, 1, true, hashEncoding, hashCharset, ELYTRON_PASSWORD_PROVIDERS, null); + FileSystemSecurityRealm securityRealm = new FileSystemSecurityRealm(getRootPath(), NameRewriter.IDENTITY_REWRITER, 1, true, hashEncoding, hashCharset, ELYTRON_PASSWORD_PROVIDERS, null, null, null); ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); newIdentity.create(); newIdentity.setCredentials(Collections.singleton(new PasswordCredential(credential))); newIdentity.dispose(); - securityRealm = new FileSystemSecurityRealm(getRootPath(false), NameRewriter.IDENTITY_REWRITER, 1, true, hashEncoding, hashCharset, ELYTRON_PASSWORD_PROVIDERS, null); + securityRealm = new FileSystemSecurityRealm(getRootPath(false), NameRewriter.IDENTITY_REWRITER, 1, true, hashEncoding, hashCharset, ELYTRON_PASSWORD_PROVIDERS, null, null, null); ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); assertTrue(existingIdentity.exists()); assertTrue(existingIdentity.verifyEvidence(new PasswordGuessEvidence(actualPassword))); @@ -1043,14 +1274,27 @@ private void assertCreateIdentityWithPassword(char[] actualPassword, Password cr newIdentity.setCredentials(Collections.singleton(new PasswordCredential(credential))); newIdentity.dispose(); - securityRealm = FileSystemSecurityRealm.builder() - .setRoot(getRootPath(false)) + ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + assertTrue(existingIdentity.exists()); + assertTrue(existingIdentity.verifyEvidence(new PasswordGuessEvidence(actualPassword))); + existingIdentity.dispose(); + } + + private void assertCreateIdentityWithPassword(char[] actualPassword, Password credential, Encoding hashEncoding, Charset hashCharset, PublicKey publicKey, PrivateKey privateKey) throws Exception { + FileSystemSecurityRealm securityRealm = FileSystemSecurityRealm.builder() + .setRoot(getRootPath()) .setLevels(1) .setHashEncoding(hashEncoding) .setHashCharset(hashCharset) - .setSecretKey(secretKey) .setProviders(ELYTRON_PASSWORD_PROVIDERS) + .setPublicKey(publicKey) + .setPrivateKey(privateKey) .build(); + ModifiableRealmIdentity newIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); + newIdentity.create(); + newIdentity.setCredentials(Collections.singleton(new PasswordCredential(credential))); + newIdentity.dispose(); + ModifiableRealmIdentity existingIdentity = securityRealm.getRealmIdentityForUpdate(new NamePrincipal("plainUser")); assertTrue(existingIdentity.exists()); assertTrue(existingIdentity.verifyEvidence(new PasswordGuessEvidence(actualPassword))); @@ -1089,4 +1333,22 @@ private Path getRootPath() throws Exception { return getRootPath(true); } + private boolean validateDigitalSignature(String path, String name, PublicKey publicKey) throws IllegalStateException { + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + Document doc = dbf.newDocumentBuilder().parse(path); + NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); + if (nl.getLength() == 0) { + throw new RealmUnavailableException("Cannot find Signature element"); + } + XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); + DOMValidateContext valContext = new DOMValidateContext(publicKey, nl.item(0)); + XMLSignature signature = fac.unmarshalXMLSignature(valContext); + return signature.validate(valContext); + } catch (ParserConfigurationException | IOException | MarshalException | XMLSignatureException | org.xml.sax.SAXException e) { + throw new IllegalStateException(String.format("Signature for the following identity is invalid: %s.", name)); + } + } + } diff --git a/tests/base/src/test/java/org/wildfly/security/auth/realm/cache/ModifiableSecurityRealmIdentityCacheTest.java b/tests/base/src/test/java/org/wildfly/security/auth/realm/cache/ModifiableSecurityRealmIdentityCacheTest.java index d5841bbc289..5b534740264 100644 --- a/tests/base/src/test/java/org/wildfly/security/auth/realm/cache/ModifiableSecurityRealmIdentityCacheTest.java +++ b/tests/base/src/test/java/org/wildfly/security/auth/realm/cache/ModifiableSecurityRealmIdentityCacheTest.java @@ -170,7 +170,7 @@ public void testInvalidateEntryAfterChangingCredentialsFromIterator() throws Exc } private ModifiableSecurityRealm createSecurityRealm() throws Exception { - FileSystemSecurityRealm realm = new FileSystemSecurityRealm(getRootPath(true), NameRewriter.IDENTITY_REWRITER, 2, true, Encoding.BASE64, StandardCharsets.UTF_8, ELYTRON_PASSWORD_PROVIDERS, null); + FileSystemSecurityRealm realm = new FileSystemSecurityRealm(getRootPath(true), NameRewriter.IDENTITY_REWRITER, 2, true, Encoding.BASE64, StandardCharsets.UTF_8, ELYTRON_PASSWORD_PROVIDERS, null, null, null); addUser(realm, "joe", "User"); diff --git a/tests/base/src/test/resources/filesystem-realm-exists/u/s/e/user-OVZWK4Q.xml b/tests/base/src/test/resources/filesystem-realm-exists/u/s/e/user-OVZWK4Q.xml new file mode 100644 index 00000000000..059f016db08 --- /dev/null +++ b/tests/base/src/test/resources/filesystem-realm-exists/u/s/e/user-OVZWK4Q.xml @@ -0,0 +1,10 @@ + + + + AXNlY3JldFBhc3N3b3Jk + + xrU4pnFXL7ln3prQzFmmsFciNwRO/cE6TefYad4riho=INVALID_SIGNATUREnQWmAHovgGbBjCdk/ihZ7JKr3M+YvXP6H700GQ834vmw8jBogB/D9l7DU5Tv5qcJngTo0haqKpzk +0YIGRApiD2Jcw7D/FgOM+R8FYmjywTC1uKFPxffL2GxXTU1e5r+ds2WcQ1BDcodiNq/tCclQyaXW +ME9o4Z0IPHgRP8N8uC+OkUuuhk0IIhDQyrLhQhDzR8MvDY1vcg9kCsg9t/ELd7VMVcV+QmDXUs+Z +xbdaKnaso2xUw8aHEniy7t6ontc7FMYWVaeF2vptB3tw9bLexynhqE1x53hDl9nDzkd8ah49s+JW +U6drnaYKM0ACXaLbkHjpMvqy1c4DRdqmP8apZw==AQAB \ No newline at end of file