How to check a digital signature from broswer with PHP
Asked Answered
P

2

6

I have a Javascript that is signing a text string in the browser. It uses CAPICOM under Internet Explorer and window.crypto under Mozilla browsers. After the signing process I receive a BASE64 encoded signature.

Using HTTPS I upload the signature and the text string to a webserver with a PHP application. From the SSL (HTTPS) I receive the user's certificate. From this certificate I can extract the user's Public Key.

Now I want to verify that the signature against the signed text string and the user's certificate and public key. I have tried with openssl_verify PHP function with no success.

I always receive an error:

error:0408D077:rsa routines:FIPS_RSA_VERIFY:wrong signature length

  • I have the certificate and it is OK;
  • I have the public key extracted from the certificate and it is also verified and OK;
  • I have the signature (BASE64 decoded);

Unfortunately I can't verify the signature? I can't provide a demo or sample because it is only in the local network at the moment.

Possess answered 27/9, 2012 at 8:30 Comment(3)
The return values of the PHP function are: 1 if the signature is correct, 0 if it is incorrect, and -1 on error. What is your return value? Also did you use the correct Signature Algorithm, i.e. the fourth parameter of the function...?Carmagnole
I get 0 - incorrect. Then I request openssl_error_string() and receive the mentioned above error.Possess
At the moment I'm trying to verify the signature with "openssl smime -verify" from the command line but I always get "Verification failure" message. I'm reading this article: 10552724Possess
P
2

OK, finally I have found a workaround.

  1. I have a web page encoded in UTF-8 (this will be very important on some next steps);
  2. Generate the signature:
  • If the user uses Mozilla/Firefox/Chrome - the signature is generated using the window.crypto. For more information read this
  • If the user uses Internet Explorer - the signature is generated using the CAPICOM (Crypot Application Interface COM object). For more information read read MSDN Both - window.crypto and CAPICOM are not very well documented for web using (CAPICOM is not only for web!)
  1. The text string and the signature are being sent to the webserver by POST request.
  2. The sever (Linux, Apache and PHP) have to verify the signature.

Now the problems:

  1. The openssl_verify() function of PHP is also not well documented and always returns zero - signature not valid.
  2. The CMD tool openssl also is not validating the signature.
  3. Because my web server requires SSL certificate authentication I wanted that the user signs the text string with the same certificate he is logged in.

So what is the workaround:

  1. I found that CAPICOM is converting the signed string to UTF-16LE before signing it. Unfortunately the webbrowser sends the text string to the webserver encoded in UTF-8. It means you have to convert the string from UTF-8 to UTF-16LE before verifying the signature. But this is valid only if the signature was generated by CAPICOM.
  2. The openssl CMD tool is working proper now. The difference between the CMD tool and the PHP function are:
  • The signature and the source are sent to PHP function as strings. In case of using the CMD tool the signature and the source are sent as file paths. So if you are using the CMD instead of PHP function you have first to save the signature and the source as files. Remember to convert the encoding if needed.
  • The PHP function expects as a parameter to receive the public key of the signer. So before this you have to check if the certificate is issued by a trusted Certificate Authority (CA). Instead - the CMD tool expects as a parameter to receive a file which contains a list of root certificates of trusted Certificate Authorities. This means - you have to check the signer before verifying.
  1. It seems that under Firefox/Mozilla/Chrome browsers it is not possible to limit the user exactly which certificate to use for signing. But it is possible to limit the options. Of course this is not documented at all (or I didn't find any proper information about this). So the signText() function expects a third parameter which must be the trusted Certificate Authority names. First I expected that this must be the CN of the issuer of the client's certificate. But actually it is not. It must be all the issuer string separated with comas like: "C=Country,ST=State,L=Location,O=Organization,CN=CommonName,STREET=Address" Unfortunately this string is slightly different from the issuer string from openssl which looks like issuer=/streetAddress=Address/CN=CommonName/O=Organization/L=Location/ST=State/C=Country. If someone have a Firefox under Linux it will be very interesting to check if this string is formatted the same way. I don't know how to separate more CA names in a single string.
  2. Under Internet Explorer, using CAPICOM it is possible to send only one certificate object to the signing object so it will not open the dialog to select a certificate from a list. You can find the proper certificate by comparing the root certificate fingerprint (A SHA1 hash of the BASE64 65 chr/line encoded certificate excluding the header and footer) and the serial number of the certificate. Just read the MSDN it is well documented.

Now. What my source code looks like:

  1. If the signature was generated by CAPICOM of window.crypto (I receive an additional parameter from the webbrowser how the signature was generated) If CAPICOM is used I convert the source data like this: $_POST['source'] = iconv('UTF-8', 'UTF-16LE', $_POST['source'])
  2. I generate 2 temporary files. The names of the files can be generated using the PHP function uniqid(). The default temp directory can be found using the sys_get_temp_dir().
  3. The source file is saved exactly as received from the POST array. If the signature was generated by CAPICOM it must be converted to UTF-16LE.
  4. The signature comes from the web browser BASE64 encoded. Do not decode it. Just save it in a new file and add a special header and footer like this: "-----BEGIN PKCS7-----\n".$_POST['signature']."\n-----END PKSC7-----" Note that the header and footer must be on separate lines. The lines separator must be only \n (ASCII #10) not \r (ASCII #13) of \r\n (ASCII #13#10).
  5. You must have a file which contains all trusted CA root certificates. The format of this file must be as follows:
  • A header "-----BEGIN CERTIFICATE-----"
  • BASE64 encoded certificate
  • A footer "-----END CERTIFICATE-----"
  • empty line
  • If you have more than one trusted CA root - each certificate must start with a header and end with a footer string. All certificates are stored in a single file
  1. Run the CMD tool openssl using next parameters:
  • smime - to use the SMIME function of the CMD tool
  • -verify - to do a verification
  • -in filepath - the path to the signature file created in step 4
  • -inform PEM - the forat of the signature is BASE64 encoded file with header and footer
  • -binary - prevents translation of the source from binary to text
  • -content filepath - the path to the source file created in step 3
  • -CAfile filepath - the path to the trusted CA root certificates file created in step 5 So the final command looks like this: openssl smime -verify -in file.pem -inform PEM -binary -content source.txt -CAfile root.pem
  1. Now call this from PHP using the shell_exec() function and read the output.
  • If the output string starts with Verification successful - the signature is OK
  • If the output string starts with Verification failure - the signature is not OK
  • If the output string is different - some error have occurred. The error description is stored in the output string.

The above works for me. Unfortunately openssl, CAPICOM and window.crypto are very tricky and it is always possible that a problem occurs. Hope this will help somebody.

Best Regards

Possess answered 1/10, 2012 at 8:51 Comment(1)
@Babailiica, did you try to use openssl_verify from php to validate with the pkcs7 signature? Or it has to be done with command line openssl?Crossman
C
0

OK, finally I have found a workaround.

I have a web page encoded in UTF-8 (this will be very important on some next steps); Generate the signature: If the user uses Mozilla/Firefox/Chrome - the signature is generated using the window.crypto. For more information read this If the user uses Internet Explorer - the signature is generated using the CAPICOM (Crypto Application Interface COM object). For more information read read MSDN Both - window.crypto and CAPICOM are not very well documented for web using (CAPICOM is not only for web!) The text string and the signature are being sent to the web server by POST request. The server (Linux, Apache and PHP) have to verify the signature. Now the problems:

The openssl_verify() function of PHP is also not well documented and always returns zero - signature not valid. The CMD tool openssl also is not validating the signature. Because my web server requires SSL certificate authentication I wanted that the user signs the text string with the same certificate he is logged in. So what is the workaround:

I found that CAPICOM is converting the signed string to UTF-16LE before signing it. Unfortunately the web browser sends the text string to the web server encoded in UTF-8. It means you have to convert the string from UTF-8 to UTF-16LE before verifying the signature. But this is valid only if the signature was generated by CAPICOM.

The openssl CMD tool is working proper now. The difference between the CMD tool and the PHP function are:

  • The signature and the source are sent to PHP function as strings. In case of using the CMD tool the signature and the source are sent as file paths. So if you are using the CMD instead of PHP function you have first to save the signature and the source as files. Remember to convert the encoding if needed.

  • The PHP function expects as a parameter to receive the public key of the signer. So before this you have to check if the certificate is issued by a trusted Certificate Authority (CA). Instead - the CMD tool expects as a parameter to receive a file which contains a list of root certificates of trusted Certificate Authorities. This means - you have to check the signer before verifying.

It seems that under Firefox/Mozilla/Chrome browsers it is not possible to limit the user exactly which certificate to use for signing. But it is possible to limit the options. Of course this is not documented at all (or I didn't find any proper information about this). So the signText() function expects a third parameter which must be the trusted Certificate Authority names. First I expected that this must be the CN of the issuer of the client's certificate. But actually it is not. It must be all the issuer string separated with commas like:

C=Country,ST=State,L=Location,O=Organization,CN=CommonName,STREET=Address 

Unfortunately this string is slightly different from the issuer string from openssl which looks like

issuer=/streetAddress=Address/CN=CommonName/O=Organization/L=Location/ST=State/C=Country. 

If someone have a Firefox under Linux it will be very interesting to check if this string is formatted the same way. I don't know how to separate more CA names in a single string. Under Internet Explorer, using CAPICOM it is possible to send only one certificate object to the signing object so it will not open the dialog to select a certificate from a list. You can find the proper certificate by comparing the root certificate fingerprint (a SHA1 hash of the BASE64 65 chr/line encoded certificate excluding the header and footer) and the serial number of the certificate. Just read the MSDN it is well documented. Now. What my source code looks like:

If the signature was generated by CAPICOM of window.crypto (I receive an additional parameter from the web browser how the signature was generated) If CAPICOM is used I convert the source data like this:

$_POST['source'] = iconv('UTF-8', 'UTF-16LE', $_POST['source'])

I generate 2 temporary files. The names of the files can be generated using the PHP function uniqid(). The default temp directory can be found using the sys_get_temp_dir(). The source file is saved exactly as received from the POST array. If the signature was generated by CAPICOM it must be converted to UTF-16LE.

The signature comes from the web browser BASE64 encoded. Do not decode it. Just save it in a new file and add a special header and footer like this:

"-----BEGIN PKCS7-----\n".$_POST['signature']."\n-----END PKSC7-----" 

Note that the header and footer must be on separate lines. The lines separator must be only \n (ASCII #10) not \r (ASCII #13) or \r\n (ASCII #13#10). You must have a file which contains all trusted CA root certificates. The format of this file must be as follows:

A header "-----BEGIN CERTIFICATE-----"
BASE64 encoded certificate
A footer "-----END CERTIFICATE-----"
empty line

If you have more than one trusted CA root - each certificate must start with a header and end with a footer string. All certificates are stored in a single file Run the CMD tool openssl using next parameters:

  • smime - to use the SMIME function of the CMD tool
  • -verify - to do a verification
  • -in filepath - the path to the signature file created in step 4
  • -inform PEM - the forat of the signature is BASE64 encoded file with header and footer
  • -binary - prevents translation of the source from binary to text
  • -content filepath - the path to the source file created in step 3
  • -CAfile filepath - the path to the trusted CA root certificates file created in step 5

So the final command looks like this:

openssl smime -verify -in file.pem -inform PEM -binary -content source.txt -CAfile root.pem

Now call this from PHP using the shell_exec() function and read the output. If the output string starts with Verification successful - the signature is OK. If the output string starts with Verification failure - the signature is not OK. If the output string is different - some error have occurred. The error description is stored in the output string.

The above works for me. Unfortunately openssl, CAPICOM and window.crypto are very tricky and it is always possible that a problem occurs. Hope this will help somebody.

Chloric answered 12/8, 2013 at 2:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.