使用基于 HTTP 查询的请求时验证 Amazon SNS 消息的签名 - Amazon Simple Notification Service
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

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

使用基于 HTTP 查询的请求时验证 Amazon SNS 消息的签名

在使用基于 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),请将其转换为其原始形式以确保完全匹配。

  2. 在 Amazon SNS 消息中找到该SigningCertURL字段。该证书包含验证消息签名所需的公钥。例如:

    SIGNING_CERT_URL=$(jq -r '.SigningCertURL' "$MESSAGE_FILE")
  3. 确保来自SigningCertURL可信 Amazon 域(例如 https://sns.us-east-1.amazonaws.com)。 出于安全考虑,拒绝任何 URLs 外部 Amazon 域名

  4. 从提供的 URL 下载 X.509 证书。例如:

    curl -s "$SIGNING_CERT_URL" -o signing_cert.pem
  5. 从下载的 X.509 证书中提取公钥。公钥允许您解密消息的签名并验证其完整性。例如:

    openssl x509 -pubkey -noout -in signing_cert.pem > public_key.pem
  6. 不同的消息类型需要在待签字符串中使用不同的键值对。确定消息类型(Amazon SNS 消息中的Type字段)以确定要包含哪些键值对

    • 通知消息-包括MessageMessageIdSubject(如果存在)TimestampTopicArn、和Type

    • SubscriptionConfirmationUnsubscribeConfirmation 消息-包括MessageMessageIdSubscribeURLTimestampTokenTopicArn、和Type

  7. Amazon SNS 要求签名字符串遵循严格的固定字段顺序进行验证。必须仅包含明确必填的字段,不能添加额外的字段。可选字段(例如Subject)只有在消息中存在时才必须包含且必须出现在必填字段顺序所定义的确切位置。例如:

    KeyNameOne\nValueOne\nKeyNameTwo\nValueTwo
    重要

    不要在字符串的末尾添加换行符。

  8. 按字节排序顺序排列键值对(按键名称的字母顺序排列)。

  9. 使用以下格式示例构造待签字符串

    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
  10. 消息中的Signature字段采用 Base64 编码。你需要对其进行解码,将其原始二进制形式派生的哈希值进行比较。例如:

    SIGNATURE=$(jq -r '.Signature' "$MESSAGE_FILE") echo "$SIGNATURE" | base64 -d > signature.bin
  11. 使用该SignatureVersion字段选择哈希算法:

    • 对于 SignatureVersion 1,使用 SHA1(例如,-sha1)。

    • 对于 SignatureVersion 2,使用 SHA256(例如,-sha256)。

  12. 要确认 Amazon SNS 消息的真实性,请生成构造字符串的哈希值,并使用钥验证签名。

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

    如果签名有效,则输出为Verified OK。否则,输出为Verification Failure

带有错误处理功能的脚本示例

以下示例脚本可自动执行验证过程:

#!/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)