Udostępnij za pośrednictwem


Storing Secrets in SharePoint 2007

Once in a while you are challenged with storing secrets like username and passwords for instance for connecting to a web service in another domain or accessing remote resources like a database, a file or through a proxy.

This task can be accomplished in many ways - some more secure that others depending on how you define security. One way is to store the values in web.config. This makes it difficult to deploy and encrypting it can be difficult as you need to encrypt a whole section of the file and you then need to sync keys across the farm to ensure it can be used in a load balanced environment. Another way would just be to hardcode the values, but hardcoded values can easily be discovered with a tool like Reflector (unless it is properly obfuscated - but as well as great obfucators exist - great disassemblers also exist).

Another way to store the secrets are through using the .Net 2.0 class System.Security.SecureString and the built-in Microsoft.SharePoint.Administration.SPPersistedObject.
The problem with a normal System.String in relation to security is that it lives unencrypted on the Managed Heap, and since String is immutable (it cannot be changed once instantiated) you cannot ‘delete’ a string, but will have to wait for GC (Garbage Collection) to be performed. Over time, you may end up with many string instances if you perform manipulation with it and these will be readable from a memory or crash dump. In SharePoint, the persistence of the secret is handled through the Microsoft.SharePoint.Administration.SPEncryptedString class which encapsulates the SecureString and handles the encryption and decryption.
The SPPersistedObject class provides a base class for all administration objects. It serializes all fields marked with the Persisted attribute to XML and writes the XML blob to the configuration database. The SPPersistedObject class contains code to serialize all its members that are base types, other persisted objects, and collections of persisted objects. Configuration data that is stored in persisted objects is automatically made available to every process on every server in the farm.

Below is the class used for encapsulating the secrets. The class could contain any information including non-secret information. To ensure that it is persisted, the fields should be decorated with the [Persisted] attribute. Inspiration for this code comes from the Content Deployment API and the way it stores and connects to remote farms.

using System;

using System.Collections.Generic;

using System.Text;

using System.Security;

using Microsoft.SharePoint.Administration;

using System.Globalization;

namespace Microsoft.ADC.SharePoint

{

    internal class SecureCredentials : SPPersistedObject

    {

        // Fields

        [Persisted]

        private SPEncryptedString encryptedPassword;

        [Persisted]

        private string userId;

        private SecureString password;

        private bool passwordSet;

       

        // Methods

        public SecureCredentials()

        {

        }

        private SecureCredentials(string name, SPPersistedObject parent)

            : base(name, parent)

        {

           

        }

        internal static SecureCredentials CreateNew()

        {

            return new SecureCredentials(string.Format(CultureInfo.InvariantCulture, "Secure Credential"), SPAdministrationWebApplication.Local.Parent);

        }

        internal static SecureCredentials GetInstance(Guid credentialId)

        {

            SecureCredentialsCollection credentials = new SecureCredentialsCollection();

            return credentials.GetValue<SecureCredentials>(credentialId);

        }

        public override void Update()

        {

            if (this.passwordSet)

            {

                if (this.encryptedPassword == null)

                {

                    this.encryptedPassword = new SPEncryptedString(null, this);

                }

                if (this.encryptedPassword != null)

                {

                    this.encryptedPassword.UpdateSecureStringValue(this.password);

                }

            }

            base.Update();

        }

        // Properties

        internal SecureString Password

        {

            get

            {

                if (this.passwordSet)

                {

                    return this.password;

                }

                while (this.encryptedPassword == null)

                {

                    return null;

                }

                return this.encryptedPassword.SecureStringValue;

            }

            set

            {

                this.password = value;

                this.passwordSet = true;

            }

        }

        internal string UserId

        {

            get

            {

                return this.userId;

            }

            set

            {

                this.userId = value;

            }

        }

    }

    internal class SecureCredentialsCollection : SPPersistedChildCollection<SecureCredentials>

    {

        // Methods

        public SecureCredentialsCollection()

            : base((SPWebService)SPAdministrationWebApplication.Local.Parent)

        {

        }

    }

}

Now both the two classes and most of the method and accessors have an internal accessibility level. This is fully intentional as the secure values otherwise could easily be compromised. When storing a value into the Config database using SPPersistedObject, the value includes the fully qualified assembly name of the persisted object. This means that in order to retrieve the object again, you would have to have the exact same object type and if the accessibility level where public, then anyone could write a simple console app and extract the secrets. When marked as internal only the classes within the same assembly can access the persisted objects class and use it. This means that you will have to have the classes for both storing the secret and retrieving and using the secret within the same assembly. Further, if someone could get access to the private key, then it would also be possible to create a new class and match the signature of the persisted class and retrieve the value. Of course the key for signing assemblies should be treated as a secret itself inside an organization and not be available to the public.

When you are persisting an object to SharePoint, you will get back a Guid as a reference. (The Guid is simply referring to the identity column in the database). This Guid needs to be stored somewhere in order to retrieve the value again and a good place to do this could be a (hidden) List in the Central Administration site. All you need to store is the Guid and some identifier to find the Guid from within the application. To set the secret follow this pattern:

SecureCredentials credential = SecureCredentials.CreateNew();

credential.UserId = @"Domain\User";

SecureString secureString = new SecureString();

//TODO: Set secureString

credential.Password = secureString;

credential.Update();

                       

Guid credentialId = credential.Id;

//TODO: Store the Id of the persisted object

To retrieve the value again, follow this pattern:

//TODO: Get credentialId from List or other location

Guid credentialId = new Guid("");

SecureCredentialsCollection scc = new SecureCredentialsCollection();

SecureCredentials sc = scc.GetValue<SecureCredentials>(credentialId);

string userId = sc.UserId;

//If the string is needed in clear text, it can be converted, but be aware that

//it now exists on the Managed Heap

string password = Marshal.PtrToStringBSTR(Marshal.SecureStringToBSTR(sc.Password));

How secure is it then

Well, nothing is 100% secure - security is an illusion and no matter how much you encrypt, you still need to have some sort of key or known secret to unlock the stored secret. Biometrics of course is pretty strong and something you have combined with something you know like a smartcard and a password is also pretty secure unless you lose your smartcard and have the password on a Post-It on the card. Security is of course also a matter of who you are trying to protect your secret from.

If a hacker where to compromise a server in the Farm, in order to retrieve the secret, he or she would have to disassemble the assembly holding the encrypted object, get a hold of the Private Key used to sign the assembly, locate the credentialId, and compile a new class to retrieve the values. Further, this would have to be executed on the server and I could think of many other thinks a hacker would do if the person would get access to a server.

Insiders are of course also a thread. In order to get a hold of the secret, they would need the Private Key for signing a new assembly. Alternatively they could also have built in a secret method in the get or set method for having the values displayed. Proper processes around development like code review and delay signing should deem up of for such attempts.

Since SecureString is only 100% secure if it is not transformed to a regular String, then there is also a potential risk during the entering of the secret and also retrieval for example if you would use it to authenticate against a Basic Authenticated web service. Some protection could come from protecting the traffic with SSL, but it would still exist in a short period of time on the Heap, and if you where to take a memory dump during this period of time, the password would be stored here. This is good to know if you ever send dumps for troubleshooting to for instance Microsoft.

Comments