示例#1
0
        /// <summary>
        /// Expand a secret key
        /// </summary>
        /// <param name="output">Output span. Length determines how much data to generate</param>
        /// <param name="key">Original key to expand</param>
        /// <param name="label">Label (treated as a salt)</param>
        /// <param name="initialSeed">Seed for expansion (treated as a salt)</param>
        public static void ExpandSecret(ByteSpan output, ByteSpan key, ByteSpan label, ByteSpan initialSeed)
        {
            ByteSpan writer = output;

            byte[] roundSeed = new byte[label.Length + initialSeed.Length];
            label.CopyTo(roundSeed);
            initialSeed.CopyTo(roundSeed, label.Length);

            byte[] hashA = roundSeed;

            using (HMACSHA256 hmac = new HMACSHA256(key.ToArray()))
            {
                byte[] input = new byte[hmac.OutputBlockSize + roundSeed.Length];
                new ByteSpan(roundSeed).CopyTo(input, hmac.OutputBlockSize);

                while (writer.Length > 0)
                {
                    // Update hashA
                    hashA = hmac.ComputeHash(hashA);

                    // generate hash input
                    new ByteSpan(hashA).CopyTo(input);

                    ByteSpan roundOutput = hmac.ComputeHash(input);
                    if (roundOutput.Length > writer.Length)
                    {
                        roundOutput = roundOutput.Slice(0, writer.Length);
                    }

                    roundOutput.CopyTo(writer);
                    writer = writer.Slice(roundOutput.Length);
                }
            }
        }
示例#2
0
        /// <summary>
        /// Validates the authentication tag against the provided additional
        /// data, then decrypts the cipher text returning the original
        /// plaintext.
        /// </summary>
        /// <param name="nonce">
        /// The unique value used to seal this message
        /// </param>
        /// <param name="ciphertext">
        /// Combined ciphertext and authentication tag
        /// </param>
        /// <param name="associatedData">
        /// Additional data used to authenticate the message
        /// </param>
        /// <param name="output">
        /// On successful validation and decryprion, Open writes the original
        /// plaintext to output. Must contain enough space to hold
        /// `ciphertext.Length - CiphertextOverhead` bytes.
        /// </param>
        /// <returns>
        /// True if the data was validated and successfully decrypted.
        /// Otherwise, false.
        /// </returns>
        public bool Open(ByteSpan output, ByteSpan nonce, ByteSpan ciphertext, ByteSpan associatedData)
        {
            if (nonce.Length != NonceSize)
            {
                throw new ArgumentException("Invalid nonce size", nameof(nonce));
            }
            if (ciphertext.Length < CiphertextOverhead)
            {
                throw new ArgumentException("Invalid ciphertext size", nameof(ciphertext));
            }
            else if (output.Length < ciphertext.Length - CiphertextOverhead)
            {
                throw new ArgumentException("Invalid output size", nameof(output));
            }

            // Split ciphertext into actual ciphertext and authentication
            // tag components.
            ByteSpan authenticationTag = ciphertext.Slice(ciphertext.Length - TagSize);

            ciphertext = ciphertext.Slice(0, ciphertext.Length - TagSize);

            // Create the initial counter block
            nonce.CopyTo(this.blockJ_);

            // Verify the tags match
            GenerateAuthenticationTag(this.blockScratch_, ciphertext, associatedData);
            if (0 == Const.ConstantCompareSpans(this.blockScratch_, authenticationTag))
            {
                return(false);
            }

            // Decrypt the cipher text to output
            GCTR(output, this.blockJ_, 2, ciphertext);
            return(true);
        }
示例#3
0
        /// <summary>
        /// Create a new instance of the AES128_GCM record protection
        /// </summary>
        /// <param name="masterSecret">Shared secret</param>
        /// <param name="serverRandom">Server random data</param>
        /// <param name="clientRandom">Client random data</param>
        public Aes128GcmRecordProtection(ByteSpan masterSecret, ByteSpan serverRandom, ByteSpan clientRandom)
        {
            ByteSpan combinedRandom = new byte[serverRandom.Length + clientRandom.Length];

            serverRandom.CopyTo(combinedRandom);
            clientRandom.CopyTo(combinedRandom.Slice(serverRandom.Length));

            // Expand master_secret to encryption keys
            const int ExpandedSize = 0
                                     + 0                 // mac_key_length
                                     + 0                 // mac_key_length
                                     + Aes128Gcm.KeySize // enc_key_length
                                     + Aes128Gcm.KeySize // enc_key_length
                                     + ImplicitNonceSize // fixed_iv_length
                                     + ImplicitNonceSize // fixed_iv_length
            ;

            ByteSpan expandedKey = new byte[ExpandedSize];

            PrfSha256.ExpandSecret(expandedKey, masterSecret, PrfLabel.KEY_EXPANSION, combinedRandom);

            ByteSpan clientWriteKey = expandedKey.Slice(0, Aes128Gcm.KeySize);
            ByteSpan serverWriteKey = expandedKey.Slice(Aes128Gcm.KeySize, Aes128Gcm.KeySize);

            this.clientWriteIV = expandedKey.Slice(2 * Aes128Gcm.KeySize, ImplicitNonceSize);
            this.serverWriteIV = expandedKey.Slice(2 * Aes128Gcm.KeySize + ImplicitNonceSize, ImplicitNonceSize);

            this.serverWriteCipher = new Aes128Gcm(serverWriteKey);
            this.clientWriteCipher = new Aes128Gcm(clientWriteKey);
        }
        /// <inheritdoc />
        public void EncodeServerKeyExchangeMessage(ByteSpan output, object privateKey)
        {
            RSA rsaPrivateKey = privateKey as RSA;

            if (rsaPrivateKey == null)
            {
                throw new ArgumentException("Invalid private key", nameof(privateKey));
            }

            output[0] = (byte)ECCurveType.NamedCurve;
            output.WriteBigEndian16((ushort)NamedCurve.x25519, 1);
            output[3] = (byte)X25519.KeySize;
            X25519.Func(output.Slice(4, X25519.KeySize), this.privateAgreementKey);

            // Hash the key parameters
            byte[] paramterDigest = this.sha256.ComputeHash(output.GetUnderlyingArray(), output.Offset, 4 + X25519.KeySize);

            // Sign the paramter digest
            RSAPKCS1SignatureFormatter signer = new RSAPKCS1SignatureFormatter(rsaPrivateKey);

            signer.SetHashAlgorithm("SHA256");
            ByteSpan signature = signer.CreateSignature(paramterDigest);

            Debug.Assert(signature.Length == rsaPrivateKey.KeySize / 8);
            output[4 + X25519.KeySize] = (byte)HashAlgorithm.Sha256;
            output[5 + X25519.KeySize] = (byte)SignatureAlgorithm.RSA;
            output.Slice(6 + X25519.KeySize).WriteBigEndian16((ushort)signature.Length);
            signature.CopyTo(output.Slice(8 + X25519.KeySize));
        }
示例#5
0
        /// <summary>
        /// Encode a HelloVerifyRequest payload to wire format
        /// </summary>
        /// <param name="peerAddress">Address of the remote peer</param>
        /// <param name="hmac">Listener HMAC signature provider</param>
        public static void Encode(ByteSpan span, EndPoint peerAddress, HMAC hmac)
        {
            ByteSpan cookie = ComputeAddressMac(peerAddress, hmac);

            span.WriteBigEndian16((ushort)ProtocolVersion.DTLS1_2);
            span[2] = (byte)CookieSize;
            cookie.CopyTo(span.Slice(3));
        }
示例#6
0
        private static void CopyMaybeOverlappingSpans(ByteSpan output, ByteSpan input)
        {
            // Early out if the ranges `output` is equal to `input`
            if (output.GetUnderlyingArray() == input.GetUnderlyingArray())
            {
                if (output.Offset == input.Offset && output.Length == input.Length)
                {
                    return;
                }
            }

            input.CopyTo(output);
        }
示例#7
0
        /// <summary>
        /// Encode a certificate to wire formate
        /// </summary>
        public static ByteSpan Encode(X509Certificate2 certificate)
        {
            ByteSpan certData  = certificate.GetRawCertData();
            int      totalSize = certData.Length + 3 + 3;

            ByteSpan result = new byte[totalSize];

            ByteSpan writer = result;

            writer.WriteBigEndian24((uint)certData.Length + 3);
            writer = writer.Slice(3);
            writer.WriteBigEndian24((uint)certData.Length);
            writer = writer.Slice(3);

            certData.CopyTo(writer);
            return(result);
        }
示例#8
0
        private static void EncryptPlaintext(ByteSpan output, ByteSpan input, ref Record record, Aes128Gcm cipher, ByteSpan writeIV)
        {
            Debug.Assert(output.Length >= GetEncryptedSizeImpl(input.Length));

            // Build GCM nonce (authenticated data)
            ByteSpan nonce = new byte[ImplicitNonceSize + ExplicitNonceSize];

            writeIV.CopyTo(nonce);
            nonce.WriteBigEndian16(record.Epoch, ImplicitNonceSize);
            nonce.WriteBigEndian48(record.SequenceNumber, ImplicitNonceSize + 2);

            // Serialize record as additional data
            Record plaintextRecord = record;

            plaintextRecord.Length = (ushort)input.Length;
            ByteSpan associatedData = new byte[Record.Size];

            plaintextRecord.Encode(associatedData);

            cipher.Seal(output, nonce, input, associatedData);
        }
示例#9
0
        /// <summary>
        /// Encryptes the specified plaintext and generates an authentication
        /// tag for the provided additional data. Returns the byte array
        /// containg both the ciphertext and authentication tag.
        /// </summary>
        /// <param name="output">
        /// Array in which to encode the encrypted ciphertext and
        /// authentication tag. This array must be large enough to hold
        /// `plaintext.Lengh + CiphertextOverhead` bytes.
        /// </param>
        /// <param name="nonce">Unique value for this message</param>
        /// <param name="plaintext">Plaintext data to encrypt</param>
        /// <param name="associatedData">
        /// Additional data used to authenticate the message
        /// </param>
        public void Seal(ByteSpan output, ByteSpan nonce, ByteSpan plaintext, ByteSpan associatedData)
        {
            if (nonce.Length != NonceSize)
            {
                throw new ArgumentException("Invalid nonce size", nameof(nonce));
            }
            if (output.Length < plaintext.Length + CiphertextOverhead)
            {
                throw new ArgumentException("Invalid output size", nameof(output));
            }

            // Create the initial counter block
            nonce.CopyTo(this.blockJ_);

            // Encrypt the plaintext to output
            GCTR(output, this.blockJ_, 2, plaintext);

            // Generate and append the authentication tag
            int tagOffset = plaintext.Length;

            GenerateAuthenticationTag(output.Slice(tagOffset), output.Slice(0, tagOffset), associatedData);
        }
示例#10
0
        /// <summary>
        /// Parse a Handshake Certificate payload from wire format
        /// </summary>
        /// <returns>True if we successfully decode the Certificate message. Otherwise false</returns>
        public static bool Parse(out X509Certificate2 certificate, ByteSpan span)
        {
            certificate = null;
            if (span.Length < 6)
            {
                return(false);
            }

            uint totalSize = span.ReadBigEndian24();

            span = span.Slice(3);

            if (span.Length < totalSize)
            {
                return(false);
            }

            uint certificateSize = span.ReadBigEndian24();

            span = span.Slice(3);
            if (span.Length < certificateSize)
            {
                return(false);
            }

            byte[] rawData = new byte[certificateSize];
            span.CopyTo(rawData, 0);
            try
            {
                certificate = new X509Certificate2(rawData);
            }
            catch (Exception)
            {
                return(false);
            }

            return(true);
        }
        public void ClientEncryptionCanoverlap()
        {
            using (Aes128GcmRecordProtection recordProtection = new Aes128GcmRecordProtection(this.masterSecret, this.serverRandom, this.clientRandom))
            {
                ByteSpan messageAsBytes = Encoding.UTF8.GetBytes(TestMessage);

                Record record = new Record();
                record.ContentType    = ContentType.ApplicationData;
                record.Epoch          = 1;
                record.SequenceNumber = 124;
                record.Length         = (ushort)recordProtection.GetEncryptedSize(messageAsBytes.Length);

                ByteSpan encrypted = new byte[record.Length];
                messageAsBytes.CopyTo(encrypted);
                recordProtection.EncryptClientPlaintext(encrypted, encrypted.Slice(0, messageAsBytes.Length), ref record);

                ByteSpan plaintext    = encrypted.Slice(0, recordProtection.GetDecryptedSize(record.Length));
                bool     couldDecrypt = recordProtection.DecryptCiphertextFromClient(plaintext, encrypted, ref record);
                Assert.IsTrue(couldDecrypt);
                Assert.AreEqual(messageAsBytes.Length, plaintext.Length);
                Assert.AreEqual(TestMessage, Encoding.UTF8.GetString(plaintext.GetUnderlyingArray(), plaintext.Offset, plaintext.Length));
            }
        }
示例#12
0
        // Multiply two Galois field elements `X` and `Y` together and store
        // the result in `X` such that at the end of the function:
        //      X = X·Y
        static void MultiplyGF128Elements(ByteSpan X, ByteSpan Y, ByteSpan scratchZ, ByteSpan scratchV)
        {
            Debug.Assert(X.Length == 16);
            Debug.Assert(Y.Length == 16);
            Debug.Assert(scratchZ.Length == 16);
            Debug.Assert(scratchV.Length == 16);

            // Galois (finite) fields represented by GF(p) define a set of
            // closed algebraic operations. For AES128_GCM we'll be dealing
            // with the GF(2^128) field.
            //
            // We treat each incoming 16 byte block as a polynomial in field
            // and define multiplication between two polynomials as the
            // polynomial product reduced by (mod) the field polynomial:
            //      1 + x + x^2 + x^7 + x^128
            //
            // Field polynomials are represented by a 128 bit string. Bit n is
            // the coefficient of the x^n term. We use little-endian bit
            // ordering (not to be confused with byte ordering) for these
            // coefficients. E.g. X[0] & 0x00000001 represents the 7th bit in
            // the bit string defined by X, _not_ the 0th bit.
            //

            // What follows is a modified version of the "peasant's algorithm"
            // to multiply two numbers:
            //
            // Z contains the accumulated product
            // V is a copy of Y (so we can modify it via shifting).
            //
            // We calculate Z = X·V as follows
            //  We loop through each of the 128 bits in X maintaining the
            //  following loop invariant: X·V + Z = the final product
            //
            // On each iteration `ii`:
            //
            //   If the `ii`th bit of `X` is set, add the add the polynomial
            //   in `V` to `X`: `X[n] = X[n] ^ V[n]`
            //
            //   Double V (Shift one bit right since we're storing little
            //   endian bit). This has the effect of multiplying V by the
            //   polynomial `x`. We track the unrepresentable coefficient
            //   of `x^128` by storing the most significant bit before the
            //   shift `V[15] >> 7` as `carry`
            //
            //   Check if we've overflowed our multiplication. If overflow
            //   occurred, there will be a non-zero coefficient for the
            //   `x^128` term in the step above `carry`
            //
            //   If we have overflowed, our polynomial is exactly of degree
            //   129 (since we're only multiplying by `x`). We reduce the
            //   polynomial back into degree 128 by adding our field's
            //   irreducible polynomial: 1 + x + x^2 + x^7 + x^128. This
            //   reduction cancels out the x^128 term (x^128 + x^128 in GF(2)
            //   is zero). Therefore this modulo can be achieved by simply
            //   adding the irreducible polynomial to the new value of `V`. The
            //   irreducible polynomial is represented by the bit string:
            //   `11100001` followed by 120 `0`s. We can add this value to `V`
            //   by: `V[0] = V[0] ^ 0xE1`.
            SetSpanToZeros(scratchZ);
            X.CopyTo(scratchV);

            for (int ii = 0; ii != 128; ++ii)
            {
                int bitIndex = 7 - (ii % 8);
                if ((Y[ii / 8] & (1 << bitIndex)) != 0)
                {
                    for (int jj = 0; jj != 16; ++jj)
                    {
                        scratchZ[jj] ^= scratchV[jj];
                    }
                }

                bool carry = false;
                for (int jj = 0; jj != 16; ++jj)
                {
                    bool newCarry = (scratchV[jj] & 0x01) != 0;
                    scratchV[jj] >>= 1;
                    if (carry)
                    {
                        scratchV[jj] |= 0x80;
                    }
                    carry = newCarry;
                }

                if (carry)
                {
                    scratchV[0] ^= 0xE1;
                }
            }

            scratchZ.CopyTo(X);
        }
示例#13
0
        // The FieldElement code below is ported from the original
        // public domain reference implemtation of X25519
        // by D. J. Bernstien
        //
        // See: https://cr.yp.to/ecdh.html

        private static void InternalFunc(ByteSpan output, ByteSpan scalar, ByteSpan point)
        {
            if (output.Length != KeySize)
            {
                throw new ArgumentException("Invalid output size", nameof(output));
            }
            else if (scalar.Length != KeySize)
            {
                throw new ArgumentException("Invalid scalar size", nameof(scalar));
            }
            else if (point.Length != KeySize)
            {
                throw new ArgumentException("Invalid point size", nameof(point));
            }

            // copy the scalar so we can properly mask it
            ByteSpan maskedScalar = new byte[32];

            scalar.CopyTo(maskedScalar);
            maskedScalar[0]  &= 248;
            maskedScalar[31] &= 127;
            maskedScalar[31] |= 64;

            FieldElement x1 = FieldElement.FromBytes(point);
            FieldElement x2 = FieldElement.One();
            FieldElement x3 = x1;
            FieldElement z2 = FieldElement.Zero();
            FieldElement z3 = FieldElement.One();

            FieldElement tmp0 = new FieldElement();
            FieldElement tmp1 = new FieldElement();

            int swap = 0;

            for (int pos = 254; pos >= 0; --pos)
            {
                int b = (int)maskedScalar[pos / 8] >> (int)(pos % 8);
                b    &= 1;
                swap ^= b;

                FieldElement.ConditionalSwap(ref x2, ref x3, swap);
                FieldElement.ConditionalSwap(ref z2, ref z3, swap);
                swap = b;

                FieldElement.Sub(ref tmp0, ref x3, ref z3);
                FieldElement.Sub(ref tmp1, ref x2, ref z2);
                FieldElement.Add(ref x2, ref x2, ref z2);
                FieldElement.Add(ref z2, ref x3, ref z3);
                FieldElement.Multiply(ref z3, ref tmp0, ref x2);
                FieldElement.Multiply(ref z2, ref z2, ref tmp1);
                FieldElement.Square(ref tmp0, ref tmp1);
                FieldElement.Square(ref tmp1, ref x2);
                FieldElement.Add(ref x3, ref z3, ref z2);
                FieldElement.Sub(ref z2, ref z3, ref z2);
                FieldElement.Multiply(ref x2, ref tmp1, ref tmp0);
                FieldElement.Sub(ref tmp1, ref tmp1, ref tmp0);
                FieldElement.Square(ref z2, ref z2);
                FieldElement.Multiply121666(ref z3, ref tmp1);
                FieldElement.Square(ref x3, ref x3);
                FieldElement.Add(ref tmp0, ref tmp0, ref z3);
                FieldElement.Multiply(ref z3, ref x1, ref z2);
                FieldElement.Multiply(ref z2, ref tmp1, ref tmp0);
            }

            FieldElement.ConditionalSwap(ref x2, ref x3, swap);
            FieldElement.ConditionalSwap(ref z2, ref z3, swap);

            FieldElement.Invert(ref z2, ref z2);
            FieldElement.Multiply(ref x2, ref x2, ref z2);
            x2.CopyTo(output);
        }
示例#14
0
        /// <summary>
        /// Process an incoming Handshake protocol message
        /// </summary>
        /// <param name="record">Parent record</param>
        /// <param name="message">Record payload</param>
        /// <returns>
        /// True if further processing of the underlying datagram
        /// should be continues. Otherwise, false.
        /// </returns>
        private bool ProcessHandshake(ref Record record, ByteSpan message)
        {
            // Each record may have multiple Handshake messages
            while (message.Length > 0)
            {
                ByteSpan originalPayload = message;

                Handshake handshake;
                if (!Handshake.Parse(out handshake, message))
                {
                    this.logger.WriteError("Dropping malformed handshake message");
                    return(false);
                }
                message = message.Slice(Handshake.Size);

                if (message.Length < handshake.Length)
                {
                    this.logger.WriteError($"Dropping malformed handshake message: AvailableBytes({message.Length}) Size({handshake.Length})");
                    return(false);
                }

                originalPayload = originalPayload.Slice(0, (int)(Handshake.Size + handshake.Length));
                ByteSpan payload = originalPayload.Slice(Handshake.Size);
                message = message.Slice((int)handshake.Length);

                // We only support fragmented Certificate messages
                // from the server
                if (handshake.MessageType != HandshakeType.Certificate && (handshake.FragmentOffset != 0 || handshake.FragmentLength != handshake.Length))
                {
                    this.logger.WriteError($"Dropping fragmented handshake message Type({handshake.MessageType}) Offset({handshake.FragmentOffset}) FragmentLength({handshake.FragmentLength}) Length({handshake.Length})");
                    continue;
                }

                switch (handshake.MessageType)
                {
                case HandshakeType.HelloVerifyRequest:
                    if (this.nextEpoch.State != HandshakeState.ExpectingServerHello)
                    {
                        this.logger.WriteError($"Dropping unexpected HelloVerifyRequest handshake message State({this.nextEpoch.State})");
                        continue;
                    }
                    else if (handshake.MessageSequence != 0)
                    {
                        this.logger.WriteError($"Dropping bad-sequence HelloVerifyRequest MessageSequence({handshake.MessageSequence})");
                        continue;
                    }

                    HelloVerifyRequest helloVerifyRequest;
                    if (!HelloVerifyRequest.Parse(out helloVerifyRequest, payload))
                    {
                        this.logger.WriteError("Dropping malformed HelloVerifyRequest handshake message");
                        continue;
                    }

                    // Save the cookie
                    this.nextEpoch.Cookie = new byte[helloVerifyRequest.Cookie.Length];
                    helloVerifyRequest.Cookie.CopyTo(this.nextEpoch.Cookie);

                    // Restart the handshake
                    this.SendClientHello();
                    break;

                case HandshakeType.ServerHello:
                    if (this.nextEpoch.State != HandshakeState.ExpectingServerHello)
                    {
                        this.logger.WriteError($"Dropping unexpected ServerHello handshake message State({this.nextEpoch.State})");
                        continue;
                    }
                    else if (handshake.MessageSequence != 1)
                    {
                        this.logger.WriteError($"Dropping bad-sequence ServerHello MessageSequence({handshake.MessageSequence})");
                        continue;
                    }

                    ServerHello serverHello;
                    if (!ServerHello.Parse(out serverHello, payload))
                    {
                        this.logger.WriteError("Dropping malformed ServerHello message");
                        continue;
                    }

                    switch (serverHello.CipherSuite)
                    {
                    case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
                        this.nextEpoch.Handshake = new X25519EcdheRsaSha256(this.random);
                        break;

                    default:
                        this.logger.WriteError($"Dropping malformed ServerHello message. Unsupported CipherSuite({serverHello.CipherSuite})");
                        continue;
                    }

                    // Save server parameters
                    this.nextEpoch.SelectedCipherSuite = serverHello.CipherSuite;
                    serverHello.Random.CopyTo(this.nextEpoch.ServerRandom);
                    this.nextEpoch.State = HandshakeState.ExpectingCertificate;
                    this.nextEpoch.CertificateFragments.Clear();
                    this.nextEpoch.CertificatePayload = ByteSpan.Empty;

                    // Append ServerHelllo message to the verification stream
                    this.nextEpoch.VerificationStream.Write(
                        originalPayload.GetUnderlyingArray()
                        , originalPayload.Offset
                        , originalPayload.Length
                        );
                    break;

                case HandshakeType.Certificate:
                    if (this.nextEpoch.State != HandshakeState.ExpectingCertificate)
                    {
                        this.logger.WriteError($"Dropping unexpected Certificate handshake message State({this.nextEpoch.State})");
                        continue;
                    }
                    else if (handshake.MessageSequence != 2)
                    {
                        this.logger.WriteError($"Dropping bad-sequence Certificate MessageSequence({handshake.MessageSequence})");
                        continue;
                    }

                    // If this is a fragmented message
                    if (handshake.FragmentLength != handshake.Length)
                    {
                        if (this.nextEpoch.CertificatePayload.Length != handshake.Length)
                        {
                            this.nextEpoch.CertificatePayload = new byte[handshake.Length];
                            this.nextEpoch.CertificateFragments.Clear();
                        }

                        // Add this fragment
                        payload.CopyTo(this.nextEpoch.CertificatePayload.Slice((int)handshake.FragmentOffset, (int)handshake.FragmentLength));
                        this.nextEpoch.CertificateFragments.Add(new FragmentRange {
                            Offset = (int)handshake.FragmentOffset, Length = (int)handshake.FragmentLength
                        });
                        this.nextEpoch.CertificateFragments.Sort((FragmentRange lhs, FragmentRange rhs) => {
                            return(lhs.Offset.CompareTo(rhs.Offset));
                        });

                        // Have we completed the message?
                        int  currentOffset = 0;
                        bool valid         = true;
                        foreach (FragmentRange range in this.nextEpoch.CertificateFragments)
                        {
                            if (range.Offset != currentOffset)
                            {
                                valid = false;
                                break;
                            }

                            currentOffset += range.Length;
                        }

                        if (currentOffset != this.nextEpoch.CertificatePayload.Length)
                        {
                            valid = false;
                        }

                        // Still waiting on more fragments?
                        if (!valid)
                        {
                            continue;
                        }

                        // Replace the message payload, and continue
                        this.nextEpoch.CertificateFragments.Clear();
                        payload = this.nextEpoch.CertificatePayload;
                    }

                    X509Certificate2 certificate;
                    if (!Certificate.Parse(out certificate, payload))
                    {
                        this.logger.WriteError("Dropping malformed Certificate message");
                        continue;
                    }

                    // Verify the certificate is authenticate
                    if (!this.serverCertificates.Contains(certificate))
                    {
                        this.logger.WriteError("Dropping malformed Certificate message: Certificate not authentic");
                        continue;
                    }

                    RSA publicKey = certificate.PublicKey.Key as RSA;
                    if (publicKey == null)
                    {
                        this.logger.WriteError("Dropping malfomed Certificate message: Certificate is not RSA signed");
                        continue;
                    }

                    // Add the final Certificate message to the verification stream
                    Handshake fullCertificateHandhake = handshake;
                    fullCertificateHandhake.FragmentOffset = 0;
                    fullCertificateHandhake.FragmentLength = fullCertificateHandhake.Length;

                    byte[] serializedCertificateHandshake = new byte[Handshake.Size];
                    fullCertificateHandhake.Encode(serializedCertificateHandshake);
                    this.nextEpoch.VerificationStream.Write(serializedCertificateHandshake, 0, serializedCertificateHandshake.Length);
                    this.nextEpoch.VerificationStream.Write(payload.GetUnderlyingArray(), payload.Offset, payload.Length);

                    this.nextEpoch.ServerPublicKey = publicKey;
                    this.nextEpoch.State           = HandshakeState.ExpectingServerKeyExchange;
                    break;

                case HandshakeType.ServerKeyExchange:
                    if (this.nextEpoch.State != HandshakeState.ExpectingServerKeyExchange)
                    {
                        this.logger.WriteError($"Dropping unexpected ServerKeyExchange handshake message State({this.nextEpoch.State})");
                        continue;
                    }
                    else if (this.nextEpoch.ServerPublicKey == null)
                    {
                        ///NOTE(mendsley): This _should_ not
                        /// happen on a well-formed client
                        Debug.Assert(false, "How are we processing a ServerKeyExchange message without a server public key?");

                        this.logger.WriteError($"Dropping unexpected ServerKeyExchange handshake message: No server public key");
                        continue;
                    }
                    else if (this.nextEpoch.Handshake == null)
                    {
                        ///NOTE(mendsley): This _should_ not
                        /// happen on a well-formed client
                        Debug.Assert(false, "How did we receive a ServerKeyExchange message without a handshake instance?");

                        this.logger.WriteError($"Dropping unexpected ServerKeyExchange handshake message: No key agreement interface");
                        continue;
                    }
                    else if (handshake.MessageSequence != 3)
                    {
                        this.logger.WriteError($"Dropping bad-sequence ServerKeyExchange MessageSequence({handshake.MessageSequence})");
                        continue;
                    }

                    ByteSpan sharedSecret = new byte[this.nextEpoch.Handshake.SharedKeySize()];
                    if (!this.nextEpoch.Handshake.VerifyServerMessageAndGenerateSharedKey(sharedSecret, payload, this.nextEpoch.ServerPublicKey))
                    {
                        this.logger.WriteError("Dropping malformed ServerKeyExchangeMessage");
                        return(false);
                    }

                    // Generate the session master secret
                    ByteSpan randomSeed = new byte[2 * Random.Size];
                    this.nextEpoch.ClientRandom.CopyTo(randomSeed);
                    this.nextEpoch.ServerRandom.CopyTo(randomSeed.Slice(Random.Size));

                    const int MasterSecretSize = 48;
                    ByteSpan  masterSecret     = new byte[MasterSecretSize];
                    PrfSha256.ExpandSecret(
                        masterSecret
                        , sharedSecret
                        , PrfLabel.MASTER_SECRET
                        , randomSeed
                        );

                    // Create record protection for the upcoming epoch
                    switch (this.nextEpoch.SelectedCipherSuite)
                    {
                    case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
                        this.nextEpoch.RecordProtection = new Aes128GcmRecordProtection(
                            masterSecret
                            , this.nextEpoch.ServerRandom
                            , this.nextEpoch.ClientRandom
                            );
                        break;

                    default:
                        ///NOTE(mendsley): this _should_ not
                        /// happen on a well-formed client.
                        Debug.Assert(false, "SeverHello processing already approved this ciphersuite");

                        this.logger.WriteError($"Dropping malformed ServerKeyExchangeMessage: Could not create record protection");
                        return(false);
                    }

                    this.nextEpoch.State        = HandshakeState.ExpectingServerHelloDone;
                    this.nextEpoch.MasterSecret = masterSecret;

                    // Append ServerKeyExchange to the verification stream
                    this.nextEpoch.VerificationStream.Write(
                        originalPayload.GetUnderlyingArray()
                        , originalPayload.Offset
                        , originalPayload.Length
                        );
                    break;

                case HandshakeType.ServerHelloDone:
                    if (this.nextEpoch.State != HandshakeState.ExpectingServerHelloDone)
                    {
                        this.logger.WriteError($"Dropping unexpected ServerHelloDone handshake message State({this.nextEpoch.State})");
                        continue;
                    }
                    else if (handshake.MessageSequence != 4)
                    {
                        this.logger.WriteError($"Dropping bad-sequence ServerHelloDone MessageSequence({handshake.MessageSequence})");
                        continue;
                    }

                    this.nextEpoch.State = HandshakeState.ExpectingChangeCipherSpec;

                    // Append ServerHelloDone to the verification stream
                    this.nextEpoch.VerificationStream.Write(
                        originalPayload.GetUnderlyingArray()
                        , originalPayload.Offset
                        , originalPayload.Length
                        );

                    this.SendClientKeyExchangeFlight(false);
                    break;

                case HandshakeType.Finished:
                    if (this.nextEpoch.State != HandshakeState.ExpectingFinished)
                    {
                        this.logger.WriteError($"Dropping unexpected Finished handshake message State({this.nextEpoch.State})");
                        continue;
                    }
                    else if (payload.Length != Finished.Size)
                    {
                        this.logger.WriteError($"Dropping malformed Finished handshake message Size({payload.Length})");
                        continue;
                    }
                    else if (handshake.MessageSequence != 7)
                    {
                        this.logger.WriteError($"Dropping bad-sequence Finished MessageSequence({handshake.MessageSequence})");
                        continue;
                    }

                    // Verify the digest from the server
                    if (1 != Crypto.Const.ConstantCompareSpans(payload, this.nextEpoch.ServerVerification))
                    {
                        this.logger.WriteError("Dropping non-verified Finished handshake message");
                        return(false);
                    }

                    ++this.nextEpoch.Epoch;
                    this.nextEpoch.State = HandshakeState.Established;
                    this.nextEpoch.NextPacketResendTime = DateTime.MinValue;
                    this.nextEpoch.ServerVerification.SecureClear();
                    this.nextEpoch.MasterSecret.SecureClear();

                    this.FlushQueuedApplicationData();
                    break;

                // Drop messages we do not support
                case HandshakeType.CertificateRequest:
                case HandshakeType.HelloRequest:
                    this.logger.WriteError($"Dropping unsupported handshake message MessageType({handshake.MessageType})");
                    break;

                // Drop messages that originate from the client
                case HandshakeType.ClientHello:
                case HandshakeType.ClientKeyExchange:
                case HandshakeType.CertificateVerify:
                    this.logger.WriteError($"Dropping client handshake message MessageType({handshake.MessageType})");
                    break;
                }
            }

            return(true);
        }
示例#15
0
        /// <summary>
        /// Handle an incoming datagram
        /// </summary>
        /// <param name="span">Bytes of the datagram</param>
        private void HandleReceive(ByteSpan span)
        {
            // Each incoming packet may contain multiple DTLS
            // records
            while (span.Length > 0)
            {
                Record record;
                if (!Record.Parse(out record, span))
                {
                    this.logger.WriteError("Dropping malformed record");
                    return;
                }
                span = span.Slice(Record.Size);

                if (span.Length < record.Length)
                {
                    this.logger.WriteError($"Dropping malformed record. Length({record.Length}) Available Bytes({span.Length})");
                    return;
                }

                ByteSpan recordPayload = span.Slice(0, record.Length);
                span = span.Slice(record.Length);

                // Early out and drop ApplicationData records
                if (record.ContentType == ContentType.ApplicationData && this.nextEpoch.State != HandshakeState.Established)
                {
                    this.logger.WriteError("Dropping ApplicationData record. Cannot process yet");
                    continue;
                }

                // Drop records from a different epoch
                if (record.Epoch != this.epoch)
                {
                    this.logger.WriteError($"Dropping bad-epoch record. RecordEpoch({record.Epoch}) Epoch({this.epoch})");
                    continue;
                }

                // Prevent replay attacks by dropping records
                // we've already processed
                int   windowIndex = (int)(this.currentEpoch.NextExpectedSequence - record.SequenceNumber - 1);
                ulong windowMask  = 1ul << windowIndex;
                if (record.SequenceNumber < this.currentEpoch.NextExpectedSequence)
                {
                    if (windowIndex >= 64)
                    {
                        this.logger.WriteError($"Dropping too-old record: Sequnce({record.SequenceNumber}) Expected({this.currentEpoch.NextExpectedSequence})");
                        continue;
                    }

                    if ((this.currentEpoch.PreviousSequenceWindowBitmask & windowMask) != 0)
                    {
                        this.logger.WriteError("Dropping duplicate record");
                        continue;
                    }
                }

                // Verify record authenticity
                int      decryptedSize    = this.currentEpoch.RecordProtection.GetDecryptedSize(recordPayload.Length);
                ByteSpan decryptedPayload = recordPayload.ReuseSpanIfPossible(decryptedSize);

                if (!this.currentEpoch.RecordProtection.DecryptCiphertextFromServer(decryptedPayload, recordPayload, ref record))
                {
                    this.logger.WriteError("Dropping non-authentic record");
                    return;
                }

                recordPayload = decryptedPayload;

                // Update out sequence number bookkeeping
                if (record.SequenceNumber >= this.currentEpoch.NextExpectedSequence)
                {
                    int windowShift = (int)(record.SequenceNumber + 1 - this.currentEpoch.NextExpectedSequence);
                    this.currentEpoch.PreviousSequenceWindowBitmask <<= windowShift;
                    this.currentEpoch.NextExpectedSequence            = record.SequenceNumber + 1;
                }
                else
                {
                    this.currentEpoch.PreviousSequenceWindowBitmask |= windowMask;
                }

                switch (record.ContentType)
                {
                case ContentType.ChangeCipherSpec:
                    if (this.nextEpoch.State != HandshakeState.ExpectingChangeCipherSpec)
                    {
                        this.logger.WriteError($"Dropping unexpected ChangeCipherSpec State({this.nextEpoch.State})");
                        break;
                    }
                    else if (this.nextEpoch.RecordProtection == null)
                    {
                        ///NOTE(mendsley): This _should_ not
                        /// happen on a well-formed client.
                        Debug.Assert(false, "How did we receive a ChangeCipherSpec message without a pending record protection instance?");
                        break;
                    }

                    if (!ChangeCipherSpec.Parse(recordPayload))
                    {
                        this.logger.WriteError("Dropping malformed ChangeCipherSpec message");
                        break;
                    }

                    // Migrate to the next epoch
                    this.epoch = this.nextEpoch.Epoch;
                    this.currentEpoch.RecordProtection              = this.nextEpoch.RecordProtection;
                    this.currentEpoch.NextOutgoingSequence          = this.nextEpoch.NextOutgoingSequence;
                    this.currentEpoch.NextExpectedSequence          = 1;
                    this.currentEpoch.PreviousSequenceWindowBitmask = 0;

                    this.nextEpoch.State = HandshakeState.ExpectingFinished;
                    this.nextEpoch.SelectedCipherSuite = CipherSuite.TLS_NULL_WITH_NULL_NULL;
                    this.nextEpoch.RecordProtection    = null;
                    this.nextEpoch.Handshake?.Dispose();
                    this.nextEpoch.Cookie = ByteSpan.Empty;
                    this.nextEpoch.VerificationStream.SetLength(0);
                    this.nextEpoch.ServerPublicKey = null;
                    this.nextEpoch.ServerRandom.SecureClear();
                    this.nextEpoch.ClientRandom.SecureClear();
                    this.nextEpoch.MasterSecret.SecureClear();
                    break;

                case ContentType.Alert:
                    this.logger.WriteError("Dropping unsupported alert record");
                    continue;

                case ContentType.Handshake:
                    if (!ProcessHandshake(ref record, recordPayload))
                    {
                        return;
                    }
                    break;

                case ContentType.ApplicationData:
                    // Forward data to the application
                    MessageReader reader = MessageReader.GetSized(recordPayload.Length);
                    reader.Length = recordPayload.Length;
                    recordPayload.CopyTo(reader.Buffer);

                    base.HandleReceive(reader, recordPayload.Length);
                    break;
                }
            }
        }