DKIM - хеш тела не проверял

Я пытаюсь отправить очень простое письмо в gmail (или любого другого провайдера электронной почты) с заголовком DKIM.

Результат в gmail: dkim= нейтральный (хеш тела не проверен)

Я предполагаю, что хэш тела неверен. Я сделал тело очень простым и все еще получаю ту же ошибку.

Вот строка данных SMTP:

DKIM-Signature:v=1; a=rsa-sha1; q=dns/txt; s=default;\r\n c=simple/simple; d=cumulo9.com; h=Date:From:To:Content-Type:Content-Transfer-Encoding;\r\n t=1489977499; bh=rtE3fSBFa/HdaPcuGaMM2mZVL7Mljo9sPTNOBjmNBdgIpGYh+ukt71Joc/qFd/nY70yn/hW0nASN+SZARGY2ri0ymA6NUrCIcSX7yJxJ6MkO78cyGZUoHY6Y+kOsDfCUcH5ANHJs88iUtu4IviWP4vWHXBd/tqP9k7Q+UKaC+m4=;\r\n b=klwC+c8qFKVD32SK22K04/YID+TerTvd26+VnlTljNA3fOEVbi2YlvTFo5LM1VksmO08hu5iJfwmF/3GgSEOnGT3mrzXxofjPbvIWU181zluxObNt8FwrP0kCIUskJEQz2SPF1VzaMQ8QvVchnkEFYrW9Pvssk6hunNr8J6CGrc=\r\nDate: Mon, 20 Mar 2017 15:38:17 +1300\r\nFrom: <leo@cumulo9.com>\r\nTo: leo@cumulo9.com\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nhelloleo\r\n.

Единственное, что я могу придумать, - это то, что в хеш-коде тела должна быть ошибка.

public string SignBody(string body)
    {
        var cb = body + "\r\n";

        IPrivateKeySigner _privateKeySigner = new MailPost.DKIM.PrivateKeySigner(PrivateKey);

        byte[] defaultEncoding = Encoding.UTF8.GetBytes(cb);

        byte[] hash = _privateKeySigner.Sign(defaultEncoding, SigningAlgorithm.RSASha1);

        string bodyHash = Convert.ToBase64String(hash);

        return bodyHash;
    }

Функция в классе "PrivateKeySigner":

public byte[] Sign(byte[] data, SigningAlgorithm algorithm)
    {
        if (data == null)
        {
            throw new ArgumentNullException("data");
        }

        using (var rsa = OpenSslKey.DecodeRSAPrivateKey(m_key))
        {
            byte[] signature = rsa.SignData(data, GetHashName(algorithm));

            return signature;

        }
    }

Функция в классе "OpenSslKey":

public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
    {
        if (privkey == null)
        {
            throw new ArgumentNullException("privkey");
        }

        byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;

        // ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
        //var mem = new MemoryStream(privkey);
        using (var binr = new BinaryReader(new MemoryStream(privkey)))    //wrap Memory Stream with BinaryReader for easy reading
        {

            ushort twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                binr.ReadByte(); //advance 1 byte
            else if (twobytes == 0x8230)
                binr.ReadInt16(); //advance 2 bytes
            else
                return null;

            twobytes = binr.ReadUInt16();
            if (twobytes != 0x0102) //version number
                return null;
            byte bt = binr.ReadByte();
            if (bt != 0x00)
                return null;


            //------  all private key components are Integer sequences ----
            int elems = GetIntegerSize(binr);
            MODULUS = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            E = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            D = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            P = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            Q = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            DP = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            DQ = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            IQ = binr.ReadBytes(elems);


            // ------- create RSACryptoServiceProvider instance and initialize with public key -----
            var RSA = new RSACryptoServiceProvider();
            var RSAparams = new RSAParameters
                                {
                                    Modulus = MODULUS,
                                    Exponent = E,
                                    D = D,
                                    P = P,
                                    Q = Q,
                                    DP = DP,
                                    DQ = DQ,
                                    InverseQ = IQ
                                };
            RSA.ImportParameters(RSAparams);
            return RSA;

        }
    }

Код для GetIntegerSize():

private static int GetIntegerSize([NotNull]BinaryReader binr)
    {
        if (binr == null)
        {
            throw new ArgumentNullException("binr");
        }

        int count;
        byte bt = binr.ReadByte();
        if (bt != 0x02)     //expect integer
            return 0;
        bt = binr.ReadByte();

        if (bt == 0x81)
            count = binr.ReadByte();    // data size in next byte
        else
            if (bt == 0x82)
            {
                byte highbyte = binr.ReadByte();
                byte lowbyte = binr.ReadByte();
                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
                count = BitConverter.ToInt32(modint, 0);
            }
            else
            {
                count = bt;     // we already have the data size
            }



        while (binr.ReadByte() == 0x00)
        {   //remove high order zeros in data
            count -= 1;
        }
        binr.BaseStream.Seek(-1, SeekOrigin.Current);       //last ReadByte wasn't a removed zero, so back up a byte
        return count;
    }

В настоящее время я действительно застрял с этой проблемой и понятия не имею, что я делаю неправильно. Я не могу использовать другие библиотеки, такие как "MimeKit" из-за характера вашего проекта. Если вам нужна дополнительная информация об этой проблеме, пожалуйста, дайте мне знать, и я сделаю все возможное, чтобы предоставить ее вам.

Спасибо огромное за помощь.

2 ответа

Решение

Скорее всего, у вас есть несколько проблем (а здесь вы думали, что у вас только 1!).

Во-первых, как вы определили body сообщения? Это просто строка, которую вы устанавливаете на MailMessage как его тело? Если это так, то это вряд ли сработает, если только вы не отправляете очень простые текстовые сообщения (и даже тогда... это может быть не так, в зависимости от того, MailMessage решает, что он должен кодировать ваш текст, используя, например, base64 или же quoted-printable кодирование). Вы должны убедиться, что Content-Transfer-Encoding применяется, прежде чем генерировать хеш тела.

Во-вторых, вы помните, чтобы преобразовать основной текст, используя Simple Правила канонизации тела описаны в rfc6376, раздел 3.4.3. Мне кажется, что ты просто "\r\n" но это не то, что правила говорят делать.

Если вы не хотите изобретать велосипед, вы можете попробовать использовать библиотеку, такую ​​как MimeKit, для создания и DKIM-подписи вашего сообщения следующим образом:

var message = new MimeMessage ();
message.From.Add (new MailboxAddress ("", "leo@cumulo9.com"));
message.To.Add (new MailboxAddress ("", "leo@cumulo9.com"));
message.Body = new TextPart ("plain") { Text = "helloleo" };

var headers = new HeaderId[] { HeaderId.Date, HeaderId.From, HeaderId.To, HeaderId.ContentType, HeaderId.ContentTransferEncoding };
var headerAlgorithm = DkimCanonicalizationAlgorithm.Simple;
var bodyAlgorithm = DkimCanonicalizationAlgorithm.Simple;
var signer = new DkimSigner ("privatekey.pem", "cumulo9.com", "default") {
    SignatureAlgorithm = DkimSignatureAlgorithm.RsaSha1,
    QueryMethod = "dns/txt",
};

// Prepare the message body to be sent over a 7bit transport (such as
// older versions of SMTP).
// Note: If the SMTP server you will be sending the message over supports
// the 8BITMIME extension, then you can use `EncodingConstraint.EightBit`
// instead, although it never hurts to use `SevenBit`.
message.Prepare (EncodingConstraint.SevenBit);

message.Sign (signer, headers, headerAlgorithm, bodyAlgorithm);

// to write out the message so you have something to compare with:
var options = FormatOptions.Default.Clone ();
options.NewLineFormat = NewLineFormat.Dos;

message.WriteTo (options, "message.txt");

Затем, получив сообщение с DKIM-подписью, вы можете отправить его по SMTP с помощью MailKit следующим образом:

using (var client = new SmtpClient ()) {
    // For demo-purposes, accept all SSL certificates
    client.ServerCertificateValidationCallback = (s,c,h,e) => true;

    client.Connect ("smtp.gmail.com", 587, SecureSocketOptions.StartTls);

    // Note: since we don't have an OAuth2 token, disable
    // the XOAUTH2 authentication mechanism.
    client.AuthenticationMechanisms.Remove ("XOAUTH2");

    // Note: only needed if the SMTP server requires authentication
    client.Authenticate ("joey@gmail.com", "password");

    client.Send (message);
    client.Disconnect (true);
}

Я бы прокомментировал вместо этого, но моя репутация еще недостаточно высока. :/

Я использую PHPMailer, но на случай, если мое решение применимо в более общем плане, я решил оставить примечание.

Мои электронные письма вызывали ошибка ПОСЛЕ того, как я уже проверил свою систему электронной почты и ключи DKIM в тестовой среде.

Оказывается, по крайней мере для PHPMailer, если в конце строки $body, которая передается в тогда хэши тела DKIM не будут совпадать, что вызовет ошибку.

Другие вопросы по тегам