Make easy storing secure password in TFS Build with DPAPI

I’ve blogged some days ago on Securing the password in build definition. I want to make a disclaimer on this subject. The technique described in that article permits you to use encrypted password in a build definition, but this password cannot be decrypted only if you have no access to the build machine. If you are a malicious user and you can schedule a build, you can simply schedule a new build that launch a custom script that decrypts the password and sends clear password by email or dump to the build output.

The previous technique is based on encrypting with DPAPI, encrypted password can be decrypted only by TfsBuild user and only in the machine used to generate the password (build machine). Despite the technique you used to encrypt the password, the build process should be able to decrypt the password, so it is possible for another user to schedule another build running a script that decrypt the password.

Every user that knows the TfsBuild user password can also remote desktop to build machine, or using Powershell Remoting to decrypt the password from the build server. This means: the technique described is not 100% secure and you should be aware of limitation.

Apart from these discussions on the real security of this technique, one of the drawbacks of using DPAPI is you need to do some PowerShell scripting in the remote machine to encrypt the password. So you need to remote Desktop build machine or you need to do a remote session with PowerShell. A better solution is creating a super simple asp.net Site that will encrypt the password with a simple HTML page, then deploy that site on the Build Server.

The purpose is having a simple page running on build server with credentials of TfsBuild that simply encrypt a password using  DPAPI

image

Figure 1: Simple page to encrypt a string.

You can test locally this technique simply running the site in localhost using the same credentials of logged user, encrypting a password and then try to decrypt in powershell.

image

Figure 2: Decrypting a password encrypted with the helper site should work correctly.

The code of this page is really stupid, here is the controller.

[HttpPost]
public ActionResult Index(String pwd)
{
    var pbytes = Protect(Encoding.Unicode.GetBytes(pwd));
    ViewBag.Encrypted = BitConverter.ToString(pbytes).Replace("-", "");
    return View();
}

public static byte[] Protect(byte[] data)
{
    try
    {
        // Encrypt the data using DataProtectionScope.CurrentUser. The result can be decrypted 
        //  only by the same current user. 
        return ProtectedData.Protect(data, null, DataProtectionScope.CurrentUser);
    }
    catch (CryptographicException e)
    {
        Console.WriteLine("Data was not encrypted. An error occurred.");
        Console.WriteLine(e.ToString());
        return null;
    }
}

And the related view.

@{
    ViewBag.Title = "Index";
}

<h2>Simple Powershell Encryptor utils</h2>
<form method="post">


    Insert your string <input type="password" name="pwd" />
    <br />
    <input type="submit" value="Encrypt" />
    <br />
    <textarea cols="80
              " rows="10" >@ViewBag.Encrypted</textarea>

</form>

Thanks to this simple site encrypting the password is much more simpler than directly using powershell and you do not need to remote desktop to build machine. To have a slightly better security you can disable remote desktop and remote powershell in the Build Machine so noone will be able to directly use PowerShell to decrypt the password, even if they know the password of TfsBuild user.

Related Articles

Gian Maria.

Is there a reason to put restriction on password?

I’ve stumbled upon this funny comic

 

I usually use long Random generated password, that I store in KeePass for all services that I really care about, (home banking, amazon account that has my credit card, etc), and tend to use easy to remember password for services I do not care very much (stupid online games, or stuff like that).

This funny comics suggests that choosing some four random common words can be a viable solution (complex to guess, but easy to remember), but sadly enough some online services does not permits you to use long password, or password that use special chars etc. My online banks forced me to choose a 10 digit number as the password o_O, another online service told me to use a password between 6 and 18 chars, but only letters, numbers are allowed, Another one forced me to use at leas one uppersize, and one digit, but limits the length to 20 chars, etc etc. My question is “why in the hell a service should limit my possibilities to choose a password I like?”.

Having such restrictions is quite annoying, because if you have a mental scheme to choose passwords, password complexity rules quite often render this scheme not valid, forcing you to use a password that will be hard to remember (thanks to keepass this is much more easier) and not more secure. And what about a Chinese or Japanese user that want to choose a password composed of Kanji characters? Maybe he want to use Kanji of Spring, mountain, sky, because it is easy for her to remember a blue mountain sky on spring.

I’m not a cryptography expert, but usually password are stored in HASHED format with a SALT (beware of Italian Railway system, last year I clicked “lost my password” and they sent me the password in CLEAR format on my e-mail O_o), this means that the user could choose an arbitrary sequence of Unicode chars, because it is simply a stream of bytes that will be hashed producing another stream of bytes of Fixed Length that can be stored in a database without problems, even if the user choose a 100 character password, the hash length is always the same.

Given this, is there really a reason to impose restrictions on password complexity? In my opinion the only restriction should be in the length, prohibiting really short password to avoid really easy-to-guess password, but every Unicode charachter should be acceptable and there should be no maximum password length, no specific char requirements (es. at least one digit, at least one Uppercase char), if I trust my KeePass program to generate a cryptography random sequence of 32 chars, or if I want to use an Haiku I like, why you should limit my freedom in choosing my password?

Alk.

How to check if a user belong to a certain role in ASP.Net

This question is really simple to answer… or no? Suppose you need to verify, in a service, if the user belongs to the xxxx group, and then take a different path of execution if the condition is true.

if (Roles.IsUserInRole("xxxx"))

{

    ...

}

Ok, this seems such a piece of innocent code, but actually it caused me a bad bug. The reason is really simple, the same service is called from a program written in windows forms, (a windows service) and a web site. The programmer that is developing the web site, took the service and add that checks in one function, and I begin to get exception from the code of the service. The reason is clear Asp.Net roles and Membership are not configured in the windows program, nor I want to configure it.

To fix this, and in general to verify roles of the current user, when you does not know in advance if the code would be called  outside the context of a web application, we need to create the group xxxx in windows, assign the user that run the service or the program to this group and add this line at the very beginning of the winform program to use windows authentication.

AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

But this is not enough since the code in the service should not use the Roles.IsUserInRole() function because it needs to have membership configuration enabled. The solution is using this code instead of the above one.

var currentPrincipal = System.Threading.Thread.CurrentPrincipal;

if (currentPrincipal.IsInRole("xxxx")

This works because in windows forms the CurrentPrincipal is a WindowsPrincipal, and thus it checks if the current user belong to the group xxxx, but when it is run from a web site, with ASP.NEt membership configured, the principal is of type RolePrincipal as you can verify from Fgure1

image

Figure 1: the current principal of code running in asp.net sites with roles and membership configured is of type RolePrincipal.

and in this situation IsInRole() method verifies the user against ASP.Net roles.

Alk.

Desiging a authentication layer with cryptoagility.

Today I was working a little bit on Dexter, and I’m trying to update the security system, the actual login system is based on a membership provider quite old, but I’d like to update it to be CryptoAgile. First of all here is the class UserDto (the name Dto should be changed because it is really a domain class but we are in the middle of a reorganization Smile so do not mind the name ) that has some methods to manage authentication.

To be flexible the class should support storing of the password in clear form (strongly discouraged) and in hashed form with salt (the default one). Here is the code of the ChangePassword function

image

The key factor is that the HashAlgorithm used is not hardcoded in source code, the trick is in the shared factory method HashAlgorithm.Create that permits to create a sha provider from a string code like: MD5, SHA1, etc. The user object stores used hash provider in a property called HashProvider and has a default value of SHA1 type.

image

This test verify that for a newly constructed User object the HashProvider is of SHA1 type, but since the HashProvider property is stored to database so we can apply a little bit of cryptoagility. Suppose that at a certain point in the future the SHA1 provider is broken. To solve this problem we can store a global application setting that tells the HashProvider to use, and when a specific algorithm is broken we can change it. I write a test to verify this.

image

This test uses a different VerifyPassword function, that pass both the password and the global Hash provider. In this test you can verify that the password is hashed with SHA1, but the software requires SHA512 to be used, so, after a successful login, the current password should change and the current HashProvider should be set to the default one.

The code to accomplish this is really simple.

image

If the password is good you can simply check if the required HashProvider is different from the current one, and if they are different you can change the HashProvider and recalculate the password using the current one. This is good because with this simple technique we are not bound to a specific hash provider and we can change the provider at any time.

alk.

Wcf over https, authentication with asp.net membership

In last article I explained how to configure WCF to secure a service with https, with no authentication, now I want to show you the configuration needed to enable role and user membership using a standard asp.net provider.

Here is the service definition on the server

<service behaviorConfiguration="WsHttpWithAuthBehavior"
                name="MyProject.DoSomethingService">
    <endpoint address="https://mydomain.it/DoSomethingService.svc"
                 binding="wsHttpBinding" 
                 name="MyService" 
                 bindingConfiguration="wsHttps"
                 contract="MyProject.IDoSomethingService">
        <identity>
            <dns value="mydomain.it" />
        </identity>
    </endpoint>
    <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
</service>

the binding is wsHttpBinding, because we need to specify credentials, so we cannot use a basicHttpBinding, also the mex uses mexHttpsBinding because we are in https and not http. The interesting stuff is in the behavior and binding configuration.

<behavior name="WsHttpWithAuthBehavior">
    <serviceMetadata  httpsGetEnabled="true" />
    <serviceDebug includeExceptionDetailInFaults="true" />
    <serviceAuthorization
        principalPermissionMode="UseAspNetRoles"
        roleProviderName="aspRoleProvider" />
    <serviceCredentials>
        <userNameAuthentication
             userNamePasswordValidationMode ="MembershipProvider"
             membershipProviderName="aspMembershipProvider"/>
    </serviceCredentials>
</behavior>

As you can see you need to use httpsGetEnabled if you want to enable http get, but the interesting part is the serviceAuthorization node, that contains the principalPermissionMode attribute where you need to specify UseAspNetRoles, then the roleProviderName where you specify the name of the roleprovider. For serviceCredentials we have a similar configuration, you need to specify that userNamePasswordValidationMode is MembershipProvider and you need also to specify the membership provider name. These settings are needed to instruct wcf to use asp.net membership provider.

Finally you need to configure binding

<bindings>
    <wsHttpBinding>
        <binding name="wsHttps" >
            <readerQuotas maxStringContentLength="128000"/>
            <security mode="TransportWithMessageCredential" >
                <transport clientCredentialType="None"/>
                <message clientCredentialType="UserName"/>
            </security>
        </binding>

The important part is the <security> tag, where you need to specify witch security mode you want to use, in my situation is TransportWithMessageCredential. This specify that the security is given by transport (https) and there are credentials in the message. Then you need to configure <transport> and <message>, since I want to transfer my credentials in message and not in transport, I specify clientCredentialType to “none” for <transport>, and to “userName” in <message> node.

Transport credential is used for  windows login, and if you want to specify custom user name and password to validate against asp.net membership, you need to pass them into message part.

Now goes for the client configuration, first of all the binding

 <binding name="DoSomethingService" closeTimeout="00:01:00" openTimeout="00:01:00"
     receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false"
     transactionFlow="false" hostNameComparisonMode="StrongWildcard"
     maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text"
     textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
     <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
      maxBytesPerRead="4096" maxNameTableCharCount="16384" />
     <reliableSession ordered="true" inactivityTimeout="00:10:00"
      enabled="false" />
     <security mode="TransportWithMessageCredential">
      <transport clientCredentialType="None" proxyCredentialType="None"
       realm="">
       <extendedProtectionPolicy policyEnforcement="Never" />
      </transport>
      <message clientCredentialType="UserName" negotiateServiceCredential="true"
       establishSecurityContext="true" />
     </security>
    </binding>

The important stuff is the <security> mode that is set again to “TransportWithMessageCredential”, and you need to specify in the trasport part that you have no credential and in message part you have UserName credential. Once you gets the server up and running you can simply use svcutil.exe to generate this one, so it is quite an automated task to do and does not worth further explanation. Now I can use the client in this way.

using (DoSomethingServiceClient client = new DoSomethingServiceClient())
{
    client.ClientCredentials.UserName.UserName = "Superadmin";
    client.ClientCredentials.UserName.Password = "xxxxxx";
    Int32 outcount;
    client.GetSuggestedStuff();
}

As you can see you need to specify credential in the wcf proxy client (in real code I use Castle Windsor to dynamically create proxy), and then in server you can write stuff like this.

[PrincipalPermission(SecurityAction.Demand, Role="Superadministrator")]
public ServiceResult<Int32> GetSuggestedStuff()
{
    return 43;
}

If you call this method with a user that is not in role Superadministrator a SecurityException get raised. And clearly you can also access the Principal attached to current thread to verify roles programmatically if you need. All is gained for free, with no code, because wcf can use memberhsip api to validate user and password you specified in the message :) and since you use https, you does not need to deal with certificates, as I explained in older posts.

Alk.

Tags: