tyde hydo
tyde hydo

Reputation: 31

Azure blob storage error showing "Make sure the value of Authorization header is formed correctly including the signature."

I am using restapi to fetch data from azure blob storage using the access keys. I have a postman collection which i can use to generate the signature string for the azure using the access keys.

But when i implement the same logic in java language it fails. It is giving me the error "403 Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.: [AuthenticationFailedServer failed to authenticate the request. Make sure the value of Authorization header is formed correctly inclu... (435 bytes)]"

here is the postman test pre req part which generate the signature string.

    // Should be UTC GMT string
    pm.environment.set("utcStr", new Date().toUTCString());

    // Get hash of all header-name:value
    const headers = pm.request.getHeaders({ ignoreCase: true, enabled: true });

    // Construct Signature value for Authorization header
    var signatureParts = [
      pm.request.method.toUpperCase(),
      headers["content-encoding"] || "",
      headers["content-language"] || "",
      headers["content-length"]  || "",
      //    pm.request.body ? pm.request.body.toString().length || "" : "",
      headers["content-md5"] || "",
      headers["content-type"] || "",
      headers["x-ms-date"] ? "" : (pm.environment.get("utcStr") || ""),
      headers["if-modified-since"] || "",
      headers["if-match"] || "",
      headers["if-none-match"] || "",
      headers["if-unmodified-since"] || "",
      headers["range"] || ""
    ];

    // Construct CanonicalizedHeaders
    const canonicalHeaderNames = [];
    Object.keys(headers).forEach(key => {
      if (key.startsWith("x-ms-")) {
          canonicalHeaderNames.push(key);
      }
    });
    // Sort headers lexographically by name
    canonicalHeaderNames.sort();

    const canonicalHeaderParts = [];
    canonicalHeaderNames.forEach(key => {
      let value = pm.request.getHeaders({ ignoreCase: true, enabled: true })[key];

      // Populate variables
      value = pm.environment.replaceIn(value);

      // Replace whitespace in value but not if its within quotes
      if (!value.startsWith("\"")) {
        value = value.replace(/\s+/, " ");
      }

      canonicalHeaderParts.push(`${key}:${value}`);
    });

    // Add headers to signature
    signatureParts.push.apply(signatureParts, canonicalHeaderParts);

    // Construct CanonicalizedResource
    const canonicalResourceParts = [
      `/${pm.environment.get("storeAcc")}${pm.request.url.getPath()}`
    ];
    const canonicalQueryNames = [];
    pm.request.url.query.each(query => {
      canonicalQueryNames.push(query.key.toLowerCase());
    });
    canonicalQueryNames.sort();
    canonicalQueryNames.forEach(queryName => {
      const value = pm.request.url.query.get(queryName);

      // NOTE: This does not properly explode multiple same query params' values
      // and turn them into comma-separated list
      canonicalResourceParts.push(`${queryName}:${value}`);
    });
    // Add resource to signature
    signatureParts.push.apply(signatureParts, canonicalResourceParts);

    console.log("Signature Parts", signatureParts);
 
    // Now, construct signature raw string
    const signatureRaw = signatureParts.join("\n");

    console.log("Signature String", JSON.stringify(signatureRaw));

    // Hash it using HMAC-SHA256 and then encode using base64
    const storageKey = pm.variables.get("accountKey");
    const signatureBytes = CryptoJS.HmacSHA256(signatureRaw,    CryptoJS.enc.Base64.parse(storageKey));
    console.log("signatureBytes",signatureBytes);
    const signatureEncoded = signatureBytes.toString(CryptoJS.enc.Base64);

    console.log("signatureEncoded",signatureEncoded);
    console.log("Storage Account", pm.environment.get("storeAcc"));
    console.log("Storage Key", storageKey);

    // Finally, make it available for headers
    pm.environment.set("sigStr",
       `SharedKey ${pm.environment.get("storeAcc")}:${signatureEncoded}`); 

This code works and the signature i generate is authenticated and i get access to blob apis.

but i implemented the same code in java and every time it gives me the same problem "403 Authentication error."

Java code

    public String generateOauthHeader(String method, UserDetails userFields, String baseUrl, String azureFileListUrl)  {

        String key = userFields.getAccountKey();

        DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ");
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        String startDate = dateFormat.format(new Date());
        
        String stringToSign = method + "\\n\\n\\n\\n\\napplication/json\\n\\n\\n\\n\\n\\n\\nx-ms-date:" + "Tue, 05 Jul 2022 11:45:00 "
                + "GMT\\nx-ms-version:2020-04-08\\n/" + userFields.getAccountName() + "/"
                + userFields.getContainerName();

        if (null != azureFileListUrl) {
            azureFileListUrl =azureFileListUrl.replace("?", "");
            azureFileListUrl =azureFileListUrl.replace("=", ":");
            String[] paramlist = azureFileListUrl.split("&");

            stringToSign = stringToSign + "\\n" + paramlist[1] + "\\n" + paramlist[0];
        }
        stringToSign="\""+stringToSign+"\"";
        System.err.println("Signature string  =  " + stringToSign);
        String signature = getHMAC256(key, stringToSign);

        signature = "SharedKey " + userFields.getAccountName() + ":" + signature;
        System.err.println("Final Signature string  =  " + signature);
        return signature;
    }

    private static String getHMAC256(String accountKey, String signStr) {
        String signature = null;
        try {
            SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(accountKey), "HmacSHA256");
            Mac sha256HMAC = Mac.getInstance("HmacSHA256");
            sha256HMAC.init(secretKey);
            signature = Base64.getEncoder().encodeToString(sha256HMAC.doFinal(signStr.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return signature;
    }

I am adding below two string which are generated around same time.

The sample signatureString before signing are identical. I have printed and checked them as well.

postman= "GET\n\n\n\n\napplication/json\n\n\n\n\n\n\nx-ms-date:Tue, 05 Jul 2022 11:45:00 GMT\nx-ms-version:2020-04-08\n/osmosconnectortest/blobcontainer\ncomp:list\nrestype:container"

code = "GET\n\n\n\n\napplication/json\n\n\n\n\n\n\nx-ms-date:Tue, 05 Jul 2022 11:44:56 GMT\nx-ms-version:2020-04-08\n/osmosconnectortest/blobcontainer\ncomp:list\nrestype:container"

EDIT: here are the headers sent with the request.

      requestHeaders.put("x-ms-date", "Tue, 05 Jul 2022 11:45:00 GMT");
      requestHeaders.put("x-ms-version", "2020-04-08");
      requestHeaders.put("Content-Type", "application/json");
      requestHeaders.put("Authorization", "SharedKey accountName:yvfdsfdssdfF0lGGtEWGU+b7BqFY0UHMkvI=
");

Upvotes: 2

Views: 1894

Answers (1)

Gaurav Mantri
Gaurav Mantri

Reputation: 136346

I believe the issue is with the following line of code:

requestHeaders.put("x-ms-date", "Tue, 05 Jul 2022 11:45:00 GMT");

Essentially in your request headers, you are sending the date as Tue, 05 Jul 2022 11:45:00 GMT however in your stringToSign you are sending the date as Tue, 05 Jul 2022 11:44:56 GMT in x-ms-date. This would cause the stringToSign that you are sending and the one computed by the server to mismatch and thus resulting in different authorization header values.

To fix this, please ensure that the value for x-ms-date header is the same in both stringToSign and your request headers. I would recommend passing the date object to generateOauthHeader method and use the same date object in your request headers for x-ms-date.

Upvotes: 0

Related Questions