0

I'm using Ruby to generate a blob URL. e.g.:

https://anasstoragetest2023.blob.core.windows.net/telestream/test.mov?sp=r&st=2023-03-14T20:23:04Z&se=2023-03-18T20:23:04Z&spr=https&sv=2021-12-02&sr=b&sig=6wpeYAlfpnGLPtk5PcIe/P+0q+XeLkIT6XLFR6uY5Os=

The error I get is "Signature did not match":

  <Error>
   <Code>AuthenticationFailed</Code>
   <Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. RequestId:0daa3721-e01e-0002-3049-58f5bf000000 Time:2023-03-16T20:55:10.7515529Z</Message>
   <AuthenticationErrorDetail>Signature did not match. String to sign used was r 2023-03-14T20:52:54Z 2023-03-18T20:52:54Z /blob/anasstoragetest2023/telestream/test.mov https 2021-12-02 b </AuthenticationErrorDetail>
  </Error>

Although output of the code shows that the signed string is identical to the one logged in the error message:

String to sign: r 2023-03-14T20:52:54Z 2023-03-18T20:52:54Z /blob/anasstoragetest2023/telestream/test.mov   https 2021-12-02 b      

I am aware that there is a SAS generator provided by Azure here but it also generates the same error:

blob_path = "/blob/#{account_name}/#{container_name}/#{blob_name}"
signature = 
    Azure::Storage::Common::Core::Auth::SharedAccessSignature.new(account_name, account_key)
sas_token = 
    signature.generate_service_sas_token(blob_path, service: 'b', resource: 'b')

puts "\nCreated SAS token: #{sas_token}"
url = "https://#{account_name}.blob.core.windows.net/#{container_name}/#{blob_name}?#{sas_token}"
puts "\nCreated URL: #{url}"

Custom ruby code below. I tried with both ruby 3.1.3 and 2.6.8. Used gem install azure-storage-blob for both.

Any help would be appreciated!

def generate_blob_url(account_name, account_key, container_name, blob_name)
start_time = Time.now
days = 60*60*24*2

signed_permissions = "r"
signed_start = (start_time - days).utc.iso8601
signed_expiry = (start_time + days).utc.iso8601
canonicalized_resource = "/blob/#{account_name}/#{container_name}/#{blob_name}"
signed_identifier = ""
signed_ip = ""
signed_protocol = "https"
signed_version = "2021-12-02" # "2018-11-09"
signed_resource = "b"
signed_snapshottime = ""
rscc = ""
rscd = ""
rsce = ""
rscl = ""
rsct = ""

string_to_sign = signed_permissions + "\n" +
      signed_start + "\n" +
      signed_expiry + "\n" +
      canonicalized_resource + "\n" +
      signed_identifier + "\n" +
      signed_ip + "\n" +
      signed_protocol + "\n" +
      signed_version + "\n" +
      signed_resource + "\n" +
      signed_snapshottime + "\n" +
      rscc + "\n" +
      rscd + "\n" +
      rsce + "\n" +
      rscl + "\n" +
      rsct

puts "\nString to sign: #{string_to_sign.gsub("\n", " ")}"
sig = Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', account_key, string_to_sign))
token = "sp=#{signed_permissions}&st=#{signed_start}&se=#{signed_expiry}&spr=#{signed_protocol}&sv=#{signed_version}&sr=#{signed_resource}&sig=#{sig}"
puts "\nGenerated token: #{token}"

url = "https://#{account_name}.blob.core.windows.net/#{container_name}/#{blob_name}?#{CGI.escape(token)}"
end
anode84
  • 308
  • 2
  • 9

1 Answers1

0

So,

The azure-ruby-storage client library doesn't work. The signature generator is outdated.

I managed to fix the custom code. I was missing a bunch of newline characters that needed to sit in between the signature body. It's ridiculous that Azure doesn't just take out additional newlines from the signature body before generating the signature. Even the error message does not include those newlines.

Anyway, here's the fixed code below:

def run_sample
    
    account_name = "*****"
    account_key = "*****"
    container_name = "my_container"
    blob_name = "my_blob"
    
    
    now = Time.now
    start_time = Time.new(now.year, now.month, now.day, 0, 0, 0, "Z")
    expiry_duration = 60*60*24*2

    sas_token = generate_signature(
        account_name, account_key, container_name, blob_name, start_time, expiry_duration)
    puts "\nSAS token: #{sas_token}"
    url = "https://#{account_name}.blob.core.windows.net/#{container_name}/#{blob_name}?#{sas_token}"
    puts "\nURL: #{url}"
end

def generate_signature(account_name, account_key, container_name, blob_name, start_time, expiry_duration)  
    signed_permissions = "r"
    signed_start = (start_time).iso8601
    signed_expiry = (start_time + expiry_duration).iso8601
    canonicalized_resource = "/blob/#{account_name}/#{container_name}/#{blob_name}"
    signed_protocol = "https"
    signed_version = "2021-12-02"
    signed_resource = "b"
    
    wc = "\n"
    string_to_sign = 
        signed_permissions + wc +
        signed_start + wc +
        signed_expiry + wc +
        canonicalized_resource + wc +
        wc + # identifier
        wc + # ip
        signed_protocol + wc +
        signed_version + wc +
        signed_resource + wc +
        wc + # timestamp 
        wc + # cache control
        wc + # content disposition
        wc + # content encoding
        wc + # content language
        wc # content type 
          
    puts "\nString to sign:\n#{string_to_sign.gsub(wc, " ")}"
    
    sig = CGI.escape(
            Base64.strict_encode64(
                OpenSSL::HMAC.digest('sha256', 
                    Base64.decode64(account_key), string_to_sign)))
    
    puts "\nSignature: #{sig}"

    token = "sp=#{signed_permissions}&st=#{signed_start}&se=#{signed_expiry}&spr=#{signed_protocol}&sv=#{signed_version}&sr=#{signed_resource}&sig=#{sig}"
end

run_sample
anode84
  • 308
  • 2
  • 9