Silverlight 4 RC – Socket Security Changes

I’ve been reading the SL4 RC docs and noticed that aspects of security have changed since the beta and since I made these screencasts on networking.

I think these are positive changes and, from what I’ve seen so far both TCP and UDP sockets drop their security limitations for an elevated application.

That is – a non-trusted application (whether in the browser or out of browser) has restrictions imposed on it;

  1. TCP sockets can only be opened to ports 4502 to 4534.
  2. TCP sockets can only be opened once a security policy allowing the opening has been downloaded via either;
    1. TCP over port 943 on the target server
    2. HTTP from port 80 on the target server ( this is new in the RC )
  3. UDP multicast sockets can only be opened to ports above 1024.
  4. UDP multicast groups can only be joined once a security policy allowing the joining has been downloaded via either;
    1. UDP unicast to port 9430 on the target server ( for a single source multicast group )
    2. UDP multicast to port 9430 on the multicast group ( for an any source multicast group )

and all those restrictions go away if you’re running trusted.

As a quick example – if I were to write a simple console application on the desktop such as this complete console application ( code hacked together in a few minutes );

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ConsoleApplication4
{
  class Program
  {
    static void Main(string[] args)
    {
      TcpListener listener = new TcpListener(IPAddress.Any, 4502);       
      listener.Start();

      networkStream = new NetworkStream(listener.AcceptSocket());

      buffer = new byte[128];
      BeginRead();

      string input = string.Empty;

      while (true)
      {
        input = Console.ReadLine();

        if (input == "x")
        {
          break;
        }
        byte[] encoded = UnicodeEncoding.Unicode.GetBytes(input);
        networkStream.Write(encoded, 0, encoded.Length);
      }
      networkStream.Close();
      listener.Stop();
    }
    static void BeginRead()
    {
      networkStream.BeginRead(buffer, 0, buffer.Length, iar =>
        {
          int bytesRead = networkStream.EndRead(iar);

          Console.WriteLine("Received [{0}]",
            UnicodeEncoding.Unicode.GetString(buffer, 0, bytesRead));

          BeginRead();
        }, null);
    }
    static byte[] buffer;
    static NetworkStream networkStream;
  }
}

and then I hit that from a simple Silverlight UI defined by this XAML;

<UserControl x:Class="SilverlightApplication1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition />
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <TextBlock
                Text="Send" />
            <TextBox
                x:Name="txtSend"
                Grid.Row="1"                
                AcceptsReturn="True" />
            <Button
                Grid.Row="2"
                HorizontalAlignment="Right"
                Content="Send" 
                Click="OnSend"/>
        </Grid>
        <Grid
            Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition />
            </Grid.RowDefinitions>
            <TextBlock
                Text="Received" />
            <TextBox
                x:Name="txtReceive"
                Grid.Row="1"
                IsReadOnly="True"/>
        </Grid>
    </Grid>
</UserControl>

with a little code behind it;

using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace SilverlightApplication1
{
  public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();
      this.Loaded += OnLoaded;
    }
    void OnLoaded(object sender, RoutedEventArgs e)
    {
      buffer = new byte[128];

      Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
        ProtocolType.Tcp);

      SocketAsyncEventArgs args = new SocketAsyncEventArgs()
      {
        SocketClientAccessPolicyProtocol = SocketClientAccessPolicyProtocol.Http,
        RemoteEndPoint = new DnsEndPoint("localhost", 4502),
      };

      args.Completed += (s, eventArgs) =>
        {
          if (eventArgs.SocketError == SocketError.Success)
          {
            this.connectedSocket = socket;
            BeginRead();
          }
        };

      socket.ConnectAsync(args);
    }
    void BeginRead()
    {
      SocketAsyncEventArgs args = new SocketAsyncEventArgs();
      args.SetBuffer(this.buffer, 0, this.buffer.Length);

      args.Completed += (s, e) =>
        {
          if (e.BytesTransferred > 0)
          {
            string text = UnicodeEncoding.Unicode.GetString(
              this.buffer, 0, e.BytesTransferred);

            AddText(text);
          }
          BeginRead();
        };
      this.connectedSocket.ReceiveAsync(args);
    }
    void AddText(string text)
    {
      this.Dispatcher.BeginInvoke(() =>
        {
          this.txtReceive.Text += text;
        });
    }
    void OnSend(object sender, RoutedEventArgs e)
    {
      if (this.connectedSocket != null)
      {
        byte[] bytes = UnicodeEncoding.Unicode.GetBytes(
          this.txtSend.Text);

        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        args.SetBuffer(bytes, 0, bytes.Length);
        this.connectedSocket.SendAsync(args);
      }
    }
    byte[] buffer;
    Socket connectedSocket;
  }
}

then notice that line 25 above is using the SocketAsyncEventArgs member SocketClientAccessPolicyProtocol to specify that we want to use HTTP to grab the security policy rather than the usual TCP port 943 that Silverlight 3 always used.

Consequently, I can drop a clientaccesspolicy.xml file onto my main IIS server as below;

<?xml version="1.0" encoding ="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from>
        <domain uri="http://localhost:10873/" />
      </allow-from>
      <grant-to>
        <socket-resource port="4502"
                         protocol="tcp" />
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

and then the socket connection is allowed because my Silverlight app is being hosted on localhost:10873.

If I take away the clientaccesspolicyfile.xml file then I can still make the application work by marking it as a trusted application and then installing it locally as trusted.

I’ll revisit this post with a UDP example.

Update 1 – I noticed that when using the new WCF TCP capabilities of Silverlight 4 it now seems to expect to make use of an HTTP Served Security Policy and not a TCP Served Security Policy. This is different to the beta but, frankly, I think that’s a smart choice as WCF TCP is new functionality and so anyone using it is likely to want the easier ( HTTP ) option and not the harder ( TCP ) option especially given that WCF is abstracting you from TCP in the first place.

Update 2 – As far as I know, UDP sockets remain using a security policy served over UDP ( either requested by unicast or by multicast depending on what kind of group you’re joining ) – this is as it was in the beta.

( of course, neither of these security policy things matters if you’re a trusted application )