Skip to content

Commit

Permalink
[ELY-2078] Add encryption support to FileSystemSecurityRealm
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashpan committed Mar 17, 2022
1 parent 310dc3c commit 22bb7be
Show file tree
Hide file tree
Showing 9 changed files with 1,563 additions and 68 deletions.
6 changes: 5 additions & 1 deletion auth/realm/base/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@
<dependency>
<groupId>org.wildfly.security</groupId>
<artifactId>wildfly-elytron-x500</artifactId>
</dependency>
</dependency>
<dependency>
<groupId>org.wildfly.security</groupId>
<artifactId>wildfly-elytron-encryption</artifactId>
</dependency>

<dependency>
<groupId>org.jboss.logging</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2021 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.util.List;
import org.wildfly.security.auth.principal.NamePrincipal;
import org.wildfly.security.auth.server.ModifiableRealmIdentity;
import org.wildfly.security.auth.server.ModifiableRealmIdentityIterator;
import org.wildfly.security.auth.server.RealmUnavailableException;
import org.wildfly.security.authz.Attributes;
import org.wildfly.security.credential.Credential;

/**
* A utility class to utilize methods from the {@code FileSystemSecurityRealm} class for the Elytron Tool.
*
* @author <a href="mailto:[email protected]">Ashpan Raskar</a>
*/
public class FileSystemRealmUtil {
public static void createEncryptedRealmFromUnencrypted(FileSystemSecurityRealm unencryptedRealm, FileSystemSecurityRealm encryptedRealm) throws RealmUnavailableException {
ModifiableRealmIdentityIterator realmIterator = unencryptedRealm.getRealmIdentityIterator();

while (realmIterator.hasNext()) {
ModifiableRealmIdentity identity = realmIterator.next();
List<Credential> credentials = ((FileSystemSecurityRealm.Identity) identity).loadCredentials();
Attributes attributes = identity.getAttributes();

ModifiableRealmIdentity newIdentity = encryptedRealm.getRealmIdentityForUpdate(new NamePrincipal(identity.getRealmIdentityPrincipal().getName()));
newIdentity.create();
newIdentity.setCredentials(credentials);
newIdentity.setAttributes(attributes);
}
realmIterator.close();
}

}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2021 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.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.security.Provider;
import java.util.function.Supplier;

import javax.crypto.SecretKey;

import org.wildfly.common.Assert;
import org.wildfly.security.auth.server.NameRewriter;
import org.wildfly.security.password.spec.Encoding;


/**
* A builder class that creates {@link FileSystemSecurityRealm} instances.
*
* @author <a href="mailto:[email protected]">Ashpan Raskar</a>
*/
public class FileSystemSecurityRealmBuilder {

private Path root;
private NameRewriter nameRewriter;
private int levels = 2;
private boolean encoded = true;
private Charset hashCharset;
private Encoding hashEncoding;
private SecretKey secretKey;
private Supplier<Provider[]> providers;

FileSystemSecurityRealmBuilder() {
}

/**
* Set the root path to be used by the realm.
*
* @param root the root path of the identity store (must not be {@code null})
* @return this builder.enc
*/
public FileSystemSecurityRealmBuilder setRoot(final Path root) {
Assert.checkNotNullParam("root", root);
this.root = root;
return this;
}

/**
* Set the name rewriter to be used by the realm.
*
* @param nameRewriter the name rewriter to apply to looked up names (must not be {@code null})
* @return this builder.
*/
public FileSystemSecurityRealmBuilder setNameRewriter(final NameRewriter nameRewriter) {
Assert.checkNotNullParam("nameRewriter", nameRewriter);
this.nameRewriter = nameRewriter;
return this;
}

/**
* Set the number of levels to be used by the realm.
*
* @param levels the number of levels of directory hashing to apply (must not be {@code null})
* @return this builder.
*/
public FileSystemSecurityRealmBuilder setLevels(final int levels) {
this.levels = levels;
return this;
}

/**
* Set whether the identity name should be encoded for the filename in the realm.
*
* @param encoded whether identity names should be BASE32 encoded before using as filename (only applies if the security realm is unencrypted)
* @return this builder.
*/
public FileSystemSecurityRealmBuilder setEncoded(final boolean encoded) {
this.encoded = encoded;
return this;
}

/**
* Set the character set to be used by the realm.
*
* @param hashCharset the character set to use when converting password strings to a byte array. Uses UTF-8 by default. (must not be {@code null})
* @return this builder.
*/
public FileSystemSecurityRealmBuilder setHashCharset(final Charset hashCharset) {
Assert.checkNotNullParam("hashCharset", hashCharset);
this.hashCharset = hashCharset;
return this;
}

/**
* Set the string format for hashed passwords to be used by the realm.
*
* @param hashEncoding the string format for the hashed passwords. Uses Base64 by default. (must not be {@code null})
* @return this builder.
*/
public FileSystemSecurityRealmBuilder setHashEncoding(final Encoding hashEncoding) {
Assert.checkNotNullParam("hashEncoding", hashEncoding);
this.hashEncoding = hashEncoding;
return this;
}

/**
* Set the SecretKey to be used by the realm.
*
* @param secretKey the symmetric SecretKey used to encrypt and decrypt the Security Realm (must not be {@code null})
* @return this builder.
*/
public FileSystemSecurityRealmBuilder setSecretKey(final SecretKey secretKey) {
Assert.checkNotNullParam("secretKey", secretKey);
this.secretKey = secretKey;
return this;
}

public FileSystemSecurityRealmBuilder setProviders(final Supplier<Provider[]> providers) {
Assert.checkNotNullParam("providers", providers);
this.providers = providers;
return this;
}

/**
* Builds a new {@link FileSystemSecurityRealm} instance based on configuration defined for this {@link FileSystemSecurityRealmBuilder} instance.
*
* @return the built realm
*/
public FileSystemSecurityRealm build() {
encoded = secretKey == null && encoded;
if (nameRewriter == null) {
nameRewriter = NameRewriter.IDENTITY_REWRITER;
}
if (hashEncoding == null) {
hashEncoding = Encoding.BASE64;
}
if (hashCharset == null) {
hashCharset = StandardCharsets.UTF_8;
}
return new FileSystemSecurityRealm(root, nameRewriter, levels, encoded, hashEncoding, hashCharset, providers, secretKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
-->

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:elytron:identity:1.1.0"
xmlns="urn:elytron:identity:1.1.0"
targetNamespace="urn:elytron:identity:1.1"
xmlns="urn:elytron:identity:1.1"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
version="1.0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,18 @@ public static String encrypt(final String clearText, final SecretKey secretKey)
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);

byte[] cipherText = cipher.doFinal(clearText.getBytes(StandardCharsets.UTF_8));
byte[] result = encrypt(clearText.getBytes(StandardCharsets.UTF_8), secretKey);
return ByteIterator.ofBytes(result).base64Encode().drainToString();
}

public static byte[] encrypt(final byte[] clearBytes, final SecretKey secretKey) throws GeneralSecurityException {
checkNotNullParam("clearBytes", clearBytes);
checkNotNullParam("secretKey", secretKey);

Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);

byte[] cipherText = cipher.doFinal(clearBytes);
byte[] iv = cipher.getIV();

byte[] result = new byte[iv.length + cipherText.length + 6];
Expand All @@ -72,42 +83,54 @@ public static String encrypt(final String clearText, final SecretKey secretKey)
// Cipher Text
System.arraycopy(cipherText, 0, result, 6 + iv.length, cipherText.length);

return ByteIterator.ofBytes(result).base64Encode().drainToString();
return result;
}

public static String decrypt(final String token, final SecretKey secretKey) throws GeneralSecurityException {
checkNotNullParam("secretKey", secretKey);

try {
ByteIterator byteIterator = CodePointIterator.ofString(checkNotNullParam("token", token)).base64Decode();
byte[] clearText = decrypt(byteIterator, secretKey);
return new String(clearText, StandardCharsets.UTF_8);
} catch (DecodeException e) {
throw log.unableToDecodeBase64Token(e);
}

byte[] prefixVersion = byteIterator.drain(5);
if (prefixVersion.length < 4 || prefixVersion[0] != 'E' || prefixVersion[1] != 'L' || prefixVersion[2] != 'Y') {
throw log.badKeyPrefix();
} else if (prefixVersion[3] != VERSION) {
throw log.unsupportedVersion(prefixVersion[3], VERSION);
} else if (prefixVersion[4] != CIPHER_TEXT_IDENTIFIER) {
throw log.unexpectedTokenType(toName((char) prefixVersion[4]), CIPHER_TEXT_NAME);
}

int ivLength = byteIterator.next();
byte[] iv = byteIterator.drain(ivLength);
byte[] cipherText = byteIterator.drain();

// We successfully disected the token, now decrypt the value.
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
AlgorithmParameterSpec spec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
}

byte[] clearText = cipher.doFinal(cipherText);
public static byte[] decrypt(final byte[] token, final SecretKey secretKey) throws GeneralSecurityException {
checkNotNullParam("secretKey", secretKey);

return new String(clearText, StandardCharsets.UTF_8);
try {
ByteIterator byteIterator = ByteIterator.ofBytes(token);
return decrypt(byteIterator, secretKey);

} catch (DecodeException e) {
throw log.unableToDecodeBase64Token(e);
}

}

private static byte[] decrypt(final ByteIterator byteIterator, final SecretKey secretKey) throws GeneralSecurityException {
byte[] prefixVersion = byteIterator.drain(5);
if (prefixVersion.length < 4 || prefixVersion[0] != 'E' || prefixVersion[1] != 'L' || prefixVersion[2] != 'Y') {
throw log.badKeyPrefix();
} else if (prefixVersion[3] != VERSION) {
throw log.unsupportedVersion(prefixVersion[3], VERSION);
} else if (prefixVersion[4] != CIPHER_TEXT_IDENTIFIER) {
throw log.unexpectedTokenType(toName((char) prefixVersion[4]), CIPHER_TEXT_NAME);
}

int ivLength = byteIterator.next();
byte[] iv = byteIterator.drain(ivLength);
byte[] cipherText = byteIterator.drain();

// We successfully dissected the token, now decrypt the value.
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
AlgorithmParameterSpec spec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);

return cipher.doFinal(cipherText);
}

}
Loading

0 comments on commit 22bb7be

Please sign in to comment.