This is an automated email from the ASF dual-hosted git repository.

weizhouapache pushed a commit to branch 4.24-new-api-sign-algorithm
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit b48d21f8305aed8d30f5702f95feba1dc1b9412b
Author: Wei Zhou <[email protected]>
AuthorDate: Fri Aug 21 16:22:17 2026 +0200

    API: sign requests by algorithm HmacSHA512 by default
---
 .../cloudstack/config/ApiServiceConfiguration.java |  7 +++-
 server/src/main/java/com/cloud/api/ApiServer.java  | 39 +++++++++++++++-------
 .../java/com/cloud/utils/security/Algorithms.java  | 26 +++++++++++++++
 3 files changed, 59 insertions(+), 13 deletions(-)

diff --git 
a/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java 
b/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java
index 113b97f43c8..9bf7b2fd376 100644
--- 
a/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java
+++ 
b/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java
@@ -35,6 +35,11 @@ public class ApiServiceConfiguration implements Configurable 
{
     public static final ConfigKey<String> ApiAllowedSourceCidrList = new 
ConfigKey<>(String.class, "api.allowed.source.cidr.list", "Advanced",
             "0.0.0.0/0,::/0", "Comma separated list of IPv4/IPv6 CIDRs from 
which API calls can be performed. Can be set on Global and Account levels.", 
true, ConfigKey.Scope.Account, null, null, null, null, null, 
ConfigKey.Kind.CSV, null);
 
+    public static final ConfigKey<Boolean> ApiLegacyAlgorithmSupported = new 
ConfigKey<>("Advanced", Boolean.class, "api.legacy.algorithm.supported",
+            "true", "In older versions, the CloudStack API server used the 
HmacSHA1 algorithm to sign and validate requests. " +
+            "If this setting is enabled, HmacSHA1 will remain supported 
(alongside the newer HmacSHA512 algorithm), ensuring compatibility with tools " 
+
+            "such as CloudMonkey, cloudstack-go, and other third-party 
projects. " +
+            "If disabled, only the HmacSHA512 algorithm will be supported.", 
true, ConfigKey.Scope.Global);
 
     public static void validateEndpointUrl() {
         String csUrl = getApiServletPathValue();
@@ -55,7 +60,7 @@ public class ApiServiceConfiguration implements Configurable {
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] {ManagementServerAddresses, ApiServletPath, 
DefaultUIPageSize, ApiSourceCidrChecksEnabled, ApiAllowedSourceCidrList};
+        return new ConfigKey<?>[] {ManagementServerAddresses, ApiServletPath, 
DefaultUIPageSize, ApiSourceCidrChecksEnabled, ApiAllowedSourceCidrList, 
ApiLegacyAlgorithmSupported};
     }
 
 }
diff --git a/server/src/main/java/com/cloud/api/ApiServer.java 
b/server/src/main/java/com/cloud/api/ApiServer.java
index 61c8ae68c5b..32d8b66b3b4 100644
--- a/server/src/main/java/com/cloud/api/ApiServer.java
+++ b/server/src/main/java/com/cloud/api/ApiServer.java
@@ -31,8 +31,9 @@ import java.net.Socket;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URLEncoder;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
-import java.security.Security;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -152,7 +153,6 @@ import org.apache.http.protocol.ResponseDate;
 import org.apache.http.protocol.ResponseServer;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.springframework.stereotype.Component;
 
 import com.cloud.api.dispatch.DispatchChainFactory;
@@ -204,6 +204,7 @@ import com.cloud.utils.db.UUIDManager;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.exception.ExceptionProxyObject;
 import com.cloud.utils.net.NetUtils;
+import com.cloud.utils.security.Algorithms;
 import com.google.gson.reflect.TypeToken;
 
 @Component
@@ -467,7 +468,6 @@ public class ApiServer extends ManagerBase implements 
HttpRequestHandler, ApiSer
 
     @Override
     public boolean start() {
-        Security.addProvider(new BouncyCastleProvider());
         Integer apiPort = IntegrationAPIPort.value(); // api port, null by 
default
         isPostRequestsAndTimestampsEnforced = 
EnforcePostRequestsAndTimestamps.value();
 
@@ -1139,18 +1139,23 @@ public class ApiServer extends ManagerBase implements 
HttpRequestHandler, ApiSer
 
             unsignedRequest = unsignedRequest.toLowerCase();
 
-            final Mac mac = Mac.getInstance("HmacSHA1");
-            final SecretKeySpec keySpec = new 
SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
-            mac.init(keySpec);
-            mac.update(unsignedRequest.getBytes());
+            String santizedSignature = 
signature.replaceAll(SANITIZATION_REGEX, "_");
 
-            final byte[] encryptedBytes = mac.doFinal();
-            final String computedSignature = 
Base64.encodeBase64String(encryptedBytes);
-            final boolean equalSig = 
ConstantTimeComparator.compareStrings(signature, computedSignature);
+            final boolean apiLegacyAlgorithmSupported = 
ApiServiceConfiguration.ApiLegacyAlgorithmSupported.value();
+            List<String> algorithms = apiLegacyAlgorithmSupported ? 
List.of(Algorithms.HMAC_SHA512, Algorithms.HMAC_SHA1) : 
List.of(Algorithms.HMAC_SHA512);
+            boolean equalSig = false;
+            for (String algorithm : algorithms) {
+                String computedSignature = getComputedSignature(algorithm, 
secretKey, unsignedRequest);
+                equalSig = ConstantTimeComparator.compareStrings(signature, 
computedSignature);
+                if (!equalSig) {
+                    logger.info("User signature [{}] is not equaled to 
computed signature [{}] with algorithm {}.", santizedSignature, 
computedSignature, algorithm);
+                } else {
+                    logger.debug("User signature [{}] is equaled to computed 
signature [{}] with algorithm {}.", santizedSignature, computedSignature, 
algorithm);
+                    break;
+                }
+            }
 
             if (!equalSig) {
-                signature = signature.replaceAll(SANITIZATION_REGEX, "_");
-                logger.info("User signature [{}] is not equaled to computed 
signature [{}].", signature, computedSignature);
                 return false;
             }
             CallContext.register(user, account);
@@ -1170,6 +1175,16 @@ public class ApiServer extends ManagerBase implements 
HttpRequestHandler, ApiSer
         return false;
     }
 
+    private String getComputedSignature(String algorithm, String secretKey, 
String unsignedRequest) throws NoSuchAlgorithmException, InvalidKeyException {
+        final Mac mac = Mac.getInstance(algorithm);
+        final SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), 
algorithm);
+        mac.init(keySpec);
+        mac.update(unsignedRequest.getBytes());
+
+        final byte[] encryptedBytes = mac.doFinal();
+        return Base64.encodeBase64String(encryptedBytes);
+    }
+
     private boolean commandAvailable(final InetAddress remoteAddress, final 
String commandName, final User user, ApiKeyPair keyPair, 
ApiKeyPairPermission... rolePermissions) {
         try {
             checkCommandAvailable(user, commandName, remoteAddress, keyPair, 
rolePermissions);
diff --git a/utils/src/main/java/com/cloud/utils/security/Algorithms.java 
b/utils/src/main/java/com/cloud/utils/security/Algorithms.java
new file mode 100644
index 00000000000..7ccf74e6db2
--- /dev/null
+++ b/utils/src/main/java/com/cloud/utils/security/Algorithms.java
@@ -0,0 +1,26 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 com.cloud.utils.security;
+
+public class Algorithms {
+
+    public static final String HMAC_SHA1 = "HmacSHA1";
+    public static final String HMAC_SHA512 = "HmacSHA512";
+}

Reply via email to