Attribute VB_Name = "JWS_Signature_RFC7515"
' $Id: JWS_Signature_RFC7515.bas $
' $Date: 2020-01-20 11:53 $
' Example using CryptoSys PKI to create and validate a JSON Web Signature (JWS)
' using test data from Appendix A.2 of RFC 7515
' ******************************* LICENSE ***********************************
' Copyright (C) 2020 David Ireland, DI Management Services Pty Limited.
' All rights reserved. <https://di-mgt.com.au> <https://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>
' ***************************************************************************
' Requires CryptoSys PKI Pro v12.2 or later to be installed on your system
' (download from https://cryptosys.net/pki/)
' and module `basCrPKI.bas` to be in the same project
' (look for this in C:\Program Files (x86)\CryptoSysPKI\VB6)
Option Explicit
Public Sub JWS_RFC7515_Example()
' Ref: RFC 7515 JSON Web Signature (JWS), Appendix A.2
' https://tools.ietf.org/html/rfc7515#appendix-A.2
Dim strJwsPriKey As String
Dim strPrivateKey As String
Dim strPublicKey As String
Dim strJwsSigningInput As String
Dim strJwsSignature As String
Dim abData() As Byte
Dim nDataLen As Long
Dim nChars As Long
Dim nRet As Long
Dim strAlgName As String
Dim strHeader As String
Dim strHeaderURL As String
Dim strPayload As String
Dim strPayloadURL As String
Dim strJwsPubKey As String
Dim strJwsSigOK As String
Debug.Print "Creating JSON Web Signature..."
strAlgName = "sha256WithRSAEncryption" ' "alg":"RS256"
' Read in JWS RSA private key
strJwsPriKey = "{""kty"":""RSA""," & vbCrLf & _
"""n"":""ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ""," & vbCrLf & _
"""e"":""AQAB""," & vbCrLf & _
"""d"":""Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97IjlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYTCBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLhBOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ""," & vbCrLf & _
"""p"":""4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdiYrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPGBY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc""," & vbCrLf & _
"""q"":""uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxaewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc""," & vbCrLf & _
"""dp"":""BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3QCLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0""," & vbCrLf & _
"""dq"":""h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-kyNlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU""," & vbCrLf & _
"""qi"":""IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2oy26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLUW0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U""" & vbCrLf & _
"}"
' 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)
strPrivateKey = rsaReadPrivateKey(strJwsPriKey, "")
Debug.Print "Key length=" & RSA_KeyBits(strPrivateKey) & " bits"
Debug.Print "Key hash=0x" & Hex(RSA_KeyHashCode(strPrivateKey))
' Compose JWS Protected Header
strHeader = "{""alg"":""RS256""}"
Debug.Print "JWS Protected Header=" & strHeader
strHeaderURL = cnvBase64urlFromString(strHeader)
Debug.Print "BASE64URL(UTF8(JWS Protected Header))=" & strHeaderURL
' Compose JWS Payload (note CR-LF line endings and single space indents)
strPayload = "{""iss"":""joe""," & vbCrLf & _
" ""exp"":1300819380," & vbCrLf & _
" ""http://example.com/is_root"":true}"
Debug.Print "JWS Payload=" & strPayload
strPayloadURL = cnvBase64urlFromString(strPayload)
Debug.Print "BASE64URL(UTF8(JWS Payload))=" & strPayloadURL
' Compose JWS Signing Input
' BASE64URL(UTF8(JWS Protected Header) || '.' || BASE64URL(JWS Payload)
strJwsSigningInput = strHeaderURL & "." & strPayloadURL
Debug.Print "JWS Signing Input=" & strJwsSigningInput
' Encode signing input as a byte array
abData = StrConv(strJwsSigningInput, vbFromUnicode)
nDataLen = UBound(abData) + 1
' Compute JWS Signature value BASE64URL(JWS Signature)
' -- Note we can use the JSW key string directly here. There is no password.
' -- Find out how many characters in the signature output
nChars = SIG_SignData("", 0, abData(0), nDataLen, strJwsPriKey, "", strAlgName, PKI_ENCODE_BASE64URL)
Debug.Print "SIG chars length=" & nChars
Debug.Assert nChars > 0
' -- Allocate memory for output
strJwsSignature = String(nChars, " ")
' -- Compute signature value encoded directly in base64url
nChars = SIG_SignData(strJwsSignature, Len(strJwsSignature), abData(0), nDataLen, strJwsPriKey, "", strAlgName, PKI_ENCODE_BASE64URL)
Debug.Print "BASE64URL(JWS Signature)=" & strJwsSignature
' The correct signature value from RFC 7515
strJwsSigOK = "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"
Debug.Print "Correct value =" & strJwsSignature
Debug.Assert strJwsSigOK = strJwsSignature
' Output full JWS Compact Serialization
' Header.Payload.Signature with period ('.') characters between the parts, all parts base64url encoded.
Debug.Print "JWS Compact Serialization="
Debug.Print strHeaderURL & "." & strPayloadURL & "." & strJwsSignature
'-------------------
' VALIDATE SIGNATURE
'-------------------
Debug.Print
Debug.Print "Validating signature..."
' INPUT: JwsSignature, JwsSigningInput, Public key, SigningAlgorithm
' OUTPUT: Signature valid (returns 0) or invalid.
' Given JWS public key
Debug.Print "Public key details given directly in a string..."
strJwsPubKey = "{""kty"":""RSA""," & _
"""n"":""ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ""," & _
"""e"":""AQAB""" & _
"}"
Debug.Print strJwsPubKey
nChars = RSA_ReadAnyPublicKey(0, 0, strJwsPubKey, 0)
Debug.Assert nChars > 0
' Read in public key to internal ephemeral key string to check details (extra check, not necessary)
strPublicKey = String(nChars, " ")
nChars = RSA_ReadAnyPublicKey(strPublicKey, Len(strPublicKey), strJwsPubKey, 0)
Debug.Print "Display public key characteristics (should be the same as private key above)..."
Debug.Print "Key length=" & RSA_KeyBits(strPublicKey) & " bits"
Debug.Print "Key hash=0x" & Hex(RSA_KeyHashCode(strPublicKey))
nRet = RSA_KeyMatch(strPrivateKey, strPublicKey)
Debug.Print "RSA_KeyMatch() returns " & nRet & " (expected 0 => keys match OK)"
' Verify the signature value against original signing input using the JSON RSA public key
nRet = SIG_VerifyData(strJwsSignature, abData(0), nDataLen, strJwsPubKey, strAlgName, 0)
Debug.Print "SIG_VerifyData() returns " & nRet & " (expecting 0 => signature is valid)"
Debug.Assert nRet = 0
End Sub
' *******************
' BASE64URL UTILITIES
' *******************
''' Encode string value in base64url encoding
Private Function cnvBase64urlFromString(strInput As String) As String
strInput = cnvB64StrFromString(strInput)
strInput = Replace(strInput, "+", "-")
strInput = Replace(strInput, "/", "_")
strInput = Replace(strInput, "=", "")
cnvBase64urlFromString = strInput
End Function
''' Decode base64url-encoded string to Unicode string
Private Function cnvBase64urlToString(strInput As String) As String
strInput = Replace(strInput, "-", "+")
strInput = Replace(strInput, "_", "/")
strInput = cnvStringFromHexStr((cnvHexStrFromB64Str(strInput)))
cnvBase64urlToString = strInput
End Function
''' Decode base64url-encoded string to Byte array
Private Function cnvBase64urlToBytes(strInput As String) As Byte()
strInput = Replace(strInput, "-", "+")
strInput = Replace(strInput, "_", "/")
cnvBase64urlToBytes = cnvBytesFromB64Str(strInput)
End Function