在使用基于 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)。出于安全考虑,拒绝任何 Amazon 域以外的 URL。

  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 message – 包括 MessageMessageIdSubscribeURLTimestampTokenTopicArnType

  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 字段选择哈希算法:

    • 对于 SignatureVersion1,请使用 SHA1(例如,-sha1)。

    • 对于 SignatureVersion2,请使用 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)