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; } } }