seanDematic
seanDematic

Reputation: 33

Generating an SAS Token in Java to download a file in an Azure Data Storage container

Trying to generate an SAS Token to access certain files in a Storage Account. I'm using the methods listed here:

https://learn.microsoft.com/en-us/rest/api/eventhub/generate-sas-token

Now, the problem I have is I cannot, for the life of me, make the sasToken string work. If I generate the token via the Portal (Shared Access Signature in the Storage Account), I can access those files via a URL with the provided Token.

However I have yet to be able to generate an SAS token programmatically via Java using the methods I linked above. I think my problem is the StringToSign that is being encrypted. I've been following this example when constructing the string to encrypt:

https://learn.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas

All my efforts have resulted in either:

<AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail>

or

<AuthenticationErrorDetail>Signature did not match. String to sign used was <insert string details here>

Looking at the Portal generated sasToken that works for me:

?sv=2017-11-09&ss=f&srt=o&sp=r&se=2018-12-06T22:15:20Z&st=2018-12-06T14:15:20Z&spr=https&sig=%2Bi1TWv5D80U%2BoaIeoBh1wjaO1p4xVFx4nRZt%2FzwiszY%3D

It seems I need a String like so:

            String stringToSign = accountName + "\n" +
                "r\n" +
                "f\n" +
                "o\n" +
                URLEncoder.encode(start, "UTF-8") + "\n" +
                URLEncoder.encode(expiry, "UTF-8") + "\n" +
                "\n" +
                "https\n" +
                azureApiVersion;

Where accountName is the storage account name from Azure, and start/expiry are the start and expiry strings (ie- 2018-12-06T22:15:20Z) and azureApiVersion is "2017-11-09".

I then try to return the token after constructing the string like so:

        String signature = getHMAC256(key, stringToSign);
        sasToken = "sv=" + azureApiVersion +
                "&ss=f" +
                "&srt=o" +
                "&sp=r" +
                "&se=" +URLEncoder.encode(expiry, "UTF-8") +
                "&st=" + URLEncoder.encode(start, "UTF-8") +
                "&spr=https" +
                "&sig=" + URLEncoder.encode(signature, "UTF-8");

I've tried URL encoding and not URL encoding the the start/expiry dates as well, just in case that was messing things up. What am I missing?

Upvotes: 1

Views: 11037

Answers (2)

Dinu Nicolae
Dinu Nicolae

Reputation: 1301

I got a simpler method

SharedAccessAccountPolicy sharedAccessAccountPolicy = new SharedAccessAccountPolicy();
sharedAccessAccountPolicy.setPermissionsFromString("racwdlup");
long date = new Date().getTime();
long expiryDate = new Date(date + 8640000).getTime();
sharedAccessAccountPolicy.setSharedAccessStartTime(new Date(date));
sharedAccessAccountPolicy.setSharedAccessExpiryTime(new Date(expiryDate));
sharedAccessAccountPolicy.setResourceTypeFromString("sco");
sharedAccessAccountPolicy.setServiceFromString("bfqt");
String sasToken = "?" + storageAccount.generateSharedAccessSignature(sharedAccessAccountPolicy);

You can get the storage account like this:

private String storageConnectionString = "DefaultEndpointsProtocol=https;AccountName=<storage name>;AccountKey=<your key>;EndpointSuffix=core.windows.net";
storageAccount = CloudStorageAccount.parse(storageConnectionString);

Upvotes: 3

Jerry Liu
Jerry Liu

Reputation: 17800

Three points to fix

  1. getHMAC256 method problem as mentioned by @Gaurav

  2. Don't encode expiry and start in stringToSign or the signature won't match. Because the encoded part in url will be decoded by Azure Storage Service to calculate the expected signature.

  3. In stringToSign, miss one \n after azureApiVersion.

Here's the complete sample.

 public static void GetFileSAS(){

    String accountName = "accountName";
    String key = "accountKey";
    String resourceUrl = "https://"+accountName+".file.core.windows.net/fileShare/fileName";

    String start = "startTime";
    String expiry = "expiry";
    String azureApiVersion = "2017-11-09";

    String stringToSign = accountName + "\n" +
                "r\n" +
                "f\n" +
                "o\n" +
                start + "\n" +
                expiry + "\n" +
                "\n" +
                "https\n" +
                azureApiVersion+"\n";

    String signature = getHMAC256(key, stringToSign);

    try{

        String sasToken = "sv=" + azureApiVersion +
            "&ss=f" +
            "&srt=o" +
            "&sp=r" +
            "&se=" +URLEncoder.encode(expiry, "UTF-8") +
            "&st=" + URLEncoder.encode(start, "UTF-8") +
            "&spr=https" +
            "&sig=" + URLEncoder.encode(signature, "UTF-8");

    System.out.println(resourceUrl+"?"+sasToken);

    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
}

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("UTF-8")));
    } catch (Exception e) {
        e.printStackTrace();
    }
    return signature;
}

Upvotes: 7

Related Questions