using System;
using System.Text;
using System.Diagnostics;
using CryptoSysPKI;

/* $Id: JWS_Signature_RFC7515.cs $
 * $Date: 2020-01-20 11:51:00 $
 */

/* Example using CryptoSys PKI to create and validate a JSON Web Signature (JWS)
 * using test data from Appendix A.2 of RFC 7515
 * Ref: RFC 7515 JSON Web Signature (JWS), Appendix A.2
 * https://tools.ietf.org/html/rfc7515#appendix-A.2
 */

/******************************* LICENSE ***********************************
 * Copyright (C) 2020 David Ireland, DI Management Services Pty Limited.
 * All rights reserved. <www.di-mgt.com.au> <www.cryptosys.net>
 * The code in this module is licensed under the terms of the MIT license.  
 * For a copy, see <http://opensource.org/licenses/MIT>
****************************************************************************
*/

namespace JWS_Signature_RFC7515
{
    class JWS_Signature_RFC7515
    {
        static void Main(string[] args)
        {
            JWS_RFC7515_Example();
        }
        static void JWS_RFC7515_Example()
        {
            int n;

            Console.WriteLine("Creating JSON Web Signature...");

            // Read in JWS RSA private key from hard-coded string
            string jwsPriKey = "{\"kty\":\"RSA\"," +
                "\"n\":\"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ\"," +
                "\"e\":\"AQAB\"," +
                "\"d\":\"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97IjlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYTCBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLhBOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ\"," +
                "\"p\":\"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdiYrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPGBY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc\"," +
                "\"q\":\"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxaewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc\"," +
                "\"dp\":\"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3QCLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0\"," +
                "\"dq\":\"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-kyNlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU\"," +
                "\"qi\":\"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2oy26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLUW0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U\"" +
            "}";

            // Read JWS key string into ephemeral RSA private key string and display key characteristics
            // (we don't need to do this to make a signature, it's just a check all is OK)
            string privateKey = Rsa.ReadPrivateKey(jwsPriKey, "").ToString();
            Debug.Assert(privateKey.Length > 0);
            Console.WriteLine("Key length={0} bits", Rsa.KeyBits(privateKey));
            Console.WriteLine("Key hash=0x{0,8:X}", Rsa.KeyHashCode(privateKey));

            // Compose JWS Protected Header
            string header = "{\"alg\":\"RS256\"}";
            Console.WriteLine("JWS Protected Header={0}", header);
            string headerURL = cnvBase64urlFromString(header);
            Console.WriteLine("BASE64URL(UTF8(JWS Protected Header))={0}", headerURL);

            // Compose JWS Payload (note CR-LF line endings and single space indents)
            string payload = "{\"iss\":\"joe\"," + "\r\n" + 
                " \"exp\":1300819380," + "\r\n" +
                " \"http://example.com/is_root\":true}";
            Console.WriteLine("JWS Payload={0}", payload);
            string payloadURL = cnvBase64urlFromString(payload);
            Console.WriteLine("BASE64URL(UTF8(JWS Payload))={0}", payloadURL);

            // Compose JWS Signing Input
            // BASE64URL(UTF8(JWS Protected Header) || '.' || BASE64URL(JWS Payload)
            string jwsSigningInput = headerURL + "." + payloadURL;
            Console.WriteLine("JWS Signing Input={0}", jwsSigningInput);

            // Encode signing input as a byte array
            byte[] b = System.Text.Encoding.Default.GetBytes(jwsSigningInput);

            // Compute JWS Signature value BASE64URL(JWS Signature)
            // -- Note we can use the JSW key string directly here. There is no password.
            string jwsSignature = Sig.SignData(b, jwsPriKey, "", SigAlgorithm.Rsa_Sha256, Sig.SigOptions.Default, Sig.Encoding.Base64url);
            Console.WriteLine("BASE64URL(JWS Signature)={0}", jwsSignature);

            // The correct signature value from RFC 7515
            string jwsSigOK = "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw";
            Console.WriteLine("Correct value           ={0}", jwsSigOK);
            Debug.Assert(jwsSigOK == jwsSignature, "Signature does not match reference");

            // Output full JWS Compact Serialization
            // Header.Payload.Signature with period ('.') characters between the parts, all parts base64url encoded.
            Console.WriteLine("JWS Compact Serialization=");
            Console.WriteLine(headerURL + "." + payloadURL + "." + jwsSignature);

            // ------------------
            // VALIDATE SIGNATURE
            // ------------------
            Console.WriteLine();
            Console.WriteLine("Validating signature...");
            // INPUT: JwsSignature, JwsSigningInput, PublicKey, SigningAlgorithm
            // OUTPUT: Signature is valid (returns 0) or invalid

            string jwsPubKey = "{\"kty\":\"RSA\"," +
                "\"n\":\"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ\"," +
                "\"e\":\"AQAB\"" + 
                "}";
            // Read in public key to internal ephemeral key string to check details (extra check, not necessary)
            string publicKey = Rsa.ReadPublicKey(jwsPubKey).ToString();
            Debug.Assert(publicKey.Length > 0);
            Console.WriteLine("Display public key characteristics (should be the same as private key above)...");
            Console.WriteLine("Key length={0} bits", Rsa.KeyBits(publicKey));
            Console.WriteLine("Key hash=0x{0,8:X}", Rsa.KeyHashCode(publicKey));
            n = Rsa.KeyMatch(privateKey, publicKey);
            Console.WriteLine("Rsa.KeyMatch() returns {0} (expected 0 => keys match OK)", n);

            // Verify the signature value against original signing input using the JSON RSA public key
            n = Sig.VerifyData(jwsSignature, b, jwsPubKey, SigAlgorithm.Rsa_Sha256);
            Console.WriteLine("Sig.VerifyData() returns {0} (expecting 0 => signature is valid)", n);
            Debug.Assert(0 == n, "Signature is invalid");

        }

        // *******************
        // BASE64URL UTILITIES
        // *******************

        /// <summary>
        /// Encode string in base64url encoding.
        /// </summary>
        /// <param name="s">String to be encoded</param>
        /// <returns>Base64url-encoded string</returns>
        static string cnvBase64urlFromString(string s)
        {
            s = Cnv.ToBase64(s);
            s = s.Replace("+", "-");
            s = s.Replace("/", "_");
            s = s.Replace("=", "");
            return s;
        }
    }
}