diff --git a/server/src/main/java/org/elasticsearch/common/SecureRandomHolder.java b/server/src/main/java/org/elasticsearch/common/SecureRandomHolder.java index 455d477f787eb..a9f936be6d7a5 100644 --- a/server/src/main/java/org/elasticsearch/common/SecureRandomHolder.java +++ b/server/src/main/java/org/elasticsearch/common/SecureRandomHolder.java @@ -10,7 +10,7 @@ import java.security.SecureRandom; -class SecureRandomHolder { +public class SecureRandomHolder { // class loading is atomic - this is a lazy & safe singleton to be used by this package public static final SecureRandom INSTANCE = new SecureRandom(); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java index 783c588565ec2..1e69945584b7f 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java @@ -738,7 +738,7 @@ public static List getAvailableAlgoStoredHash() { return Arrays.stream(Hasher.values()) .map(Hasher::name) .map(name -> name.toLowerCase(Locale.ROOT)) - .filter(name -> (name.startsWith("pbkdf2") || name.startsWith("bcrypt"))) + .filter(name -> (name.startsWith("pbkdf2") || name.startsWith("bcrypt") || "ssha256".equals(name))) .collect(Collectors.toList()); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java index 90566e25b4ea5..41318ad308198 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java @@ -36,8 +36,8 @@ import org.elasticsearch.client.internal.Client; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.SecureRandomHolder; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.cache.Cache; @@ -160,7 +160,7 @@ public class ApiKeyService implements Closeable { public static final Setting PASSWORD_HASHING_ALGORITHM = XPackSettings.defaultStoredHashAlgorithmSetting( "xpack.security.authc.api_key.hashing.algorithm", - (s) -> Hasher.PBKDF2.name() + (s) -> Hasher.SSHA256.name() ); public static final Setting DELETE_TIMEOUT = Setting.timeSetting( "xpack.security.authc.api_key.delete.timeout", @@ -541,7 +541,9 @@ private void createApiKeyAndIndexIt( ) { final Instant created = clock.instant(); final Instant expiration = getApiKeyExpiration(created, request.getExpiration()); - final SecureString apiKey = UUIDs.randomBase64UUIDSecureString(); + // the difference between 16 and 18 effectively results in the same "encoded" API Key that's sent in HTTP request headers, + // dues to base64 padding + final SecureString apiKey = getBase64SecureRandomString(request.getType() == ApiKey.Type.CROSS_CLUSTER ? 16 : 18); assert ApiKey.Type.CROSS_CLUSTER != request.getType() || API_KEY_SECRET_LENGTH == apiKey.length() : "Invalid API key (name=[" + request.getName() + "], type=[" + request.getType() + "], length=[" + apiKey.length() + "])"; @@ -2725,4 +2727,22 @@ public void invalidateAll() { roleDescriptorsBytesCache.invalidateAll(); } } + + private static SecureString getBase64SecureRandomString(int randomBytesCount) { + byte[] randomBytes = null; + byte[] encodedBytes = null; + try { + randomBytes = new byte[randomBytesCount]; + SecureRandomHolder.INSTANCE.nextBytes(randomBytes); + encodedBytes = Base64.getUrlEncoder().withoutPadding().encode(randomBytes); + return new SecureString(CharArrays.utf8BytesToChars(encodedBytes)); + } finally { + if (randomBytes != null) { + Arrays.fill(randomBytes, (byte) 0); + } + if (encodedBytes != null) { + Arrays.fill(encodedBytes, (byte) 0); + } + } + } }