2011年5月20日金曜日

Azure Storage 関係の REST API で利用する Authorization ヘッダ

現在 Azure のストレージ関係で利用する REST API を色々試行錯誤しながら試しているところですが、これら ストレージ用 REST API を利用するにあたり、真面目にやると物凄くよくわからない部分が Authorization ヘッダの SharedKey 値ではないでしょうか。
MSDN にある程度記述があるのですが、読んでいてもいまいちピンとこないというかさっぱりわかりませんw 色々と web 上の資料やサンプルコードを探しているうちに、海外サイトで丁度良いサンプルソースがあったのでこれを VB 用に移植したものを紹介します。

   1: Protected Overridable Function CreateAuthStrings(ByVal wq As WebRequest) As String
   2:      Dim headerStrings As New System.Text.StringBuilder
   3:  
   4:      headerStrings.Append(wq.Method + ControlChars.Lf) 'HTTP Verb
   5:      headerStrings.Append(ControlChars.Lf) 'Content-Encoding 
   6:      headerStrings.Append(ControlChars.Lf) 'Content-Language 
   7:      headerStrings.Append(ControlChars.Lf) 'Content-Length 
   8:      headerStrings.Append(ControlChars.Lf) 'Content-MD5
   9:      headerStrings.Append(wq.ContentType + ControlChars.Lf) 'Content-Type
  10:  
  11:      headerStrings.Append(ControlChars.Lf) 'Date
  12:      headerStrings.Append(ControlChars.Lf) 'If-Modified-Since 
  13:      headerStrings.Append(ControlChars.Lf) 'If-Match 
  14:      headerStrings.Append(ControlChars.Lf) 'If-None-Match  
  15:      headerStrings.Append(ControlChars.Lf) 'If-Unmodified-Since 
  16:      headerStrings.Append(ControlChars.Lf) 'Range
  17:  
  18:      'CanonicalizedHeaders
  19:      headerStrings.Append(GetCanonicalizedHeaders(wq))
  20:      'CanonicalizedResource
  21:      headerStrings.Append(GetCanonicalizedResource(wq.RequestUri, Me.Account))
  22:  
  23:      ' UTF8 -> Byte -> Base64
  24:      Dim headerUTF8 = System.Text.Encoding.UTF8.GetBytes(headerStrings.ToString())
  25:      Dim skey = Convert.FromBase64String(Me.SharedKey)
  26:      Using hmacSHA256 As New System.Security.Cryptography.HMACSHA256(skey)
  27:          Return Convert.ToBase64String(hmacSHA256.ComputeHash(headerUTF8))
  28:      End Using
  29:  
  30:  End Function
まず SharedKey というのは Azure ストレージへアクセスする際に必要となるセキュリティ用の値です。ストレージという「管理者以外もアクセスする可能性が高い」部分でのセキュリティ対策ですので、今までに利用していたクライアント証明書ではなくこれを利用します。
まず SharedKey を組み立てるのに必要な要素ですが、リクエストのヘッダに設定される各値、リクエスト先の Uri アドレス、ストレージのアカウント名などが必要です。上記サンプルソースは後日公開予定のストレージ関係アクティビティの一部ですが、大まかなイメージはつかめると思います。
この中で最も困っていたのが CanonicalizedHeaders と CanonicalizedResource です。CanonicalizedHeaders はリクエストヘッダに設定されている一部の値を編集したもので、CanonicalizedResource はリクエスト Uri (パラメータを含む)を編集したものとなります。まずCanonicalizedHeaders を作成するロジックは次のような形になります。


   1: Private Function GetCanonicalizedHeaders(ByVal request As HttpWebRequest) As String
   2:     Dim headerList As New List(Of String)
   3:     Dim result As New StringBuilder()
   4:     'x-ms- で始まるヘッダの抽出
   5:     For Each headValue As String In request.Headers.Keys
   6:         If headValue.ToLowerInvariant().StartsWith("x-ms-", StringComparison.Ordinal) Then
   7:             headerList.Add(headValue.ToLowerInvariant())
   8:         End If
   9:     Next headValue
  10:     headerList.Sort()
  11:     '抽出したヘッダの編集
  12:     For Each headValue In headerList
  13:         Dim builder As New StringBuilder(headValue)
  14:         Dim separateChar As String = ":"
  15:         For Each str4 In GetHeaderValues(request.Headers, headValue)
  16:             builder.Append(separateChar)
  17:             builder.Append(str4.Replace(ControlChars.CrLf, String.Empty))
  18:             separateChar = ","
  19:         Next str4
  20:         result.Append(builder.ToString())
  21:         result.Append(ControlChars.Lf)
  22:     Next headValue
  23:  
  24:     Return result.ToString()
  25: End Function
  26:  
  27: Private Function GetHeaderValues(ByVal headers As WebHeaderCollection, ByVal headerName As String) As List(Of String)
  28:     'ヘッダ値の抽出
  29:     Dim list As New List(Of String)
  30:     Dim values As String() = headers.GetValues(headerName)
  31:     If Not values Is Nothing Then
  32:         For Each str As String In values
  33:             list.Add(str.TrimStart(New Char() {}))
  34:         Next str
  35:     End If
  36:     Return list
  37: End Function
このロジックで編集された結果はこのような形になります。
x-ms-date:Fri, 20 May 2011 10:32:11 GMT\nx-ms-version:2009-09-19\n
この中で \n と記載されている部分ですが、これは Windows 上における改行 ( CR-LF ) ではなく LF となりますので、 ControlChars.LF でプログラム中は処理する必要があります。
もう一つの CanonicalizedResource を編集するロジックは次のようになります。

   1: Private Function GetCanonicalizedResource(ByVal address As Uri, ByVal accountName As String) As String
   2:     Dim resultbuilder As New StringBuilder("/")
   3:     resultbuilder.Append(accountName)
   4:     resultbuilder.Append(address.AbsolutePath)
   5:  
   6:     Dim str As New CanonicalizedString(resultbuilder.ToString())
   7:     Dim values = System.Web.HttpUtility.ParseQueryString(address.Query)
   8:     Dim values2 As New WebHeaderCollection
   9:     For Each str2 As String In values.Keys
  10:         Dim list As New ArrayList(values.GetValues(str2))
  11:         list.Sort()
  12:         Dim builder2 As New StringBuilder()
  13:         For Each obj2 As Object In list
  14:             If builder2.Length > 0 Then
  15:                 builder2.Append(",")
  16:             End If
  17:             builder2.Append(obj2.ToString())
  18:         Next obj2
  19:         If (str2 Is Nothing) Then
  20:             values2.Add(str2, builder2.ToString())
  21:         Else
  22:             values2.Add(str2.ToLowerInvariant(), builder2.ToString())
  23:         End If
  24:     Next str2
  25:  
  26:     Dim list2 As New ArrayList(values2.AllKeys)
  27:     list2.Sort()
  28:     For Each str3 As String In list2
  29:         Dim builder3 As New StringBuilder(String.Empty)
  30:         builder3.Append(str3)
  31:         builder3.Append(":")
  32:         builder3.Append(values2(str3))
  33:         str.AppendCanonicalizedElement(builder3.ToString())
  34:     Next str3
  35:  
  36:     Return str.Value
  37: End Function
  38:  
  39: Friend Class CanonicalizedString
  40:     ' Fields 
  41:     Private canonicalizedString_Renamed As New StringBuilder()
  42:  
  43:     ' Methods 
  44:     Friend Sub New(ByVal initialElement As String)
  45:         Me.canonicalizedString_Renamed.Append(initialElement)
  46:     End Sub
  47:  
  48:     Friend Sub AppendCanonicalizedElement(ByVal element As String)
  49:         Me.canonicalizedString_Renamed.Append(ControlChars.Lf)
  50:         Me.canonicalizedString_Renamed.Append(element)
  51:     End Sub
  52:  
  53:     ' Properties 
  54:     Friend ReadOnly Property Value() As String
  55:         Get
  56:             Return Me.canonicalizedString_Renamed.ToString()
  57:         End Get
  58:     End Property
  59: End Class
ここで行う処理はリクエストの Uri アドレスとそれに付随するパラメータを編集する事です。例えばストレージのコンテナ一覧を抽出する場合はこのようになります。
http://{アカウント名}.blob.core.windows.net/?comp=list
/{アカウント名}/\ncomp:list
このようにして編集した結果を SHA256 アルゴリズムにて暗号化したものが SharedKey 値となります。なお SharedKeyLite という SharedKey を少しばかり簡易にしたものがありますが、行われている処理自体は全く同一で、CanonicalizedHeaders にて対象となるヘッダの数が減るだけだったりします。

0 件のコメント:

コメントを投稿