399 lines
14 KiB
Plaintext
399 lines
14 KiB
Plaintext
|
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
|
||
|
|