Sunday, November 10, 2013

Java Cross-Site Request Forgery (CSRF) Remedy, and Reverse Proxy Considerations

Recently, an application that I wrote and support got scanned by a more sophisticated web application security tool, and it revealed some issues.  One of the issues was in regard to “Cross-Site Request Forgery”.  Basically, Cross-Site Request Forgery (I’m going to abbreviate this as CSRF) means that data is being submitted to your application by a form that was not generated by your application.  For example, a hacker might save the source of your web page and form and submit the form from their saved version.

If an authenticated web application user (I assume you are authenticating your users) submits valid data (I assume you are validating the data) from a form that you didn’t originate, there should be no problem, right?  Valid user and valid data should be OK, right?

In reality, the examples of where this exploit has been used are the justification for preventing it.  I'll describe one CSRF attack vector.  You may be authenticated to use my application, and you may load a page in your browser that has NOT come from me.  If that page has a couple features, we will both be upset: (1) perhaps the page has been maliciously coded to emulate a page delivered by my application and submits some valid data, and (2) that data performs some valid function that you have not approved, like transferring money to a hacker.  That kind of potential scenario, and others like it should motivate us to keep CSRF from succeeding in our applications. 

Additionally, if there is a chance that bad data might seep through your server-side validation or that an authenticated user might be spoofed or that you just need to pass intense application security scanning that is going to reveal CSRF vulnerabilities, then there are a couple things you can do to prevent CSRF.

But first, let me take a minute to talk about server names. Often a server will have multiple names: an official name and one or more DNS aliases. Most likely, one of the DNS aliases, if they exist, is what users will browse to in the URL -- that gives you flexibility to move an alternate system into place (physically or logically) as that same DNS alias, without having to change all your URLs.  In addition, you may have more elaborate access to your corporate servers from the Internet through reverse proxies that may sit on the boundary of your intranet.  In that case, the proxy server may rewrite both the header of the request and the referrer. So there are four name types: 1) the machine name, 2) a DNS name, 3) a potentially rewritten referrer name, and 4) a potentially rewritten header "server" name.  How should we use each of these?  In the simplest arrangement, all of these will be the same.

The machine name (hostSrvrName in the code below) should be used for anything that applies to everything being served from that specific server, even when served at URLs using potentially different DNS aliases.  I choose to name my authentication cookies with the machine name so that other applications can share the authentication.  Our nonce cookie is not quite of that type, it is specific to our application forms, at present, but could be made into a general service for the entire server.  We can get the machine name (which only needs to be read once) from an InetAddress class, through a static initializer block outside the doGet(), doPost() and other methods, like this:

    private static boolean isDebug = true;
    private static String applName = "myapp";
    private static String defaultNetworkName = "org.com";
    private static String defaultServerName = "myappsrv" 
        + "." + defaultNetworkName;
    private static String hostSrvrName = null; 
    private static InetAddress[] hostAddresses = null;
    static { 
        try {
            try { 
                hostSrvrName = InetAddress.getLocalHost().getHostName() 
                    + "." + defaultNetworkName;
            } catch( Exception y ) {} 
            if( hostSrvrName == null ) hostSrvrName = defaultServerName;
            hostAddresses = InetAddress.getAllByName( hostSrvrName ); 
        } catch( Exception x ) {} 
    } 
    // This is what we will see in the request at the server
    // It may be rewritten to the client
    private static String reverseProxyName = "access" 
        + "." + defaultNetworkName;

The first thing you can do is what I would term as “poor-man’s CSRF prevention.”  Basically, you check to assure the referrer in the html header is what you expect.  Note that if you are using a reverse proxy, the proxy will occur as the referrer name; so in addition to your web app server, you will need to allow that host name as a referrer.  In a Java servlet, this test might look like the following (note that the standard parameter name “referer” is misspelled).

        // A reverse proxy may rewrite the header server name
        String rqstSrvrName = request.getServerName();
        if( rqstSrvrName == null ) rqstSrvrName = hostSrvrName;
        // The referrer is important to us!
        // That should be the name of the server that the client addresses
        // If it is acceptable, we will reflect that back to the client
        String referrer = request.getParameter( "referer" );
        // If the request or referrer server name equals
        // our standard reverse proxy server name,
        // then set the referrer to the reverse proxy
        // else assure the referrer is this host
        String referrerName = rqstSrvrName;
        // May have remnant referrer on a GET - use request server 
        if( referrer != null && 
                        ! request.getMethod().equals( "GET" ) ) 
        { 
            int start = referrer.indexOf( "//" ) + 2;
            int end = referrer.indexOf( "/", start );
            referrerName = referrer.substring( start, end );
        }
        if( rqstSrvrName.equals( reverseProxyName )  
            || referrerName.equals( reverseProxyName ) ) 
        {
            referrerName = reverseProxyName;
        } else {
            // Test if the server name from the referrer or request header
            // Has one of the same addresses as our host (DNS alias)
            boolean isThisAddr = false;
            try {
                InetAddress referInetAddr = 
                    InetAddress.getByName( referrerName );
                String requestAddr = referInetAddr.getHostAddress();
                // InetAddresses are keyed by IP Address, so may not work
                // With hosts with multiple network homes (cards or VPNs)
                if( hostAddresses != null ) 
                  for( InetAddress iAddr: hostAddresses ) {
                    if( iAddr.getHostAddress().equals( requestAddr ) ) {
                        isThisAddr = true;
                        break;
                    }
                }
            } catch( Exception x ) {}
            // Referrer may also be another acceptable host
            if( referrerName.equalsIgnoreCase( "menu.org.com" ) ) isThisAddr = true; 
            if( referrerName.equalsIgnoreCase( "lookup.org.com" ) ) isThisAddr = true; 
            if( referrerName.equalsIgnoreCase( "test.org.com" ) ) isThisAddr = true;
            if( ! isThisAddr ) {
                System.out.println( "Referrer Problem coming from: " + 
                    clientIP + ", Referrer: " + referrer );
                throw new ServletException();
            }
        }
        if( isDebug ) {
            System.out.println( "hostSrvrName: " + hostSrvrName );
            System.out.println( "rqstSrvrName: " + rqstSrvrName );
            System.out.println( "referrerName: " + referrerName );
        }

The DNS name (or machine name if no DNS aliases are used) will usually appear in all the places we might look for the server name: the URL, the referrer and the header for each client request coming to the server.  We can't really enforce security around the server name in the URL; it is just the address where someone found us.  Once they find us, however, we can enforce security around the server name in the referrer, as shown above, and in the header.  If these are not what we are expecting, we should reject the request or fix the server name.  In particular, we need a good server name to use for the cookie Domain. You can provide a cookie to the browser using any Domain, but since you want the browser to return it, the cookie Domain setting needs to match the target server of THE CLIENT request.  From the other side of a reverse proxy, your best bet for a working cookie Domain is the server name that the client places in the request referrer.  But if there is no referrer, the acceptable server name in the request object should be used.

If that is the poor-man’s solution, what does the wealthy Java programmer do?  Well, he can stamp every web page he delivers with a random value and validate the value when that web page is submitted.  If the random value is stored in a session cookie, then it will be returned only with valid requests.  We will see how I implemented that solution.

There are several places where a per-page random value can be stored, and we are going to place that value in three of them:  the web page form, a web page cookie and in the server-side session for that client.  There are a couple scenarios where we can be assured that CSRF has not occurred – when all the stored values are null (that would be the first request for this client session), or when all the stored values are equal.  One more instance where we do not flag the submission as a potential CSRF is when the request is coming from an http GET and has no form – we will be pretty strict about that exception.  This would only be something we need to deal with when the client has an existing session for this application and then clicks on a link (GET request) for the same application.

To test whether a form has been submitted, and a form is the only way our per-page random value will be returned, we check the method of the request.  If it is a GET request, then there is no form.  But that is not a sufficient reason to throw open the gate.  We need to assure that the client is not submitting unexpected data in the GET (in the URL).  However, we may want to submit some data through a GET request, and we need to test for each valid GET.  In this example, the request is a “form” submission (and will need to include a valid per-page random value) unless it meets these criteria:  It is a GET request and has no data parameters or has one data parameter that is “reload”.

        // Anchor tags into myapp (isForm = false) don't have nonce. 
        // Flush has one parameter, otherwise zero. 
        boolean isForm = true;
        String doFlush = request.getParameter( "reload" );
        if( request.getMethod().equals( "GET" ) ) {
            @SuppressWarnings("rawtypes")
            Map pMap = request.getParameterMap();
            if( pMap.size() == 0 ) isForm = false;
            else if( pMap.size() == 1 && doFlush != null ) isForm = false;
        }

Here is a fun word to say, and one that technically fits this scenario: “nonce”, or “number used once”.  We will deliver a nonce with each web page and validate it with each form submittal.

        // On forms, check nonce
        if( isForm ) {
            boolean isNonceOK = 
                checkNonceCookie( hostSrvrName, request, response );
            // Need to set cookie before throw Exception if( ! isNonceOK )
            setNonceCookie( hostSrvrName, referrerName, request, response);
            if( ! isNonceOK ) { 
                System.out.println( "Nonce Problem coming from: " + 
                    clientIP );
                throw new ServletException();
            }
        } else setNonceCookiehostSrvrName, referrerName, request, response);

In my application coding, I normally log the exception where it happens, then throw an application-specific Exception (not shown in the code above), and simply pass that along in any other methods, finally catching it at the doPost() or doGet() method in order to return an error page with a form and nonce of its own.  For that reason, before I throw the exception, I set a new nonce value to be sent with the Error page.  I will plan to provide the details of that process in another blog message.

Note that even if we are not coming from a form, we need to set a nonce cookie in the form we will be returning.

There are two methods that I will describe here, checkNonceCookie() and setNonceCookie().  Let me explain the set method first.  Here is the basic code:

    // The nonce cookie is intended to avoid Cross-Site Request Forgery
    private static SecureRandom rand = new SecureRandom();
    static {
        rand.nextLong();
        rand.nextLong();
    }
    private static void setNonceCookie(
        String hostSrvrName, String referrerName,
        HttpServletRequest request, HttpServletResponse response )
    {
        try {
            String randNonce = String.valueOf( rand.nextLong() );
            String encodedNonce = encode( randNonce );
            HttpSession session = request.getSession(true);
            session.setAttribute( "nonce", encodedNonce );
            request.setAttribute( "nonce", encodedNonce );
            // We encrypt the nonce value and place it in the cookie so that
            // a hacker may not generate a cookie and matching form nonce
            // (can't spoof our cookie)
            String cryptNonce =
                getCryptResult( hostSrvrName, encodedNonce, request );
            // Cookie class does not support Expires date - manually one hour
            SimpleDateFormat cookieDateFormat = new SimpleDateFormat(
                "EEE, dd-MMM-yyyy HH:mm:ss" );
            // Set the formatter timezone so the numeric value
            // matches "GMT" in the cookie
            cookieDateFormat.setTimeZone( TimeZone.getTimeZone("GMT") );
            Date cookieDate = new Date();
            cookieDate = new Date( cookieDate.getTime() + 60L * 60L * 1000L);
            String expiresDate = cookieDateFormat.format( cookieDate );
            // Perhaps use defaultNetworkName as Domain to permit this cookie
            // for all hostnames in network
            // including DNS aliases and reverse proxy header rewrites
            String mCookieString = applName + "_nonce_" + hostSrvrName + "="
                + cryptNonce + "; Path=/; Domain="
                //+ defaultNetworkName + "; Expires=" + expiresDate
                + referrerName + "; Expires=" + expiresDate
                + " GMT; Secure; HttpOnly";
            response.addHeader( "Set-Cookie", mCookieString );
        } catch (Exception x) {
            System.out.println( "setNonceCookie: " + x.toString() );
        }
    }

Observe that we are encrypting the nonce value, using a method that I often employ (shown below) so that the cookie cannot be easily spoofed – a cookie and matching nonce would be difficult for a hacker to generate.  We don’t want to be totally dependent on a “difficulty”, so any matching nonce value from a form and from a cookie will need to also match a value that we set in the server-side session.  Notice in the code above that in addition to creating the cookie and setting it in the header, we also set the session attribute, “nonce” with that value and the request attribute, “nonce” with that value.  One common practice for placing the nonce value in an html form is by using Java Server Pages (JSPs) and perhaps the JSP Standard Tag Library (JSTL) core functions to get the value from the request attribute.  Here is an example JSP snippet:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<FORM NAME='mForm' METHOD=POST />'>
<INPUT TYPE='hidden' NAME='nonce' VALUE='<c:out value="${nonce}" />' />
<INPUT TYPE='submit' VALUE='Retry' />
</FORM>

Those three value settings (the form, the cookie and the session) will be compared in the checkNonceCookie() method.  If they are all blank, or if they all match, then a legitimate form is being submitted.  In certain circumstances (occasionally from a reverse proxy) a cookie will be returned even though a new browser session has been established, so there is no form or session nonce.  So for that reason, just check if those two are blank and ignore the cookie.  Here’s the code:

    private static boolean checkNonceCookie( String hostSrvrName,
        HttpServletRequest request, HttpServletResponse response )
    {
        boolean rtrnBool = true;
        try {
            HttpSession session = request.getSession(true); 
            Object sessNonceObj = session.getAttribute( "nonce" ); 
            // If user leaves page up, will have form nonce and cookie, but no session 
            // So don't do anything (ignore all input), just display start page 
            if( sessNonceObj == null || sessNonceObj.toString().equals("")) { 
                    ifisDebug ) System.out.println( 
                            "Going to Start: " + request.getRemoteUser() ); 
                    request.setAttribute( "goToStart""true" ); 
                    return true; 
            } 
            String formNonce = request.getParameter( "nonce" ); 
            String cookieCryptNonce = null; 
            String cookie = request.getHeader("cookie") + ";";
            // TEMPORARY ONLY
            ////if( isDebug ) System.out.println( "cookie: " + cookie );
            int start = cookie.indexOf( applName + "_nonce_" +
                hostSrvrName + "=" );
            if (-1 < start) { // cookie exists
                int end = cookie.indexOf( ";", start );
                String nonceCookie = cookie.substring( start, end );
                start = nonceCookie.indexOf( "=" );
                cookieCryptNonce = nonceCookie.substring( start + 1 );
            }
            if( isDebug ) {
                System.out.println( "sessNonceObj: " + 
                    (String)sessNonceObj );
                System.out.println( "formNonce: " + formNonce );
                System.out.println( "cookieCryptNonce: " + 
                    cookieCryptNonce );
            }
            rtrnBool = false; // if exception is thrown - this is returned
            if( ! formNonce.equals( (String)sessNonceObj ) ) return false;
            String cryptNonce = getCryptResult( hostSrvrName,
               (String)sessNonceObj, request );
            if( isDebug ) System.out.println( "cryptNonce: " + cryptNonce );
            if( cryptNonce.equals( cookieCryptNonce ) ) return true;
            return false;
        } catch( Exception x ) {
            System.out.println( "checkNonceCookie: " + x.toString() );
            return rtrnBool;
        }
    }

In checkNonceCookie(), above we get the encrypted value that we stored in the cookie, then we encrypt the values again.  We compare the cookie value with the newly encrypted value.
My code for the getCryptResult() method is like the following.  It creates an encrypted value that is also rather specific to the client browser.  Also, to get the encrypted value into a representation that is easily included in a web page, we encode it.  I’ll also show a Hex encoding method that I’ve published in a previous blog post; although, I have a hardened version I present in my book, Expert Oracle and Java Security.

    // Requires Java Cryptography Extension (JCE)
    // Unlimited Strength Jurisdiction Policy Files 6
    private static byte[] salt = { (byte) 0x8d, (byte) 0xc8, (byte) 0xdf,
        (byte) 0x65, (byte) 0xb4, (byte) 0x94, (byte) 0x43, (byte) 0x8e };
    private static int itCount = 24;
    private static PBEParameterSpec pbeParamSpec = new
        PBEParameterSpec( salt, itCount );
    private static SecretKeyFactory keyFac;
    private static Cipher pbeCipher;
    private static String algorithm = "PBEWithSHA1AndDESede";
    static {
        try {
            keyFac = SecretKeyFactory.getInstance( algorithm );
            algorithm = keyFac.getAlgorithm();
            pbeCipher = Cipher.getInstance( algorithm );
        } catch (Exception x) {}
    }
    static String getCryptResult( String srvrName, String randNonce,
            HttpServletRequest request ) 
    {
        String rtrnString = "";
        try {
            PBEKeySpec pbeKeySpec =
                new PBEKeySpec(randNonce.toCharArray());
            SecretKey pbeKey;
            String clearText = request.getRemoteAddr().substring(0, 10)
                + request.getHeader( "profile" )
                + randNonce + srvrName
                + request.getHeader( "user-agent" );
            byte[] bytesOfMessage = clearText.getBytes( "UTF-8" );
            MessageDigest md = MessageDigest.getInstance( "MD5" );
            byte[] thedigest = md.digest( bytesOfMessage );
            byte[] cryptText;
            synchronized( pbeCipher ) {
                pbeKey = keyFac.generateSecret( pbeKeySpec );
                pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
                cryptText = pbeCipher.doFinal( thedigest );
            }
            rtrnString = encode( cryptText );
        } catch (Exception x) {}
        return rtrnString;
    }

    static String encode( byte[] bytes ) {
        StringBuffer sBuf = new StringBuffer();
        try {
            for( byte b: bytes ) {
                sBuf.append( String.format( "%02X", b ) );
            }
        } catch( Exception x ){}
        return sBuf.toString();
    }

Note that this blog site and my book, Expert Oracle and Java Security, will give you further tools for your application security efforts.

Saturday, November 2, 2013

Secure Quick Real Login in Java

am all for pushing the envelope in computer security; however, I don’t live on the bleeding edge.  I am a security practitioner and implementor. I like currently deployed encryption technologies.  It is the failure to encrypt and the failure to protect that really gets me upset.

Recently, the Secure Quick Reliable Login (SQRL) protocol (or should I say process) has been getting a fair amount of press.  I had some initial doubts.  After reviewing the various blogs and security newsgroups, I believe that the basic technical premise may be critically flawed.  It is primarily the concept embodied in this phrase from the SQRL website that causes concern:  “The primary enabling feature of the SQRL system's underlying crypto technology is its ability to use the randomly distributed arbitrary output of the 256-bit SHA256 hash function as its private key.”  What Steve Gibson seems to be saying is that SQRL will calculate a value based on a master key and site address to use as the private key.  From that calculated private key, SQRL purports to calculate a consistent public key to be used for asymmetrical encryption and identity (authentication).  That's far from standard practice in public key cryptography.

In any case, in the meantime, I decided to demonstrate an implementation of something like SQRL (just the guts) without some of the technological blood and hype.  This uses currently existing and readily accessible technologies to accomplish the PRIMARY features of SQRL -- web application authentication.  I call this Secure Quick Real Login (SQReaL).

I don’t want to delve into discussions of the philosophy of application authentication, mobile device security or privacy security data protection.  If you are not taking the basic steps of backing up your mobile device, setting a device timeout and login password, etc.  then there is no security system that can protect you.  I’m also not persuaded that there is more value in this than in regular username / password authentication, nor that it bests other authentication services.  Perhaps to attain a consistent, protected anonymous persona, this might be a good authentication mechanism, if that even makes sense.

So what limited features does SQReaL attempt to implement?

1)        A master key, stored on the device that provides (access to) private / public key pairs

2)        Site-specific private / public key pairs that are consistent across sessions (hence, can be used for identity)

3)        Encryption of a challenge code with the private key that can be decrypted by the authentication service using the public key that is delivered with the encrypted challenge code

And, no rocket science is required.

This is a quick implementation using code that I’ve published elsewhere, including my book, Expert Oracle and Java Security.  For ease of discussion, I’m storing all the data in files.  On the application server, you would want to use a database.  I’m also using Triple-DES for encrypted local storage and RSA for public / private keys.  I’m not trying to persuade anyone that this is a superior set of algorithms – they are just common, available and quick enough for this demonstration.

On a practical issue with SQRL, to change Master Keys in SQReaL, you simply move the Site Key Store into memory, change the DES Cipher to use the new Master Key and serialize the Site Key Store back to storage.

Here is SQReaL:

// David Coffin, 11/02/2013

// Requires Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 6

// Installed in every JRE and JDK/jre

package dac;


import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.io.Serializable;

import java.math.BigInteger;

import java.security.Key;

import java.security.KeyFactory;

import java.security.KeyPair;

import java.security.KeyPairGenerator;

import java.security.SecureRandom;

import java.security.interfaces.RSAPublicKey;

import java.security.spec.RSAPublicKeySpec;

import java.util.HashMap;

import java.util.Map;


import javax.crypto.Cipher;

import javax.crypto.CipherInputStream;

import javax.crypto.CipherOutputStream;

import javax.crypto.SecretKey;

import javax.crypto.SecretKeyFactory;

import javax.crypto.spec.PBEKeySpec;

import javax.crypto.spec.PBEParameterSpec;


/*

 * The difficult (mis-)conception in the SQRL description comes in these words:

 * "The primary enabling feature ... is its ability to use ... as its private key"

 * Simply put, SQRL portends to use a calculated value AS the private key

 * And to calculate a public key from that value to be used for asymmetrical encryption

 * That is not done

 * 

 * Here is a solution which uses a secure local data store of fixed key pairs

 * That are designated for use with specific URL hosts.

 * 

 * Note that the Master key has nothing to do with generation of site keys, 

 *      but is used for security of the store

 * And, likewise, the site URL is not used for generation of the site key, 

 *      but is used as the index for the key store value

 */

public class SQReaL {

    private static boolean isTest = false;

    private static String masterKeyLocation = "C:/dac/Master.ser";

    private static String publicKeyLocation = "C:/dac/Public.ser";


    public static void main(String[] args) {

        SQReaL.ReturnValues returnValues = getReturnValues( "http://try.this.one/", "abcdefghijk" );

        System.out.println( "Modulus: " + returnValues.sitePublicModulus );

        System.out.println( "Exponent: " + returnValues.sitePublicExponent );

        System.out.println( "Encrypted: " + returnValues.encryptedChallengeCode );

        System.out.println( "User: " + serverIdentifyUser( 

            returnValues.sitePublicModulus, returnValues.sitePublicExponent ) );

        System.out.println( "Decrypted: " + serverDecryptChallengeCode(

            returnValues.sitePublicModulus, returnValues.sitePublicExponent,

            returnValues.encryptedChallengeCode ) );

        System.out.println( "----------------" );

        // Next will use same site key (modulus and exponent) but different challenge

        returnValues = getReturnValues( "http://try.this.one/", "1abcdefghijk" );

        System.out.println( "Modulus: " + returnValues.sitePublicModulus );

        System.out.println( "Exponent: " + returnValues.sitePublicExponent );

        System.out.println( "Encrypted: " + returnValues.encryptedChallengeCode );

        System.out.println( "User: " + serverIdentifyUser(

            returnValues.sitePublicModulus, returnValues.sitePublicExponent ) );

        System.out.println( "Decrypted: " + serverDecryptChallengeCode(

            returnValues.sitePublicModulus, returnValues.sitePublicExponent,

            returnValues.encryptedChallengeCode ) );

        System.out.println( "----------------" );

        // Next will use different site key, but same challenge - should be encrypted differently

        returnValues = getReturnValues( "http://try.this.two/", "1abcdefghijk" );

        System.out.println( "Modulus: " + returnValues.sitePublicModulus );

        System.out.println( "Exponent: " + returnValues.sitePublicExponent );

        System.out.println( "Encrypted: " + returnValues.encryptedChallengeCode );

        // Note that for this test, we are acting like a single server application

        // So since we passed a different URL, we will have a different key

        // And we will report this as a different user

        System.out.println( "User: " + serverIdentifyUser(

            returnValues.sitePublicModulus, returnValues.sitePublicExponent ) );

        System.out.println( "Decrypted: " + serverDecryptChallengeCode(

            returnValues.sitePublicModulus, returnValues.sitePublicExponent,

            returnValues.encryptedChallengeCode ) );

        System.out.println( "----------------" );

    }

    

    private static String masterKey = null;

    

    /**

     * The sitePublicKey (since using RSA, the key is a modulus and exponent)

     *      is returned to the host and is used for two processes:

     * 1) To identify me uniquely (should never change) - lack of randomness needed for identity

     * 2) To decrypt the challenge code - assures that I have the matching public key

     * Note that the algorithm must be common between the client and server

     * Returning as Strings for inclusion in html post, or other

     * Using my encode() method for value obfuscation

     */

    public class ReturnValues {

        String sitePublicModulus;

        String sitePublicExponent;

        String encryptedChallengeCode;

    }


    private static Map<String,KeyPubPriv> keyStore = null;


    public static ReturnValues getReturnValues( String hostUR, String challengeCode ) {

        SQReaL sqrl = new SQReaL();

        return sqrl.calcReturnValues( hostUR, challengeCode );

    }


    private ReturnValues calcReturnValues( String hostUR, String challengeCode ) {

        ReturnValues returnValues = new ReturnValues();

        try {

            getMasterKey();

            KeyPubPriv sitepair = getSiteKeyPair( hostUR );

            // encode() for return, but don't store encoded

            returnValues.encryptedChallengeCode =

                encodeBytes( getEncryptedChallengeCode( sitepair.privateKey, challengeCode ) );

            returnValues.sitePublicModulus = sitepair.publicKey.getModulus().toString();

            returnValues.sitePublicExponent = sitepair.publicKey.getPublicExponent().toString();

        } catch( Exception x ) {

            x.toString();

        }

        return returnValues;

    }


    // Synchronized in order to keep masterKeyFile access limited to one thread at a time

    private static synchronized void getMasterKey() throws Exception {

        if( masterKey != null ) return;

        ObjectInputStream oIn = null;

        ObjectOutputStream oOut = null;

        try {

            File masterKeyFile = new File( masterKeyLocation );

            if( masterKeyFile.exists() ) {

                oIn = new ObjectInputStream( new FileInputStream( masterKeyFile ) );

                masterKey = (String)oIn.readObject();

                oIn.close();

            } else {

                oOut = new ObjectOutputStream( new FileOutputStream( masterKeyFile ) );

                // No need to recreate this, so just use random values

                String newMasterKey = "";

                // 32 bytes is 256 bits

                for( int i = 0; i < 32; i++ ) {

                    // I want printable ASCII characters (32 to 126) only

                    newMasterKey += (char)( rand.nextInt( 126 - 32 ) + 32 );

                }

                masterKey = new String( newMasterKey );

                oOut.writeObject( masterKey );

                oOut.flush();

                oOut.close();

            }

            if( isTest ) System.out.println( "master Key: " + masterKey );

        } catch( Exception x ) {

            x.printStackTrace();

            throw x;

        } finally {

            if( oOut != null ) oOut.close();

            if( oIn != null ) oIn.close();

        }

    }

    

    private static byte[] salt = { (byte) 0x7f, (byte) 0xd7, (byte) 0xef, 

        (byte) 0x59, (byte) 0xd2, (byte) 0x74, (byte) 0x54, (byte) 0x7a }; 

    private static int itCount = 18;    

    private static PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, itCount);

    private static SecretKeyFactory keyFac;

    private static Cipher pbeCipher;

    private static String algorithm = "PBEWithSHA1AndDESede";

    static {

        try {

            keyFac = SecretKeyFactory.getInstance(algorithm);

            algorithm = keyFac.getAlgorithm();

            pbeCipher = Cipher.getInstance( algorithm );

        } catch (Exception x) {}

    }


    // Synchronized in order to keep publicKeyFile access limited to one thread at a time

    // Synchronized method instead of blocks to assure we retain all keyStore updates

    @SuppressWarnings("unchecked")

    private static synchronized KeyPubPriv getSiteKeyPair( String hostUR ) throws Exception {

        KeyPubPriv sitePair = new KeyPubPriv();

        ObjectInputStream oIn = null;

        ObjectOutputStream oOut = null;

        try {

            File publicKeyFile = new File( publicKeyLocation );

            PBEKeySpec pbeKeySpec = new PBEKeySpec(masterKey.toCharArray());

            SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);

            if( keyStore == null ) {

                if( publicKeyFile.exists() ) {

                    pbeCipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec);

                    oIn = new ObjectInputStream( 

                        new CipherInputStream( new FileInputStream( publicKeyFile ), pbeCipher ) );

                    keyStore = (HashMap<String,KeyPubPriv>)oIn.readObject();

                    if( isTest ) System.out.println( "entry count: " + keyStore.size() );

                    oIn.close();

                }

                else keyStore = new HashMap<String,KeyPubPriv>();

            }

            if( keyStore.containsKey( hostUR ) ) 

                sitePair = keyStore.get( hostUR );

            else {

                sitePair = makeRSAKeyPair();

                keyStore.put( hostUR, sitePair );

                pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);

                oOut = new ObjectOutputStream( 

                    new CipherOutputStream( new FileOutputStream( publicKeyFile ), pbeCipher ) );

                oOut.writeObject( keyStore );

                oOut.flush();

                oOut.close();

            }

        } catch( Exception x ) {

            x.printStackTrace();

            throw x;

        } finally {

            if( oOut != null ) oOut.close();

            if( oIn != null ) oIn.close();

        }

        return sitePair;

    }


    private static int keyLengthRSA = 1024;

    private static SecureRandom rand = new SecureRandom();

    static {

        rand.nextLong();

        rand.nextLong();

    }


    private static KeyPubPriv makeRSAKeyPair() throws Exception {

        KeyPubPriv sitePair = new KeyPubPriv();

        try {

            KeyPairGenerator generator = KeyPairGenerator.getInstance( "RSA" );

            generator.initialize( keyLengthRSA, rand );

            KeyPair pair = generator.generateKeyPair();

            sitePair.privateKey = pair.getPrivate();

            sitePair.publicKey = ( RSAPublicKey )pair.getPublic();

        } catch( Exception x ) {

            throw x;

        }

        return sitePair;

    }


    private static Cipher cipherRSA;

    static {

        try {

            cipherRSA = Cipher.getInstance( "RSA" );

        } catch( Exception x ) {}

    }

    

    private byte[] getEncryptedChallengeCode( Key privateKey, String challengeCode ) 

        throws Exception

    {

        byte[] encryptedBytes = new byte[0];

        cipherRSA.init( Cipher.ENCRYPT_MODE, privateKey );

        byte[] challengeCodeBytes = challengeCode.getBytes();

        if( isTest ) System.out.println( "clear String: " + challengeCode );

        if( isTest ) System.out.println( "clear bytes: " + new String( challengeCodeBytes ) );

        encryptedBytes = cipherRSA.doFinal( challengeCodeBytes );

        if( isTest ) System.out.println( "encrypted bytes: " + new String( encryptedBytes ) );

        return encryptedBytes;

    }


    private static String userIDLocation = "C:/dac/UserID.ser";

    private static Map<String,UserClass> userStore = null;  

    private static int userNo = 0;

    // Synchronized in order to keep userIDFile access limited to one thread at a time

    // Synchronized method instead of blocks to assure we retain all userID updates

    // Preferably, in your web app server, you would store the userIDs in a database

    @SuppressWarnings("unchecked")

    private static synchronized String serverIdentifyUser( 

            String sitePublicModulus, String sitePublicExponent ) 

    {

        UserClass thisUser = new UserClass();

        ObjectInputStream oIn = null;

        ObjectOutputStream oOut = null;

        try {

            String userID = sitePublicModulus + "/" + sitePublicExponent;

            File userIDFile = new File( userIDLocation );

            if( userStore == null ) {

                if( userIDFile.exists() ) {

                    oIn = new ObjectInputStream( new FileInputStream( userIDFile ) );

                    userStore = (HashMap<String,UserClass>)oIn.readObject();

                    userNo = userStore.size();

                    if( isTest ) System.out.println( "user count: " + userNo );

                    oIn.close();

                }

                else userStore = new HashMap<String,UserClass>();

            }

            if( userStore.containsKey( userID ) ) { 

                thisUser = userStore.get( userID );

                if( thisUser.name.startsWith( "New User ") )

                    thisUser.name = thisUser.name.substring( 4 );

            } else {

                thisUser.userID = userID;

                thisUser.name = "New User " + userNo;

                userNo++;

                userStore.put( userID, thisUser );

                oOut = new ObjectOutputStream( new FileOutputStream( userIDFile ) );

                oOut.writeObject( userStore );

                oOut.flush();

                oOut.close();

            }

        } catch( Exception x ) {

            x.printStackTrace();

        } finally {

            try {

                if( oOut != null ) oOut.close();

                if( oIn != null ) oIn.close();

            } catch( Exception y ) {}

        }

        return thisUser.name;

    }


    private static String serverDecryptChallengeCode( String sitePublicModulus, 

            String sitePublicExponent, String encryptedChallengeCode )

    {

        byte[] encryptedBytes = decodeByteString( encryptedChallengeCode );

        if( isTest ) System.out.println( "decoded encrypted bytes: " + new String( encryptedBytes ) );

        return serverDecryptChallengeCode( sitePublicModulus, sitePublicExponent, encryptedBytes );

    }

    

    private static String serverDecryptChallengeCode( String sitePublicModulus, 

            String sitePublicExponent, byte[] encryptedBytes )

    {

        String decryptedChallengeCode = "";

        try {

            BigInteger modulus = new BigInteger( sitePublicModulus );

            BigInteger exponent = new BigInteger( sitePublicExponent );

            RSAPublicKeySpec keySpec = new RSAPublicKeySpec( modulus, exponent );

            KeyFactory kFactory = KeyFactory.getInstance( "RSA" );

            RSAPublicKey extRSAPubKey = ( RSAPublicKey )kFactory.generatePublic( keySpec );

            cipherRSA.init( Cipher.DECRYPT_MODE, extRSAPubKey );

            encryptedBytes = cipherRSA.doFinal( encryptedBytes );

            if( isTest ) System.out.println( "decrypted bytes: " + new String( encryptedBytes ) );

            decryptedChallengeCode = new String( encryptedBytes );

            if( isTest ) System.out.println( "decrypted String: " + decryptedChallengeCode );

        } catch( Exception x ) {

            x.printStackTrace();

        }

        return decryptedChallengeCode;

    }

    

    static String encodeBytes( byte[] bytes ) {

        StringBuffer sBuf = new StringBuffer();

        try {

            for( byte b: bytes ) {

                sBuf.append( String.format( "%02X", b ) );

            }

        } catch( Exception x ){}

        return sBuf.toString();

    }

    

    static byte[] decodeByteString( String byteString ) {

        ByteArrayOutputStream out = new ByteArrayOutputStream(); 

        try {

            String byteChars;

            int byteVal;

            for( int i = 0; i < byteString.length(); i++ ) {

                byteChars = byteString.substring( i, i+2 );

                i++;

                //if( isTest ) System.out.println( byteChars );

                // radix 16 is Hex

                byteVal = Integer.parseInt( byteChars, 16 );

                out.write( byteVal );

            }

        } catch( Exception x ){

        } finally {

            try {

                out.flush();

                out.close();

            } catch( Exception y ) {}

        }

        return out.toByteArray();

    }

}


class KeyPubPriv implements Serializable {

    private static final long serialVersionUID = 1L;

    RSAPublicKey publicKey;

    Key privateKey;     

}


class UserClass implements Serializable {

    private static final long serialVersionUID = 1L;

    String userID = "";

    String name;        

}