Attribute VB_Name = "PortugalTax2" Option Explicit Option Base 0 ' $Id: PortugalTax2.bas $ ' $Date: 2010-11-25 09:13Z $ ' $Revision: 2.0 $ ' $Author: dai $ ' *************************** COPYRIGHT NOTICE ****************************** ' This code was originally written by David Ireland and is copyright ' (C) 2010 DI Management Services Pty Ltd <www.di-mgt.com.au>. ' Provided "as is". No warranties. Use at your own risk. You must make your ' own assessment of its accuracy and suitability for your own purposes. ' It is not to be altered or distributed, except as part of an application. ' You are free to use it in any application, provided this copyright notice ' is left unchanged. ' ************************ END OF COPYRIGHT NOTICE ************************** ' This module uses functions from the CryptoSys (tm) PKI Toolkit available from ' <www.cryptosys.net/pki/>. ' Include the module `basCrPKI` in your project. ' NOTES: ' (1) The key files in these tests are expected to exist in the current working directory. ' (2) The word "signature" or "signature value" == the <Hash> field of the specification. ' REFERENCES: ' [ESPECIF-2010] "Especificação das Regras Técnicas para Certificação de Software ' Portaria n.º 363/2010, de 23 de Junho", Direcção Geral dos Impostos (DGCI), Especificacao_regras_tecnicas_Certificacao_Softwar.pdf ' <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/BF3D4A62-3243-404F-8F94-DCB2B19547C3/44379/Especificacao_regras_tecnicas_Certificacao_Softwar.pdf> ' (accessed 7 August 2010). ' ' [ADIT-2010] "- 1º Aditamento - Especificação das Regras Técnicas para Certificação de Software ' Portaria n.º 363/2010, de 23 de Junho", Direcção Geral dos Impostos (DGCI), 1_Aditamento_Especificaca_regras_tecnicas.pdf ' <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/84B18C77-577B-4581-A846-2DB0201B0FB4/0/1_Aditamento_Especificaca_regras_tecnicas.pdf> ' (accessed 21 November 2010). ' ******************* ' GENERIC FUNCTIONS * ' ******************* Public Function rsaCreateSignatureInBase64(strMessage As String, strKeyFile As String, Optional ShowDebug As Boolean = False) As String ' $GENERIC-FUNCTION$ ' INPUT: Message string to be signed; filename of private RSA key file (unencrypted OpenSSL format) ' OUTPUT: Signature in base64 format Dim strPrivateKey As String Dim nRet As Long Dim abMessage() As Byte Dim nMsgLen As Long Dim abBlock() As Byte Dim nBlkLen As Long Dim strSigBase64 As String ' 1. Convert message into unambigous array of bytes and compute length abMessage = StrConv(strMessage, vbFromUnicode) nMsgLen = UBound(abMessage) + 1 ' NB Arrays start at zero If ShowDebug Then Debug.Print "Message length = " & nMsgLen & " bytes." ' 1a. While we're here, compute the digest of the input. (We don't need it but it's a check for later) Dim strDigest As String strDigest = String(PKI_SHA1_CHARS, " ") nRet = HASH_HexFromBytes(strDigest, Len(strDigest), abMessage(0), nMsgLen, PKI_HASH_SHA1) If ShowDebug Then Debug.Print "DIGEST=" & strDigest ' 2. Read the private key file into our internal string format ' (Note that strPrivateKey is a one-off, ephemeral, internal string we made when reading the key file. ' You can't save it to use again.) strPrivateKey = rsaReadPrivateKeyInfo(strKeyFile) If Len(strPrivateKey) <= 0 Then rsaCreateSignatureInBase64 = "**ERROR: cannot read private key file" Exit Function End If If ShowDebug Then Debug.Print "Private key size is " & RSA_KeyBits(strPrivateKey) & " bits." ' 3. Encode (i.e. digest and pad) the message into format required for PKCS#1v1.5 signature ' Required block length is key size in bytes nBlkLen = RSA_KeyBytes(strPrivateKey) If ShowDebug Then Debug.Print "Key/block size is " & nBlkLen & " bytes." ' Pre-dimension the block (NB zero-based array in VB6) ReDim abBlock(nBlkLen - 1) nRet = RSA_EncodeMsg(abBlock(0), nBlkLen, abMessage(0), nMsgLen, PKI_EMSIG_PKCSV1_5 + PKI_HASH_SHA1) If ShowDebug Then Debug.Print "RSA_EncodeMsg returns " & nRet & " (0 => success)" ' Show the encoded block in hex format (should be 0001FFFF...ending with the 20-byte digest) If ShowDebug Then Debug.Print cnvHexStrFromBytes(abBlock) ' 4. Create the signature block using the private key nRet = RSA_RawPrivate(abBlock(0), nBlkLen, strPrivateKey, 0) If ShowDebug Then Debug.Print "RSA_RawPrivate returns " & nRet & " (0 => success)" ' Show the signature block in hex format If ShowDebug Then Debug.Print cnvHexStrFromBytes(abBlock) ' 5. Convert to base64 format strSigBase64 = cnvB64StrFromBytes(abBlock) If ShowDebug Then Debug.Print strSigBase64 ' Return base64 signature rsaCreateSignatureInBase64 = strSigBase64 End Function Public Function rsaVerifySignature(strSigBase64 As String, strPublicKeyFileOrCert As String, strTextToSign As String, _ Optional ShowDebug As Boolean = False) As String ' $GENERIC-FUNCTION$ ' INPUT: Signature value in base64 format (the <Hash> field); ' filename of RSA public key file or X.509 certificate containing the same public key; ' text that was signed. ' OUTPUT: "OK" if signature is valid or error message beginning "**ERROR" if not. Dim strPublicKey As String Dim nRet As Long Dim nMsgLen As Long Dim abBlock() As Byte Dim nBlkLen As Long Dim abDigest() As Byte Dim nDigLen As Long Dim strDigest As String Dim strDigest1 As String ' 1. Read the public key file into our internal string format strPublicKey = rsaReadPublicKey(strPublicKeyFileOrCert) If Len(strPublicKey) <= 0 Then ' Was not a public key file, so try reading an X.509 certificate instead strPublicKey = rsaGetPublicKeyFromCert(strPublicKeyFileOrCert) If Len(strPublicKey) <= 0 Then rsaVerifySignature = "**ERROR: cannot read public key file" Exit Function End If End If If ShowDebug Then Debug.Print "Public key size is " & RSA_KeyBits(strPublicKey) & " bits." ' 2. Convert base64 signature to byte array abBlock = cnvBytesFromB64Str(strSigBase64) nBlkLen = UBound(abBlock) + 1 If ShowDebug Then Debug.Print "Signature block length = " & nBlkLen & " bytes" If ShowDebug Then Debug.Print cnvHexStrFromBytes(abBlock) ' 3. Decrypt the signature block using the RSA public key ' (Note that strPublicKey is a one-off, ephemeral, internal string we made when reading the key file. ' You can't save it to use again.) nRet = RSA_RawPublic(abBlock(0), nBlkLen, strPublicKey, 0) If ShowDebug Then Debug.Print "RSA_RawPublic returns " & nRet & " (0 => success)" ' Show the decrypted signature block in hex format If ShowDebug Then Debug.Print cnvHexStrFromBytes(abBlock) ' 4. Extract the message digest from the block (presumed SHA-1) nDigLen = RSA_DecodeMsg(0, 0, abBlock(0), nBlkLen, PKI_EMSIG_PKCSV1_5) If nDigLen < 0 Then rsaVerifySignature = "**ERROR: invalid signature" Exit Function End If If ShowDebug Then Debug.Print "Message digest is " & nDigLen & " bytes long" ReDim abDigest(nDigLen - 1) nDigLen = RSA_DecodeMsg(abDigest(0), nDigLen, abBlock(0), nBlkLen, PKI_EMSIG_PKCSV1_5) strDigest = cnvHexStrFromBytes(abDigest) If ShowDebug Then Debug.Print "EXTRACTED DIGEST=" & strDigest ' 5. Compute the SHA-1 message digest of the text that was signed strDigest1 = String(PKI_SHA1_CHARS, " ") nRet = HASH_HexFromString(strDigest1, Len(strDigest1), strTextToSign, Len(strTextToSign), PKI_HASH_SHA1) If ShowDebug Then Debug.Print "COMPUTED DIGEST =" & strDigest1 ' 6. Compare these two digest values and return OK only if they match If UCase(strDigest) = UCase(strDigest1) Then rsaVerifySignature = "OK" Else rsaVerifySignature = "**ERROR: invalid signature" End If End Function Public Function hashHexFromString_SHA1(strMessage As String) As String ' $GENERIC-FUNCTION$ ' INPUT: Message to be hashed in a string of ANSI characters ' OUTPUT: SHA-1 digest in hex-encoded format Dim nRet As Long Dim strDigest As String strDigest = String(PKI_SHA1_CHARS, " ") nRet = HASH_HexFromString(strDigest, Len(strDigest), strMessage, Len(strMessage), PKI_HASH_SHA1) hashHexFromString_SHA1 = strDigest End Function Public Function rsaGetDigestFromBase64Signature(strSigBase64 As String, strKeyFile As String, Optional ShowDebug As Boolean = False) As String ' $GENERIC-FUNCTION$ ' INPUT: Signature value in base64 format; filename of public key file. ' OUTPUT: SHA-1 digest of signed message in hex-encoded format Dim strPublicKey As String Dim nRet As Long Dim nMsgLen As Long Dim abBlock() As Byte Dim nBlkLen As Long Dim abDigest() As Byte Dim nDigLen As Long Dim strDigest As String ' 1. Convert to byte array abBlock = cnvBytesFromB64Str(strSigBase64) nBlkLen = UBound(abBlock) + 1 If ShowDebug Then Debug.Print "Signature block length = " & nBlkLen & " bytes" If ShowDebug Then Debug.Print cnvHexStrFromBytes(abBlock) ' 2. Read the public key file into our internal string format strPublicKey = rsaReadPublicKey(strKeyFile) If Len(strPublicKey) <= 0 Then rsaGetDigestFromBase64Signature = "**ERROR: cannot read public key file" Exit Function End If If ShowDebug Then Debug.Print "Public key size is " & RSA_KeyBits(strPublicKey) & " bits." ' 3. Decrypt the signature block using the public key nRet = RSA_RawPublic(abBlock(0), nBlkLen, strPublicKey, 0) If ShowDebug Then Debug.Print "RSA_RawPublic returns " & nRet & " (0 => success)" ' Show the decrypted signature block in hex format If ShowDebug Then Debug.Print cnvHexStrFromBytes(abBlock) ' 4. Extract the SHA-1 message digest from the block nDigLen = RSA_DecodeMsg(0, 0, abBlock(0), nBlkLen, PKI_EMSIG_PKCSV1_5) If nDigLen < 0 Then rsaGetDigestFromBase64Signature = "**ERROR: Decryption Error" Exit Function End If If ShowDebug Then Debug.Print "Message digest is " & nDigLen & " bytes long" ReDim abDigest(nDigLen - 1) nDigLen = RSA_DecodeMsg(abDigest(0), nDigLen, abBlock(0), nBlkLen, PKI_EMSIG_PKCSV1_5) strDigest = cnvHexStrFromBytes(abDigest) If ShowDebug Then Debug.Print "DIGEST=" & strDigest ' Return extracted digest in hex form rsaGetDigestFromBase64Signature = strDigest End Function ' ******* ' TESTS * ' ******* Public Sub Pt_CreateSignature_Especificacao() ' Compute the correct signature values for the examples given in [ESPECIF-2010] Dim strMessage As String Dim strKeyFile As String Dim strSigBase64 As String ' Private key file: sample provided by DGCI strKeyFile = "Chave_Privada.txt" ' Registo 1 ' Message string to be signed as per [REF] specifications ' (not including quotes; no intermediate spaces or CR-LF chars) strMessage = "2010-05-18;2010-05-18T11:22:19;FAC 001/14;3.12;" strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile) Debug.Print "1: " & strSigBase64 ' Original value = "Am1K5+CP4LDNVDZYvcL...UnuJrca+7emgb/kpU=" ' Correct value = "OpE9IFpK5cJO8SwC5BUy3XTCkjVK5JsjHo3TvWjM9D09aw9wabH+sGNOs7hx4iEoOP9UY6DGsR6PgIkAZSTYInhbgs2x9sxWkr417aCKoSGY4awDIVB9aUlQ91SseH3Hk5S24PfjXFDn44acWhQL4INp9Re+dC51YNC7MrpAmP4=" ' Registo 2 strMessage = "2010-05-18;2010-05-18T15:43:25;FAC 001/15;25.62;" & _ "Am1K5+CP4LDNVDZYvcLYGpnu8/1b+WWkzgoe8sbZhvk6QFzFvNN77Zsq+cHNm52jCVS" & _ "EDgWLGHgPS1wcT8ZG7w6KgVq+2/VgOU+xKNt0lcC3gouyarZvcZpZclIReDgLh6m3nv8D" & _ "YYHKAOQc+eCi/BQ4LqUnuJrca+7emgb/kpU=" strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile) Debug.Print "2: " & strSigBase64 ' Original value = "Jh7/rmIILVwbrPLTdk...RG8JS1Uos78=" ' Correct value = "hsR2TYJtl0mad+zVAhGxNLxs6matD+T8Y8IpEo12I3szSohdwwWVOfPclnu6D23pZ0w8g/Eh0TOMzYNsdkkUJpM68/nKH2d8ehI8HT85NUyLgrGhC8msXHK+ASCCOU0RN4mr04249IG+MuOAlnW8EcMJNZA+lTf94MbpJNqRYUw=" End Sub Public Sub Pt_CreateSignature_SAFT_IDEMO() ' Reproduce the signatures (<Hash>) values in SAFT_IDEMO599999999.XML Dim strMessage As String Dim strKeyFile As String Dim strSigBase64 As String ' Sample XML data file and private key file provided by DGCI at: ' <http://info.portaldasfinancas.gov.pt/pt/apoio_contribuinte/certificacaosoftware.htm> ' <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/371795DE-D83B-4B0E-B673-010C0F523EFB/0/SAFT_IDEMO599999999.XML> ' Private key file: ' <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/70FDBA7F-1C48-496C-B9C3-4F45B4FAA55F/0/Chave_Privada.txt> strKeyFile = "Chave_Privada.txt" ' Message string from 1st record in SAFT_IDEMO599999999.XML (starting at line 9492) ' This is the first record of the series, so the "Hash" field is empty strMessage = "2008-03-10;2008-03-10T15:58:00;FT 1/1;28.07;" strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile) Debug.Print "FT 1/1: " & strSigBase64 ' Expected signature = "F8952fjEClltx2tF9m6/...jsablpR6A4=" ' Record 2: carry forward the Hash field from record 1 strMessage = "2008-09-16;2008-09-16T15:58:00;FT 1/2;235.15;" & _ "F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4=" strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile) Debug.Print "FT 1/2: " & strSigBase64 ' Expected signature = "wh0uUgI/fLTt9Kpb/hFw.../bU651c3va0=" ' Record 3: carry forward the Hash field from record 2 strMessage = "2008-09-16;2008-09-16T15:58:00;FT 1/3;679.61;" & _ "wh0uUgI/fLTt9Kpb/hFwN6VIkjWZWI8R2TxtHUMyRL0a7hyQLIvoxuqGzKfzUfvAV3E1gxpKZtai5qli6Nx7unqzC4vIoc6rtb3ObuxifXiBAUD95BMh31T73O6cgcwhGR0YhiV/E6jfCbihJL2B/2s+/qsaL7OY/bU651c3va0=" strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile) Debug.Print "FT 1/3: " & strSigBase64 ' Expected signature = "iVYbEDuefMedP5DHBfl+...Z4+0oX3qdxY=" ' ...etc, etc, ... Debug.Print "..." ' Record 6: carry forward the Hash field from record 5 strMessage = "2008-10-21;2008-10-21T15:32:00;FT 1/6;3600.00;" & _ "nv2NKxZ5c/1aC/D6RgCL0Z1EmvkELlxQ0qUQwu/5C+5fvDwb5+nigoN8G5NZjebQTJefCK3nT7DxYjfuTLaVwkDHsHDqW+WzNJ7r2VlGeeBV/TKpgYwy45Vb9dlpx3pwDftlfV44yLJN/uO6RIQnTU4o9+r0DtoPibhm8zEAaA4=" strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile) Debug.Print "FT 1/6: " & strSigBase64 ' Expected signature = "V5HNew6rKFxmSeNTSmp5...AqTsAdmi9WU=" ' Record NC 1/1: We start a new series, so leave hash field empty strMessage = "2008-09-16;2008-09-16T15:58:00;NC 1/1;235.15;" & _ "" strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile) Debug.Print "NC 1/1: " & strSigBase64 ' Expected signature = "jTCuqNUzz+QDJiHeOGwk...DpQ3kO770ko=" ' Record NC 1/2: carry forward the Hash field from record 1 strMessage = "2008-09-16;2008-09-16T15:58:00;NC 1/2;2261.34;" & _ "jTCuqNUzz+QDJiHeOGwkJzBoJwqNOLRMs0ISI7TXddv5RrH8KmKtaMgzaZxWY9QO4U5aoasqHRieqof+7oXq0fALKcROyVxU/PQRsh7eKani46ENkrkQNXREjAdz1nvoCSAKphd21nfMJupWlYTAJV2H0A7I+MGcDpQ3kO770ko=" strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile) Debug.Print "NC 1/2: " & strSigBase64 ' Expected signature = "YIt8KKn+0m9HpK2BpsnY...vfxhM7re2SU=" End Sub Public Sub Pt_VerifySignature() Dim strMessage As String Dim strKeyFile As String Dim strSigBase64 As String Dim strStatus As String ' Public key file: ' <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/547D8EFD-4B88-4072-8CD8-17DF08FE847A/0/Chave_Publica.txt> strKeyFile = "Chave_Publica.txt" ' Message string and "Hash" from 1st record in SAFT_IDEMO599999999.XML (starting at line 9492) strMessage = "2008-03-10;2008-03-10T15:58:00;FT 1/1;28.07;" strSigBase64 = "F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4=" strStatus = rsaVerifySignature(strSigBase64, strKeyFile, strMessage, True) Debug.Print "Result=" & strStatus ' Corrected version of Registo 2 in Especificacao [RE-1] strMessage = "2010-05-18;2010-05-18T15:43:25;FAC 001/15;25.62;" & _ "Am1K5+CP4LDNVDZYvcLYGpnu8/1b+WWkzgoe8sbZhvk6QFzFvNN77Zsq+cHNm52jCVS" & _ "EDgWLGHgPS1wcT8ZG7w6KgVq+2/VgOU+xKNt0lcC3gouyarZvcZpZclIReDgLh6m3nv8D" & _ "YYHKAOQc+eCi/BQ4LqUnuJrca+7emgb/kpU=" strSigBase64 = "hsR2TYJtl0mad+zVAhGxNLxs6matD+T8Y8IpEo12I3szSohdwwWVOfPclnu6D23pZ0w8g/Eh0TOMzYNsdkkUJpM68/nKH2d8ehI8HT85NUyLgrGhC8msXHK+ASCCOU0RN4mr04249IG+MuOAlnW8EcMJNZA+lTf94MbpJNqRYUw=" strStatus = rsaVerifySignature(strSigBase64, strKeyFile, strMessage, True) Debug.Print "Result=" & strStatus End Sub ' Extract the message digest from a given signature. ' (Use this in your debugging) Public Sub Pt_ExtractDigest() Dim strKeyFile As String Dim strSigBase64 As String Dim strDigest As String ' Public key file we created ourselves strKeyFile = "Chave_Publica.txt" ' Signature in base64 form. strSigBase64 = "F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4=" strDigest = rsaGetDigestFromBase64Signature(strSigBase64, strKeyFile) Debug.Print "DIGEST FOUND=" & strDigest Debug.Print "EXPECTED =" & "BB5C0F8FF294016FA4F0A3265410249D275B0986" End Sub ' Example to read in key files directly as a string... ' Use this as an alternative to passing filenames. ' The CryptoSys RSA_Read* functions will accept a string containing the file contents. ' You must still use the RSA_Read* functions to obtain the ephemeral "internal" key strings to use with the RSA_Raw* functions. Public Sub Pt_CreateSignatureWithKeyAsString() Dim strMessage As String Dim strKeyFile As String Dim strSigBase64 As String Dim strDigest As String Dim strStatus As String ' As an alternative to passing a filename, you may instead pass the key data directly as a "PEM" string strKeyFile = _ "-----BEGIN RSA PRIVATE KEY-----" & _ "MIICXgIBAAKBgQDWDX9wVqj6ZqNZU1ojwBpyKKkuzHTCmfK39xx/T9vWkqpcV7h3sx++ZOv2KhhNkIe/1I4OCWDPCXRE4g0uIQr0NS29vMlP3aHHayy76+lbBCNVcHFxM0ggjre1acnD0qUpZ6Vza7F+PpCyuypD2V/pkL1nX9Z6z5uYyqc0XaSFdwIDAQABAoGBAJCA7j6Vkl/w+GeuOJUX9AK" & _ "LZqN8TXquWUhOX4OnEt9Jhg7u/U55s31iPlWh12RNpQcg5IGfXSaH2GFEReeVUQGMrb89kkfbeY5HSRHh3/sBSyJTMn2cjsqfUnUJhywJPxT8NFIcS2pRBJe/QN/pL+M2jk+Fl40wyVXRhnog+4fhAkEA//Tijl5SA7a/uCyfOQkJ6yop13dfN4EHEWYMzI6SlnYWuJfdIOz4wkzBWgD0r/btFA" & _ "ths1zElmRWINjWsB84ZwJBANYWywqsZA4FShXkDEWfG1GbrEIXiOnPJay2p7en3DQ+lx4GfE10iO52f54QRu13SZp06050YkrWcRfBGCXaYHECQQCU8vMsmmLr2ltzWDRIQqRM/7pdsw/sAuAUFej42Tcg7BOI1IdQc9bHa1dRgyDhjbalZYIzmJamVjlw3/7/ewudAkB/ipatpiP5YldPkUtqU" & _ "q5QwOAvg5vSRtEYAr0KIZuDGGKoxY5aCnnlLn06qlHG+JDFzq+8ToOcOAKp9yQusNlRAkEA+0DarosTmn2I7+fj2/3ojVKdW/eIisz547U3bGbW/hBCZRi+y+cQnPlZ7Cr4LcGInhdxR+fSWptMNwrDCUiYHA==" & _ "-----END RSA PRIVATE KEY-----" ' Exact message string to be signed: strMessage = "2008-03-10;2008-03-10T15:58:00;FT 1/1;28.07;" strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile) Debug.Print "<Hash>=" & strSigBase64 ' Expected signature = "F8952fjEClltx2tF9m6/...jsablpR6A4=" ' Similarly, we can pass the public key data as a "PEM" string strKeyFile = _ "-----BEGIN PUBLIC KEY-----" & _ "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDWDX9wVqj6ZqNZU1ojwBpyKKkuzHTCmfK39xx/T9vWkqpcV7h3sx++ZOv2KhhNkIe/1I4OCWDPCXRE4g0uIQr0NS29vMlP3aHHayy76+lbBCNVcHFxM0ggjre1acnD0qUpZ6Vza7F+PpCyuypD2V/pkL1nX9Z6z5uYyqc0XaSFdwIDAQAB" & _ "-----END PUBLIC KEY-----" strDigest = rsaGetDigestFromBase64Signature(strSigBase64, strKeyFile) Debug.Print "DIGEST EXTRACTED=" & strDigest strStatus = rsaVerifySignature(strSigBase64, strKeyFile, strMessage) Debug.Print "Result=" & strStatus End Sub '**************************************************************************************************************** ' THE FOLLOWING SHOWS HOW TO CREATE A PAIR OF RSA PUBLIC AND PRIVATE KEYS COMPATIBLE WITH THE OPENSSL PEM FORMAT. ' AND HOW TO CREATE AN X.509 CERTIFICATE FROM THE KEYS AND HOW TO VERIFY THAT A PAIR OF KEYS MATCH. '**************************************************************************************************************** Public Sub Pt_Create_Keys() ' Create an RSA key pair Dim nRet As Long Dim strPublicKeyFile As String Dim strEncPrivateKeyFile As String Dim strPemPrivateKeyFile As String Dim strIntPrivateKey As String Dim strIntPublicKey As String Dim strPassword As String Dim nHashCodePub As Long Dim nHashCodePri As Long strPassword = "password" ' Password for encrypted private key file (please pick something stronger!) ' OpenSSL commands to create an RSA key pair: ' cmd> openssl genrsa -out PrivateKey.PEM 1024 ' cmd> openssl rsa -in PrivateKey.PEM -out PublicKey.PEM -outform PEM –pubout ' strPublicKeyFile = "Pt_PublicKey.PEM" ' This is created in OpenSSL PEM format strEncPrivateKeyFile = "Pt_PrivateKey.EPK" ' This is in encrypted form strPemPrivateKeyFile = "Pt_PrivateKey.PEM" ' This is in OpenSSL PEM format ' RSA_MakeKeys creates the key pair with an encrypted private key. Debug.Print "Creating keys. This may take a few seconds..." nRet = RSA_MakeKeys(strPublicKeyFile, strEncPrivateKeyFile, 1024, PKI_RSAEXP_EQ_65537, 128, 4096, strPassword, "", 0, PKI_KEY_FORMAT_SSL) Debug.Print "RSA_MakeKeys returns " & nRet & " (expected 0)" ' Adjust the white space in the public key file created here to suit the requirements of the DGCI. ' The file is a valid PEM format either way, but DGCI insists it should be exactly 272 bytes long. ' See <http://www.cryptosys.net/pki/portugal_DGCI_billing_software.html#key278vs272>. Call FixFileDosToUnix(strPublicKeyFile) ' To save the encrypted private key in unencrypted OpenSSL format, we read the key into an internal key string ' and then save to a file in the correct format. ' Note that the "internal" key string ' CAUTION: saving a production private key in unencrypted form is a huge security risk! strIntPrivateKey = rsaReadPrivateKey(strEncPrivateKeyFile, strPassword) If Len(strIntPrivateKey) = 0 Then MsgBox "Error reading encrypted private key file", vbCritical Exit Sub End If ' Now save in correct form nRet = RSA_SavePrivateKeyInfo(strPemPrivateKeyFile, strIntPrivateKey, PKI_KEY_FORMAT_SSL) Debug.Print "RSA_SavePrivateKeyInfo returns " & nRet & " (expected 0)" ' Do some checks that the OpenSSL keys match If Not Pt_DoKeyPairFilesMatch(strPemPrivateKeyFile, strPublicKeyFile) Then MsgBox "Error: keys do not match", vbCritical Exit Sub End If End Sub Public Function FixFileDosToUnix(strFileName As String) As Boolean ' Converts the line endings in a file from CR-LF pairs to single LF characters Dim strBuffer As String Dim hFile As Integer ' Check if file exists If Len(Dir(strFileName)) = 0 Then Exit Function ' Read in the file to a string hFile = FreeFile Open strFileName For Binary Access Read As #hFile strBuffer = Input(LOF(hFile), #hFile) Close #hFile ' Edit the string Debug.Print "Input is " & Len(strBuffer) & " bytes long" strBuffer = Replace(strBuffer, vbCrLf, vbLf) Debug.Print "Output is " & Len(strBuffer) & " bytes long" ' Re-write the file Kill strFileName hFile = FreeFile Open strFileName For Binary Access Write As #hFile Put #hFile, , strBuffer Close #hFile ' Success FixFileDosToUnix = True End Function Public Sub Pt_Test_DoKeyPairFilesMatch() ' Check that the two OpenSSL-format key files match... Dim strPublicKeyFile As String Dim strPemPrivateKeyFile As String strPublicKeyFile = "Pt_PublicKey.PEM" ' This is in OpenSSL PEM format strPemPrivateKeyFile = "Pt_PrivateKey.PEM" ' This is in OpenSSL PEM format If Not Pt_DoKeyPairFilesMatch(strPemPrivateKeyFile, strPublicKeyFile) Then Debug.Print "Error: keys do not match" Else Debug.Print "OK, keys match." End If End Sub Public Function Pt_DoKeyPairFilesMatch(strPemPrivateKeyFile As String, strPublicKeyFile As String) As Boolean ' Returns TRUE if the public and private keys in the given files match or FALSE if they do not. Dim nRet As Long Dim strIntPrivateKey As String Dim strIntPublicKey As String Dim nHashCodePub As Long Dim nHashCodePri As Long ' Read in the keys from the files to internal key strings strIntPrivateKey = rsaReadPrivateKeyInfo(strPemPrivateKeyFile) If Len(strIntPrivateKey) = 0 Then MsgBox "Error reading PEM private key file", vbCritical Exit Function End If strIntPublicKey = rsaReadPublicKey(strPublicKeyFile) If Len(strIntPublicKey) = 0 Then MsgBox "Error reading PEM private key file", vbCritical Exit Function End If ' Display the key lengths Debug.Print "Private key is " & RSA_KeyBits(strIntPrivateKey) & " bits" Debug.Print "Public key is " & RSA_KeyBits(strIntPublicKey) & " bits" ' Display the "hashcode" (this is an internal hash code which should be equal for matching keys) nHashCodePri = RSA_KeyHashCode(strIntPrivateKey) nHashCodePub = RSA_KeyHashCode(strIntPublicKey) Debug.Print "Hashcodes are " & Hex(nHashCodePri) & " and " & Hex(nHashCodePub) ' Verify that a pair of "internal" RSA private and public key strings are matched nRet = RSA_KeyMatch(strIntPrivateKey, strIntPublicKey) Debug.Print "RSA_KeyMatch returns " & nRet & " (0 => keys match)" Pt_DoKeyPairFilesMatch = (nRet = 0) End Function Public Sub Pt_SavePrivateKeyAsEncrypted() ' Save an unencrypted OpenSSL private key in PKCS-8 encrypted form Dim nRet As Long Dim strEncPrivateKeyFile As String Dim strOpenSSLPrivateKeyFile As String Dim strIntPrivateKey As String Dim strPassword As String strOpenSSLPrivateKeyFile = "Pt_PrivateKey.pem" ' This was created in unencrypted OpenSSL PEM format strEncPrivateKeyFile = "Pt_PrivateKeyEncrypted.pem" ' This is in encrypted form strPassword = "password" ' CAUTION: Pick something better than this! ' Read private key info into ephemeral internal string strIntPrivateKey = rsaReadPrivateKeyInfo(strOpenSSLPrivateKeyFile) ' Save in encrypted file form (set nCount to 2000 or so) nRet = RSA_SaveEncPrivateKey(strEncPrivateKeyFile, strIntPrivateKey, 2000, strPassword, PKI_KEY_FORMAT_PEM) Debug.Print "RSA_SaveEncPrivateKey returns " & nRet & " (expected 0)" End Sub Public Sub Pt_Make_X509_CertSelfSigned() ' Use the RSA key file to make a self-signed X.509 certificate containing the public key ' Requirements from [ESPECIF-2010] 5.2.2: ' Formato = x.509 ' Charset = UTF-8 ' Encoding = Base-64 ' Endianess = Little Endian [COMMENT: this is not relevant for X.509!] ' OAEP Padding = PKCS1 v1.5 padding [COMMENT: This is NOT "OAEP" padding] ' Tamanho da chave privada = 1024 bytes ' Formato do Hash da mensagem = SHA-1 Dim nRet As Long Dim strPublicKeyFile As String Dim strEncPrivateKeyFile As String Dim strPassword As String Dim nKeyUsage As Long Dim nOptions As Long Dim strCertFile As String Dim strDN As String Dim nYearsValid As Long Dim nCertNum As Long ' With CryptoSys PKI we need to use the encrypted private key file we created with RSA_MakeKeys, not the OpenSSL one. strEncPrivateKeyFile = "Pt_PrivateKey.EPK" ' This is in encrypted form strPassword = "password" ' The password for the encrypted private key strCertFile = "Pt_SelfSigned.cer" ' The certificate file we are going to create nYearsValid = 10 ' Make this as long as you want. nCertNum = &H101 ' Pick a number. Change this if you issue another certificate with a different key. ' The distinguished name of both the subject and the issuer of the certificate... strDN = "C=PT;O=Exemplo Organização;CN=Certificado auto-assinado" ' Options... nKeyUsage = PKI_X509_KEYUSAGE_DIGITALSIGNATURE + PKI_X509_KEYUSAGE_KEYCERTSIGN + PKI_X509_KEYUSAGE_CRLSIGN ' We want UTF-8 text and the output in PEM format... nOptions = PKI_X509_UTF8 + PKI_X509_FORMAT_PEM ' Create the certificate file Debug.Print "Creating self-signed X.509 certificate serial number 0x" & Hex(nCertNum) & " for subject '" & strDN & "'" nRet = X509_MakeCertSelf(strCertFile, strEncPrivateKeyFile, nCertNum, nYearsValid, strDN, "", nKeyUsage, strPassword, nOptions) Debug.Print "X509_MakeCertSelf returns " & nRet & " (expected 0)" End Sub Public Sub Pt_QueryCert() ' Query an X.509 certificate for selected information Dim nRet As Long Dim strOutput As String Dim strQuery As String Dim strCertFile As String strCertFile = "Pt_SelfSigned.cer" ' Make a large buffer to receive output strOutput = String(512, " ") Debug.Print "For certificate file " & strCertFile strQuery = "serialNumber" nRet = X509_QueryCert(strOutput, Len(strOutput), strCertFile, strQuery, 0) If nRet <= 0 Then Exit Sub ' catch error Debug.Print strQuery & "=" & Left(strOutput, nRet) strQuery = "subjectName" ' NB use of option to obtain UTF-8-encoded name in Latin-1 format nRet = X509_QueryCert(strOutput, Len(strOutput), strCertFile, strQuery, PKI_X509_LATIN1) If nRet <= 0 Then Exit Sub ' catch error Debug.Print strQuery & "=" & Left(strOutput, nRet) strQuery = "notAfter" nRet = X509_QueryCert(strOutput, Len(strOutput), strCertFile, strQuery, 0) If nRet <= 0 Then Exit Sub ' catch error Debug.Print strQuery & "=" & Left(strOutput, nRet) End Sub Public Sub Pt_GetPublicKeyFromFileAndCert() ' Read the public key from both the original public key file and from the X.509 certificate we created. ' Display some info about the key. Dim strPublicKeyFile As String Dim strCertFile As String Dim strIntPublicKey As String strPublicKeyFile = "Pt_PublicKey.PEM" strCertFile = "Pt_SelfSigned.cer" ' NOTE: ' The internal key string is ephemeral and encrypted: it will be different each time you read it, ' although it will contain the same underlying key data.. ' It is only intended for "internal" use by CryptoSys PKI functions like RSA_RawPublic in the same process. ' But the HashCode will always be the same for the same key value. ' Read in public key from file created by RSA_MakeKeys strIntPublicKey = rsaReadPublicKey(strPublicKeyFile) Debug.Print "Public key is " & RSA_KeyBits(strIntPublicKey) & " bits" Debug.Print "HashCode=0x" & Hex(RSA_KeyHashCode(strIntPublicKey)) Debug.Print Pt_GetPublicKeyAsXml(strIntPublicKey) ' Read in (the same) public key from certificate file strIntPublicKey = rsaGetPublicKeyFromCert(strCertFile) Debug.Print "Public key is " & RSA_KeyBits(strIntPublicKey) & " bits" Debug.Print "HashCode=0x" & Hex(RSA_KeyHashCode(strIntPublicKey)) Debug.Print Pt_GetPublicKeyAsXml(strIntPublicKey) End Sub Public Function Pt_GetPublicKeyAsXml(strIntPublicKey As String) As String ' Get the public key in <RSAKeyValue> XML form from an "internal" key string Dim strXml As String Dim nLen As String nLen = RSA_ToXMLString("", 0, strIntPublicKey, 0) If nLen <= 0 Then Exit Function End If strXml = String(nLen, " ") nLen = RSA_ToXMLString(strXml, Len(strXml), strIntPublicKey, 0) Pt_GetPublicKeyAsXml = strXml End Function