/// <summary> /// Fill a byte span with random data /// </summary> /// <param name="random">Entropy source</param> public static void FillWithRandom(this ByteSpan span, RandomNumberGenerator random) { if (span.Offset == 0 && span.Length == span.GetUnderlyingArray().Length) { random.GetBytes(span.GetUnderlyingArray()); return; } byte[] temp = new byte[span.Length]; random.GetBytes(temp); new ByteSpan(temp).CopyTo(span); }
// Run the GCTR cipher void GCTR(ByteSpan output, ByteSpan counterBlock, uint counter, ByteSpan data) { Debug.Assert(counterBlock.Length == 16); Debug.Assert(output.Length >= data.Length); // Loop through plaintext blocks int writeIndex = 0; int numBlocks = (data.Length + 15) / 16; for (int ii = 0; ii != numBlocks; ++ii) { // Encode counter into block // CB[1] = J0 // CB[i] = inc[32](CB[i-1]) counterBlock.WriteBigEndian32(counter, 12); ++counter; // CIPH[k](CB[i]) this.encryptor_.TransformBlock(counterBlock.GetUnderlyingArray(), counterBlock.Offset, 16, this.blockScratch_.GetUnderlyingArray(), this.blockScratch_.Offset); // Y[i] = X[i] xor CIPH[k](CB[i]) for (int jj = 0; jj != 16 && writeIndex < data.Length; ++jj, ++writeIndex) { output[writeIndex] = (byte)(data[writeIndex] ^ this.blockScratch_[jj]); } } }
/// <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)); }
/// <summary> /// Clear a span's contents to zero /// </summary> public static void SecureClear(this ByteSpan span) { if (span.Length > 0) { Array.Clear(span.GetUnderlyingArray(), span.Offset, span.Length); } }
/// <summary> /// Add data to the stream /// </summary> public void AddData(ByteSpan data) { while (data.Length > 0) { int offset = this.hash.TransformBlock(data.GetUnderlyingArray(), data.Offset, data.Length, null, 0); data = data.Slice(offset); } }
/// <inheritdoc/> public int EncryptBlock(ByteSpan inputSpan, ByteSpan outputSpan) { if (inputSpan.Length != outputSpan.Length) { throw new ArgumentException($"ouputSpan length ({outputSpan.Length}) does not match inputSpan length ({inputSpan.Length})", nameof(outputSpan)); } return(this.encryptor_.TransformBlock(inputSpan.GetUnderlyingArray(), inputSpan.Offset, inputSpan.Length, outputSpan.GetUnderlyingArray(), outputSpan.Offset)); }
public void InjectPacket(ByteSpan packet, IPEndPoint peerAddress, ConnectionId connectionId) { MessageReader reader = MessageReader.GetSized(packet.Length); reader.Length = packet.Length; Array.Copy(packet.GetUnderlyingArray(), packet.Offset, reader.Buffer, reader.Offset, packet.Length); this.ProcessIncomingMessageFromOtherThread(reader, peerAddress, connectionId); }
/// <inheritdoc /> protected override void WriteBytesToConnectionSync(byte[] bytes, int length) { ByteSpan wireData = this.WriteBytesToConnectionInternal(bytes, length); if (wireData.Length > 0) { Debug.Assert(wireData.Offset == 0, "Got a non-zero write data offset"); base.WriteBytesToConnectionSync(wireData.GetUnderlyingArray(), wireData.Length); } }
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); }
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)); } }
/// <inheritdoc /> public bool VerifyServerMessageAndGenerateSharedKey(ByteSpan output, ByteSpan serverKeyExchangeMessage, object publicKey) { RSA rsaPublicKey = publicKey as RSA; if (rsaPublicKey == null) { return(false); } else if (output.Length != X25519.KeySize) { return(false); } // Verify message is compatible with this cipher suite if (serverKeyExchangeMessage.Length != CalculateServerMessageSize(rsaPublicKey.KeySize)) { return(false); } else if (serverKeyExchangeMessage[0] != (byte)ECCurveType.NamedCurve) { return(false); } else if (serverKeyExchangeMessage.ReadBigEndian16(1) != (ushort)NamedCurve.x25519) { return(false); } else if (serverKeyExchangeMessage[3] != X25519.KeySize) { return(false); } else if (serverKeyExchangeMessage[4 + X25519.KeySize] != (byte)HashAlgorithm.Sha256) { return(false); } else if (serverKeyExchangeMessage[5 + X25519.KeySize] != (byte)SignatureAlgorithm.RSA) { return(false); } ByteSpan keyParameters = serverKeyExchangeMessage.Slice(0, 4 + X25519.KeySize); ByteSpan othersPublicKey = keyParameters.Slice(4); ushort signatureSize = serverKeyExchangeMessage.ReadBigEndian16(6 + X25519.KeySize); ByteSpan signature = serverKeyExchangeMessage.Slice(4 + keyParameters.Length); if (signatureSize != signature.Length) { return(false); } // Hash the key parameters byte[] parameterDigest = this.sha256.ComputeHash(keyParameters.GetUnderlyingArray(), keyParameters.Offset, keyParameters.Length); // Verify the signature RSAPKCS1SignatureDeformatter verifier = new RSAPKCS1SignatureDeformatter(rsaPublicKey); verifier.SetHashAlgorithm("SHA256"); if (!verifier.VerifySignature(parameterDigest, signature.ToArray())) { return(false); } // Signature has been validated, generate the shared key return(X25519.Func(output, this.privateAgreementKey, othersPublicKey)); }
/// <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); }