

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 在使用基于 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）。出于安全考虑，拒绝任何 URLs **外部 AWS 域名**。

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. 不同的消息类型需要在待签名字符串中使用不同的键值对。确定**消息类型**（Amazon SNS 消息中的 `Type` 字段）以确定要包含哪些**键值对**：
   + **通知消息**：包括 `Message`、`MessageId`、`Subject`（如果存在）`Timestamp`、`TopicArn`、和 `Type`。
   + **SubscriptionConfirmation**或**UnsubscribeConfirmation 消息**-包括`Message`、`MessageId``SubscribeURL`、`Timestamp`、`Token`、`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 编码的。您需要对其进行**解码**，将其**原始二进制形式**与**派生的哈希值**进行比较。例如：

   ```
   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)
```