Export (0) Print
Expand All
16 out of 31 rated this helpful - Rate this topic

SslStream Class

Provides a stream used for client-server communication that uses the Secure Socket Layer (SSL) security protocol to authenticate the server and optionally the client.

Namespace:  System.Net.Security
Assembly:  System (in System.dll)
public ref class SslStream : public AuthenticatedStream

SSL protocols help to provide confidentiality and integrity checking for messages transmitted using an SslStream. An SSL connection, such as that provided by SslStream, should be used when communicating sensitive information between a client and a server. Using an SslStream helps to prevent anyone from reading and tampering with information while it is in transit on the network.

An SslStream instance transmits data using a stream that you supply when creating the SslStream. When you supply this underlying stream, you have the option to specify whether closing the SslStream also closes the underlying stream. Typically, the SslStream class is used with the TcpClient and TcpListener classes. The GetStream method provides a NetworkStream suitable for use with the SslStream class.

After creating an SslStream, the server and optionally, the client must be authenticated. The server must provide an X509 certificate that establishes proof of its identity and can request that the client also do so. Authentication must be performed before transmitting information using an SslStream. Clients initiate authentication using the synchronous AuthenticateAsClient methods, which block until the authentication completes, or the asynchronous BeginAuthenticateAsClient methods, which do not block waiting for the authentication to complete. Servers initiate authentication using the synchronous AuthenticateAsServer or asynchronous BeginAuthenticateAsServer methods. Both client and server must initiate the authentication.

The authentication is handled by the Security Support Provider (SSPI) channel provider. The client is given an opportunity to control validation of the server's certificate by specifying a RemoteCertificateValidationCallback delegate when creating an SslStream. The server can also control validation by supplying a RemoteCertificateValidationCallback delegate. The method referenced by the delegate includes the remote party's certificate and any errors SSPI encountered while validating the certificate. Note that if the server specifies a delegate, the delegate's method is invoked regardless of whether the server requested client authentication. If the server did not request client authentication, the server's delegate method receives a null certificate and an empty array of certificate errors.

If the server requires client authentication, the client must specify one or more certificates for authentication. If the client has more than one certificate, the client can provide a LocalCertificateSelectionCallback delegate to select the correct certificate for the server. The client's certificates must be located in the current user's "My" certificate store. Client authentication via certificates is not supported for the Ssl2 (SSL version 2) protocol.

If the authentication fails, you receive a AuthenticationException, and the SslStream is no longer useable. You should close this object and remove all references to it so that it can be collected by the garbage collector.

When the authentication process, also known as the SSL handshake, succeeds, the identity of the server (and optionally, the client) is established and the SslStream can be used by the client and server to exchange messages. Before sending or receiving information, the client and server should check the security services and levels provided by the SslStream to determine whether the protocol, algorithms, and strengths selected meet their requirements for integrity and confidentiality. If the current settings are not sufficient, the stream should be closed. You can check the security services provided by the SslStream using the IsEncrypted and IsSigned properties. The following table shows the elements that report the cryptographic settings used for authentication, encryption and data signing.

Element

Members

The security protocol used to authenticate the server and, optionally, the client.

The SslProtocol property and the associated SslProtocols enumeration.

The key exchange algorithm.

The KeyExchangeAlgorithm property and the associated ExchangeAlgorithmType enumeration.

The message integrity algorithm.

The HashAlgorithm property and the associated HashAlgorithmType enumeration.

The message confidentiality algorithm.

The CipherAlgorithm property and the associated CipherAlgorithmType enumeration.

The strengths of the selected algorithms.

The KeyExchangeStrength, HashStrength, and CipherStrength properties.

After a successful authentication, you can send data using the synchronous Write or asynchronous BeginWrite methods. You can receive data using the synchronous Read or asynchronous BeginRead methods.

If you specified to the SslStream that the underlying stream should be left open, you are responsible for closing that stream when you are done using it.

NoteNote:

If the application that creates the SSLStream object runs with the credentials of a Normal user, the application will not be able to access certificates installed in the local machine store unless permission has been explicitly given to the user to do so.

The following code example demonstrates creating an TcpListener that uses the SslStream class to communicate with clients.

#using <System.dll>

using namespace System;
using namespace System::Collections;
using namespace System::Net;
using namespace System::Net::Sockets;
using namespace System::Net::Security;
using namespace System::Security::Authentication;
using namespace System::Text;
using namespace System::Security::Cryptography::X509Certificates;
using namespace System::IO;
public ref class SslTcpServer sealed
{
private:
   static X509Certificate^ serverCertificate = nullptr;

public:

   // The certificate parameter specifies the name of the file  
   // containing the machine certificate. 
   static void RunServer( String^ certificate )
   {
      serverCertificate = X509Certificate::CreateFromCertFile( certificate );

      // Create a TCP/IP (IPv4) socket and listen for incoming connections.
      TcpListener^ listener = gcnew TcpListener( IPAddress::Any,8080 );
      listener->Start();

      while (true) 
      {
         Console::WriteLine( L"Waiting for a client to connect..." );

         // Application blocks while waiting for an incoming connection. 
         // Type CNTL-C to terminate the server.
         TcpClient^ client = listener->AcceptTcpClient();
         ProcessClient( client );

      }
   }


   static void ProcessClient( TcpClient^ client )
   {

      // A client has connected. Create the  
      // SslStream using the client's network stream.
      SslStream^ sslStream = gcnew SslStream( client->GetStream(),false );

      // Authenticate the server but don't require the client to authenticate. 
      try
      {
         sslStream->AuthenticateAsServer( serverCertificate, false, 
             SslProtocols::Tls, true );

         // Display the properties and settings for the authenticated stream.
         DisplaySecurityLevel( sslStream );
         DisplaySecurityServices( sslStream );
         DisplayCertificateInformation( sslStream );
         DisplayStreamProperties( sslStream );

         // Set timeouts for the read and write to 5 seconds.
         sslStream->ReadTimeout = 5000;
         sslStream->WriteTimeout = 5000;

         // Read a message from the client.   
         Console::WriteLine( L"Waiting for client message..." );
         String^ messageData = ReadMessage( sslStream );
         Console::WriteLine( L"Received: {0}", messageData );

         // Write a message to the client. 
         array<Byte>^message = Encoding::UTF8->GetBytes( L"Hello from the server.<EOF>" );
         Console::WriteLine( L"Sending hello message." );
         sslStream->Write( message );
      }
      catch ( AuthenticationException^ e ) 
      {
         Console::WriteLine( L"Exception: {0}", e->Message );
         if ( e->InnerException != nullptr )
         {
            Console::WriteLine( L"Inner exception: {0}", e->InnerException->Message );
         }
         Console::WriteLine( L"Authentication failed - closing the connection." );
         sslStream->Close();
         client->Close();
         return;
      }
      finally
      {

         // The client stream will be closed with the sslStream 
         // because we specified this behavior when creating 
         // the sslStream.
         sslStream->Close();
         client->Close();
      }

   }


   static String^ ReadMessage( SslStream^ sslStream )
   {

      // Read the  message sent by the client. 
      // The client signals the end of the message using the 
      // "<EOF>" marker.
      array<Byte>^buffer = gcnew array<Byte>(2048);
      StringBuilder^ messageData = gcnew StringBuilder;
      int bytes = -1;
      do
      {

         // Read the client's test message.
         bytes = sslStream->Read( buffer, 0, buffer->Length );

         // Use Decoder class to convert from bytes to UTF8 
         // in case a character spans two buffers.
         Decoder^ decoder = Encoding::UTF8->GetDecoder();
         array<Char>^chars = gcnew array<Char>(decoder->GetCharCount( buffer, 0, bytes ));
         decoder->GetChars( buffer, 0, bytes, chars, 0 );
         messageData->Append( chars );

         // Check for EOF or an empty message. 
         if ( messageData->ToString()->IndexOf( L"<EOF>" ) != -1 )
         {
            break;
         }
      }
      while ( bytes != 0 );

      return messageData->ToString();
   }


   static void DisplaySecurityLevel( SslStream^ stream )
   {
      Console::WriteLine( L"Cipher: {0} strength {1}", stream->CipherAlgorithm, stream->CipherStrength );
      Console::WriteLine( L"Hash: {0} strength {1}", stream->HashAlgorithm, stream->HashStrength );
      Console::WriteLine( L"Key exchange: {0} strength {1}", stream->KeyExchangeAlgorithm, stream->KeyExchangeStrength );
      Console::WriteLine( L"Protocol: {0}", stream->SslProtocol );
   }


   static void DisplaySecurityServices( SslStream^ stream )
   {
      Console::WriteLine( L"Is authenticated: {0} as server? {1}", stream->IsAuthenticated, stream->IsServer );
      Console::WriteLine( L"IsSigned: {0}", stream->IsSigned );
      Console::WriteLine( L"Is Encrypted: {0}", stream->IsEncrypted );
   }


   static void DisplayStreamProperties( SslStream^ stream )
   {
      Console::WriteLine( L"Can read: {0}, write {1}", stream->CanRead, stream->CanWrite );
      Console::WriteLine( L"Can timeout: {0}", stream->CanTimeout );
   }


   static void DisplayCertificateInformation( SslStream^ stream )
   {
      Console::WriteLine( L"Certificate revocation list checked: {0}", stream->CheckCertRevocationStatus );
      X509Certificate^ localCertificate = stream->LocalCertificate;
      if ( stream->LocalCertificate != nullptr )
      {
         Console::WriteLine( L"Local cert was issued to {0} and is valid from {1} until {2}.", 
             localCertificate->Subject, 
             localCertificate->GetEffectiveDateString(), 
             localCertificate->GetExpirationDateString() );
      }
      else
      {
         Console::WriteLine( L"Local certificate is null." );
      }

      X509Certificate^ remoteCertificate = stream->RemoteCertificate;
      if ( stream->RemoteCertificate != nullptr )
      {
         Console::WriteLine( L"Remote cert was issued to {0} and is valid from {1} until {2}.", 
            remoteCertificate->Subject, 
            remoteCertificate->GetEffectiveDateString(), 
            remoteCertificate->GetExpirationDateString() );
      }
      else
      {
         Console::WriteLine( L"Remote certificate is null." );
      }
   }


private:

   static void DisplayUsage()
   {
      Console::WriteLine( L"To start the server specify:" );
      Console::WriteLine( L"serverSync certificateFile.cer" );
      Environment::Exit( 1 );
   }

public:
   int RunServerASync()
   {
      array<String^>^args = Environment::GetCommandLineArgs();
      String^ certificate = nullptr;
      if ( args == nullptr || args->Length < 2 )
      {
         DisplayUsage();
      }

      certificate = args[ 1 ];
      SslTcpServer::RunServer( certificate );
      return 0;
   }

};

int main(){
    SslTcpServer^ sts = gcnew SslTcpServer();
    sts->RunServerASync();
}
#using <mscorlib.dll>
#using <System.dll>
using namespace System;
using namespace System::Collections;
using namespace System::Net;
using namespace System::Net::Sockets;
using namespace System::Net::Security;
using namespace System::Security::Authentication;
using namespace System::Text;
using namespace System::Security::Cryptography::X509Certificates;
using namespace System::IO;

public __sealed __gc class SslTcpServer 
{
    static X509Certificate* serverCertificate = 0;
    // The certificate parameter specifies the name of the file 
    // containing the machine certificate.
public:
    static void RunServer(String* certificate) 
    {
        serverCertificate = X509Certificate::CreateFromCertFile(certificate);
        // Create a TCP/IP (IPv4) socket and listen for incoming connections.
        TcpListener* listener = new TcpListener(IPAddress::Any, 8080);    
        listener->Start();
        while (true) 
        {
            Console::WriteLine(S"Waiting for a client to connect...");
            // Application blocks while waiting for an incoming connection.
            // Type CNTL-C to terminate the server.
            TcpClient* client = listener->AcceptTcpClient();
            ProcessClient(client);
        }
    }
    static void ProcessClient (TcpClient* client)
    {
        // A client has connected. Create the 
        // SslStream using the client's network stream.
        SslStream* sslStream = new SslStream(
            client->GetStream(), false);
        // Authenticate the server but don't require the client to authenticate.
        try 
        {
            sslStream->ServerAuthenticate(serverCertificate, 
                false, SslProtocolType::Tls, true);
            // Display the properties and settings for the authenticated stream.
            DisplaySecurityLevel(sslStream);
            DisplaySecurityServices(sslStream);
            DisplayCertificateInformation(sslStream);
            DisplayStreamProperties(sslStream);

            // Set timeouts for the read and write to 5 seconds.
            sslStream->ReadTimeout = 5000;
            sslStream->WriteTimeout = 5000;
            // Read a message from the client.   
            Console::WriteLine(S"Waiting for client message...");
            String* messageData = ReadMessage(sslStream);
            Console::WriteLine(S"Received: {0}", messageData);

            // Write a message to the client.
            Byte message[] = Encoding::UTF8->GetBytes(S"Hello from the server.<EOF>");
            Console::WriteLine(S"Sending hello message.");
            sslStream->Write(message);
        }
        catch (AuthenticationException* e)
        {
            Console::WriteLine(S"Exception: {0}", e->Message);
            if (e->InnerException != 0)
            {
                Console::WriteLine(S"Inner exception: {0}", e->InnerException->Message);
            }
            Console::WriteLine (S"Authentication failed - closing the connection.");
            sslStream->Close();
            client->Close();
            return;
        }
        __finally
        {
            // The client stream will be closed with the sslStream
            // because we specified this behavior when creating
            // the sslStream.
            sslStream->Close();
            client->Close();
        }
    }
    static String* ReadMessage(SslStream* sslStream)
    {
        // Read the  message sent by the client.
        // The client signals the end of the message using the
        // "<EOF>" marker.
        Byte buffer[] = new Byte[2048];
        StringBuilder* messageData = new StringBuilder();
        int bytes = -1;
        do
        {
            // Read the client's test message.
            bytes = sslStream->Read(buffer, 0, buffer->Length);

            // Use Decoder class to convert from bytes to UTF8
            // in case a character spans two buffers.
            Decoder* decoder = Encoding::UTF8->GetDecoder();
            Char chars[] = new Char[decoder->GetCharCount(buffer,0,bytes)];
            decoder->GetChars(buffer, 0, bytes, chars,0);
            messageData->Append (chars);
            // Check for EOF or an empty message.
            if (messageData->ToString()->IndexOf(S"<EOF>") != -1)
            {
                break;
            }
        } while (bytes !=0); 

        return messageData->ToString();
    }
    static void DisplaySecurityLevel(SslStream* stream)
    {
        Console::WriteLine(S"Cipher: {0} strength {1}", __box(stream->CipherAlgorithm), __box(stream->CipherStrength));
        Console::WriteLine(S"Hash: {0} strength {1}", __box(stream->HashAlgorithm), __box(stream->HashStrength));
        Console::WriteLine(S"Key exchange: {0} strength {1}", __box(stream->KeyExchangeAlgorithm), __box(stream->KeyExchangeStrength));
        Console::WriteLine(S"Protocol: {0}", __box(stream->SslProtocol));
    }
    static void DisplaySecurityServices(SslStream* stream)
    {
        Console::WriteLine(S"Is authenticated: {0} as server? {1}", __box(stream->IsAuthenticated), __box(stream->IsServer));
        Console::WriteLine(S"IsSigned: {0}", __box(stream->IsSigned));
        Console::WriteLine(S"Is Encrypted: {0}", __box(stream->IsEncrypted));
    }
    static void DisplayStreamProperties(SslStream* stream)
    {
        Console::WriteLine(S"Can read: {0}, write {1}", __box(stream->CanRead), __box(stream->CanWrite));
        Console::WriteLine(S"Can timeout: {0}", __box(stream->CanTimeout));
    }
    static void DisplayCertificateInformation(SslStream* stream)
    {
        Console::WriteLine(S"Certificate revocation list checked: {0}", __box(stream->CheckCertRevocationStatus));

        X509Certificate* localCertificate = stream->LocalCertificate;
        if (stream->LocalCertificate != 0)
        {
            Console::WriteLine(S"Local cert was issued to {0} and is valid from {1} until {2}.",
                localCertificate->GetName(),
                localCertificate->GetEffectiveDateString(),
                localCertificate->GetExpirationDateString());
        } else
        {
            Console::WriteLine(S"Local certificate is null.");
        }
        // Display the properties of the client's certificate.
        X509Certificate* remoteCertificate = stream->RemoteCertificate;
        if (stream->RemoteCertificate != 0)
        {
            Console::WriteLine(S"Remote cert was issued to {0} and is valid from {1} until {2}.",
                remoteCertificate->GetName(),
                remoteCertificate->GetEffectiveDateString(),
                remoteCertificate->GetExpirationDateString());
        } else
        {
            Console::WriteLine(S"Remote certificate is null.");
        }
    }
private:
    static void DisplayUsage()
    { 
        Console::WriteLine(S"To start the server specify:");
        Console::WriteLine(S"serverSync certificateFile.cer");
        Environment::Exit(1);
    }
public:
    int RunServerASync()
    {
        String* args[] = Environment::GetCommandLineArgs();

        String* certificate = 0;
        if (args == 0 ||args->Length < 2 )
        {
            DisplayUsage();
        }
        certificate = args[1];
        SslTcpServer::RunServer (certificate);
        return 0;
    } 
};

int main(){
    SslTcpServer* sts = new SslTcpServer();
    sts->RunServerASync();
}

The following code example demonstrates creating a TcpClient that uses the SslStream class to communicate with a server.

#using <System.dll>
#using <System.Security.dll>

using namespace System;
using namespace System::Collections;
using namespace System::Globalization;
using namespace System::Net;
using namespace System::Net::Security;
using namespace System::Net::Sockets;
using namespace System::Security::Authentication;
using namespace System::Text;
using namespace System::Security::Cryptography::X509Certificates;
using namespace System::IO;

namespace NlsClientSync
{
    public ref class SslTcpClient
    {
    private:
        static Hashtable^ certificateErrors = gcnew Hashtable;
        // Load a table of errors that might cause  
        // the certificate authentication to fail. 
        static void InitializeCertificateErrors()
        {
            certificateErrors->Add(0x800B0101,
                "The certification has expired.");
            certificateErrors->Add(0x800B0104,
                "A path length constraint " 
                "in the certification chain has been violated.");
            certificateErrors->Add(0x800B0105,
                "A certificate contains an unknown extension " 
                "that is marked critical.");
            certificateErrors->Add(0x800B0107,
                "A parent of a given certificate in fact " 
                "did not issue that child certificate.");
            certificateErrors->Add(0x800B0108,
                "A certificate is missing or has an empty value " 
                "for a necessary field.");
            certificateErrors->Add(0x800B0109,
                "The certificate root is not trusted.");
            certificateErrors->Add(0x800B010C,
                "The certificate has been revoked.");
            certificateErrors->Add(0x800B010F,
                "The name in the certificate does not not match " 
                "the host name requested by the client.");
            certificateErrors->Add(0x800B0111,
                "The certificate was explicitly marked " 
                "as untrusted by the user.");
            certificateErrors->Add(0x800B0112,
                "A certification chain processed correctly, " 
                "but one of the CA certificates is not trusted.");
            certificateErrors->Add(0x800B0113,
                "The certificate has an invalid policy.");
            certificateErrors->Add(0x800B0114,
                "The certificate name is either not " 
                "in the permitted list or is explicitly excluded.");
            certificateErrors->Add(0x80092012,
                "The revocation function was unable to check " 
                "revocation for the certificate.");
            certificateErrors->Add(0x80090327,
                "An unknown error occurred while " 
                "processing the certificate.");
            certificateErrors->Add(0x80096001,
                "A system-level error occurred " 
                "while verifying trust.");
            certificateErrors->Add(0x80096002,
                "The certificate for the signer of the message " 
                "is invalid or not found.");
            certificateErrors->Add(0x80096003,
                "One of the counter signatures was invalid.");
            certificateErrors->Add(0x80096004,
                "The signature of the certificate " 
                "cannot be verified.");
            certificateErrors->Add(0x80096005,
                "The time stamp signature or certificate " 
                "could not be verified or is malformed.");
            certificateErrors->Add(0x80096010,
                "The digital signature of the object " 
                "was not verified.");
            certificateErrors->Add(0x80096019,
                "The basic constraint extension of a certificate " 
                "has not been observed.");
        }

        static String^ CertificateErrorDescription(UInt32 problem)
        {
            // Initialize the error message dictionary  
            // if it is not yet available. 
            if (certificateErrors->Count == 0)
            {
                InitializeCertificateErrors();
            }

            String^ description = safe_cast<String^>(
                certificateErrors[problem]);
            if (description == nullptr)
            {
                description = String::Format(
                    CultureInfo::CurrentCulture,
                    "Unknown certificate error - 0x{0:x8}",
                    problem);
            }

            return description;
        }

    public:
        // The following method is invoked  
        // by the CertificateValidationDelegate. 
    static bool ValidateServerCertificate(
            Object^ sender,
            X509Certificate^ certificate,
            X509Chain^ chain,
            SslPolicyErrors sslPolicyErrors)
        {

            Console::WriteLine("Validating the server certificate.");
            if (sslPolicyErrors == SslPolicyErrors::None)
                return true;

            Console::WriteLine("Certificate error: {0}", sslPolicyErrors);

            // Do not allow this client to communicate with unauthenticated servers. 
            return false;
        }

        static void RunClient(String^ machineName, String^ serverName)
        {

            // Create a TCP/IP client socket. 
            // machineName is the host running the server application.
            TcpClient^ client = gcnew TcpClient(machineName, 8080);
            Console::WriteLine("Client connected.");

            // Create an SSL stream that will close  
            // the client's stream.
            SslStream^ sslStream = gcnew SslStream(
                client->GetStream(), false,
                gcnew RemoteCertificateValidationCallback(ValidateServerCertificate),
                nullptr);

            // The server name must match the name 
            // on the server certificate. 
            try
            {
                sslStream->AuthenticateAsClient(serverName);
            }
            catch (AuthenticationException^ ex) 
            {
                Console::WriteLine("Exception: {0}", ex->Message);
                if (ex->InnerException != nullptr)
                {
                    Console::WriteLine("Inner exception: {0}", 
                        ex->InnerException->Message);
                }

                Console::WriteLine("Authentication failed - " 
                    "closing the connection.");
                sslStream->Close();
                client->Close();
                return;
            }
            // Encode a test message into a byte array. 
            // Signal the end of the message using the "<EOF>".
            array<Byte>^ messsage = Encoding::UTF8->GetBytes(
                "Hello from the client.<EOF>");

            // Send hello message to the server.
            sslStream->Write(messsage);
            sslStream->Flush();
            // Read message from the server.
            String^ serverMessage = ReadMessage(sslStream);
            Console::WriteLine("Server says: {0}", serverMessage);

            // Close the client connection.
            sslStream->Close();
            client->Close();
            Console::WriteLine("Client closed.");
        }
    private:
        static String^ ReadMessage(SslStream^ sslStream)
        {

            // Read the  message sent by the server. 
            // The end of the message is signaled using the 
            // "<EOF>" marker.
            array<Byte>^ buffer = gcnew array<Byte>(2048);
            StringBuilder^ messageData = gcnew StringBuilder;
            // Use Decoder class to convert from bytes to UTF8 
            // in case a character spans two buffers.
            Encoding^ u8 = Encoding::UTF8;
            Decoder^ decoder = u8->GetDecoder();

            int bytes = -1;
            do
            {
                bytes = sslStream->Read(buffer, 0, buffer->Length);

                array<__wchar_t>^ chars = gcnew array<__wchar_t>(
                    decoder->GetCharCount(buffer, 0, bytes));
                decoder->GetChars(buffer, 0, bytes, chars, 0);
                messageData->Append(chars);

                // Check for EOF. 
                if (messageData->ToString()->IndexOf("<EOF>") != -1)
                {
                    break;
                }
            }
            while (bytes != 0);

            return messageData->ToString();
        }
    };
}

int main()
{
    array<String^>^ args = Environment::GetCommandLineArgs();
    String^ serverCertificateName = nullptr;
    String^ machineName = nullptr;
    if (args == nullptr || args->Length < 2)
    {
        Console::WriteLine("To start the client specify:");
        Console::WriteLine("clientSync machineName [serverName]");
        return 1;
    }

    // User can specify the machine name and server name. 
    // Server name must match the name on  
    // the server's certificate.
    machineName = args[1];
    if (args->Length < 3)
    {
        serverCertificateName = machineName;
    }
    else
    {
        serverCertificateName = args[2];
    };

    NlsClientSync::SslTcpClient::RunClient(machineName,
        serverCertificateName);

    return 0;
}
Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

Windows 7, Windows Vista, Windows XP SP2, Windows XP Media Center Edition, Windows XP Professional x64 Edition, Windows XP Starter Edition, Windows Server 2008 R2, Windows Server 2008, Windows Server 2003, Windows Server 2000 SP4, Windows Millennium Edition, Windows 98

The .NET Framework and .NET Compact Framework do not support all versions of every platform. For a list of the supported versions, see .NET Framework System Requirements.

.NET Framework

Supported in: 3.5, 3.0, 2.0
Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback

Community Additions

ADD
Show:
© 2014 Microsoft. All rights reserved.