API: sign requests by algorithm HmacSHA512 by default
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 113b97f..9bf7b2f 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 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 @@
 
     @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 61c8ae6..32d8b66 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.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.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.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 @@
 
     @Override
     public boolean start() {
-        Security.addProvider(new BouncyCastleProvider());
         Integer apiPort = IntegrationAPIPort.value(); // api port, null by default
         isPostRequestsAndTimestampsEnforced = EnforcePostRequestsAndTimestamps.value();
 
@@ -1139,18 +1139,23 @@
 
             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 @@
         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 0000000..7ccf74e
--- /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";
+}