swiftcryptographyamazon-snsvapor

How can I verify SNS messages in a server-side Swift HTTPS endpoint?


Amazon provides documentation for verifying that SNS messages routed to an HTTP/HTTPS endpoint are valid. Using Vapor and Swift on the server, along with swift-certificates and swift-crypto, I implemented the verification logic:

func messageHasValidSignature<Message: VerifiableMessage>(_ message: Message) async throws -> Bool {
    guard let certificateBytes = try await client.get(.init(message.signingCertificateURL)).body else {
        logger.error("Failed to get signing certificate body")
        throw Abort(.badRequest)
    }

    let certificateString = String(buffer: certificateBytes)
    let certificate = try Certificate(pemEncoded: certificateString)
    let publicKey = try _RSA.Signing.PublicKey(derRepresentation: certificate.publicKey.subjectPublicKeyInfoBytes)
    guard let signatureData = Data(base64Encoded: message.signature) else {
        logger.error("Failed to decode signature")
        throw Abort(.badRequest)
    }

    let signature = _RSA.Signing.RSASignature(rawRepresentation: signatureData)

    guard let dataToSign = message.stringToSign.data(using: .utf8) else {
        logger.error("Failed to convert string to sign to data")
        throw Abort(.badRequest)
    }

    let digest: any Digest
    switch message.signatureVersion {
    case ._1:
        digest = Insecure.SHA1.hash(data: dataToSign)
    case ._2:
        digest = SHA256.hash(data: dataToSign)
    }

    return publicKey.isValidSignature(signature, for: digest)
}

I am constructing the string to sign as follows:

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

        """
    }
}

But isValidSignature always returns false. How can I properly verify the message?


Solution

  • The problem was with the padding passed to isValidSignature. The default is PSS ("PSS padding using MGF1"). By checking the source of the C# implementation I found that it should be insecurePKCS1v1_5 ("PKCS#1 v1.5").

    So the correct way to verify the signature is:

    return publicKey.isValidSignature(signature, for: digest,  padding: .insecurePKCS1v1_5)