/*
* JBoss, Home of Professional Open Source
*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* 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.password.spec;
import org.wildfly.security._private.ElytronMessages;
import org.wildfly.security.password.Password;
import org.wildfly.security.password.PasswordFactory;
import org.wildfly.security.util.ByteIterator;
import org.wildfly.security.util.ByteStringBuilder;
import org.wildfly.security.util.CodePointIterator;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.util.function.Supplier;
/**
* Provide methods for encoding and decoding of {@link PasswordSpec}.
*
* @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public final class BasicPasswordSpecEncoding {
// the value for each identifier must be kept and can not change
private static final byte CLEAR_PASSWORD_SPEC_ID = 1;
private static final byte DIGEST_PASSWORD_SPEC_ID = 2;
private static final byte HASH_PASSWORD_SPEC_ID = 3;
private static final byte SALTED_HASH_PASSWORD_SPEC_ID = 4;
private static final byte ITERATED_SALTED_HASH_SPEC_ID = 5;
private BasicPasswordSpecEncoding() {}
/**
* Encode the given {@link PasswordSpec} to a byte array.
*
* @param passwordSpec the password spec to encode
* @return a byte array representing the encoded password or null if no encoder was capable to encode the given password
*/
public static byte[] encode(PasswordSpec passwordSpec) throws NoSuchAlgorithmException, InvalidKeySpecException {
if (passwordSpec instanceof ClearPasswordSpec) {
return encodeClearPasswordSpec((ClearPasswordSpec) passwordSpec);
} else if (passwordSpec instanceof DigestPasswordSpec) {
return encodeDigestPasswordSpec((DigestPasswordSpec) passwordSpec);
} else if (passwordSpec instanceof SaltedHashPasswordSpec) {
return encodeSaltedHashPasswordSpec((SaltedHashPasswordSpec) passwordSpec);
} else if (passwordSpec instanceof IteratedSaltedHashPasswordSpec) {
return encodeIteratedSaltedHashSpec((IteratedSaltedHashPasswordSpec) passwordSpec);
} else if (passwordSpec instanceof HashPasswordSpec) {
return encodeHashPasswordSpec((HashPasswordSpec) passwordSpec);
}
return null;
}
/**
* Encode the given {@link Password} to a byte array.
*
* @param password the password to encode
* @return a byte array representing the encoded password or null if no encoder was capable to encode the given password
*/
public static byte[] encode(Password password) throws NoSuchAlgorithmException, InvalidKeySpecException {
return encode(password, Security::getProviders);
}
/**
* Encode the given {@link Password} to a byte array.
*
* @param password the password to encode
* @param providers providers to use with the underlying {@link PasswordFactory}
* @return a byte array representing the encoded password or null if no encoder was capable to encode the given password
*/
public static byte[] encode(Password password, Supplier<Provider[]> providers) throws NoSuchAlgorithmException, InvalidKeySpecException {
PasswordFactory passwordFactory = PasswordFactory.getInstance(password.getAlgorithm(), providers);
if (passwordFactory.convertibleToKeySpec(password, ClearPasswordSpec.class)) {
return encodeClearPasswordSpec(passwordFactory.getKeySpec(password, ClearPasswordSpec.class));
} else if (passwordFactory.convertibleToKeySpec(password, DigestPasswordSpec.class)) {
return encodeDigestPasswordSpec(passwordFactory.getKeySpec(password, DigestPasswordSpec.class));
} else if (passwordFactory.convertibleToKeySpec(password, SaltedHashPasswordSpec.class)) {
return encodeSaltedHashPasswordSpec(passwordFactory.getKeySpec(password, SaltedHashPasswordSpec.class));
} else if (passwordFactory.convertibleToKeySpec(password, IteratedSaltedHashPasswordSpec.class)) {
return encodeIteratedSaltedHashSpec(passwordFactory.getKeySpec(password, IteratedSaltedHashPasswordSpec.class));
} else if (passwordFactory.convertibleToKeySpec(password, HashPasswordSpec.class)) {
return encodeHashPasswordSpec(passwordFactory.getKeySpec(password, HashPasswordSpec.class));
}
return null;
}
/**
* Decode the given byte array and create a {@link PasswordSpec} from it.
*
* @param encoded the byte array representing the encoded password
* @return a {@link PasswordSpec} instance created from the encoded password or null if no decoder was capable to decode the given format.
*/
public static PasswordSpec decode(byte[] encoded) {
ByteIterator iterator = ByteIterator.ofBytes(encoded);
int identifier;
try {
identifier = iterator.next();
} catch (Exception e) {
throw ElytronMessages.log.couldNotObtainKeySpecEncodingIdentifier();
}
switch (identifier) {
case CLEAR_PASSWORD_SPEC_ID:
return decodeClearPasswordSpec(iterator);
case DIGEST_PASSWORD_SPEC_ID:
return decodeDigestPasswordSpec(iterator);
case HASH_PASSWORD_SPEC_ID:
return decodeHashPasswordSpec(iterator);
case SALTED_HASH_PASSWORD_SPEC_ID:
return decodeSaltedHashPasswordSpec(iterator);
case ITERATED_SALTED_HASH_SPEC_ID:
return decodeIteratedSaltedHashPasswordSpec(iterator);
default:
return null;
}
}
private static byte[] encodeIteratedSaltedHashSpec(IteratedSaltedHashPasswordSpec keySpec) throws InvalidKeySpecException {
byte[] salt = keySpec.getSalt();
return new ByteStringBuilder().append(ITERATED_SALTED_HASH_SPEC_ID)
.appendPackedUnsignedBE(keySpec.getIterationCount()).appendPackedUnsignedBE(salt.length).append(salt).append(keySpec.getHash()).toArray();
}
private static PasswordSpec decodeIteratedSaltedHashPasswordSpec(ByteIterator iterator) {
int iterationCount = iterator.getPackedBE32();
byte[] salt = iterator.drain(iterator.getPackedBE32());
byte[] hash = iterator.drain();
return new IteratedSaltedHashPasswordSpec(hash, salt, iterationCount);
}
private static byte[] encodeSaltedHashPasswordSpec(SaltedHashPasswordSpec keySpec) throws InvalidKeySpecException {
byte[] salt = keySpec.getSalt();
return new ByteStringBuilder().append(SALTED_HASH_PASSWORD_SPEC_ID)
.appendPackedUnsignedBE(salt.length).append(salt).append(keySpec.getHash()).toArray();
}
private static PasswordSpec decodeSaltedHashPasswordSpec(ByteIterator iterator) {
byte[] salt = iterator.drain(iterator.getPackedBE32());
byte[] hash = iterator.drain();
return new SaltedHashPasswordSpec(hash, salt);
}
private static byte[] encodeHashPasswordSpec(HashPasswordSpec keySpec) throws InvalidKeySpecException {
return new ByteStringBuilder().append(HASH_PASSWORD_SPEC_ID).append(keySpec.getDigest()).toArray();
}
private static PasswordSpec decodeHashPasswordSpec(ByteIterator iterator) {
return new HashPasswordSpec(iterator.drain());
}
private static byte[] encodeDigestPasswordSpec(DigestPasswordSpec keySpec) throws InvalidKeySpecException {
byte[] u = keySpec.getUsername().getBytes(StandardCharsets.UTF_8);
byte[] r = keySpec.getRealm().getBytes(StandardCharsets.UTF_8);
return new ByteStringBuilder().append(DIGEST_PASSWORD_SPEC_ID)
.appendPackedUnsignedBE(u.length).append(u)
.appendPackedUnsignedBE(r.length).append(r)
.append(keySpec.getDigest()).toArray();
}
private static PasswordSpec decodeDigestPasswordSpec(ByteIterator iterator) {
String username = iterator.drainToUtf8(iterator.getPackedBE32());
String realm = iterator.drainToUtf8(iterator.getPackedBE32());
byte[] digest = iterator.drain();
return new DigestPasswordSpec(username, realm, digest);
}
private static byte[] encodeClearPasswordSpec(ClearPasswordSpec keySpec) throws InvalidKeySpecException {
byte[] passwordBytes = CodePointIterator.ofChars(keySpec.getEncodedPassword()).asUtf8().drain();
return new ByteStringBuilder().append(CLEAR_PASSWORD_SPEC_ID).append(passwordBytes).toArray();
}
private static PasswordSpec decodeClearPasswordSpec(ByteIterator iterator) {
return new ClearPasswordSpec(iterator.asUtf8String().drainToString().toCharArray());
}
}