

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 在使用 HTTP 查詢型請求時，驗證 Amazon SNS 訊息的簽章
<a name="sns-verify-signature-of-message-verify-message-signature"></a>

在使用 HTTP 查詢型請求時，驗證 Amazon SNS 訊息的簽章可確保訊息的真實性和完整性。此程序會確認訊息來自 Amazon SNS，且未在傳輸期間遭到竄改。透過剖析訊息、建構正確的簽署字串，以及針對信任的公有金鑰驗證簽章，您可以保護您的系統免於詐騙和未經授權的訊息變更。

1. 從 Amazon SNS 傳送的 HTTP POST 請求內文中的 JSON 文件擷取**金鑰/值對**。這些欄位是建構**要簽署的字串**的必要欄位。
   + `Message`
   + `Subject` （如果有）
   + `MessageId`
   + `Timestamp`
   + `TopicArn`
   + `Type`

   例如：

   ```
   MESSAGE_FILE="message.json"
   FIELDS=("Message" "MessageId" "Subject" "Timestamp" "TopicArn" "Type")
   ```
**注意**  
如果任何欄位包含逸出字元 （例如 `\n`)，請將它們轉換為**原始格式**，以確保完全相符。

1. 在 Amazon SNS 訊息中尋找 `SigningCertURL` 欄位。此憑證包含驗證訊息簽章所需的公有金鑰。例如：

   ```
   SIGNING_CERT_URL=$(jq -r '.SigningCertURL' "$MESSAGE_FILE")
   ```

1. 確定 `SigningCertURL` 來自信任的 AWS 網域 （例如 https://sns.us-east-1.amazonaws.com)。基於安全考量，拒絕** AWS 網域外**的任何 URLs。

1. 從提供的 URL 下載 **X.509 憑證**。例如：

   ```
   curl -s "$SIGNING_CERT_URL" -o signing_cert.pem
   ```

1. 從下載的 X.509 憑證中擷取**公有金鑰**。公有金鑰可讓您解密訊息的簽章並驗證其完整性。例如：

   ```
   openssl x509 -pubkey -noout -in signing_cert.pem > public_key.pem
   ```

1. 不同的訊息類型需要字串中不同的鍵值對才能簽署。識別**訊息類型** (`Type`Amazon SNS 訊息中的 欄位），以決定要包含哪些**鍵值對**：
   + **通知訊息** – 包括 `Message`、`MessageId`、 `Subject`（如果有）`TopicArn`、、 `Timestamp`和 `Type`。
   + **SubscriptionConfirmation** 或 **UnsubscribeConfirmation 訊息** – 包括 `Message`、`MessageId`、`SubscribeURL`、`Token`、`Timestamp`、 `TopicArn`和 `Type`。

1. Amazon SNS 需要字串簽署，以遵循嚴格的固定欄位順序進行驗證。**僅必須包含明確的必要欄位**，無法新增任何額外的欄位。只有在訊息中存在時`Subject`，才會包含選用欄位，例如 ，且必須出現在必要欄位順序所定義的確切位置。例如：

   ```
   KeyNameOne\nValueOne\nKeyNameTwo\nValueTwo
   ```
**重要**  
請勿在字串結尾新增換行字元。

1. 依位元組排序順序排列**索引鍵/值對** （按索引鍵名稱的字母）。

1. 使用下列格式範例建構**字串以簽署**：

   ```
   STRING_TO_SIGN=""
   for FIELD in "${FIELDS[@]}"; do
       VALUE=$(jq -r --arg field "$FIELD" '.[$field]' "$MESSAGE_FILE")
       STRING_TO_SIGN+="$FIELD\n$VALUE"
       # Append a newline after each field except the last one
       if [[ "$FIELD" != "Type" ]]; then
           STRING_TO_SIGN+="\n"
       fi
   done
   ```

   **通知訊息範例：**

   ```
   Message
   My Test Message
   MessageId
   4d4dc071-ddbf-465d-bba8-08f81c89da64
   Subject
   My subject
   Timestamp
   2019-01-31T04:37:04.321Z
   TopicArn
   arn:aws:sns:us-east-2:123456789012:s4-MySNSTopic-1G1WEFCOXTC0P
   Type
   Notification
   ```

   **SubscriptionConfirmation 範例：**

   ```
   Message
   Please confirm your subscription
   MessageId
   3d891288-136d-417f-bc05-901c108273ee
   SubscribeURL
   https://sns.us-east-2.amazonaws.com/...
   Timestamp
   2024-01-01T00:00:00.000Z
   Token
   abc123...
   TopicArn
   arn:aws:sns:us-east-2:123456789012:MyTopic
   Type
   SubscriptionConfirmation
   ```

1. 訊息中的 `Signature` 欄位以 Base64-encoded。您需要**解碼**它，將其**原始二進位格式**與**衍生的雜湊**進行比較。例如：

   ```
   SIGNATURE=$(jq -r '.Signature' "$MESSAGE_FILE")
   echo "$SIGNATURE" | base64 -d > signature.bin
   ```

1. 使用 `SignatureVersion` 欄位來選取雜湊演算法：
   + 對於 `SignatureVersion`**1**，請使用 **SHA1** （例如，`-sha1`)。
   + 對於 `SignatureVersion`**2**，請使用 **SHA256** （例如 `-sha256`)。

1. 若要確認 Amazon SNS 訊息的真實性，請產生建構字串的**雜湊**，並使用**公有金鑰**驗證簽章。

   ```
   openssl dgst -sha256 -verify public_key.pem -signature signature.bin <<< "$STRING_TO_SIGN"
   ```

   如果簽章有效，則輸出為 `Verified OK`。否則，輸出為 `Verification Failure`。

## 具有錯誤處理的範例指令碼
<a name="sns-verify-signature-of-message-example"></a>

下列範例指令碼會自動執行驗證程序：

```
#!/bin/bash

# Path to the local message file
MESSAGE_FILE="message.json"

# Extract the SigningCertURL and Signature from the message
SIGNING_CERT_URL=$(jq -r '.SigningCertURL' "$MESSAGE_FILE")
SIGNATURE=$(jq -r '.Signature' "$MESSAGE_FILE")

# Fetch the X.509 certificate
curl -s "$SIGNING_CERT_URL" -o signing_cert.pem

# Extract the public key from the certificate
openssl x509 -pubkey -noout -in signing_cert.pem > public_key.pem

# Define the fields to include in the string to sign
FIELDS=("Message" "MessageId" "Subject" "Timestamp" "TopicArn" "Type")

# Initialize the string to sign
STRING_TO_SIGN=""

# Iterate over the fields to construct the string to sign
for FIELD in "${FIELDS[@]}"; do
    VALUE=$(jq -r --arg field "$FIELD" '.[$field]' "$MESSAGE_FILE")
    STRING_TO_SIGN+="$FIELD\n$VALUE"
    # Append a newline after each field except the last one
    if [[ "$FIELD" != "Type" ]]; then
        STRING_TO_SIGN+="\n"
    fi
done

# Verify the signature
echo -e "$STRING_TO_SIGN" | openssl dgst -sha256 -verify public_key.pem -signature <(echo "$SIGNATURE" | base64 -d)
```