Подтвердить что ты не робот

Классическое разрешение ASP для амазонки s3

Я смущен тем, что я делаю неправильно здесь...

<script language="javascript" runat="server">
  function GMTNow(){return new Date().toGMTString()}
</script>
<%

Const AWS_BUCKETNAME = "uk-bucketname"
Const AWS_ACCESSKEY = "GOES HERE"
Const AWS_SECRETKEY = "SECRET"
LocalFile = Server.Mappath("/test.jpg")

Dim sRemoteFilePath
    sRemoteFilePath = "/files/test.jpg" 'Remote Path, note that AWS paths (in fact they aren't real paths) are strictly case sensitive

Dim strNow
    strNow = GMTNow() ' GMT Date String

Dim StringToSign
    StringToSign = Replace("PUT\n\nimage/jpeg\n\nx-amz-date:" & strNow & "\n/"& AWS_BUCKETNAME & sRemoteFilePath, "\n", vbLf)

Dim Signature
    Signature = BytesToBase64(HMACSHA1(AWS_SECRETKEY, StringToSign))

Dim Authorization
    Authorization = "AWS " & AWS_ACCESSKEY & ":" & Signature

Dim AWSBucketUrl
    AWSBucketUrl = "http://s3.amazonaws.com/" & AWS_BUCKETNAME

With Server.CreateObject("Microsoft.XMLHTTP")
    .open "PUT", AWSBucketUrl & sRemoteFilePath, False
    .setRequestHeader "Authorization", Authorization
    .setRequestHeader "Content-Type", "image/jpeg"
    .setRequestHeader "Host", AWS_BUCKETNAME & ".s3.amazonaws.com"  
    .setRequestHeader "x-amz-date", strNow
    .send GetBytes(LocalFile) 'Get bytes of local file and send
    If .status = 200 Then ' successful
        Response.Write "<a href="& AWSBucketUrl & sRemoteFilePath &" target=_blank>Uploaded File</a>"
    Else ' an error ocurred, consider xml string of error details
        Response.ContentType = "text/xml"
        Response.Write .responseText
    End If
End With

Function GetBytes(sPath)
    dim fs,f
set fs=Server.CreateObject("Scripting.FileSystemObject")
set f=fs.GetFile(sPath)
GetBytes = f.Size
set f=nothing
set fs=nothing
End Function

Function BytesToBase64(varBytes)
    With Server.CreateObject("MSXML2.DomDocument").CreateElement("b64")
        .dataType = "bin.base64"
        .nodeTypedValue = varBytes
        BytesToBase64 = .Text
    End With
End Function

Function HMACSHA1(varKey, varValue)
    With Server.CreateObject("System.Security.Cryptography.HMACSHA1")
        .Key = UTF8Bytes(varKey)
        HMACSHA1 = .ComputeHash_2(UTF8Bytes(varValue))
    End With
End Function

Function UTF8Bytes(varStr)
    With Server.CreateObject("System.Text.UTF8Encoding")
        UTF8Bytes = .GetBytes_4(varStr)
    End With
End Function
%>

Теперь получаем ошибку.

msxml3.dll error '800c0008'

The download of the specified resource has failed.

/s3.asp, line 39
4b9b3361

Ответ 1

Я хотел бы объяснить, как работает S3 Rest Api, насколько мне известно.
Во-первых, вам нужно узнать, что должно быть строкой для подписки Amazon.

Формат:

StringToSign = HTTP-Verb + "\n" +
    Content-MD5 + "\n" +
    Content-Type + "\n" +
    Date + "\n" +
    CanonicalizedAmzHeaders +
    CanonicalizedResource;

Генерация подписанной строки:

Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );

Передача заголовка авторизации:

Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;

К сожалению, вы будете играть байт в байт, поскольку для классического asp нет никакого SDK. Итак, следует понять, прочитав всю страницу http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html

Для строки, которую нужно подписать, как вы можете видеть выше в формате, три API-заголовка зарезервированы для API. Content-Type, Content-MD5 и Дата. Эти заголовки должны существовать в строке для подписывания, даже если ваш запрос не является пустым без имени заголовка, просто его значение. Существует исключение, заголовок Date должен быть пустым в строке для подписи, если заголовок x-amz-date уже существует в запросе. Затем, если запрос имеет канонические заголовки амазонок, вы должны добавить их в качестве пар ключ-значение, например x-amz-headername:value. Но есть еще одно исключение, которое необходимо учитывать для нескольких заголовков. Несколько заголовков должны объединяться в один заголовок со значениями, разделенными запятыми.

Правильно

х-AMZ-HeaderName: значение1, значение2

Неверно

x-amz-headername: value1\n
х-AMZ-HeaderName: значение2

Самое главное, заголовки должны быть в порядке возрастания по своей группе в строке для подписи. Во-первых, зарезервированные заголовки с возрастающим порядком, затем канонические заголовки с возрастающим порядком.

Я бы рекомендовал использовать DomDocument для генерации строк с кодировкой Base64. Кроме того, вместо компонента Windows Scripting Component (файлы .wsc) вы можете использовать .Net-interops, такие как System.Security.Cryptography, для генерации хэшей с ключом более эффективно с мощностью System.Text. Все эти взаимодействия доступны на современных веб-серверах IIS.
Итак, в качестве примера, который я написал ниже, script просто отправляет файл в указанный вами ведро. Рассмотрите и протестируйте его.
Предполагаемое локальное имя файла myimage.jpg и будет загружено с тем же именем в корень из ведра.

<script language="javascript" runat="server">
function GMTNow(){return new Date().toGMTString()}
</script>
<%
Const AWS_BUCKETNAME = "uk-bucketname"
Const AWS_ACCESSKEY = "GOES HERE"
Const AWS_SECRETKEY = "SECRET"

LocalFile = Server.Mappath("/test.jpg")

Dim sRemoteFilePath
    sRemoteFilePath = "/files/test.jpg" 'Remote Path, note that AWS paths (in fact they aren't real paths) are strictly case sensitive

Dim strNow
    strNow = GMTNow() ' GMT Date String

Dim StringToSign
    StringToSign = Replace("PUT\n\nimage/jpeg\n\nx-amz-date:" & strNow & "\n/"& AWS_BUCKETNAME & sRemoteFilePath, "\n", vbLf)

Dim Signature
    Signature = BytesToBase64(HMACSHA1(AWS_SECRETKEY, StringToSign))

Dim Authorization
    Authorization = "AWS " & AWS_ACCESSKEY & ":" & Signature

Dim AWSBucketUrl
    AWSBucketUrl = "https://" & AWS_BUCKETNAME & ".s3.amazonaws.com"

With Server.CreateObject("MSXML2.ServerXMLHTTP.6.0")
    .open "PUT", AWSBucketUrl & sRemoteFilePath, False
    .setRequestHeader "Authorization", Authorization
    .setRequestHeader "Content-Type", "image/jpeg"
    .setRequestHeader "Host", AWS_BUCKETNAME & ".s3.amazonaws.com"  
    .setRequestHeader "x-amz-date", strNow
    .send GetBytes(LocalFile) 'Get bytes of local file and send
    If .status = 200 Then ' successful
        Response.Write "<a href="& AWSBucketUrl & sRemoteFilePath &" target=_blank>Uploaded File</a>"
    Else ' an error ocurred, consider xml string of error details
        Response.ContentType = "text/xml"
        Response.Write .responseText
    End If
End With

Function GetBytes(sPath)
    With Server.CreateObject("Adodb.Stream")
        .Type = 1 ' adTypeBinary
        .Open
        .LoadFromFile sPath
        .Position = 0
        GetBytes = .Read
        .Close
    End With
End Function

Function BytesToBase64(varBytes)
    With Server.CreateObject("MSXML2.DomDocument").CreateElement("b64")
        .dataType = "bin.base64"
        .nodeTypedValue = varBytes
        BytesToBase64 = .Text
    End With
End Function

Function HMACSHA1(varKey, varValue)
    With Server.CreateObject("System.Security.Cryptography.HMACSHA1")
        .Key = UTF8Bytes(varKey)
        HMACSHA1 = .ComputeHash_2(UTF8Bytes(varValue))
    End With
End Function

Function UTF8Bytes(varStr)
    With Server.CreateObject("System.Text.UTF8Encoding")
        UTF8Bytes = .GetBytes_4(varStr)
    End With
End Function
%>


Ответ 2

Подпись Amazon должна быть закодирована по-другому, чем кодируется VBSCript. Следующая функция будет правильно кодировать результат:

Версия JScript:

function amazonEncode(s)
{
    return Server.UrlEncode(s).replace(/\+/g,"%20").replace(/\%2E/g,".").replace(/\%2D/g,"-").replace(/\%7E/g,"~").replace(/\%5F/g,"_");
}

Версия VBScript:

function amazonEncode(s)
    dim retval
    retval = Server.UrlEncode(s)
    retval = replace(retval,"+","%20")
    retval = replace(retval,"%2E",".")
    retval = replace(retval,"%2D","-")
    retval = replace(retval,"%7E","~")
    retval = replace(retval,"%5F","_")
    amazonEncode = retval
end function

Что касается base64, я использовал для него уже встроенную функциональность .NET. Мне пришлось создать DLL, чтобы обернуть его, чтобы я мог использовать его из JScript (или VBScript).

Здесь, как создать эту DLL:

Download the free C# 2010 Express and install it.
You also need to use two other tools that you won’t have a path to, so you will need to add the path to your PATH environment variable, so at a cmd prompt search for regasm.exe, guidgen.exe and sn.exe (you might find several versions – select the one with the latest date).
•   cd\
•   dir/s regasm.exe
•   dir/s sn.exe
•   dir/s guidgen.exe


So as an example, a COM object that has just one method which just returns "Hello":
Our eventual aim is to use it like this:
<%@Language=JScript%>
<%
var x = Server.CreateObject("blah.whatever");
Response.Write(x.someMethod());
%>

or 

<%@Language=VBScript%>
<%
dim x
set x = Server.CreateObject("blah.whatever")
Response.Write x.someMethod()
%>

•   Start C# and create a new project
•   Select "Empty Project"
•   Give it a name – this becomes the namespace by default (the blah in the sample above)
•   Next save the project (so you know where to go for the next bit).  This will create a folder structure like so:
o   blah    this contains your solution files that the editor needs (blah.sln etc)
   blah    this contains your source code and project files
•   bin
o   Debug           the compiled output ends up here
•   Next, using the cmd console, navigate to the root blah folder and create a key pair file:
   sn –k key.snk
•   Next you need a unique guid (enter guidgen at the cmd prompt)
o   Select registry format
o   Click "New Guid"
o   Click "Copy"
•   Back to C# editor – from the menu, select Project – Add Class
•   Give it a name – this is the whatever in the sample above
•   After the opening brace just after the namespace line type:
   [GuidAttribute("paste your guid here")]
   remove the curly brackets from your pasted guid
•   You will need to add another "using" at the top
  using System.Runtime.InteropServices;
•   Finally you need to create someMethod

The final C# code looks like this (the bits in red may be different in your version):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace blah
{
    [GuidAttribute("AEF4F27F-9E97-4189-9AD5-64386A1699A7")]
    public class whatever
    {
        public string someMethod()
        {
            return "Hello";
        }
    }
}

•   Next, from the menu, select Project – Properties
o   On the left, select Application and, for the Output type dropdown, select "Class Library"
o   On the left, select Signing and tick the "Sign the assembly" box, then browse to the key.snk file you made earlier
o   Save the properties (CTRL-S)
•   Next build the dll (Press F6) – This will create a dll in the Debug folder
•   Open a cmd window as administrator (right click cmd.exe and select "Run as Administrator")
•   Navigate to the Debug folder and enter the following to register the assembly:
  regasm blah.dll /tlb:blah.tlb /codebase blah

That’s it – the above is a genuine COM component and will work in other applications, the example below allows for event handling and only really works in ASP due to the default property mechanism of ASP:

Код для файла base64:

    // returns a base 64 encoded string that has been encrypted with SHA256
    // parameters:
    //  s   string to encrypt
    //  k   key to use during encryption
    public string getBase64SHA256(string s, string k)
    {
        HMACSHA256 sha = new HMACSHA256();
        System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
        sha.Key = encoding.GetBytes(k);
        byte[] hashBytes = sha.ComputeHash(encoding.GetBytes(s));
        return System.Convert.ToBase64String(hashBytes);
    }

    // returns a base 64 encoded string that has been encrypted with SHA1
    // parameters:
    //  s   string to encrypt
    //  k   key to use during encryption
    public string getBase64SHA1(string s, string k)
    {
        HMACSHA1 sha = new HMACSHA1();
        System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
        sha.Key = encoding.GetBytes(k);
        byte[] hashBytes = sha.ComputeHash(encoding.GetBytes(s));
        return System.Convert.ToBase64String(hashBytes);
    }

Вам понадобятся соответствующие данные:

using System.Security.Cryptography;

Подпись в полном объеме должна иметь все пары имя-значение строки запроса в алфавитном порядке перед вычислением SHA и base64. Вот моя версия функции создателя подписи:

function buildAmazonSignature(host,req,qstring)
{
    var str="", i, arr = String(qstring).split("&");

    for (i=0; i<arr.length; i++)
        arr[i] = arr[i].split("=");
    arr.sort(amazonSortFunc);

    for (i=0; i<arr.length; i++)
    {
        if (str != "")
            str += "&";

        str += arr[i][0] + "=" + arr[i][1];
    }

    str = "GET\n"+host+"\n"+req+"\n"+str;

    var utils = Server.CreateObject("FMAG.Utils");
    var b64 = utils.getBase64SHA256(str, "xxxxxxxxxx");
    utils = null;

    return amazonEncode(b64);
}

function amazonSortFunc(a,b)
{
    return (a[0]<b[0])?-1:((a[0]>b[0])?1:0);
}

VBScript не имеет очень хорошего средства сортировки массива, поэтому вам придется работать с этим самим - извините

Также у меня есть отметка времени в этом формате:

YYYY-MM-DDThh: ММ: SSZ

Также материал в строке запроса включал следующее:

AWSAccessKeyId
SignatureMethod
SignatureVersion
Version
Expires
Action

Надеюсь, что поможет

Ответ 3

Большое вам спасибо за этот вопрос, это была отличная помощь для запуска моего WSH/VBScript для моей службы резервного копирования S3; -)

У меня мало времени, поэтому я не буду разбираться в деталях вещей, которые я изменил из кода Криса, но, пожалуйста, найдите ниже мой маленький прототип script, который отлично работает; -)

Это просто WSH/VBScript, поэтому вам не нужно, чтобы IIS запускал его, вам просто нужно вставить содержимое в файл с расширением ".vbs", и вы можете выполнить его непосредственно; -)

Option Explicit
'-- Amazon Web Services > My Account > Access Credentials > Access Keys --'
Dim strAccessKeyID: strAccessKeyID = "..."
Dim strSecretAccessKey: strSecretAccessKey = "..."
'-- Parameters: --'
Dim strLocalFile: strLocalFile = "..."
Dim strRemoteFile: strRemoteFile = "..."
Dim strBucket: strBucket = "..."
'-- Authentication: --'
Dim strNowInGMT: strNowInGMT = NowInGMT()
Dim strStringToSign: strStringToSign = _
  "PUT" & vbLf & _
  "" & vbLf & _
  "text/xml" & vbLf & _
  strNowInGMT & vbLf & _
  "/" & strBucket + "/" & strRemoteFile
Dim strSignature: strSignature = ConvertBytesToBase64(HMACSHA1(strSecretAccessKey, strStringToSign))
Dim strAuthorization: strAuthorization = "AWS " & strAccessKeyID & ":" & strSignature
'-- Upload: --'
Dim xhttp: Set xhttp = CreateObject("MSXML2.ServerXMLHTTP")
xhttp.open "PUT", "http://" & strBucket & ".s3.amazonaws.com/" & strRemoteFile, False
xhttp.setRequestHeader "Content-Type", "text/xml"
xhttp.setRequestHeader "Date", strNowInGMT 'Yes, this line is mandatory ;-) --'
xhttp.setRequestHeader "Authorization", strAuthorization
xhttp.send GetBytesFromFile(strLocalFile)
If xhttp.status = "200" Then
  WScript.Echo "The file has been successfully uploaded ;-)"
Else
  WScript.Echo "There was an error :-(" & vbCrLf & vbCrLf & _
  xhttp.responseText
End If
Set xhttp = Nothing
'-- NowInGMT ------------------------------------------------------------------'
Function NowInGMT()
  'This is probably not the best implementation, but it works ;-) --'
  Dim sh: Set sh = WScript.CreateObject("WScript.Shell")
  Dim iOffset: iOffset = sh.RegRead("HKLM\System\CurrentControlSet\Control\TimeZoneInformation\ActiveTimeBias")
  Dim dtNowGMT: dtNowGMT = DateAdd("n", iOffset, Now())
  Dim strDay: strDay = "NA"
  Select Case Weekday(dtNowGMT)
    Case 1 strDay = "Sun"
    Case 2 strDay = "Mon"
    Case 3 strDay = "Tue"
    Case 4 strDay = "Wed"
    Case 5 strDay = "Thu"
    Case 6 strDay = "Fri"
    Case 7 strDay = "Sat"
    Case Else strDay = "Error"
  End Select
  Dim strMonth: strMonth = "NA"
  Select Case Month(dtNowGMT)
    Case 1 strMonth = "Jan"
    Case 2 strMonth = "Feb"
    Case 3 strMonth = "Mar"
    Case 4 strMonth = "Apr"
    Case 5 strMonth = "May"
    Case 6 strMonth = "Jun"
    Case 7 strMonth = "Jul"
    Case 8 strMonth = "Aug"
    Case 9 strMonth = "Sep"
    Case 10 strMonth = "Oct"
    Case 11 strMonth = "Nov"
    Case 12 strMonth = "Dec"
    Case Else strMonth = "Error"
  End Select
  Dim strHour: strHour = CStr(Hour(dtNowGMT))
  If Len(strHour) = 1 Then strHour = "0" & strHour End If
  Dim strMinute: strMinute = CStr(Minute(dtNowGMT))
  If Len(strMinute) = 1 Then strMinute = "0" & strMinute End If
  Dim strSecond: strSecond = CStr(Second(dtNowGMT))
  If Len(strSecond) = 1 Then strSecond = "0" & strSecond End If
  Dim strNowInGMT: strNowInGMT = _
    strDay & _
    ", " & _
    Day(dtNowGMT) & _
    " " & _
    strMonth & _
    " " & _
    Year(dtNowGMT) & _
    " " & _
    strHour & _
    ":" & _
    strMinute & _
    ":" & _
    strSecond & _
    " +0000"
  NowInGMT = strNowInGMT
End Function
'-- GetBytesFromString --------------------------------------------------------'
Function GetBytesFromString(strValue)
  Dim stm: Set stm = CreateObject("ADODB.Stream")
  stm.Open
  stm.Type = 2
  stm.Charset = "ascii"
  stm.WriteText strValue
  stm.Position = 0
  stm.Type = 1
  GetBytesFromString = stm.Read
  Set stm = Nothing
End Function
'-- HMACSHA1 ------------------------------------------------------------------'
Function HMACSHA1(strKey, strValue)
  Dim sha1: Set sha1 = CreateObject("System.Security.Cryptography.HMACSHA1")
  sha1.key = GetBytesFromString(strKey)
  HMACSHA1 = sha1.ComputeHash_2(GetBytesFromString(strValue))
  Set sha1 = Nothing
End Function
'-- ConvertBytesToBase64 ------------------------------------------------------'
Function ConvertBytesToBase64(byteValue)
  Dim dom: Set dom = CreateObject("MSXML2.DomDocument")
  Dim elm: Set elm = dom.CreateElement("b64")
  elm.dataType = "bin.base64"
  elm.nodeTypedValue = byteValue
  ConvertBytesToBase64 = elm.Text
  Set elm = Nothing
  Set dom = Nothing
End Function
'-- GetBytesFromFile ----------------------------------------------------------'
Function GetBytesFromFile(strFileName)
  Dim stm: Set stm = CreateObject("ADODB.Stream")
  stm.Type = 1 'adTypeBinary --'
  stm.Open
  stm.LoadFromFile strFileName
  stm.Position = 0
  GetBytesFromFile = stm.Read
  stm.Close
  Set stm = Nothing
End Function

Уважаемый каменщик-технологии-VBScript-помощники (*), сообщите мне, если он работает и для вас; -)

(*) Это ссылка на комментарий от Spudley, см. выше; -)