Printer Friendly Version      Send     
Click to Rate and Give Feedback
Related Articles

Understanding the ACLs that govern permissions and rights before an operation is allowed to proceed is critical to enhancing security.

John R. Michener

MSDN Magazine November 2008

...

Read more!

This month Scott looks at improving development skills, writing regular expressions, a web scheduling control and a SQL tips blog.

Scott Mitchell

MSDN Magazine November 2008

...

Read more!

The heart of Windows Workflow Foundation is its declarative programming model. Here are some best practices to consider when using WF to realize software solutions in the real world.

Josh Lane

MSDN Magazine December 2008

...

Read more!

With Windows Presentation Foundation (WPF) you can lay out text on a path, then animate the individual points defining the path and watch the characters bounce around in response.

Charles Petzold

MSDN Magazine December 2008

...

Read more!

This month we take a look at FxCop and other tools that enforce your design rules, along with jQuery.

Scott Mitchell

MSDN Magazine December 2008

...

Read more!

Also by this Author

Keith Brown introduces you to the new identity model in the Microsoft .NET Framework 3.0.

Keith Brown

MSDN Magazine September 2007

...

Read more!

In my last column I introduced Password Minder, the tool I use to manage all of my passwords. It generates a long, random password for each site I visit, and makes it possible for me to use the most complex passwords possible, without ever having to see the actual password material or type it in manually.

Keith Brown

MSDN Magazine October 2004

...

Read more!

When something goes wrong, a manageable application will tell the administrator how to fix the problem. The Windows Event Log can provide the necessary information.

Keith Brown

MSDN Magazine April 2007

...

Read more!

In my April 2006 column I began a discussion of InfoCard, the upcoming identity metasystem, which is being prepared for release in the Windows Vista™ timeframe. If you haven’t read that column, you should definitely start there because I’m going to assume you’re familiar with the basics I covered.

Keith Brown

MSDN Magazine May 2006

...

Read more!

As I write this column, version 2. 0 of the Microsoft® . NET Framework is at Beta 1. When I got my bits, I hacked together a little program to dump all of the public members of all public types in the entire Framework and ran it on version 1.

Keith Brown

MSDN Magazine January 2005

...

Read more!

Popular Articles

Kenny Kerr sings the praises of the new Visual C++ 2008 Feature Pack, which brings modern conveniences to Visual C++.

Kenny Kerr

MSDN Magazine May 2008

...

Read more!

Here we describe some of the more common challenges to concurrent programming and present advice for coping with them in your software.

Joe Duffy

MSDN Magazine October 2008

...

Read more!

Here are some design patterns that allow you to achieve higher cohesion and looser coupling for more flexible, reusable applications.

Jeremy Miller

MSDN Magazine October 2008

...

Read more!

This article presents an overview of the motivation behind new techniques that decompose problems into independent pieces for optimal use of parallel programming.

David Callahan

MSDN Magazine October 2008

...

Read more!

When incorporating the ASP.NET DataGrid control into your Web apps, common operations such as paging, sorting, editing, and deleting data require more effort than you might like to expend. But all that is about to change. The GridView control--the successor to the DataGrid-- extends the DataGrid's functionality it in a number of ways. First, it fully supports data source components and can automatically handle data operations, such as paging, sorting, and editing, as long as its bound data source object supports these capabilities. In addition, ...

Read more!

Our Blog

It’s helpful to think about secure design from a more holistic perspective by using threat models to drive your security engineering process.

In the November 2008 issue of MSDN Magazine, Michael Howard proposes using the threat model to help drive other SDL security requirements, primarily code review priority, fuzz testing priority, ...

Read more!

Windows Workflow Foundation (WF) imposes some restrictions on the developer authoring programs that target it. But in return WF offers a powerful, flexible, and extensible set of runtime services such as support for long-running code.

In the December 2008 issue of MSDN Magazine, Josh Lane provides some best practices to consider ...

Read more!

We're currently in the process of stepping back and taking a critical look at our Web site to see how you all are using it - and how we can redesign parts of it (big or small) to make that experience better.  We are continuously receiving your feedback on existing frustrations and we are working hard to remedy those (as a general fyi, most of the frustrations have to do with navigation).  However, in order to get a sense of whether we need to look at some of the more fundamental ...

Read more!

Silverlight provides a browser interoperability layer that allows managed code to access the document object model (DOM) of the underlying page. At the same time, JavaScript code running in the page can access the XAML content of the plug-in and even make modifications.

In the November 2008 issue of MSDN Magazine, Dino Esposito discusses the ...

Read more!

Earlier this year MSDN Magazine embarked on a collaborative project with Behind the Code, an interview program airing on MSDN Channel 9. In this program, Robert Hess interviews prominent developers at Microsoft, and those developers also write a column for { End Bracket } in MSDN Magazine. In the newest interview, Richard Ward talks about working on the core infrastructure components of future versions of Windows, as well as ...

Read more!

Security Briefs
Limited User Problems and Split Knowledge
Keith Brown

Code download available at: SecurityBriefs2006_11.exe (165 KB)
Browse the Code Online

This month I'll answer more reader questions. I'll discuss Windows Communication Foundation Web services running under normal user accounts, and the use of split knowledge and dual control of keys for protecting credit card data.

Q Why won't my simple Windows® Communication Foundation service start when I run it as a non-administrator?
Q Why won't my simple Windows® Communication Foundation service start when I run it as a non-administrator?

A First, let me say that it's good to hear that you're testing your code under a normal user account! This is an important aspect of testing that developers should not overlook.
A First, let me say that it's good to hear that you're testing your code under a normal user account! This is an important aspect of testing that developers should not overlook.
Now, the first approach I recommend for people trying to track down this type of problem is to fire up Filemon and Regmon, available from Sysinternals (which was recently acquired by Microsoft) at www.sysinternals.com, and look for failed attempts to open files or registry keys. Unfortunately, these tools can't diagnose every issue that could cause this problem. Even the simplest of Web services implemented in Windows Communication Foundation that listen on an HTTP channel will not run as a normal user by default, unless it's hosted in IIS. This is because the Windows Communication Foundation HTTP channel uses the HTTP.SYS driver to set up its listener, and HTTP.SYS does not allow non-administrators to register listeners without an administrator explicitly granting permission.
To demonstrate the problem, I've built a simple Web service that consists of two files: the source for the service and an application configuration file. Figure 1 shows the code for the service. When you launch this service while running as an administrator, it works just fine. But try running it as a normal user, and bang! Figure 2 shows the exception that occurs when using Beta 2 of the Microsoft® .NET Framework 3.0.
System.ServiceModel.Diagnostics.CallbackException: 
A user callback threw an exception.  
Check the exception stack and inner exception to determine the callback that failed. 
---> System.NullReferenceException: Object reference not set to 
     an instance of an object.
   at System.ServiceModel.Channels.DatagramChannelDemuxer`2.
      OnListenerClosed(Object source, EventArgs args)
   at System.ServiceModel.Channels.CommunicationObject.OnClosed()
   --- End of inner exception stack trace ---
   at System.ServiceModel.Channels.CommunicationObject.OnClosed()
   at System.ServiceModel.Channels.CommunicationObject.Abort()
   at System.ServiceModel.Channels.ChannelListenerBase.OnAbort()
   at System.ServiceModel.Channels.SecurityChannelListener`1.OnAbort()
   at System.ServiceModel.Channels.CommunicationObject.Abort()
   at System.ServiceModel.Channels.ChannelListenerBase.OnAbort()
   at System.ServiceModel.Channels.CommunicationObject.Abort()
   at System.ServiceModel.Dispatcher.ChannelDispatcher.OnAbort()
   at System.ServiceModel.Channels.CommunicationObject.Abort()
   at System.ServiceModel.ServiceHostBase.OnAbort()
   at System.ServiceModel.Channels.CommunicationObject.Abort()
   at System.ServiceModel.Channels.CommunicationObject.Close(
      TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Close()
   at System.ServiceModel.Channels.CommunicationObject.Dispose()
   at Program.Main(String[] args) in 
      C:\work\HelloService\Program.cs:line 24
    	
//hello.cs - simple WCF Web service that listens on HTTP
using System;
using System.ServiceModel;

[ServiceContract(Namespace = "http://example.org")]
public interface IHello {
    [OperationContract]
    void SayHello();
}

public class Hello : IHello {
    public void SayHello() {
        Console.WriteLine("Hello");
    }
}

class ConsoleHost {
    static void Main(string[] args) {
        try {
            using (ServiceHost host = new ServiceHost(typeof(Hello), 
                    new Uri("http://localhost:8080/MyServices/"))) {
                host.Open();

                Console.WriteLine(
                    "Service is listening, press any key to quit.");
                Console.ReadKey();
            }
        }
        catch (Exception x) {
            Console.WriteLine(x);
        }
    }
}

<!--app.config-->
<configuration>
  <system.serviceModel>
    <services>
      <service name="Hello">
        <endpoint address="Hello"
                  binding="wsHttpBinding"
                  contract="IHello"/>
      </service>
    </services>
  </system.serviceModel>
</configuration>
As I mentioned, one way to solve this problem is to host your service in IIS. But for some people, this isn't an option. If, for example, you're using Windows Communication Foundation in a Windows Forms-based application to receive notifications via an HTTP Web service, you'll need to deal with this problem in a different way, following the guidance in this column. If you're hosting in a Windows NT® service, you've got the same issue.
The problem is not difficult to solve—you just need an administrator to grant your application what is called a namespace reservation with HTTP.SYS. In doing so, the administrator is essentially saying, "It's OK for this user to listen over HTTP on a URL prefix that I specify." An administrator can grant these listening permissions to either an individual user or to a group.
Your setup program can grant a namespace reservation programmatically using the HTTP API, which unfortunately doesn't yet have a public .NET wrapper. Figure 3 shows some sample code that you can use to get started if you're working in C#. This particular sample expects you to pass a user or group account by name, and it must be executed by a member of the local Administrators group. This code also uses P/Invoke to call the HTTP API, which means it needs to run fully trusted as far as evidence-based security in the common language runtime (CLR) is concerned. If you're packaging this inside a Microsoft Installer, it should be fine. But if you're trying to do this from a downloaded application that runs with partial trust under a normal user account, it will not work.
using System;
using System.Xml;
using System.Security.Principal;
using System.Runtime.InteropServices;

public class ReserveHttpNamespace {
    static void Main(string[] args) {
        if (args.Length != 2) {
            Console.WriteLine(
                "Usage: reserveHttpNamespace " +
                "prefix account");
            return;
        }
        try {
            ModifyReservation(args[0], args[1], false);
            Console.WriteLine("Success!");
        }
        catch (Exception x) {
            Console.WriteLine(x.Message);
        }
    }

    static void ModifyReservation(
            string urlPrefix, string accountName,
            bool removeReservation) {
        string sddl = createSddl(accountName);
        HTTP_SERVICE_CONFIG_URLACL_SET configInfo;
        configInfo.Key.UrlPrefix = urlPrefix;
        configInfo.Param.Sddl = sddl;
        HTTPAPI_VERSION httpApiVersion =
            new HTTPAPI_VERSION(1, 0);
        int errorCode = HttpInitialize(httpApiVersion,
            HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
        if (0 != errorCode) 
            throw getException("HttpInitialize", errorCode);
        try {
            // do our best to delete any existing ACL
            errorCode = HttpDeleteServiceConfigurationAcl(
                IntPtr.Zero, HttpServiceConfigUrlAclInfo,
                ref configInfo, Marshal.SizeOf(
                    typeof(HTTP_SERVICE_CONFIG_URLACL_SET)),
                IntPtr.Zero);
            if (removeReservation) {
                if (0 != errorCode) throw getException(
                    "HttpDeleteServiceConfigurationAcl",
                    errorCode);
                return;
            }
            errorCode = HttpSetServiceConfigurationAcl(
                IntPtr.Zero, HttpServiceConfigUrlAclInfo,
                ref configInfo, Marshal.SizeOf(
                    typeof(HTTP_SERVICE_CONFIG_URLACL_SET)),
                IntPtr.Zero);
            if (0 != errorCode) throw getException(
                "HttpSetServiceConfigurationAcl", errorCode);
        }
        finally {
            errorCode = HttpTerminate(
                HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
            if (0 != errorCode) throw getException(
                "HttpTerminate",errorCode);
        }
    }

    static Exception getException(string fcn, int errorCode) {
        return new Exception(
            string.Format("{0} failed: {1}",
            fcn, getWin32ErrorMessage(errorCode)));
    }

    static string createSddl(string account) {
        string sid = new NTAccount(account).Translate(
            typeof(SecurityIdentifier)).ToString();
        // DACL that allows generic execute for the user
        // specified by account.
        // See help for HTTP_SERVICE_CONFIG_URLACL_PARAM
        // for details on what this means.
        return string.Format("D:(A;;GX;;;{0})", sid);
    }

    static string getWin32ErrorMessage(int errorCode) {
        return Marshal.GetExceptionForHR(HRESULT_FROM_WIN32(errorCode));
    }
    static int HRESULT_FROM_WIN32(int errorCode) {
        if (errorCode <= 0) return errorCode;
        return (int)((0x0000FFFFU & ((uint)errorCode)) | (7U << 16) |
            0x80000000U);
    }

    // P/Invoke stubs from http.h
    const int HttpServiceConfigUrlAclInfo = 2;
    const int HTTP_INITIALIZE_CONFIG = 2;
    [StructLayout(LayoutKind.Sequential)]
    struct HTTPAPI_VERSION {
        public HTTPAPI_VERSION(short maj, short min) {
            Major = maj; Minor = min;
        }
        short Major;
        short Minor;
    }
    [StructLayout(LayoutKind.Sequential)]
    struct HTTP_SERVICE_CONFIG_URLACL_KEY {
        [MarshalAs(UnmanagedType.LPWStr)]
        public string UrlPrefix;
    }
    [StructLayout(LayoutKind.Sequential)]
    struct HTTP_SERVICE_CONFIG_URLACL_PARAM {
        [MarshalAs(UnmanagedType.LPWStr)]
        public string Sddl;
    }
    [StructLayout(LayoutKind.Sequential)]
    struct HTTP_SERVICE_CONFIG_URLACL_SET {
        public HTTP_SERVICE_CONFIG_URLACL_KEY Key;
        public HTTP_SERVICE_CONFIG_URLACL_PARAM Param;
    }
    [DllImport("httpapi.dll", ExactSpelling = true,
            EntryPoint = "HttpSetServiceConfiguration")]
    static extern int HttpSetServiceConfigurationAcl(
        IntPtr mustBeZero, int configID,
        [In] ref HTTP_SERVICE_CONFIG_URLACL_SET configInfo,
        int configInfoLength, IntPtr mustBeZero2);
    [DllImport("httpapi.dll", ExactSpelling = true,
            EntryPoint = "HttpDeleteServiceConfiguration")]
    static extern int HttpDeleteServiceConfigurationAcl(
        IntPtr mustBeZero, int configID,
        [In] ref HTTP_SERVICE_CONFIG_URLACL_SET configInfo,
        int configInfoLength, IntPtr mustBeZero2);
    [DllImport("httpapi.dll")]
    static extern int HttpInitialize(
        HTTPAPI_VERSION version,
        int flags, IntPtr mustBeZero);
    [DllImport("httpapi.dll")]
    static extern int HttpTerminate(int flags,
        IntPtr mustBeZero);
}
You can also use a tool called HTTPCFG.EXE, which is part of the support tools found in the SUPPORT subdirectory of your operating system installation disk. You can use this tool to list existing namespace reservations, like so:
httpcfg query urlacl
Here's how to create a namespace reservation for a user account using HTTPCFG:
httpcfg set urlacl –u http://+:8080/MyServices/ 
-a D:(A;;GX;;;S-1-5-21-1681502023-2202157333-1552196959-1028)
The -a argument to HTTPCFG stands for ACL, or Access Control List, which you must specify in a rather arcane language called Security Description Definition Language (SDDL). There isn't enough space here to explain SDDL, so I'll leave that as an exercise you can do on your own. (For more information on SDDL, check out "Security Descriptor Definition Language".) The GX you see refers to GENERIC_EXECUTE permission. HTTP.SYS expects you to grant this permission if your goal is to grant permission to listen on the prefix.
The -u argument is the URL prefix that tells HTTP.SYS the shape of the URLs that you're referring to when you grant permission. The form of the URL is as follows. The scheme must be http or https, in lowercase. The host is case-insensitive and may use either the + or * wildcards (I'll talk more about this shortly). The port is an integer value and is required, even if you're talking about the default port for the scheme. Following this is an optional case-insensitive relative URI (in the previous example, this is /MyServices). And finally, regardless of whether you supply a relative URI, you need to terminate the string with a trailing slash.
Under the covers, the Windows Communication Foundation HTTP channel registers namespaces like the URL shown earlier. It does this using the HTTP API, which has a few rules about routing requests to HTTP listeners. The way you specify the host in the URL determines the priority in which your listener will be considered when a request comes in that matches more than one listener's prefix. For example, one app might register foo.com:8080/ while another app might register foo.com:8080/MyServices/. Generally the more explicit registration wins. However, there are also wildcards that can be used to control how this prioritization works. An application might register http://*:8080/. Since this listener uses the * wildcard, it picks up the dregs of whatever the other listeners don't want. In other words, * is a low priority or weak wildcard. On the other hand, if the application registered http://+:8080/, it's going to be given top priority and any HTTP request on port 8080 will go to this application without checking for other, more specific registrations. You can read more about how these URL prefixes are formed at "UrlPrefix Strings", but suffice it to say that if you want to successfully reserve a namespace prefix for the Windows Communication Foundation HTTP channel, you'll have to use a form that is general enough to cover the actual prefix that Windows Communication Foundation is going to register.
If you refer back to Figure 1, you can see that the base address I registered with ServiceHost is localhost:8080/MyServices/. Unfortunately, if you simply do the obvious thing and try to register this URL prefix explicitly with HTTPCFG, it will not work. This is because under the covers, Windows Communication Foundation appears to be registering a strong wildcard, which casts a wider net and won't be covered by an explicit host name registration. Instead, you need to use the strong wildcard syntax and register a URL prefix of http://+:8080/MyServices/. This is true regardless of whether you do this programmatically or via the HTTPCFG tool.
You might be asking yourself, "Why do I need to go through all of this hassle? Why can't any old user just register an HTTP listener like they can open a socket?" I believe the answer has to do with the way HTTP.SYS does port sharing. With a socket, once you bind to a port and start listening, that port is in use and no other application can listen on it. But with HTTP.SYS, two applications can listen on the same port because it's actually HTTP.SYS that opens the port and routes each request based on the URL prefix. Say your company allows its users to run a legitimate application that happens to listen on port 8080 by registering an HTTP listener, and the administrator has opened his firewall to allow HTTP requests through to that port. That port is a potential target for malware. Now imagine that one of the users accidentally opened an executable attachment from a bad guy, and he ended up with malware on his machine that listens for instructions from its creator. This malware might hide on port 8080 by registering a listener through the HTTP API, sharing the port with the legitimate application! But since HTTP.SYS requires you to have permission from the administrator before installing a listener, as long as you're running as a normal user, the malware won't be able to register its listener due to your security context.
There are a lot of people out there who write code while running as an administrator. Sadly they aren't going to notice this problem until they deploy their code in a non-privileged environment, which is one reason you should always test your code as a non-admin. Better yet, you can try writing code while running as a non-admin! I won't jump on that soapbox here, but if you're interested in learning more about developing as a non-admin, check out Aaron Margosis's blog, where he's dedicated many posts to the topic.

Q What is the best way to implement split knowledge and dual control of keys?
Q What is the best way to implement split knowledge and dual control of keys?

A Many Web sites accept credit card holder data, including credit card numbers, billing addresses, and so on. In late 2004, a standard called the Payment Card Industry Data Security Standards (PCI-DSS) was published to specify security requirements for companies that store, process, or transmit this type of data. One of the newer requirements is Section 3.6.6, which requires, "Split knowledge and dual control of keys (so that it requires two or three people, each knowing only their part of the key, to reconstruct the whole key)."
A Many Web sites accept credit card holder data, including credit card numbers, billing addresses, and so on. In late 2004, a standard called the Payment Card Industry Data Security Standards (PCI-DSS) was published to specify security requirements for companies that store, process, or transmit this type of data. One of the newer requirements is Section 3.6.6, which requires, "Split knowledge and dual control of keys (so that it requires two or three people, each knowing only their part of the key, to reconstruct the whole key)."
In an online transaction system, it's pretty hard to imagine having two or three people standing around typing in a secret key each time it's needed to process a credit card request. But if you have long-term storage of sensitive data like this, the concept starts to seem a lot more reasonable. So how would you go about building such a system if you wanted to satisfy this requirement?
The simplest way to split a message up among N different parties is to generate N-1 random secrets (each must be exactly as long as the message to be protected) and XOR each byte of those random secrets into each corresponding byte of the message. The blob of data you end up with after all of these operations is treated as the Nth secret. You can then give out each of these secrets to different people and destroy the original message. If one person looks at his own secret, it's going to look like random garbage (after all, it is just random data). The only way to reconstruct the original message is to bring all of the people back together, gather their secrets, and XOR them together. Because of the commutative property of XOR, the order in which this happens doesn't matter. As long as every one of the split secrets is mixed back in, you'll ultimately get back the original message.
I'm using the term "message" to refer to the set of bytes that needs to be protected. This could be a string, like "Attack at dawn!" or binary data, such as a cryptographic key used to protect persistent cardholder data.
The security of this technique is perfect in theory. (I say in theory because implementations are usually less than perfect!) If one person refuses to give up her secret, the other people cannot put their secrets together to get any closer to the original message than they were on their own. But this also leads to the drawback: if anyone loses his secret, the original message can never be recovered. If this is a concern, there are other, considerably more complicated approaches that involve solving equations in a finite field (see Bruce Schneier's book Applied Cryptography, Second Edition). I'll leave implementing these more complicated schemes for a future column. For now, let me present a simple solution to the secret-splitting problem using System.Security.Cryptography.
My proof-of-concept is a layered solution that starts with two core functions in a class called SecretSplitter. These functions split or join a message using the technique I described earlier. The code for these functions is shown in Figure 4. The first function, SplitSecret, takes a byte array that represents the message to be split and an integer that indicates how many secrets you want it to be split into. JoinSecret takes an array of split secrets and joins them together to form the original message. You'll get something different if you don't provide the exact keys that were originally split.
public static List<byte[]> SplitSecret(byte[] secret, int count) {
    if (null == secret || 0 == secret.Length) 
        throw new ArgumentException("Non-empty value required",
        "secret");
    if (count < 2) 
        throw new ArgumentException("Must be greater than one", "count");

    RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();

    // Get N-1 new secrets.
    List<byte[]> newSecrets = new List<byte[]>(count);
    for (int i = 0; i < count - 1; ++i) {
        byte[] newSecret = new byte[secret.Length];
        random.GetBytes(newSecret);
        newSecrets.Add(newSecret);
    }

    // XOR all secrets into the existing one to get the final secret.
    byte[] finalSecret = (byte[])secret.Clone();
    foreach (byte[] newSecret in newSecrets) {
        for (int i = 0; i < finalSecret.Length; ++i) {
            finalSecret[i] ^= newSecret[i];
        }
    }
    newSecrets.Add(finalSecret);

    return newSecrets;
}

public static byte[] JoinSecret(List<byte[]> splitSecrets) {
    if (null == splitSecrets) throw new ArgumentNullException();

    byte[] secret = null;
    foreach (byte[] splitSecret in splitSecrets) {
        if (null == splitSecret) throw new ArgumentNullException();
        if (null == secret) {
            secret = (byte[])splitSecret.Clone();
            continue;
        }
        if (splitSecret.Length != secret.Length) 
            throw new ArgumentException(
                "All secrets must be of the same length");

        // XOR all the split secrets together to get the original secret.
        for (int i = 0; i < secret.Length; ++i) {
            secret[i] ^= splitSecret[i];
        }
    }
    return secret;
}
Note the use of the RNGCryptoServiceProvider class to generate the secret keys. This is a much better technique than using something like System.Random. RNGCryptoServiceProvider was specifically designed to generate random data for cryptographic operations and thus isn't nearly as predictable as the stream of data you'll get from something like System.Random. It also seeds itself from many different sources of entropy on your computer.
Layered on top of these two functions are helpers that allow you to work purely with strings by Base64 encoding the data. This code is less relevant, so I won't show it here, but it's useful because we often deal with string-based messages and text files. All of the functionality is packaged in a library assembly, which I called SecretSplittingLibrary.dll.
On top of this library, I built a console application to demonstrate how key splitting might work in a specific application. This application is called ThumbDriveSecretSplitter and has two commands: one to split a message and one to join a message.
To split a message, you can run the following command:
ThumbDriveSecretSplitter split "Attack at dawn!"
This should split the message "Attack at dawn!" onto all the thumb drives that are currently plugged into the machine. Well, that was my original goal at least. But when I first tried this out, I discovered a problem: the app had some difficulty determining which volumes represented removable media. I have a thumb drive that shows up as a fixed drive in Disk Manager and I suspect that it's not the only one with this issue.
So I cheated and gave the program a little hint. In the application's configuration file, I included a list of volume names that the app should look for to determine which drives should be used:
<appSettings>
  <add key="volumeNames"
       value="FatDrive;PSThumbDrv"/>
</appSettings>
Now when I run the command, the program looks for drives that are included in the configured list and splits the message among those drives by dropping a Base64-encoded split secret into a file called "mysecret.txt" in the root of each drive. This means I can now split the message among all the thumb drives, and then hand out the drives to the individuals responsible for guarding the secret.
When you want to restore the original message, you just need to get those folks to plug their thumb drives into your machine, and then you run the other command:
ThumbDriveSecretSplitter join
This combines the secrets and prints out the protected message. Here's the output of a session using ThumbDriveSecretSplitter:
C:>ThumbDriveSecretSplitter.exe split "Attack at dawn!"
Writing E:\mysecret.txt
Writing F:\mysecret.txt

C:>ThumbDriveSecretSplitter.exe join
Reading E:\mysecret.txt
Reading F:\mysecret.txt

Joining secrets...

Attack at dawn!
When using this technique in a real system, you probably don't want to display the secret for all parties to see—that would defeat the purpose of splitting it up in the first place. Instead, you'd have the program collect the keys, compute the secret, and operate on it (most likely using it as a secret key to decrypt a sensitive backup of cardholder data).

Send your questions and comments for Keith to  briefs@microsoft.com.


Keith Brown is a cofounder of Pluralsight, a premier Microsoft .NET training provider. Keith is the author of Pluralsight's Applied .NET Security course, as well as several books, including the .NET Developer's Guide to Windows Security, available in print and on the Web. Learn more at pluralsight.com/keith.

Page view tracker