This commit was generated by cvs2svn to track changes on a CVS vendor
[openssl.git] / doc / ms3-ca.doc
1 Date: Mon, 9 Jun 97 08:00:33 +0200
2 From: Holger.Reif@PrakInf.TU-Ilmenau.DE (Holger Reif)
3 Subject: ms3-ca.doc
4 Organization: TU Ilmenau, Fak. IA, FG Telematik
5 Content-Length: 14575
6 Status: RO
7 X-Status: 
8
9 Loading client certs into MSIE 3.01
10 ===================================
11
12 This document conatains all the information necessary to succesfully set up 
13 some scripts to issue client certs to Microsoft Internet Explorer. It 
14 includes the required knowledge about the model MSIE uses for client 
15 certification and includes complete sample scripts ready to play with. The 
16 scripts were tested against a modified ca program of SSLeay 0.6.6 and should 
17 work with the regular ca program that comes with version 0.8.0. I haven't 
18 tested against MSIE 4.0
19
20 You can use the information contained in this document in either way you 
21 want. However if you feel it saved you a lot of time I ask you to be as fair 
22 as to mention my name: Holger Reif <reif@prakinf.tu-ilmenau.de>.
23
24 1.) The model used by MSIE
25 --------------------------
26
27 The Internet Explorer doesn't come with a embedded engine for installing 
28 client certs like Netscape's Navigator. It rather uses the CryptoAPI (CAPI) 
29 defined by Microsoft. CAPI comes with WindowsNT 4.0 or is installed together 
30 with Internet Explorer since 3.01. The advantage of this approach is a higher 
31 flexibility because the certificates in the (per user) system open 
32 certificate store may be used by other applications as well. The drawback 
33 however is that you need to do a bit more work to get a client cert issued.
34
35 CAPI defines functions which will handle basic cryptographic work, eg. 
36 generating keys, encrypting some data, signing text or building a certificate 
37 request. The procedure is as follows: A CAPI function generates you a key 
38 pair and saves it into the certificate store. After that one builds a 
39 Distinguished Name. Together with that key pair another CAPI function forms a 
40 PKCS#10 request which you somehow need to submit to a CA. Finally the issued 
41 cert is given to a yet another CAPI function which saves it into the 
42 certificate store.
43
44 The certificate store with the user's keys and certs is in the registry. You 
45 will find it under HKEY_CURRENT_USER/Software/Microsoft/Cryptography/ (I 
46 leave it to you as a little exercise to figure out what all the entries mean 
47 ;-). Note that the keys are protected only with the user's usual Windows 
48 login password.
49
50 2.) The practical usage
51 -----------------------
52
53 Unfortunatly since CAPI is a system API you can't access its functions from 
54 HTML code directly. For this purpose Microsoft provides a wrapper called 
55 certenr3.dll. This DLL accesses the CAPI functions and provides an interface 
56 usable from Visual Basic Script. One needs to install that library on the 
57 computer which wants to have client cert. The easiest way is to load it as an 
58 ActiveX control (certenr3.dll is properly authenticode signed by MS ;-). If 
59 you have ever enrolled e cert request at a CA you will have installed it.
60
61 At time of writing certenr3.dll is contained in 
62 http://www.microsoft.com/workshop/prog/security/csa/certenr3.exe. It comes 
63 with an README file which explains the available functions. It is labeled 
64 beta but every CA seems to use it anyway. The license.txt allows you the 
65 usage for your own purposes (as far as I understood) and a somehow limited 
66 distribution. 
67
68 The two functions of main interest are GenerateKeyPair and AcceptCredentials. 
69 For complete explanation of all possible parameters see the README file. Here 
70 are only minimal required parameters and their values.
71
72 GenerateKeyPair(sessionID, FASLE, szName, 0, "ClientAuth", TRUE, FALSE, 1)
73 - sessionID is a (locally to that computer) unique string to correlate the 
74 generated key pair with a cert installed later.
75 - szName is the DN of the form "C=DE; S=Thueringen; L=Ilmenau; CN=Holger 
76 Reif; 1.2.840.113549.1.9.1=reif@prakinf.tu-ilmenau.de". Note that S is the 
77 abreviation for StateOrProvince. The recognized abreviation include CN, O, C, 
78 OU, G, I, L, S, T. If the abreviation is unknown (eg. for PKCS#9 email addr) 
79 you need to use the full object identifier. The starting point for searching 
80 them could be crypto/objects.h since all OIDs know to SSLeay are listed 
81 there.
82 - note: the possible ninth parameter which should give a default name to the 
83 certificate storage location doesn't seem to work. Changes to the constant 
84 values in the call above doesn't seem to make sense. You can't generate 
85 PKCS#10 extensions with that function.
86
87 The result of GenerateKeyPair is the base64 encoded PKCS#10 request. However 
88 it has a little strange format that SSLeay doesn't accept. (BTW I feel the 
89 decision of rejecting that format as standard conforming.) It looks like 
90 follows:
91         1st line with 76 chars
92         2nd line with 76 chars
93         ...
94         (n-2)th line with 76 chars
95         (n-1)th line contains a multiple of 4 chars less then 76 (possible 
96 empty)
97         (n)th line has zero or 4 chars (then with 1 or 2 equal signs - the 
98                 original text's lenght wasn'T a multiple of 3) 
99         The line separator has two chars: 0x0d 0x0a
100
101 AcceptCredentials(sessionID, credentials, 0, FALSE)
102 - sessionID needs to be the same as while generating the key pair
103 - credentials is the base64 encoded PKCS#7 object containing the cert. 
104
105 CRL's and CA certs are not required simply just the client cert. (It seems to 
106 me that both are not even checked somehow.) The only format of the base64 
107 encoded object I succesfully used was all characters in a very long string 
108 without line feeds or carriage returns. (Hey, it doesn't matter, only a 
109 computer reads it!)
110
111 The result should be S_OK. For error handling see the example that comes with 
112 certenr3.dll.
113
114 A note about ASN.1 character encodings. certenr3.dll seems to know only about 
115 2 of them: UniversalString and PrintableString. First it is definitely wrong 
116 for an email address which is IA5STRING (checked by ssleay's ca). Second 
117 unfortunately MSIE (at least until version 3.02) can't handle UniversalString 
118 correctly - they just blow up you cert store! Therefore ssleay's ca (starting 
119 from version 0.8.0) tries to convert the encodings automatically to IA5STRING 
120 or TeletexString. The beef is it will work only for the latin-1 (western) 
121 charset. Microsoft still has to do abit of homework...
122
123 3.) An example
124 --------------
125
126 At least you need two steps: generating the key & request and then installing 
127 the certificate. A real world CA would have some more steps involved, eg. 
128 accepting some license. Note that both scripts shown below are just 
129 experimental state without any warrenty!
130
131 First how to generate a request. Note that we can't use a static page because 
132 of the sessionID. I generate it from system time plus pid and hope it is 
133 unique enough. Your are free to feed it through md5 to get more impressive 
134 ID's ;-) Then the intended text is read in with sed which inserts the 
135 sessionID. 
136
137 -----BEGIN ms-enroll.cgi-----
138 #!/bin/sh
139 SESSION_ID=`date '+%y%m%d%H%M%S'`$$
140 echo Content-type: text/html
141 echo
142 sed s/template_for_sessId/$SESSION_ID/ <<EOF
143 <HTML><HEAD>
144 <TITLE>Certificate Enrollment Test Page</TITLE>
145 </HEAD><BODY>
146
147 <OBJECT
148     classid="clsid:33BEC9E0-F78F-11cf-B782-00C04FD7BF43"
149     codebase=certenr3.dll
150     id=certHelper
151     >
152 </OBJECT>
153
154 <CENTER>
155 <H2>enrollment for a personal cert</H2>
156 <BR><HR WIDTH=50%><BR><P>
157 <FORM NAME="MSIE_Enrollment" ACTION="ms-gencert.cgi" ENCTYPE=x-www-form-
158 encoded METHOD=POST>
159 <TABLE>
160     <TR><TD>Country</TD><TD><INPUT NAME="Country" VALUE=""></TD></TR>
161     <TR><TD>State</TD><TD><INPUT NAME="StateOrProvince" VALUE=""></TD></TR>
162     <TR><TD>Location</TD><TD><INPUT NAME="Location" VALUE=""></TD></TR>
163     <TR><TD>Organization</TD><TD><INPUT NAME="Organization" 
164 VALUE=""></TD></TR>
165     <TR><TD>Organizational Unit</TD>
166         <TD><INPUT NAME="OrganizationalUnit" VALUE=""></TD></TR>
167     <TR><TD>Name</TD><TD><INPUT NAME="CommonName" VALUE=""></TD></TR>
168     <TR><TD>eMail Address</TD>
169         <TD><INPUT NAME="EmailAddress" VALUE=""></TD></TR>
170     <TR><TD></TD>
171         <TD><INPUT TYPE="BUTTON" NAME="submit" VALUE="Beantragen"></TD></TR>
172 </TABLE>
173         <INPUT TYPE="hidden" NAME="SessionId" VALUE="template_for_sessId">
174         <INPUT TYPE="hidden" NAME="Request" VALUE="">
175 </FORM>
176 <BR><HR WIDTH=50%><BR><P>
177 </CENTER>
178
179 <SCRIPT LANGUAGE=VBS>
180     Dim DN
181
182     Sub Submit_OnClick
183         Dim TheForm
184         Set TheForm = Document.MSIE_Enrollment
185         sessionId       = TheForm.SessionId.value
186         reqHardware     = FALSE
187         C               = TheForm.Country.value
188         SP              = TheForm.StateOrProvince.value
189         L               = TheForm.Location.value
190         O               = TheForm.Organization.value
191         OU              = TheForm.OrganizationalUnit.value
192         CN              = TheForm.CommonName.value
193         Email           = TheForm.EmailAddress.value
194         szPurpose       = "ClientAuth"
195         doAcceptanceUINow   = FALSE
196         doOnline        = TRUE
197
198         DN = ""
199
200         Call Add_RDN("C", C)
201         Call Add_RDN("S", SP)
202         Call Add_RDN("L", L)
203         Call Add_RDN("O", O)
204         Call Add_RDN("OU", OU)
205         Call Add_RDN("CN", CN)
206         Call Add_RDN("1.2.840.113549.1.9.1", Email)
207                       ' rsadsi
208                                      ' pkcs
209                                        ' pkcs9
210                                          ' eMailAddress
211         On Error Resume Next
212         sz10 = certHelper.GenerateKeyPair(sessionId, _
213                 FALSE, DN, 0, ClientAuth, FASLE, TRUE, 1)_
214         theError = Err.Number
215         On Error Goto 0
216         if (sz10 = Empty OR theError <> 0) Then
217             sz = "The error '" & Hex(theError) & "' occurred." & chr(13) & _
218                 chr(10) & "Your credentials could not be generated."
219             result = MsgBox(sz, 0, "Credentials Enrollment")
220             Exit Sub
221         else 
222             TheForm.Request.value = sz10
223             TheForm.Submit
224         end if
225     End Sub
226
227     Sub Add_RDN(sn, value)
228         if (value <> "") then
229             if (DN <> "") then
230                 DN = DN & "; "
231             end if
232             DN = DN & sn & "=" & value
233         end if
234     End Sub
235 </SCRIPT>
236 </BODY>
237 </HTML>
238 EOF
239 -----END ms-enroll.cgi-----
240
241 Second, how to extract the request and feed the certificate back? We need to 
242 "normalize" the base64 encoding of the PKCS#10 format which means 
243 regenerating the lines and wrapping with BEGIN and END line. This is done by 
244 gawk. The request is taken by ca the normal way. Then the cert needs to be 
245 packed into a PKCS#7 structure (note: the use of a CRL is necessary for 
246 crl2pkcs7 as of version 0.6.6. Starting with 0.8.0 it it might probably be 
247 ommited). Finally we need to format the PKCS#7 object and generate the HTML 
248 text. I use two templates to have a clearer script.
249
250 1st note: postit2 is slightly modified from a program I found at ncsa's ftp 
251 site. Grab it from http://www.easterngraphics.com/certs/IX9704/postit2.c. You 
252 need utils.c from there too.
253
254 2nd note: I'm note quite sure wether the gawk script really handles all 
255 possible inputs for the request right! Today I don't use this construction 
256 anymore myself.
257
258 3d note: the cert must be of version 3! This could be done with the nsComment 
259 line in ssleay.cnf...
260
261 ------BEGIN ms-gencert.cgi-----
262 #!/bin/sh
263 FILE="/tmp/"`date '+%y%m%d%H%M%S'-`$$
264 rm -f "$FILE".*
265
266 HOME=`pwd`; export HOME  # as ssleay.cnf insists on having such an env var
267 cd /usr/local/ssl #where demoCA (as named in ssleay.conf) is located
268
269 postit2 -s " " -i 0x0d > "$FILE".inp  # process the FORM vars
270
271 SESSION_ID=`gawk '$1 == "SessionId" { print $2; exit }' "$FILE".inp`
272
273 gawk \
274         'BEGIN { \
275                 OFS = ""; \
276                 print "-----BEGIN CERTIFICATE REQUEST-----"; \
277                 req_seen=0 \
278         } \
279         $1 == "Request" { \
280                 req_seen=1; \
281                 if (length($2) == 72) print($2); \
282                 lastline=$2; \
283                 next; \
284         } \
285         { \
286                 if (req_seen == 1) { \
287                         if (length($1) >= 72) print($1); \
288                         else if (length(lastline) < 72) { \
289                                 req_seen=0; \
290                                 print (lastline,$1); \
291                         } \
292                 lastline=$1; \
293                 } \
294         } \
295         END { \
296                 print "-----END CERTIFICATE REQUEST-----"; \
297         }' > "$FILE".pem < "$FILE".inp 
298
299 ssleay ca -batch -in "$FILE".pem -key passwd -out "$FILE".out
300 ssleay crl2pkcs7 -certfile "$FILE".out -out "$FILE".pkcs7 -in demoCA/crl.pem
301
302 sed s/template_for_sessId/$SESSION_ID/ <ms-enroll2a.html >"$FILE".cert
303 /usr/local/bin/gawk \
304         'BEGIN  { \
305                 OFS = ""; \
306                 dq = sprintf("%c",34); \
307         } \
308         $0 ~ "PKCS7" { next; } \
309         { \
310                 print dq$0dq" & _"; \
311         }' <"$FILE".pkcs7 >> "$FILE".cert
312 cat  ms-enroll2b.html >>"$FILE".cert
313
314 echo Content-type: text/html
315 echo Content-length: `wc -c "$FILE".cert`
316 echo
317 cat "$FILE".cert
318 rm -f "$FILE".*
319 -----END ms-gencert.cgi-----
320
321 ----BEGIN ms-enroll2a.html----
322 <HTML><HEAD><TITLE>Certificate Acceptance Test Page</TITLE></HEAD><BODY>
323
324 <OBJECT
325     classid="clsid:33BEC9E0-F78F-11cf-B782-00C04FD7BF43"
326     codebase=certenr3.dll
327     id=certHelper
328     >
329 </OBJECT>
330
331 <CENTER>
332 <H2>Your personal certificate</H2>
333 <BR><HR WIDTH=50%><BR><P>
334 Press the button!
335 <P><INPUT TYPE=BUTTON VALUE="Nimm mich!" NAME="InstallCert">
336 </CENTER>
337 <BR><HR WIDTH=50%><BR>
338
339 <SCRIPT LANGUAGE=VBS>
340     Sub InstallCert_OnClick
341
342         sessionId       = "template_for_sessId"
343 credentials = "" & _
344 ----END ms-enroll2a.html----
345
346 ----BEGIN ms-enroll2b.html----
347 ""
348         On Error Resume Next
349         result = certHelper.AcceptCredentials(sessionId, credentials, 0, 
350 FALSE)
351         if (IsEmpty(result)) Then
352            sz = "The error '" & Err.Number & "' occurred." & chr(13) & 
353 chr(10) & "This Digital ID could not be registered."
354            msgOut = MsgBox(sz, 0, "Credentials Registration Error")
355            navigate "error.html"
356         else
357            sz = "Digital ID successfully registered."
358            msgOut = MsgBox(sz, 0, "Credentials Registration")
359            navigate "success.html"
360         end if
361         Exit Sub
362     End Sub
363 </SCRIPT>
364 </BODY>
365 </HTML>
366 ----END ms-enroll2b.html----
367
368 4.) What do do with the cert?
369 -----------------------------
370
371 The cert is visible (without restarting MSIE) under the following menu:
372 View->Options->Security->Personal certs. You can examine it's contents at 
373 least partially.
374
375 To use it for client authentication you need to use SSL3.0 (fortunately 
376 SSLeay supports it with 0.8.0). Furthermore MSIE is told to only supports a 
377 kind of automatic selection of certs (I personally wasn't able to test it 
378 myself). But there is a requirement that the issuer of the server cert and 
379 the issuer of the client cert needs to be the same (according to a developer 
380 from MS). Which means: you need may more then one cert to talk to all 
381 servers...
382
383 I'm sure we will get a bit more experience after ApacheSSL is available for 
384 SSLeay 0.8.8.
385
386
387 I hope you enjoyed reading and that in future questions on this topic will 
388 rarely appear on ssl-users@moncom.com ;-)
389
390 Ilmenau, 9th of June 1997
391 Holger Reif <reif@prakinf.tu-ilmenau.de>
392 -- 
393 read you later  -  Holger Reif
394 ----------------------------------------  Signaturprojekt Deutsche Einheit
395 TU Ilmenau - Informatik - Telematik                      (Verdamp lang her)
396 Holger.Reif@PrakInf.TU-Ilmenau.DE         Alt wie ein Baum werden, um ueber
397 http://Remus.PrakInf.TU-Ilmenau.DE/Reif/  alle 7 Bruecken gehen zu koennen
398