You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在服务端Swift HTTPS端点正确验证SNS消息?

解决Amazon SNS消息签名验证失败问题(Swift/Vapor环境)

问题根源分析

你的签名验证始终失败,核心原因有三点:

  1. 待签名字符串格式不符合要求:亚马逊SNS要求stringToSign严格遵循键值对顺序,每个键和对应值各占一行,末尾不能有多余空行。你的代码中最后多了两个换行,导致生成的哈希值与亚马逊原始哈希不匹配。
  2. 缺少签名证书合法性验证:直接使用获取到的证书存在安全风险,且未验证证书有效期、签发者,虽然这不是签名失败的直接原因,但必须补上以避免伪造证书攻击。
  3. Swift Crypto签名验证调用方式错误:亚马逊SNS使用RSA-PKCS#1 v1.5签名方案,你直接传入预计算的Digest对象,未明确指定对应哈希算法方案,导致无法匹配亚马逊的签名格式。

修正后的代码实现

1. 修正待签名字符串格式

extension SubscriptionConfirmationMessage: VerifiableMessage {
    var stringToSign: String {
        """
        Message
        \(message)
        MessageId
        \(messageID)
        SubscribeURL
        \(subscribeURL)
        Timestamp
        \(timestamp)
        Token
        \(token)
        TopicArn
        \(topicARN)
        Type
        SubscriptionConfirmation
        """
    }
}

2. 完善签名验证逻辑

func messageHasValidSignature<Message: VerifiableMessage>(_ message: Message) async throws -> Bool {
    // 验证签名证书URL的域名合法性(必须是亚马逊官方域名)
    guard let url = URL(string: message.signingCertificateURL),
          let host = url.host,
          (host.hasSuffix(".amazonaws.com") || host.hasSuffix(".amazonaws.cn")) else {
        logger.error("非法的签名证书URL域名")
        throw Abort(.badRequest)
    }

    // 获取证书内容
    guard let certificateBytes = try await client.get(.init(message.signingCertificateURL)).body else {
        logger.error("获取签名证书内容失败")
        throw Abort(.badRequest)
    }

    let certificateString = String(buffer: certificateBytes)
    let certificate = try Certificate(pemEncoded: certificateString)
    
    // 验证证书有效期
    guard Date() >= certificate.validity.notBefore, Date() <= certificate.validity.notAfter else {
        logger.error("签名证书已过期或未到生效时间")
        throw Abort(.badRequest)
    }
    
    // 验证证书签发者为Amazon SNS(生产环境建议配置根CA做完整链验证)
    guard certificate.subject.commonName?.contains("Amazon SNS") == true else {
        logger.error("证书非Amazon SNS签发")
        throw Abort(.badRequest)
    }

    // 提取公钥
    let publicKey = try _RSA.Signing.PublicKey(derRepresentation: certificate.publicKey.subjectPublicKeyInfoBytes)
    
    // 解码签名数据
    guard let signatureData = Data(base64Encoded: message.signature) else {
        logger.error("签名数据Base64解码失败")
        throw Abort(.badRequest)
    }
    let signature = _RSA.Signing.RSASignature(rawRepresentation: signatureData)
    
    // 转换待签名字符串为UTF-8数据
    guard let dataToSign = message.stringToSign.data(using: .utf8) else {
        logger.error("待签名字符串转UTF-8数据失败")
        throw Abort(.badRequest)
    }
    
    // 根据签名版本选择对应的签名方案
    let scheme: _RSA.Signing.RSASigningScheme
    switch message.signatureVersion {
    case ._1:
        scheme = .insecureSHA1
    case ._2:
        scheme = .sha256
    }
    
    do {
        return try publicKey.isValidSignature(signature, for: dataToSign, using: scheme)
    } catch {
        logger.error("签名验证失败: \(error.localizedDescription)")
        return false
    }
}

额外注意事项

  • 对于Notification、UnsubscribeConfirmation等其他类型的SNS消息,需按照亚马逊文档对应的字段顺序构造stringToSign,不能混用字段。
  • 生产环境建议配置亚马逊根CA证书,做完整的证书链验证,避免仅检查主题名称的局限性。

内容的提问来源于stack exchange,提问作者Ryan Burgoyne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 09:54:53