Exemplo n.º 1
0
        /// <summary>
        ///     Initializes a new instance of the <see cref="EncryptedMessage" /> class from a plain inner message data.
        /// </summary>
        /// <param name="authKey">
        ///     Authorization Key a 2048-bit key shared by the client device and the server, created upon user
        ///     registration directly on the client device be exchanging Diffie-Hellman keys, and never transmitted over a network.
        ///     Each authorization key is user-specific. There is nothing that prevents a user from having several keys (that
        ///     correspond to “permanent sessions” on different devices), and some of these may be locked forever in the event the
        ///     device is lost.
        /// </param>
        /// <param name="salt">
        ///     Server Salt is a (random) 64-bit number periodically (say, every 24 hours) changed (separately for
        ///     each session) at the request of the server. All subsequent messages must contain the new salt (although, messages
        ///     with the old salt are still accepted for a further 300 seconds). Required to protect against replay attacks and
        ///     certain tricks associated with adjusting the client clock to a moment in the distant future.
        /// </param>
        /// <param name="sessionId">
        ///     Session is a (random) 64-bit number generated by the client to distinguish between individual sessions (for
        ///     example, between different instances of the application, created with the same authorization key). The session in
        ///     conjunction with the key identifier corresponds to an application instance. The server can maintain session state.
        ///     Under no circumstances can a message meant for one session be sent into a different session. The server may
        ///     unilaterally forget any client sessions; clients should be able to handle this.
        /// </param>
        /// <param name="messageId">
        ///     Message Identifier is a (time-dependent) 64-bit number used uniquely to identify a message within a session. Client
        ///     message identifiers are divisible by 4, server message identifiers modulo 4 yield 1 if the message is a response to
        ///     a client message, and 3 otherwise. Client message identifiers must increase monotonically (within a single
        ///     session), the same as server message identifiers, and must approximately equal unixtime*2^32. This way, a message
        ///     identifier points to the approximate moment in time the message was created. A message is rejected over 300 seconds
        ///     after it is created or 30 seconds before it is created (this is needed to protect from replay attacks). In this
        ///     situation, it must be re-sent with a different identifier (or placed in a container with a higher identifier). The
        ///     identifier of a message container must be strictly greater than those of its nested messages.
        /// </param>
        /// <param name="seqNumber">
        ///     Message Sequence Number is a 32-bit number equal to twice the number of “content-related” messages (those requiring
        ///     acknowledgment, and in particular those that are not containers) created by the sender prior to this message and
        ///     subsequently incremented by one if the current message is a content-related message. A container is always
        ///     generated after its entire contents; therefore, its sequence number is greater than or equal to the sequence
        ///     numbers of the messages contained in it.
        /// </param>
        /// <param name="messageData">Plain inner message data.</param>
        /// <param name="sender">Sender of the message.</param>
        /// <param name="hashServices">Hash services.</param>
        /// <param name="encryptionServices">Encryption services.</param>
        public EncryptedMessage([NotNull] byte[] authKey, ulong salt, ulong sessionId, ulong messageId, uint seqNumber, [NotNull] byte[] messageData, Sender sender,
                                [NotNull] IHashServices hashServices, [NotNull] IEncryptionServices encryptionServices)
        {
            Argument.IsNotNull(() => authKey);
            Argument.IsNotNull(() => messageData);
            Argument.IsNotNull(() => hashServices);
            Argument.IsNotNull(() => encryptionServices);

            _authKeyId   = ComputeAuthKeyId(authKey, hashServices);
            _salt        = salt;
            _sessionId   = sessionId;
            _messageId   = messageId;
            _seqNumber   = seqNumber;
            _messageData = (byte[])messageData.Clone();

            _messageDataLength = _messageData.Length;
            int innerDataLength            = InnerHeaderLength + _messageDataLength;
            int mod                        = innerDataLength % Alignment;
            int paddingLength              = mod > 0 ? Alignment - mod : 0;
            int innerDataWithPaddingLength = innerDataLength + paddingLength;

            _length = OuterHeaderLength + innerDataWithPaddingLength;

            // Writing inner data.
            var innerDataWithPadding = new byte[innerDataWithPaddingLength];

            using (var streamer = new TLStreamer(innerDataWithPadding))
            {
                streamer.WriteUInt64(_salt);
                streamer.WriteUInt64(_sessionId);
                streamer.WriteUInt64(_messageId);
                streamer.WriteUInt32(_seqNumber);
                streamer.WriteInt32(_messageDataLength);
                streamer.Write(_messageData);
                streamer.WriteRandomData(paddingLength);
            }

            _msgKey = ComputeMsgKey(new ArraySegment <byte>(innerDataWithPadding, 0, innerDataLength), hashServices);

            // Encrypting.
            byte[] aesKey, aesIV;
            ComputeAesKeyAndIV(authKey, _msgKey, out aesKey, out aesIV, hashServices, sender);
            byte[] encryptedData = encryptionServices.Aes256IgeEncrypt(innerDataWithPadding, aesKey, aesIV);

            Debug.Assert(encryptedData.Length == innerDataWithPaddingLength, "Wrong encrypted data length.");

            _messageBytes = new byte[_length];
            using (var streamer = new TLStreamer(_messageBytes))
            {
                // Writing header.
                streamer.WriteUInt64(_authKeyId);
                streamer.WriteInt128(_msgKey);

                // Writing encrypted data.
                streamer.Write(encryptedData, 0, innerDataWithPaddingLength);
            }
        }
Exemplo n.º 2
0
        public byte[] EncodeEncryptedMessage(IMessage message, byte[] authKey, ulong salt, ulong sessionId, Sender sender)
        {
            Argument.IsNotNull(() => authKey);
            Argument.IsNotNull(() => message);

            ulong authKeyId = ComputeAuthKeyId(authKey);

            byte[] serBody = _tlRig.Serialize(message.Body, TLSerializationMode.Boxed);

            int serBodyLength   = serBody.Length;
            int innerDataLength = EncryptedInnerHeaderLength + serBodyLength;
            int mod             = innerDataLength % Alignment;
            int paddingLength   = mod > 0 ? Alignment - mod : 0;

            _randomGenerator.FillWithRandom(_alignmentBuffer);
            int innerDataWithPaddingLength = innerDataLength + paddingLength;

            int length = EncryptedOuterHeaderLength + innerDataWithPaddingLength;

            // Writing inner data.
            var innerDataWithPadding = new byte[innerDataWithPaddingLength];

            using (var streamer = new TLStreamer(innerDataWithPadding))
            {
                streamer.WriteUInt64(salt);
                streamer.WriteUInt64(sessionId);
                streamer.WriteUInt64(message.MsgId);
                streamer.WriteUInt32(message.Seqno);
                streamer.WriteInt32(serBodyLength);
                streamer.Write(serBody);
                streamer.Write(_alignmentBuffer, 0, paddingLength);
            }

            Int128 msgKey = ComputeMsgKey(new ArraySegment <byte>(innerDataWithPadding, 0, innerDataLength));

            // Encrypting.
            byte[] aesKey, aesIV;
            ComputeAesKeyAndIV(authKey, msgKey, out aesKey, out aesIV, sender);
            byte[] encryptedData = _encryptionServices.Aes256IgeEncrypt(innerDataWithPadding, aesKey, aesIV);

            Debug.Assert(encryptedData.Length == innerDataWithPaddingLength, "Wrong encrypted data length.");

            var messageBytes = new byte[length];

            using (var streamer = new TLStreamer(messageBytes))
            {
                // Writing header.
                streamer.WriteUInt64(authKeyId);
                streamer.WriteInt128(msgKey);

                // Writing encrypted data.
                streamer.Write(encryptedData, 0, innerDataWithPaddingLength);
            }
            return(messageBytes);
        }
Exemplo n.º 3
0
        public async Task <AuthInfo> CreateAuthKey(CancellationToken cancellationToken)
        {
Restart:

            IMTProtoClientConnection connection = _mtProtoBuilder.BuildConnection(_clientTransportConfig);
            var methods = connection.Methods;

            try
            {
                Int128 nonce = _nonceGenerator.GetNonce(16).ToInt128();

                Console.WriteLine(string.Format("Creating auth key (nonce = {0:X16})...", nonce));

                // Connecting.
                Console.WriteLine("Connecting...");
                MTProtoConnectResult result = await connection.Connect(cancellationToken);

                if (result != MTProtoConnectResult.Success)
                {
                    throw new CouldNotConnectException("Connection trial was unsuccessful.", result);
                }

                // Requesting PQ.
                Console.WriteLine("Requesting PQ...");
                var resPQ = await methods.ReqPqAsync(new ReqPqArgs { Nonce = nonce }) as ResPQ;

                if (resPQ == null)
                {
                    throw new InvalidResponseException();
                }
                CheckNonce(nonce, resPQ.Nonce);

                Console.WriteLine(string.Format("Response PQ = {0}, server nonce = {1:X16}, {2}.", resPQ.Pq.ToHexString(), resPQ.ServerNonce,
                                                resPQ.ServerPublicKeyFingerprints.Aggregate("public keys fingerprints:", (text, fingerprint) => text + " " + fingerprint.ToString("X8"))));

                Int128 serverNonce      = resPQ.ServerNonce;
                byte[] serverNonceBytes = serverNonce.ToBytes();

                // Requesting DH params.
                PQInnerData     pqInnerData;
                ReqDHParamsArgs reqDhParamsArgs = CreateReqDhParamsArgs(resPQ, out pqInnerData);
                Int256          newNonce        = pqInnerData.NewNonce;
                byte[]          newNonceBytes   = newNonce.ToBytes();

                Console.WriteLine(string.Format("Requesting DH params with the new nonce: {0:X32}...", newNonce));

                IServerDHParams serverDHParams = await methods.ReqDHParamsAsync(reqDhParamsArgs);

                if (serverDHParams == null)
                {
                    throw new InvalidResponseException();
                }
                var dhParamsFail = serverDHParams as ServerDHParamsFail;
                if (dhParamsFail != null)
                {
                    if (CheckNewNonceHash(newNonce, dhParamsFail.NewNonceHash))
                    {
                        throw new MTProtoException("Requesting of the server DH params failed.");
                    }
                    throw new InvalidResponseException("The new nonce hash received from the server does NOT match with hash of the sent new nonce hash.");
                }
                var dhParamsOk = serverDHParams as ServerDHParamsOk;
                if (dhParamsOk == null)
                {
                    throw new InvalidResponseException();
                }
                CheckNonce(nonce, dhParamsOk.Nonce);
                CheckNonce(serverNonce, dhParamsOk.ServerNonce);

                Console.WriteLine("Received server DH params. Computing temp AES key and IV...");

                byte[] tmpAesKey;
                byte[] tmpAesIV;
                ComputeTmpAesKeyAndIV(newNonceBytes, serverNonceBytes, out tmpAesKey, out tmpAesIV);

                Console.WriteLine("Decrypting server DH inner data...");

                ServerDHInnerData serverDHInnerData = DecryptServerDHInnerData(dhParamsOk.EncryptedAnswer, tmpAesKey, tmpAesIV);
                // TODO: Implement checking.

                #region Checking instructions

                /****************************************************************************************************************************************
                *
                * Client is expected to check whether p = dh_prime is a safe 2048-bit prime (meaning that both p and (p-1)/2 are prime,
                * and that 2^2047 < p < 2^2048), and that g generates a cyclic subgroup of prime order (p-1)/2, i.e. is a quadratic residue mod p.
                * Since g is always equal to 2, 3, 4, 5, 6 or 7, this is easily done using quadratic reciprocity law,
                * yielding a simple condition on p mod 4g — namely, p mod 8 = 7 for g = 2; p mod 3 = 2 for g = 3;
                * no extra condition for g = 4; p mod 5 = 1 or 4 for g = 5; p mod 24 = 19 or 23 for g = 6; and p mod 7 = 3, 5 or 6 for g = 7.
                * After g and p have been checked by the client, it makes sense to cache the result, so as not to repeat lengthy computations in future.
                *
                * If the verification takes too long time (which is the case for older mobile devices), one might initially
                * run only 15 Miller—Rabin iterations for verifying primeness of p and (p - 1)/2 with error probability not exceeding
                * one billionth, and do more iterations later in the background.
                *
                * Another optimization is to embed into the client application code a small table with some known “good” couples (g,p)
                * (or just known safe primes p, since the condition on g is easily verified during execution),
                * checked during code generation phase, so as to avoid doing such verification during runtime altogether.
                * Server changes these values rarely, thus one usually has to put the current value of server's dh_prime into such a table.
                *
                * For example, current value of dh_prime equals (in big-endian byte order):
                * C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C3720FD51
                * F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F642477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4A4A695811051
                * 907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4E418FC15E83EBEA0F8
                * 7FA9FF5EED70050DED2849F47BF959D956850CE929851F0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5B
                *
                * IMPORTANT: Apart from the conditions on the Diffie-Hellman prime dh_prime and generator g,
                * both sides are to check that g, g_a and g_b are greater than 1 and less than dh_prime - 1.
                * We recommend checking that g_a and g_b are between 2^{2048-64} and dh_prime - 2^{2048-64} as well.
                *
                ****************************************************************************************************************************************/
                #endregion

                byte[] authKeyAuxHash = null;

                // Setting of client DH params.
                for (int retry = 0; retry < AuthRetryCount; retry++)
                {
                    Console.WriteLine(string.Format("Trial #{0} to set client DH params...", retry + 1));

                    byte[] b  = _nonceGenerator.GetNonce(256);
                    byte[] g  = serverDHInnerData.G.ToBytes(false);
                    byte[] ga = serverDHInnerData.GA;
                    byte[] p  = serverDHInnerData.DhPrime;

                    DHOutParams dhOutParams = _encryptionServices.DH(b, g, ga, p);
                    byte[]      authKey     = dhOutParams.S;

                    var clientDHInnerData = new ClientDHInnerData
                    {
                        Nonce       = nonce,
                        ServerNonce = serverNonce,
                        RetryId     = authKeyAuxHash == null ? 0 : authKeyAuxHash.ToUInt64(),
                        GB          = dhOutParams.GB
                    };

                    Console.WriteLine(string.Format("DH data: B={0}, G={1}, GB={2}, P={3}, S={4}.", b.ToHexString(), g.ToHexString(), dhOutParams.GB.ToHexString(),
                                                    p.ToHexString(), authKey.ToHexString()));

                    // byte[] authKeyHash = ComputeSHA1(authKey).Skip(HashLength - 8).Take(8).ToArray(); // Not used in client.
                    authKeyAuxHash = ComputeSHA1(authKey).Take(8).ToArray();

                    byte[] data = _tlRig.Serialize(clientDHInnerData);

                    // data_with_hash := SHA1(data) + data + (0-15 random bytes); such that length be divisible by 16;
                    byte[] dataWithHash = PrependHashAndAlign(data, 16);

                    // encrypted_data := AES256_ige_encrypt (data_with_hash, tmp_aes_key, tmp_aes_iv);
                    byte[] encryptedData         = _encryptionServices.Aes256IgeEncrypt(dataWithHash, tmpAesKey, tmpAesIV);
                    var    setClientDHParamsArgs = new SetClientDHParamsArgs {
                        Nonce = nonce, ServerNonce = serverNonce, EncryptedData = encryptedData
                    };

                    Console.WriteLine("Setting client DH params...");

                    ISetClientDHParamsAnswer setClientDHParamsAnswer = await methods.SetClientDHParamsAsync(setClientDHParamsArgs);

                    var dhGenOk = setClientDHParamsAnswer as DhGenOk;
                    if (dhGenOk != null)
                    {
                        Console.WriteLine("OK.");

                        CheckNonce(nonce, dhGenOk.Nonce);
                        CheckNonce(serverNonce, dhGenOk.ServerNonce);
                        var newNonceHash1 = ComputeNewNonceHash(newNonce, 1, authKeyAuxHash);

                        try
                        {
                            CheckNonce(newNonceHash1, dhGenOk.NewNonceHash1);
                        }
                        catch
                        {
                            Console.WriteLine("Failed to match new nonce hash. Restarting authentication.");
                            goto Restart;
                        }

                        Console.WriteLine(string.Format("Negotiated auth key: {0}", authKey.ToHexString()));

                        var initialSalt = ComputeInitialSalt(newNonceBytes, serverNonceBytes);
                        return(new AuthInfo(authKey, initialSalt));
                    }
                    var dhGenRetry = setClientDHParamsAnswer as DhGenRetry;
                    if (dhGenRetry != null)
                    {
                        Console.WriteLine("Retry.");

                        CheckNonce(nonce, dhGenRetry.Nonce);
                        CheckNonce(serverNonce, dhGenRetry.ServerNonce);
                        Int128 newNonceHash2 = ComputeNewNonceHash(newNonce, 2, authKeyAuxHash);

                        try
                        {
                            CheckNonce(newNonceHash2, dhGenRetry.NewNonceHash2);
                        }
                        catch
                        {
                            Console.WriteLine("Failed to match new nonce hash 2. Restarting authentication.");
                            goto Restart;
                        }

                        continue;
                    }
                    var dhGenFail = setClientDHParamsAnswer as DhGenFail;
                    if (dhGenFail != null)
                    {
                        Console.WriteLine("Fail.");

                        CheckNonce(nonce, dhGenFail.Nonce);
                        CheckNonce(serverNonce, dhGenFail.ServerNonce);
                        Int128 newNonceHash3 = ComputeNewNonceHash(newNonce, 3, authKeyAuxHash);

                        // REDUNDANT, as we'll fail anyway
                        //CheckNonce(newNonceHash3, dhGenFail.NewNonceHash3);

                        throw new MTProtoException("Failed to set client DH params.");
                    }
                }
                throw new MTProtoException(string.Format("Failed to negotiate an auth key in {0} trials.", AuthRetryCount));
            }
            catch (Exception e)
            {
                Console.WriteLine("Could not create auth key: " + e);
                throw;
            }
            finally
            {
                if (connection != null)
                {
                    connection.Dispose();
                }
            }
        }