| Date: Mon, 9 Jun 97 08:00:33 +0200 |
| From: Holger.Reif@PrakInf.TU-Ilmenau.DE (Holger Reif) |
| Subject: ms3-ca.doc |
| Organization: TU Ilmenau, Fak. IA, FG Telematik |
| Content-Length: 14575 |
| Status: RO |
| X-Status: |
| |
| Loading client certs into MSIE 3.01 |
| =================================== |
| |
| This document conatains all the information necessary to succesfully set up |
| some scripts to issue client certs to Microsoft Internet Explorer. It |
| includes the required knowledge about the model MSIE uses for client |
| certification and includes complete sample scripts ready to play with. The |
| scripts were tested against a modified ca program of SSLeay 0.6.6 and should |
| work with the regular ca program that comes with version 0.8.0. I haven't |
| tested against MSIE 4.0 |
| |
| You can use the information contained in this document in either way you |
| want. However if you feel it saved you a lot of time I ask you to be as fair |
| as to mention my name: Holger Reif <reif@prakinf.tu-ilmenau.de>. |
| |
| 1.) The model used by MSIE |
| -------------------------- |
| |
| The Internet Explorer doesn't come with a embedded engine for installing |
| client certs like Netscape's Navigator. It rather uses the CryptoAPI (CAPI) |
| defined by Microsoft. CAPI comes with WindowsNT 4.0 or is installed together |
| with Internet Explorer since 3.01. The advantage of this approach is a higher |
| flexibility because the certificates in the (per user) system open |
| certificate store may be used by other applications as well. The drawback |
| however is that you need to do a bit more work to get a client cert issued. |
| |
| CAPI defines functions which will handle basic cryptographic work, eg. |
| generating keys, encrypting some data, signing text or building a certificate |
| request. The procedure is as follows: A CAPI function generates you a key |
| pair and saves it into the certificate store. After that one builds a |
| Distinguished Name. Together with that key pair another CAPI function forms a |
| PKCS#10 request which you somehow need to submit to a CA. Finally the issued |
| cert is given to a yet another CAPI function which saves it into the |
| certificate store. |
| |
| The certificate store with the user's keys and certs is in the registry. You |
| will find it under HKEY_CURRENT_USER/Software/Microsoft/Cryptography/ (I |
| leave it to you as a little exercise to figure out what all the entries mean |
| ;-). Note that the keys are protected only with the user's usual Windows |
| login password. |
| |
| 2.) The practical usage |
| ----------------------- |
| |
| Unfortunatly since CAPI is a system API you can't access its functions from |
| HTML code directly. For this purpose Microsoft provides a wrapper called |
| certenr3.dll. This DLL accesses the CAPI functions and provides an interface |
| usable from Visual Basic Script. One needs to install that library on the |
| computer which wants to have client cert. The easiest way is to load it as an |
| ActiveX control (certenr3.dll is properly authenticode signed by MS ;-). If |
| you have ever enrolled e cert request at a CA you will have installed it. |
| |
| At time of writing certenr3.dll is contained in |
| http://www.microsoft.com/workshop/prog/security/csa/certenr3.exe. It comes |
| with an README file which explains the available functions. It is labeled |
| beta but every CA seems to use it anyway. The license.txt allows you the |
| usage for your own purposes (as far as I understood) and a somehow limited |
| distribution. |
| |
| The two functions of main interest are GenerateKeyPair and AcceptCredentials. |
| For complete explanation of all possible parameters see the README file. Here |
| are only minimal required parameters and their values. |
| |
| GenerateKeyPair(sessionID, FASLE, szName, 0, "ClientAuth", TRUE, FALSE, 1) |
| - sessionID is a (locally to that computer) unique string to correlate the |
| generated key pair with a cert installed later. |
| - szName is the DN of the form "C=DE; S=Thueringen; L=Ilmenau; CN=Holger |
| Reif; 1.2.840.113549.1.9.1=reif@prakinf.tu-ilmenau.de". Note that S is the |
| abreviation for StateOrProvince. The recognized abreviation include CN, O, C, |
| OU, G, I, L, S, T. If the abreviation is unknown (eg. for PKCS#9 email addr) |
| you need to use the full object identifier. The starting point for searching |
| them could be crypto/objects.h since all OIDs know to SSLeay are listed |
| there. |
| - note: the possible ninth parameter which should give a default name to the |
| certificate storage location doesn't seem to work. Changes to the constant |
| values in the call above doesn't seem to make sense. You can't generate |
| PKCS#10 extensions with that function. |
| |
| The result of GenerateKeyPair is the base64 encoded PKCS#10 request. However |
| it has a little strange format that SSLeay doesn't accept. (BTW I feel the |
| decision of rejecting that format as standard conforming.) It looks like |
| follows: |
| 1st line with 76 chars |
| 2nd line with 76 chars |
| ... |
| (n-2)th line with 76 chars |
| (n-1)th line contains a multiple of 4 chars less then 76 (possible |
| empty) |
| (n)th line has zero or 4 chars (then with 1 or 2 equal signs - the |
| original text's lenght wasn'T a multiple of 3) |
| The line separator has two chars: 0x0d 0x0a |
| |
| AcceptCredentials(sessionID, credentials, 0, FALSE) |
| - sessionID needs to be the same as while generating the key pair |
| - credentials is the base64 encoded PKCS#7 object containing the cert. |
| |
| CRL's and CA certs are not required simply just the client cert. (It seems to |
| me that both are not even checked somehow.) The only format of the base64 |
| encoded object I succesfully used was all characters in a very long string |
| without line feeds or carriage returns. (Hey, it doesn't matter, only a |
| computer reads it!) |
| |
| The result should be S_OK. For error handling see the example that comes with |
| certenr3.dll. |
| |
| A note about ASN.1 character encodings. certenr3.dll seems to know only about |
| 2 of them: UniversalString and PrintableString. First it is definitely wrong |
| for an email address which is IA5STRING (checked by ssleay's ca). Second |
| unfortunately MSIE (at least until version 3.02) can't handle UniversalString |
| correctly - they just blow up you cert store! Therefore ssleay's ca (starting |
| from version 0.8.0) tries to convert the encodings automatically to IA5STRING |
| or TeletexString. The beef is it will work only for the latin-1 (western) |
| charset. Microsoft still has to do abit of homework... |
| |
| 3.) An example |
| -------------- |
| |
| At least you need two steps: generating the key & request and then installing |
| the certificate. A real world CA would have some more steps involved, eg. |
| accepting some license. Note that both scripts shown below are just |
| experimental state without any warrenty! |
| |
| First how to generate a request. Note that we can't use a static page because |
| of the sessionID. I generate it from system time plus pid and hope it is |
| unique enough. Your are free to feed it through md5 to get more impressive |
| ID's ;-) Then the intended text is read in with sed which inserts the |
| sessionID. |
| |
| -----BEGIN ms-enroll.cgi----- |
| #!/bin/sh |
| SESSION_ID=`date '+%y%m%d%H%M%S'`$$ |
| echo Content-type: text/html |
| echo |
| sed s/template_for_sessId/$SESSION_ID/ <<EOF |
| <HTML><HEAD> |
| <TITLE>Certificate Enrollment Test Page</TITLE> |
| </HEAD><BODY> |
| |
| <OBJECT |
| classid="clsid:33BEC9E0-F78F-11cf-B782-00C04FD7BF43" |
| codebase=certenr3.dll |
| id=certHelper |
| > |
| </OBJECT> |
| |
| <CENTER> |
| <H2>enrollment for a personal cert</H2> |
| <BR><HR WIDTH=50%><BR><P> |
| <FORM NAME="MSIE_Enrollment" ACTION="ms-gencert.cgi" ENCTYPE=x-www-form- |
| encoded METHOD=POST> |
| <TABLE> |
| <TR><TD>Country</TD><TD><INPUT NAME="Country" VALUE=""></TD></TR> |
| <TR><TD>State</TD><TD><INPUT NAME="StateOrProvince" VALUE=""></TD></TR> |
| <TR><TD>Location</TD><TD><INPUT NAME="Location" VALUE=""></TD></TR> |
| <TR><TD>Organization</TD><TD><INPUT NAME="Organization" |
| VALUE=""></TD></TR> |
| <TR><TD>Organizational Unit</TD> |
| <TD><INPUT NAME="OrganizationalUnit" VALUE=""></TD></TR> |
| <TR><TD>Name</TD><TD><INPUT NAME="CommonName" VALUE=""></TD></TR> |
| <TR><TD>eMail Address</TD> |
| <TD><INPUT NAME="EmailAddress" VALUE=""></TD></TR> |
| <TR><TD></TD> |
| <TD><INPUT TYPE="BUTTON" NAME="submit" VALUE="Beantragen"></TD></TR> |
| </TABLE> |
| <INPUT TYPE="hidden" NAME="SessionId" VALUE="template_for_sessId"> |
| <INPUT TYPE="hidden" NAME="Request" VALUE=""> |
| </FORM> |
| <BR><HR WIDTH=50%><BR><P> |
| </CENTER> |
| |
| <SCRIPT LANGUAGE=VBS> |
| Dim DN |
| |
| Sub Submit_OnClick |
| Dim TheForm |
| Set TheForm = Document.MSIE_Enrollment |
| sessionId = TheForm.SessionId.value |
| reqHardware = FALSE |
| C = TheForm.Country.value |
| SP = TheForm.StateOrProvince.value |
| L = TheForm.Location.value |
| O = TheForm.Organization.value |
| OU = TheForm.OrganizationalUnit.value |
| CN = TheForm.CommonName.value |
| Email = TheForm.EmailAddress.value |
| szPurpose = "ClientAuth" |
| doAcceptanceUINow = FALSE |
| doOnline = TRUE |
| |
| DN = "" |
| |
| Call Add_RDN("C", C) |
| Call Add_RDN("S", SP) |
| Call Add_RDN("L", L) |
| Call Add_RDN("O", O) |
| Call Add_RDN("OU", OU) |
| Call Add_RDN("CN", CN) |
| Call Add_RDN("1.2.840.113549.1.9.1", Email) |
| ' rsadsi |
| ' pkcs |
| ' pkcs9 |
| ' eMailAddress |
| On Error Resume Next |
| sz10 = certHelper.GenerateKeyPair(sessionId, _ |
| FALSE, DN, 0, ClientAuth, FASLE, TRUE, 1)_ |
| theError = Err.Number |
| On Error Goto 0 |
| if (sz10 = Empty OR theError <> 0) Then |
| sz = "The error '" & Hex(theError) & "' occurred." & chr(13) & _ |
| chr(10) & "Your credentials could not be generated." |
| result = MsgBox(sz, 0, "Credentials Enrollment") |
| Exit Sub |
| else |
| TheForm.Request.value = sz10 |
| TheForm.Submit |
| end if |
| End Sub |
| |
| Sub Add_RDN(sn, value) |
| if (value <> "") then |
| if (DN <> "") then |
| DN = DN & "; " |
| end if |
| DN = DN & sn & "=" & value |
| end if |
| End Sub |
| </SCRIPT> |
| </BODY> |
| </HTML> |
| EOF |
| -----END ms-enroll.cgi----- |
| |
| Second, how to extract the request and feed the certificate back? We need to |
| "normalize" the base64 encoding of the PKCS#10 format which means |
| regenerating the lines and wrapping with BEGIN and END line. This is done by |
| gawk. The request is taken by ca the normal way. Then the cert needs to be |
| packed into a PKCS#7 structure (note: the use of a CRL is necessary for |
| crl2pkcs7 as of version 0.6.6. Starting with 0.8.0 it it might probably be |
| ommited). Finally we need to format the PKCS#7 object and generate the HTML |
| text. I use two templates to have a clearer script. |
| |
| 1st note: postit2 is slightly modified from a program I found at ncsa's ftp |
| site. Grab it from http://www.easterngraphics.com/certs/IX9704/postit2.c. You |
| need utils.c from there too. |
| |
| 2nd note: I'm note quite sure wether the gawk script really handles all |
| possible inputs for the request right! Today I don't use this construction |
| anymore myself. |
| |
| 3d note: the cert must be of version 3! This could be done with the nsComment |
| line in ssleay.cnf... |
| |
| ------BEGIN ms-gencert.cgi----- |
| #!/bin/sh |
| FILE="/tmp/"`date '+%y%m%d%H%M%S'-`$$ |
| rm -f "$FILE".* |
| |
| HOME=`pwd`; export HOME # as ssleay.cnf insists on having such an env var |
| cd /usr/local/ssl #where demoCA (as named in ssleay.conf) is located |
| |
| postit2 -s " " -i 0x0d > "$FILE".inp # process the FORM vars |
| |
| SESSION_ID=`gawk '$1 == "SessionId" { print $2; exit }' "$FILE".inp` |
| |
| gawk \ |
| 'BEGIN { \ |
| OFS = ""; \ |
| print "-----BEGIN CERTIFICATE REQUEST-----"; \ |
| req_seen=0 \ |
| } \ |
| $1 == "Request" { \ |
| req_seen=1; \ |
| if (length($2) == 72) print($2); \ |
| lastline=$2; \ |
| next; \ |
| } \ |
| { \ |
| if (req_seen == 1) { \ |
| if (length($1) >= 72) print($1); \ |
| else if (length(lastline) < 72) { \ |
| req_seen=0; \ |
| print (lastline,$1); \ |
| } \ |
| lastline=$1; \ |
| } \ |
| } \ |
| END { \ |
| print "-----END CERTIFICATE REQUEST-----"; \ |
| }' > "$FILE".pem < "$FILE".inp |
| |
| ssleay ca -batch -in "$FILE".pem -key passwd -out "$FILE".out |
| ssleay crl2pkcs7 -certfile "$FILE".out -out "$FILE".pkcs7 -in demoCA/crl.pem |
| |
| sed s/template_for_sessId/$SESSION_ID/ <ms-enroll2a.html >"$FILE".cert |
| /usr/local/bin/gawk \ |
| 'BEGIN { \ |
| OFS = ""; \ |
| dq = sprintf("%c",34); \ |
| } \ |
| $0 ~ "PKCS7" { next; } \ |
| { \ |
| print dq$0dq" & _"; \ |
| }' <"$FILE".pkcs7 >> "$FILE".cert |
| cat ms-enroll2b.html >>"$FILE".cert |
| |
| echo Content-type: text/html |
| echo Content-length: `wc -c "$FILE".cert` |
| echo |
| cat "$FILE".cert |
| rm -f "$FILE".* |
| -----END ms-gencert.cgi----- |
| |
| ----BEGIN ms-enroll2a.html---- |
| <HTML><HEAD><TITLE>Certificate Acceptance Test Page</TITLE></HEAD><BODY> |
| |
| <OBJECT |
| classid="clsid:33BEC9E0-F78F-11cf-B782-00C04FD7BF43" |
| codebase=certenr3.dll |
| id=certHelper |
| > |
| </OBJECT> |
| |
| <CENTER> |
| <H2>Your personal certificate</H2> |
| <BR><HR WIDTH=50%><BR><P> |
| Press the button! |
| <P><INPUT TYPE=BUTTON VALUE="Nimm mich!" NAME="InstallCert"> |
| </CENTER> |
| <BR><HR WIDTH=50%><BR> |
| |
| <SCRIPT LANGUAGE=VBS> |
| Sub InstallCert_OnClick |
| |
| sessionId = "template_for_sessId" |
| credentials = "" & _ |
| ----END ms-enroll2a.html---- |
| |
| ----BEGIN ms-enroll2b.html---- |
| "" |
| On Error Resume Next |
| result = certHelper.AcceptCredentials(sessionId, credentials, 0, |
| FALSE) |
| if (IsEmpty(result)) Then |
| sz = "The error '" & Err.Number & "' occurred." & chr(13) & |
| chr(10) & "This Digital ID could not be registered." |
| msgOut = MsgBox(sz, 0, "Credentials Registration Error") |
| navigate "error.html" |
| else |
| sz = "Digital ID successfully registered." |
| msgOut = MsgBox(sz, 0, "Credentials Registration") |
| navigate "success.html" |
| end if |
| Exit Sub |
| End Sub |
| </SCRIPT> |
| </BODY> |
| </HTML> |
| ----END ms-enroll2b.html---- |
| |
| 4.) What do do with the cert? |
| ----------------------------- |
| |
| The cert is visible (without restarting MSIE) under the following menu: |
| View->Options->Security->Personal certs. You can examine it's contents at |
| least partially. |
| |
| To use it for client authentication you need to use SSL3.0 (fortunately |
| SSLeay supports it with 0.8.0). Furthermore MSIE is told to only supports a |
| kind of automatic selection of certs (I personally wasn't able to test it |
| myself). But there is a requirement that the issuer of the server cert and |
| the issuer of the client cert needs to be the same (according to a developer |
| from MS). Which means: you need may more then one cert to talk to all |
| servers... |
| |
| I'm sure we will get a bit more experience after ApacheSSL is available for |
| SSLeay 0.8.8. |
| |
| |
| I hope you enjoyed reading and that in future questions on this topic will |
| rarely appear on ssl-users@moncom.com ;-) |
| |
| Ilmenau, 9th of June 1997 |
| Holger Reif <reif@prakinf.tu-ilmenau.de> |
| -- |
| read you later - Holger Reif |
| ---------------------------------------- Signaturprojekt Deutsche Einheit |
| TU Ilmenau - Informatik - Telematik (Verdamp lang her) |
| Holger.Reif@PrakInf.TU-Ilmenau.DE Alt wie ein Baum werden, um ueber |
| http://Remus.PrakInf.TU-Ilmenau.DE/Reif/ alle 7 Bruecken gehen zu koennen |
| |