Securing OAuth Secrets

A secure password storage strategy is critical to mitigating data breaches. This article provides an overview and practical details on securing OAuth Secrets in the Ed-Fi ODS / API.

Overview

Hashing is the foundation of secure password storage. It guards against the possibility that someone who gains unauthorized access to the database can retrieve the passwords of every client in the system.

Hashing performs a one-way transformation on a password, turning the password into a string called the hashed password. "One-way" simply means that it is practically impossible to derive the original password once it has been transformed into the hashed password. There are several mathematically complex hashing algorithms that work for this purpose.

By default, the Ed-Fi ODS / API stores OAuth secrets securely using PBKDF2-HMACSHA1 algorithm in Shared and YearSpecific instances. For ease of use, Sandbox instances do not use hashing by default.

The hash implementation in the Ed-Fi ODS / API is resilient to hash algorithm changes in future and allows for customization if implementations require a different hashing algorithm. The OAuth secret database (EdFi_Admin) stores the hashing details used when the hash was generated so those settings can be used to regenerate the password hash from plain text while transitioning to a new provider. The general flow for updates is that an OAuth secret is presented to the application and the secret is verified using the hash method indicated in the database. If valid, the original OAuth secret that was presented is re-hashed using the new algorithm and the hashing method details are updated.

Default Hashing

As noted above, the default algorithm used for hashing is PBKDF2-HMACSHA1, and is implemented in EdFi.Ods.Common.Security. The API and the supporting tools (Security Configuration, Security Key Retrieval, and Admin App) use the following values. No manual scripts are necessary to hash existing secrets. Existing vendor secrets are upgraded from plain text to the a hashed version when the vendor connects to the ODS / API for the first time. Plain text secrets will not be retrievable from the database for the vendor then on.

namespace EdFi.Ods.Common.Security
{
    public class DefaultHashConfigurationProvider : IHashConfigurationProvider
    {
        private const string DefaultAlgorithm = "PBKDF2-HMACSHA1";
        private const int DefaultIterations = 10000;
        private const int DefaultSaltSize = 128;

        private readonly HashConfiguration _hashConfiguration;

        public DefaultHashConfigurationProvider()
        {
            _hashConfiguration = new HashConfiguration
                                 {
                                     Algorithm = DefaultAlgorithm,
                                     Iterations = DefaultIterations,
                                     SaltSize = DefaultSaltSize
                                 };
        }

        public HashConfiguration GetHashConfiguration()
        {
            return _hashConfiguration;
        }
    }
}

Sandbox Admin

The Sandbox Admin tool however does not hash OAuth secrets by default. However, hashing can be instated by modifying the SandboxStartupBase.cs class and commenting out the InstallSecretVerifier method (so you can test the implementation of your hashing routine).

When first stored, the secret will be in plain text within the database. After the first use, the API will hash the secret and it will no longer be retrievable from the Sandbox Admin Website.


using Castle.MicroKernel.Registration;
using Castle.Windsor;
using EdFi.Ods.Admin.Models.Security;
using EdFi.Ods.Api.Startup._Installers;

namespace EdFi.Ods.Api.Startup
{
    public abstract class SandboxStartupBase : StartupBase
    {
        protected override void InstallConfigurationSpecificInstaller(IWindsorContainer container)
        {
            container.Install(new SandboxInstaller());
        }

//        protected override void InstallSecretVerifier(IWindsorContainer container)
//        {
//            container.Register(Component.For<ISecretVerifier>().ImplementedBy<PlainTextSecretVerifier>());
//        }
    }
}

Customizing the ODS / API Hash Configuration

You can modify the as-shipped ODS / API configuration to reduce or increase iterations, change the salt size, or even change the algorithm. Generally speaking, the mechanism is to change out the DefaultHashConfigurationProvider via the Startup class. Details follow.

It goes without saying, but we'll say it anyway: changing the hash configuration touches on the security of the ODS / API. Developers should be familiar with the underlying concepts of hashes, salting, and hash iteration, and should consult with their security team before implementing any security configuration or code changes destined for use in live implementations.

Changing the Hash Iteration and Salt Size

Step 1. Override InstallHashConfigProvider

In your startup override InstallHashConfigProvider, and use the class ApplicationSettingHashConfigurationProvider in the implementation startup class:

protected override void InstallHashConfigProvider(IWindsorContainer container)
{
    container.Register(
       Component.For<IHashConfigurationProvider>().ImplementedBy<ApplicationSettingHashConfigurationProvider>());
}

Step 2. Modify the Web.config File

Add the following keys to the settings:

<appSettings>
    <add key="Password.Algorithm" value="PBKDF2-HMACSHA1" />
    <add key="Password.Iterations" value="10000"/>
    <add key="Password.SaltSize" value="128"/>
</appSettings>

Changing the Hashing Algorithm

Step 1. Enable the Web.config settings

Override the InstallHashConfigProvider and add the Web.config keys as noted above. You'll change these settings in later steps.

Step 2. Implement ISecureHasher

Implement the ISecureHasher interface. Note that you also need to inherit from ChainOfResponsibilityBase<ISecureHasher, SecureHasRequest, PackHash> as this handles the automatic upgrading process of changing the hashed values when the client system logs in following the update to the new hashing method. 

using System.Security.Cryptography;
using EdFi.Ods.Common.ChainOfResponsibility;

namespace EdFi.Ods.Common.Security
{
    public class Pbkdf2HmacSha256SecureHasher : ChainOfResponsibilityBase<ISecureHasher, SecureHashRequest, PackedHash>, ISecureHasher
    {
        private readonly int _hashAlgorithm = "PBKDF2-HMACSHA256".GetHashCode();

        public Pbkdf2HmacSha256SecureHasher(ISecureHasher next)
            : base(next)
        {
        }

        public PackedHash ComputeHash(string secret, int hashAlgorithm, int iterations, byte[] salt)
        {
			//TODO Implement
        }

        public PackedHash ComputeHash(string secret, int hashAlgorithm, int iterations, int saltSize)
        {
           //TODO Implement
        }

        protected override bool CanHandleRequest(SecureHashRequest request)
        {
            return request.HashAlgorithm == _hashAlgorithm;
        }

        protected override PackedHash HandleRequest(SecureHashRequest request)
        {
            return request.Salt != null
                ? ComputeHash(request.Secret, request.HashAlgorithm, request.Iterations, request.Salt)
                : ComputeHash(request.Secret, request.HashAlgorithm, request.Iterations, request.SaltSize);
        }
    }
}

Step 3. Register the HashRoutine in the Startup

Override the method InstallSecureHasher in the implementation startup class.

protected override void InstallSecureHasher(IWindsorContainer container)
{
    container.Register(
    Component.For<ISecureHasher>()
             .ImplementedBy<Pbkdf2HmacSha256SecureHasher>());
}

Step 4. Update Web.config Keys

Modify the Web.config keys to use the new routine.

<appSettings>
    <add key="Password.Algorithm" value="PBKDF2-HMACSHA256" />
    <add key="Password.Iterations" value="100000"/>
    <add key="Password.SaltSize" value="512"/>
</appSettings>