Sunday 11 June 2023

Web Tutorial: The PKCE Generator

Security in the web comes in several different forms. If you're engaged in using any kind of web-based API with REST protocol, chances are you would have to deal with authentication protocols such as OAuth. In such cases, a PKCE (Proof Key Certification Exchange) may be required.

Some further reading here.

Suffice to say, you'll need to generate a Code Verifier and use the Code Verifier to generate a Code Challenge. We will use JavaScript for the first part and PHP for the second. We could use PHP for both, but it's better for our purposes to use JavaScript.

What is a Code Verifier?

It's a string that has length between 43 to 128, and only with the characters "-", "_", ".", "~", a to z (and uppercase equivalents) and numbers 0 to 9. As mentioned earlier, we could easily use PHP to generate this code with just one line, but then the purpose of this web tutorial would be largely lost. I want to bring you through the logical process of creating such a string,

So without further ado, here's some bare-bones HTML in a PHP file. Note that there won't be much styling; this code isn't meant to be pretty.
<!DOCTYPE html>
<html>
    <head>
        <title>PKCE Generator</title>

        <style>

        </style>

        <script>

        </script>
    </head>

    <body>

    </body>
</html>


We start with a fieldset tag in the HTML, and a legend. And a form. There's no action attribute specified for the form, because it's just going to call itself.
<!DOCTYPE html>
<html>
    <head>
        <title>PKCE Generator</title>

        <style>

        </style>

        <script>

        </script>
    </head>

    <body>    
        <fieldset>
            <legend>PKCE Generator</legend>
            <form method="POST">

            </form>            
        </fieldset>

    </body>
</html>


In here, I am going to outline the steps needed for the PKCE. Take note of the buttons. The first button is not meant to submit the form, so we have to explicitly specify its type as "button". For the second button, we leave that specification out because it is going to submit the form.
<!DOCTYPE html>
<html>
    <head>
        <title>PKCE Generator</title>

        <style>

        </style>

        <script>

        </script>
    </head>

    <body>   
        <fieldset>
            <legend>PKCE Generator</legend>
            <form method="POST">
                1. Generate a Code Verifier. <button type="button">Generate</button><br />
                <br />        
                <br />
                2. Generate Code Challenge. <button>Generate</button><br />

            </form>            
        </fieldset>
    </body>
</html>


Then we insert two input tags, ids txtVerifier and txtChallenge, respectively. Also make sure they have name attributes as well, because these will be needed when submitting the form. For txtVerifier, the maxlength attribute has been set to 128 just in case the user wants to enter a verifier manually. Both of these input fields have been styled using the CSS class inputlength, which basically sets the width to 100 em.
<!DOCTYPE html>
<html>
    <head>
        <title>PKCE Generator</title>

        <style>
            .inputlength
            {
                width: 100em;
            }

        </style>

        <script>

        </script>
    </head>

    <body>
        <fieldset>
            <legend>PKCE Generator</legend>
            <form method="POST">
                1. Generate a Code Verifier. <button type="button">Generate</button><br />
                <input id="txtVerifier" name="txtVerifier" maxlength="128" class="inputlength" value="" />
                <br />        
                <br />
                2. Generate Code Challenge. <button>Generate</button><br />
                <input class="inputlength" id="txtChallenge" value="" />
            </form>            
        </fieldset>
    </body>
</html>


Now, we add some PHP, declaring verifier and challenge as empty strings. Then we make sure that the value attribute of the input tags have these strings in them.
<!DOCTYPE html>
<html>
    <head>
        <title>PKCE Generator</title>

        <style>
            .inputlength
            {
                width: 100em;
            }
        </style>

        <script>

        </script>
    </head>

    <body>
        <?php
            $verifier="";
            $challenge = "";
        ?>  
 
    
        <fieldset>
            <legend>PKCE Generator</legend>
            <form method="POST">
                1. Generate a Code Verifier. <button type="button">Generate</button><br />
                <input id="txtVerifier" name="txtVerifier" maxlength="128" class="inputlength" value="<?php echo $verifier;?>" />
                <br />        
                <br />
                2. Generate Code Challenge. <button>Generate</button><br />
                <input class="inputlength" id="txtChallenge" value="<?php echo $challenge;?>" />
            </form>            
        </fieldset>
    </body>
</html>


Got all that? Cool. Visually, this is all the change we're going to make here.




Let's start writing JavaScript. We begin with a pkcegen object to properly encapsulate things.
<script>
    let pkcegen = {

    };

</script>


In here, we want a nice useful random number generator that will give us a value between min and max. Nothing much to see here, but if you want more elaboration, check out this previous blogpost!
<script>
    let pkcegen = {
        randomNumber: function(min, max)
        {
            return Math.floor(Math.random() * (max - min)) + min;
        }

    };
</script>


Now, let's create a method to generate a verifier! It will be called genVerifier(). Yes, yes, not very creative. Blow me.
<script>
    let pkcegen = {
        genVerifier: function()
        {

        },

        randomNumber: function(min, max)
        {
            return Math.floor(Math.random() * (max - min)) + min;
        }
    };
</script>


We declare a variable, strLength, which will be the length of the string we are abut to generate. Using the randomNumber() function, we make sure that it's a number between 43 and 128.
genVerifier: function()
{
    var strLength = this.randomNumber(43, 128);
},


Then we declare verifier as an empty string. At the end of this method, verifier will be populated in the textbox txtVerifier.
genVerifier: function()
{
    var strLength = this.randomNumber(43, 128);
    var verifier = "";

    document.getElementById("txtVerifier").value = verifier;

},


Now, we create a For loop to use strLength's value to create the string.
genVerifier: function()
{
    var strLength = this.randomNumber(43, 128);
    var verifier = "";

    for(let i = 0; i < strLength; i++)
    {

    }


    document.getElementById("txtVerifier").value = verifier;
},


We declare currentChar within the loop, then set it to the value returned by calling the genVerifierValidChar() method. And then we concatenate verifier with the value of currentChar.
genVerifier: function()
{
    var strLength = this.randomNumber(43, 128);
    var verifier = "";

    for(let i = 0; i < strLength; i++)
    {
        var currentChar;
        currentChar = this.genVerifierValidChar();

        verifier = verifier + currentChar;

    }

    document.getElementById("txtVerifier").value = verifier;
},


Let's create genVerifierValidChar() next!
genVerifier: function()
{
    var strLength = this.randomNumber(43, 128);
    var verifier = "";

    for(let i = 0; i < strLength; i++)
    {
        var currentChar;
        currentChar = this.genVerifierValidChar();

        verifier = verifier + currentChar;
    }

    document.getElementById("txtVerifier").value = verifier;
},
genVerifierValidChar: function()
{

},

randomNumber: function(min, max)
{
    return Math.floor(Math.random() * (max - min)) + min;
}


We first declare index, and set it to a random number between 0 and 5.
genVerifierValidChar: function()
{
    var index = this.randomNumber(0, 5);
},


Then we use a switch statement on index.
genVerifierValidChar: function()
{
    var index = this.randomNumber(0, 5);
    
    switch(index)
    {

    }

},


So for the first four cases, we return different values. The break statements are redundant since there are return statements preceding them, but I like to leave them there. For the time being, we just return an empty string for the default case.
switch(index)
{
    case 0: return "-"; break;
    case 1: return "_"; break;
    case 2: return "~"; break;
    case 3: return "."; break;
    default:
        return "";
        break;
}


Right now, all we have are these characters.




Now let's comment off the empty string. Instead, as a default, we declare letterIndex as a number between 0 to 3.
switch(index)
{
    case 0: return "-"; break;
    case 1: return "_"; break;
    case 2: return "~"; break;
    case 3: return "."; break;
    default:
        //return "";
        var letterIndex = this.randomNumber(0, 3);
        break;
}


Then we run another switch statement on letterIndex.
switch(index)
{
    case 0: return "-"; break;
    case 1: return "_"; break;
    case 2: return "~"; break;
    case 3: return "."; break;
    default:
        //return "";
        var letterIndex = this.randomNumber(0, 3);
        switch(letterIndex)
        {

        }

        break;
}


For the first case, we use the fromCharCode() method of the String class, passing in a random number from 48 to 57 to give us numbers from the ASCII character set.
switch(index)
{
    case 0: return "-"; break;
    case 1: return "_"; break;
    case 2: return "~"; break;
    case 3: return "."; break;
    default:
        //return "";
        var letterIndex = this.randomNumber(0, 3);
        switch(letterIndex)
        {
            case 0: return String.fromCharCode(this.randomNumber(48, 57)); break;
        }
        break;
}


The second case gives us lowercase letters.
switch(index)
{
    case 0: return "-"; break;
    case 1: return "_"; break;
    case 2: return "~"; break;
    case 3: return "."; break;
    default:
        //return "";
        var letterIndex = this.randomNumber(0, 3);
        switch(letterIndex)
        {
            case 0: return String.fromCharCode(this.randomNumber(48, 57)); break;
            case 1: return String.fromCharCode(this.randomNumber(65, 90)); break;
        }
        break;
}


And by default, we return uppercase letters.
switch(index)
{
    case 0: return "-"; break;
    case 1: return "_"; break;
    case 2: return "~"; break;
    case 3: return "."; break;
    default:
        //return "";
        var letterIndex = this.randomNumber(0, 3);
        switch(letterIndex)
        {
            case 0: return String.fromCharCode(this.randomNumber(48, 57)); break;
            case 1: return String.fromCharCode(this.randomNumber(65, 90)); break;
            default: return String.fromCharCode(this.randomNumber(97, 122)); break;
        }
        break;
}


Click the first Generate button. You'll get a value!




But if there's a little too many special characters for your taste, hey, just adjust this value.
genVerifierValidChar: function()
{
    var index = this.randomNumber(0, 50);

    switch(index)
    {
        case 0: return "-"; break;
        case 1: return "_"; break;
        case 2: return "~"; break;
        case 3: return "."; break;
        default:
            //return "";
            var letterIndex = this.randomNumber(0, 3);
            switch(letterIndex)
            {
                case 0: return String.fromCharCode(this.randomNumber(48, 57)); break;
                case 1: return String.fromCharCode(this.randomNumber(65, 90)); break;
                default: return String.fromCharCode(this.randomNumber(97, 122)); break;
            }
            break;
    }

},


That's it for the Code Verifier...

What is a Code Challenge?

The Code Challenge is the Base64-encoded SHA-256 hash of the Code Verifier. Sounds complicated? Not really, it just means that you have to put the Code Verifier that you can now generate, through a few processes.

So let's write some PHP. If there's POST data, set verifier to the value sent by txtVerifier. You know, the value inside the textbox you just filled by clicking the first Generate button?
<?php
    $verifier="";
    $challenge = "";

    if (sizeof($_POST) > 0)
    {
        $verifier = $_POST["txtVerifier"];            
    }

?>    


Now use the hash() function to get the SHA-256 hash of verifier. Assign the value to the variable hash.
<?php
    $verifier="";
    $challenge = "";

    if (sizeof($_POST) > 0)
    {
        $verifier = $_POST["txtVerifier"];
        $hash = hash("sha256", $verifier);
    }
?>    


We now use the base64_encode() function on hash. However, due to the limitations of PHP, we can't just use it on hash. We need to first put hash through the pack() function. We pass "H*" as the first argument. "H" is for Hex strings and the "*" just means you want to work on the entire string.
<?php
    $verifier="";
    $challenge = "";

    if (sizeof($_POST) > 0)
    {
        $verifier = $_POST["txtVerifier"];
        $hash = hash("sha256", $verifier);
        $challenge = base64_encode(pack("H*", $hash));        
    }
?>    


So far this is what you should get if you generate.




Your output should not contain slashes, the plus sign or the trailing equal sign. So here, we'll use the strtr() function to replaces plusses and slashes with the hyphen and underscore characters respectively, and the rtrim() function to remove the trailing equal signs.
<?php
    $verifier="";
    $challenge = "";

    if (sizeof($_POST) > 0)
    {
        $verifier = $_POST["txtVerifier"];
        $hash = hash("sha256", $verifier);
        $challenge = base64_encode(pack("H*", $hash));
        $challenge = strtr($challenge, "+/", "-_");
        $challenge = rtrim($challenge, "=");

    }
?>    


There you have it.




Challenge accepted,
T___T

No comments:

Post a Comment