U-Prove and why U should care

Privacy and minimal disclosure of information are important aspects of any identity verification system. However, end-users are often unaware of exactly what information is being disclosed to online service providers.

The U-Prove protocol has been devised to resolve these problems by putting into the hands of the end-user the control of what information is passed between Relying Parties (RP) and Identity Providers (IdP).

Before diving into U-Prove it is instructive to take a look at a protocol that demonstrates the intractability of the privacy problem. We can then look at how U-Prove solves this problem.

Take the simplest configuration of a federated login:

Simplest_4_02DA8180

  1. End-user attempts to access the RP website
  2. The RP has no knowledge of the end-user and instructs the client to authenticate the end-user at an IdP
  3. The end-user logs in and the IdP instructs the client to send the end-user back to the RP with a token containing attributes signed with the IdP’s private key, {A1 ….. An}sig

Also, let’s assume a typical token format, a SAML assertion, is being passed:

 <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion">
    ... removed for brevity
    <saml:Attribute
        AttributeName="name"
        AttributeNamespace="https://schemas.xmlsoap.org/ws/2005/05/claims">
        <saml:AttributeValue>Mrs Jane Doe</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute
        AttributeName="streetaddress"
        AttributeNamespace="https://schemas.xmlsoap.org/ws/2005/05/claims">
        <saml:AttributeValue>17 Banana St, London, W1 5TG</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute
        AttributeName="dateofbirth"
        AttributeNamespace="https://schemas.xmlsoap.org/ws/2005/05/claims">
        <saml:AttributeValue>14/04/1983</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute
        AttributeName="nationality"
        AttributeNamespace="https://schemas.xmlsoap.org/ws/2005/05/claims">
        <saml:AttributeValue>British</saml:AttributeValue>
    </saml:Attribute>
    <ds:Signature xmlns:ds="https://www.w3.org/2000/09/xmldsig#">
        ... removed for brevity
    </ds:Signature>
</saml:Assertion>

The RP that receives the assertion may only need the name attribute but is getting everything else for free. For some RP’s this is great as they may want to harvest all the details they can get about an end-user. However, a passive client has no visibility of the assertion as it is passed back to the RP. Therefore the question is, armed with the knowledge that unnecessary information was being passed, would the end-user have given consent?

Another example could be an electronic referendum vote put forward by government. All the government really needs to know is that the end-user is old enough to vote and is a citizen. However, in this supposedly anonymous vote the end-user has unwittingly supplied their name and address in the assertion.

One of the problems with SAML (and most other claims based protocols, I don’t want to single out SAML here) is that disclosure of information is an all or nothing affair because the assertion is digitally signed by the IdP for the purpose of authenticity, and so cannot be altered.

That said, it would be possible to get a new assertion issued by an intermediate authority after asking the end-user which attributes to include. However, the problem is again one of information disclosure as the intermediate authority has now seen the original assertion.

Another approach might be to use a claims query language such as SAML2 AttributeQuery. With query languages only those attributes that the RP requests are returned. Unfortunately, there is nothing to stop the RP from querying additional information unbeknown to the client, or for the IdP to return extra information. Also, the all or nothing nature of information disclosure still remains.

Even an active client cannot avoid the privacy problem. Assuming the IdP issues an assertion with unecessary information:

  • In the case where the active client is a server-side authority working on behalf of the end-user, the assertion could be re-signed with a reduced set of attributes. However, unnecessary disclosure has occurred because the server-side authority has seen the original assertion
  • In the case where the active client is fully in the hands of the end-user (e.g. – a browser plug-in) there is only a tamperproof assertion to do anything with as the end-user cannot resign the assertion with a reduced set of attributes (if he/she could then chaos would ensue as every end-user is by implication an authority)
Enter U-Prove!

U-Prove is an innovative user-centric protocol for token exchange that gives the end-user total control over what information is seen by RP’s, thus doing away with the all or nothing approach of other protocols.

With U-Prove it is possible, but not limited, to:

  • Selectively disclose attributes to a RP whilst maintaining the authoritative aspects of the message (i.e – the IdP’s digital signature)
  • Issue tokens in such a way that is not possible for the RP and IdP to collude, even if they are in possession of all of the tokens and EVEN if they are one and the same entity
  • An IdP can issue a token containing attributes that it never sees and that it is unable to learn

There are many other unique features, and I encourage you to check them out:

In the U-Prove world the terminology for the main actors is slightly different than those used thus far. The IdP is known as the ‘Issuer’, the client (end-user) as the ‘Prover’, and the RP as the ‘Verifier’.

U-Prove works by employing two protocols, both coordinated by the Prover. First, a series of messages is exchanged between the Prover and Issuer; this is known as the ‘issuance protocol’. The end result is a set of attribute-rich tokens that the Prover can selectively disclose to the Verifier using the ‘presentation protocol’. This is shown below:

image_6_34F8C8F0

  1. End-user attempts to access the Verifier website
  2. The Verifier has no knowledge of the end-user and instructs the client to authenticate at the Issuer
  3. The Prover and Issuer complete the Issuance Protocol
  4. The resulting token(s), containing attributes signed with the IdP’s private key, {A1 ….. An}sig, are optionally stored away for future use
  5. A token is retrieved from the token store
  6. The Prover and Verifier complete the Presentation Protocol, resulting in a token that selectively discloses attributes 1 and 4, {A1, A4}sig, whilst maintaining the authoritative signature of the Issuer

As you can see, the Prover is central to the process and this is why U-Prove lends itself, but is not limited to, client-side technologies such as Windows CardSpace. It doesn’t make as much sense (although it can be done) for a server-side authority to hold the U-Prove tokens on behalf of the Prover, as the authority then has visibility of the full set of attributes and the privacy problem rears its head again.

The underlying cryptographic techniques employed by U-Prove are too complex to go into here but it has been shown that compromising the cryptography is equivalent to solving the discrete logarithm problem. This means that U-Prove is at least as secure as most other modern cryptographic techniques.

Thankfully, we don’t have to know all of the complexities. The U-Prove CTP has recently been launched which adds U-Prove support to Windows Identity Foundation, ADFS 2.0 and Windows CardSpace 2.0. Using these technologies it is possible to incorporate U-Prove into an identity verification system with very few lines of code. Issuer, Prover and Verifier websites are provided as part of the CTP samples.

A sample console application is shown below that demonstrates the manner in which the Issuer, Prover and Verifier communicate with each other. In a realistic scenario the code would not be written in-line as here because the Issuer, Prover and Verifier would be on separate networks, with communication being in the form of XML over HTTP.

N.B - This sample will not work without the Issuance parameters and Issuer private key used by the SampleIssuerParametersStore class. The parameters can be created by following the simple instructions in the CTP samples. I have also included the SampleIssuerParametersStore class so you can see where the Issuance parameters would slot in.

Have fun!

 //-----------------------------------------------------------------------------
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------------
namespace UProve
{
    using System;
    using System.Collections.Generic;
    using Microsoft.IdentityModel.UProve;
    using Microsoft.IdentityModel.UProve.Crypto;
    using UPAttribute = Microsoft.IdentityModel.UProve.Attribute;

    class Program
    {
        static void Main(string[] args)
        {
            // This the unique id for the issuers parameters (see below) and is 
            // used by all of the players
            string uidp = "https://fabrikam.com/2010/03/UProveManagedCardSample";
            byte[] parameters;
            byte[] key;

            // This is the set of issuer parameters that are shared between issuer
            // prover and verifier. Only the issuer holds the private key in order
            // to create authoritative signatures on the U-Prove tokens given out
            // to provers
            SampleIssuerParametersStore store = new SampleIssuerParametersStore();
            store.GetIssuerParametersAndKey(uidp, out parameters, out key);
            IssuerParametersWithPrivateKey ipwpk = new IssuerParametersWithPrivateKey(
                IssuerParametersUtil.Deserialize(parameters),
                key);

            Issuer issuer = new Issuer();

            // Both prover and verifier require the issuer parameters given out by the issuer
            Prover prover = new Prover(store);
            Verifier verifier = new Verifier(store);

            // Long lived Tokens can be requested in batches so that the prover can store them
            // and use them when required. There is also the concept of on-demand tokens which
            // involve the verifier
            int numTokens = 1;

            // This represents the number of attributes requested by the prover, of the issuer
            // The attributes are 1-index based so that claim type is not given away in the
            // event that the message is intercepted
            // This number must be less than total number of attributes offered by the issuer
            // which can be determined by inspecting the issuance parameters
            int numAttributes = 3;

            Attributes attributes = new Attributes();
            attributes.Attribute = new UPAttribute[numAttributes];

            for (int i = 0; i < attributes.Attribute.Length; i++)
            {
                attributes.Attribute[i] = new UPAttribute();
                attributes.Attribute[i].Value = "value" + (i + 1); // indices are 1-based
                attributes.Attribute[i].Index = (short)(i + 1); // indices are 1-based
            }


            // This is the token information field which can contain authoritative data such
            // as token expiry, start date etc
            TI TI = TI.CreateTI(DateTime.Now.AddYears(10), DateTimeGranularity.Day);

            // The next 4 lines shows the issuance protocol performed between issuer
            // and prover
            Instance instance1 = issuer.InitIssuance(ipwpk, numTokens, TokenClass.claim, attributes, TI);
            Instance instance2 = prover.InitIssuance(uidp, instance1);
            Instance instance3 = issuer.CompleteIssuance(instance2);

            // At the end of the issuance protocol the tokens are available to the prover
            TokenWithPrivateKey[] tokensAndKeys = prover.CompleteIssuance(instance3);

            // Presentation example
            // In this case only attribute 1 will be selectively disclosed to
            // the verifier but any combination of 1,2,3 could be chosen
            List<short> disclosedList = new List<short>();
            disclosedList.Add(1);

            // The message parameter can take any form but can be meaningful information
            // which is embedded in the presentation token
            Random random = new Random();
            byte[] m = new byte[random.Next(100)];
            random.NextBytes(m);

            // The subset proof represents the bulk of the presentation token
            SubsetProof proof = prover.GenerateSubsetProof(
                m,
                tokensAndKeys[0],
                attributes,
                disclosedList.ToArray());

            // Finally, the verifier checks the authenticity of the presentation token
            // using the shared issuer parameters
            string tokenIdentifier;
            IssuerParameters outParams;
            verifier.VerifyProof(m, tokensAndKeys[0].Token, proof, out tokenIdentifier, out outParams);
        }
    }
}
 //-----------------------------------------------------------------------------
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------------
namespace UProve
{
    using System;
    using System.IO;
    using Microsoft.IdentityModel.UProve.Crypto;

    /// <summary>
    /// Sample implementation of an Issuer parameters store. Issuer parameters and associated
    /// private keys are stored as individual files.
    ///
    /// THIS SAMPLE IMPLEMENTATION IS INSECURE. REAL IMPLEMENTATION SHOULD PROVIDE BETTER PROTECTION
    /// OF THE ISSUER PARAMETERS AND KEYS (E.G., THROUGH ENCRYPTION, ACCESS RIGHTS, ETC.)
    /// </summary>
    public class SampleIssuerParametersStore : IIssuerParametersAndKeyStore, IIssuerParametersStore
    {
        // Storage path
        private string _storagePath;

        // Issuer parameters file extensions
        private static readonly string IssuerParametersFile = "ip.xml";
        private static readonly string PrivateKeyFile = "key.bin";

        /// <summary>
        /// Constructs a new SampleIssuerParametersStore storing files in 
        /// %CommonApplicationData%\Microsoft\UProveCTPSamples\Store.
        /// </summary>
        public SampleIssuerParametersStore()
            : this(null)
        { }

        /// <summary>
        /// Constructs a new SampleIssuerParametersStore given a storage path. Equivalent to
        /// SampleIssuerParametersStore() if storagePath is null.
        /// </summary>
        /// <param name="storagePath">Storage path.</param>
        public SampleIssuerParametersStore(string storagePath)
        {
            if (storagePath == null)
            {
                storagePath = Path.Combine(
                    System.Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                    "Microsoft\\UProveCTPSamples\\Store");
            }


            if (!Directory.Exists(storagePath))
            {
                Directory.CreateDirectory(storagePath);
            }

            _storagePath = storagePath;
        }

        /// <summary>
        /// Gets the storage path.
        /// </summary>
        public virtual string StoragePath
        {
            get { return _storagePath; }
        }

        /// <summary>
        /// Gets the issuer parameter and key.
        /// </summary>
        /// <param name="uid">Issuer unique id.</param>
        /// <param name="issuerParameters">The issuer paramaters.</param>
        /// <param name="key">The issuer key.</param>
        public void GetIssuerParametersAndKey(string uid, out byte[] issuerParameters, out byte[] key)
        {
            if (uid == null)
            {
                throw new ArgumentNullException("uid");
            }

            issuerParameters = ReadBytesFromFile(IssuerParametersFile);
            key = ReadBytesFromFile(PrivateKeyFile);
        }

        /// <summary>
        /// Gets the issuer parameters.
        /// </summary>
        /// <param name="uid">Issuer unique id.</param>
        /// <param name="issuerParameters">The issuer paramaters.</param>
        public void GetIssuerParameters(string uid, out byte[] issuerParameters)
        {
            if (uid == null)
            {
                throw new ArgumentNullException("uid");
            }


            issuerParameters = ReadBytesFromFile(IssuerParametersFile);
        }

        /// <summary>
        /// Reads a file from the store.
        /// </summary>
        /// <param name="fileName">The file name.</param>
        /// <returns>The file bytes.</returns>
        private byte[] ReadBytesFromFile(string fileName)
        {
            using (FileStream fStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                return ReadFileBytes(fStream);
            }
        }

        /// <summary>
        /// Reads the file bytes.
        /// </summary>
        /// <param name="fs">The file stream.</param>
        /// <returns>The file bytes.</returns>
        private static byte[] ReadFileBytes(FileStream fs)
        {
            if (fs == null)
            {
                throw new ArgumentNullException("fs");
            }

            if (!fs.CanRead)
            {
                throw new ArgumentException("fs must support reading");
            }

            // Read the source file into a byte array.
            byte[] bytes = new byte[fs.Length];
            int numBytesToRead = (int)fs.Length;
            int numBytesRead = 0;
            while (numBytesToRead > 0)
            {
                // Read may return anything from 0 to numBytesToRead.
                int n = fs.Read(bytes, numBytesRead, numBytesToRead);

                // Break when the end of the file is reached.
                if (n == 0)
                    break;

                numBytesRead += n;
                numBytesToRead -= n;
            }
            return bytes;
        }
    }
}

Written by Bradley Cotier