Currently the different browsers, servers and CA´s all implement different and incompatible ways to use SSL certificates for several VHosts on the same server.
The VHost SSL Taskforce tries to find an agreement between those ways, and publish that as an RFC afterwards, so that all the software vendors can agree on one way.
1. Way: SubjectAltName Only
- This is the official way, according to the RFC 2818. This was my discussion with Eric Rescorla (the author of the RFC) on the topic:
> Hi Eric, > > I would like to know your position regarding Multiple SSL/TLS Vhosts on the > same machine with the same IP Adress. (Name-based). > > In RFC 2818 you have written: > > Matching is performed using the matching rules specified by > [RFC2459]. If more than one identity of a given type is present in > the certificate (e.g., more than one dNSName name, a match in any one > of the set is considered acceptable.) Names may contain the wildcard > character * which is considered to match any single domain name > component or component fragment. E.g., *.a.com matches foo.a.com but > not bar.foo.a.com. f*.com matches foo.com but not bar.com. > > I would interpret it as if the solution for the problem is to have several > identities (dNSName lines) in one certificate for the different DNS Names, > and that the Browser has to accept any of them: > > dNSName: www.customer1.at > dNSName: www.customer2.com > dNSName: www.customer3.de > > Is that a correct interpretation? That's certainly one possibility, and it's the only one that will work with Name Based Virtual Hosts without the domain name extension (not yet widely deployed)
How can I generate a certificate for that?
Add the following into your openssl.cnf:
[ req_distinguished_name ] 0.subjectAltName =Alternativer Name 1 0.subjectAltName_default =DNS:www.welservice.com 1.subjectAltName =Alternativer Name 2 1.subjectAltName_default =DNS:sig.cacert.at
This example will generate a Subject like this:
Subject: C=CH, ST=Zurich, L=Zurich, CN=www.example.com/subjectAltName=DNS:www.example.com
which will IMHO not work.
After studing of "RFC 2459 - Section 4.2.1.7: Subject Alternative Name" and crossreferencing to the result of isakmpd's certpatch(8) the following should work:
Subject: C=CH, ST=Zurich, L=Zurich, CN=www.example.com ... Requested Extensions: X509v3 Subject Alternative Name: DNS:www.example.com
To accomplish such an CSR the following entries are needed in the openssl.cnf (please note: I only added the relevant parts for this Attribute)
[ req ] req_extensions = v3_req [ v3_req ] subjectAltName = DNS:www.example.com
There's also the possibility to mark this Extension "critical" by
subjectAltName = critical,DNS:www.example.com
IMHO multiple alternative Names are accomplished by
subjectAltName = DNS:www.example.com, DNS:www2.example.com
Currently, CSRs created in this way, will not show a subjectAltName on the cacert.org confirmation page to submit the CSR, so it is assumed that the CA will not honor the Extension Request
The following text should explain the meaning of "critical"
- When an implementation processing a certificate does not recognize an extension, if the criticality flag is FALSE, it may ignore that extension. If the criticality flag is TRUE, unrecognized extensions shall cause the structure to be considered invalid, i.e. in a certificate, an unrecognized critical extension would cause validation of a signature using that certificate to fail. When a certificate-using implementation recognizes and is able to process an extension, then the certificate-using implementation shall process the extension regardless of the value of the criticality flag. Note that any extension that is flagged non-critical will cause inconsistent behaviour between certificate-using systems that will process the extension and certificate-using systems that do not recognize the extension and will ignore it.
How does such a certificate look like?
Host: 212.55.212.241 (provided by jcl) [https://host1.way1.vhosts.cacert.org/] [https://host2.way1.vhosts.cacert.org/]
Robert Trebula: The hosts above have only subjectAltName set in their certificates. They do not have any Common Name. To make this work in Mozilla Firefox, just add one of the domain names as Common Name. For example see https://hq.sk/ = https://hq.alert.sk/
2. Way: Multiple CommonName´s in the same certificate
How can I generate a certificate for that?
Add the following into your openssl.cnf:
[ req_distinguished_name ] 0.commonName = Common Name (eg, YOUR name) 0.commonName_default = www.domain1.com 0.commonName_max = 64 1.commonName = Common Name (eg, YOUR name) 1.commonName_default = www.domain2.org 1.commonName_max = 64
How does such a certificate look like?
openssl x509 -in server.crt -text Certificate: Data: Version: 3 (0x2) Serial Number: 63209 (0xf6e9) Signature Algorithm: md5WithRSAEncryption Issuer: O=Root CA, OU=http://www.cacert.org, CN=CA Cert Signing Authority/emailAddress=support@cacert.org Validity Not Before: Mar 6 03:06:42 2005 GMT Not After : Mar 6 03:06:42 2007 GMT Subject: CN=www.domain1.com, CN=www.domain2.com
Host: 212.55.212.242 (provided by jcl) [https://host1.way2.vhosts.cacert.org/] [https://host2.way2.vhosts.cacert.org/]
3. Way: Certificate with 1 Common Name + 2 Subject Alt Names
Host: 212.55.212.243 (provided by jcl) [https://host1.way3.vhosts.cacert.org/] [https://host2.way3.vhosts.cacert.org/]
4. Way: Multiple certificates in the certificate chain/graph
Host: 212.55.212.244 (provided by jcl) [https://host1.way4.vhosts.cacert.org/] [https://host2.way4.vhosts.cacert.org/]
5. Way: TLS "server name indication".
- There is a TLS extension called "server name indication". It is currently not implemented by NSS . There are RFEs, you can search bugzilla. I'm not aware of any client or server that implements this extension at this time, but if you are, that might provide an incentive to add it to NSS .
There is another testing plattform for that way here: [https://sni.corelands.com/]
[http://www.mail-archive.com/mozilla-crypto%40mozilla.org/msg06633.html]
Host: 212.55.212.245 (provided by jcl) [https://host1.way5.vhosts.cacert.org/] [https://host2.way5.vhosts.cacert.org/]
mod_gnutls now supports TLS Server Name Indication: [https://sni.corelands.com/]
Discussion
Here, all related parties (Software vendors, ...) are invited to comment on pros/cons of the different ways.
Legal
A message from our legal department: In the situation of ISP´s hosting WebShops of their customers, it is necessary to have a bind the Vhosts to the different customers, so that they are liable for what they do. Having one certificate with all the names in it would give legal problems to the ISP. (author can be contacted through sourcerer)
Interoperability Test
Vendor/App |
Several CN´s |
Regexp |
Several Cert |
TLSserverNam |
|
Testsystem1 |
[https://host1.way1.vhosts.cacert.org/ Host1/1] |
[https://host1.way2.vhosts.cacert.org/ Host1/2] |
[https://host1.way3.vhosts.cacert.org/ Host1/3] |
[https://host1.way4.vhosts.cacert.org/ Host1/4] |
[https://host1.way5.vhosts.cacert.org/ Host1/5] |
Testsystem2 |
[https://host2.way1.vhosts.cacert.org/ Host2/1] |
[https://host2.way2.vhosts.cacert.org/ Host2/2] |
[https://host2.way3.vhosts.cacert.org/ Host2/3] |
[https://host2.way4.vhosts.cacert.org/ Host2/4] |
[https://host2.way5.vhosts.cacert.org/ Host2/5] |
CAcert |
Yes |
Yes |
No |
? |
Yes |
Firefox |
No/Yes 1 |
No |
Yes |
? |
Patch 2 |
Konqueror |
Yes |
Yes |
No |
? |
No |
IE |
Yes |
Yes |
No |
? |
No |
Opera |
? |
? |
? |
? |
Yes |
Safari |
? |
? |
? |
? |
No |
1 - See comment about setting Common Name
2 - Patch attached to https://bugzilla.mozilla.org/show_bug.cgi?id=116169
Useless ways
Multiple CommonName´s and a wildcard first CommonName in the same certificate
How can I generate a certificate for that?
Add the following into your openssl.cnf:
[ req_distinguished_name ] 0.commonName = Common Name (eg, YOUR name) 0.commonName_default = *.*.* 0.commonName_max = 64 1.commonName = Common Name (eg, YOUR name) 1.commonName_default = www.domain1.com 1.commonName_max = 64 2.commonName = Common Name (eg, YOUR name) 2.commonName_default = www.domain2.org 2.commonName_max = 64
How does such a certificate look like?
openssl x509 -in server.crt -text Certificate: Data: Version: 3 (0x2) Serial Number: 63209 (0xf6e9) Signature Algorithm: md5WithRSAEncryption Issuer: O=Root CA, OU=http://www.cacert.org, CN=CA Cert Signing Authority/emailAddress=support@cacert.org Validity Not Before: Mar 6 03:06:42 2005 GMT Not After : Mar 6 03:06:42 2007 GMT Subject: CN=*.*.*, CN=www.domain1.com, CN=www.domain2.org
== Why is that way useless?
Because it is insecure. With a *.*.* wildcard certificate, you could impersonate www.amazon.com, www.paypal.com and everyone else you want with one certificate.
Easy way to generate CSRs with subjectAltNames
use strict; my $config_file = "/tmp/openssl.cnf.$$"; #Where to write the config file my $keyname; #Generally, the primary FQDN my $org_unit; #Organizational unit for csr, ie Web Services my @altnames; #Alternate dns names for csr, ie www1.example.com, www1.example.net etc my $altname; #Alternate dns names for csr, ie www1.example.com, www1.example.net etc my $altcount = 1; #Can only have 16 or less alts (when submitting to Verisign) my $altflag = 1; #flag for my while loop my @config_out; #Populate with all info for the config file. my $email_address; #email address to be tagged on the certificate my $proc_base_name; #basename of the process, simply for naming conventions my $mutual_tls = "y"; #flag for mutual tls for client app stuff my $home = `echo \$HOME`; #Get user's home directory to store files in. chomp $home; my $random_file = "$home/.rnd"; #openssl for hpux needs a .rnd file to work # If config file exists, exit and alert user if (-e $config_file) { `rm $config_file`; } if (!-e $random_file) { `dd bs=512 count=4 if=/dev/null of=$random_file` ##die ("Random file couldn't be found at $random_file\n"); } # Open config file to write ssl stuff out to open (CONFIG, ">$config_file")||die "Can't open $config_file for writing\n"; # Start getting info from user print ("Generate SSL Cert stuff for SAPI\n"); print ("FQDN/Keyname for Cert \(ie www.example.com\)\t\t:"); chomp ($keyname=<STDIN>); print ("Organizational Unit \(default [Web Services]\)\t\t\t:"); chomp ($org_unit=<STDIN>); if ($org_unit eq "") { $org_unit = "Web Services"; } print ("Email address (default [webmaster\@example.com])\t\t\t:"); chomp ($email_address=<STDIN>); if ($email_address eq "") { $email_address = "webmaster\@example.com"; } print ("Mutual TLS? y/n(default [y])\t\t\t\t\t:"); chomp ($mutual_tls=<STDIN>); while ($altflag) { print ("Alt Names (ie www1.example.com or <return> for none)\t\t\t:"); chomp($altname=<STDIN>); if ($altcount < 16) { if ($altname eq $keyname) { print (" ****** Alternate name can not equal main keyname ******\n"); } elsif ($altname ne "") { push (@altnames, "DNS:$altname"); $altcount++; } else { $altflag=0; } } else { $altflag=0; } } print ("Host short name (ie imap big_srv etc)\t\t\t\t:"); chomp ($proc_base_name=<STDIN>); # Start building the config file push (@config_out, "# -------------- BEGIN custom openssl.cnf -----\n"); push (@config_out, "HOME = $home\n"); push (@config_out, "RANDFILE = $random_file\n"); push (@config_out, "oid_section = new_oids\n"); push (@config_out, "[ new_oids ]\n"); push (@config_out, "[ req ]\n"); push (@config_out, "default_bits = 2048\n"); push (@config_out, "default_days = 730 # how long to certify for\n"); push (@config_out, "default_keyfile = $home/${proc_base_name}_privatekey.pem\n"); push (@config_out, "distinguished_name = req_distinguished_name\n"); push (@config_out, "encrypt_key = no\n"); push (@config_out, "string_mask = nombstr\n"); push (@config_out, "req_extensions = v3_req # Extensions to add to certificate request\n"); push (@config_out, "[ req_distinguished_name ]\n"); push (@config_out, "countryName = Country Name (2 letter code)\n"); push (@config_out, "countryName_default = US\n"); push (@config_out, "countryName_min = 2\n"); push (@config_out, "countryName_max = 2\n"); push (@config_out, "stateOrProvinceName = State or Province Name (full name)\n"); push (@config_out, "stateOrProvinceName_default = Virginia\n"); push (@config_out, "stateOrProvinceName_max = 64\n"); push (@config_out, "localityName = Locality Name (eg, city)\n"); push (@config_out, "localityName_default = Sterling\n"); push (@config_out, "localityName_max = 64\n"); push (@config_out, "0.organizationName = Organization Name (eg, company)\n"); push (@config_out, "0.organizationName_default = MegaCorp\n"); push (@config_out, "0.organizationName_max = 64\n"); push (@config_out, "organizationalUnitName = Org. Unit Name(Web Services,Investor Relations)\n"); push (@config_out, "organizationalUnitName_default = $org_unit\n"); push (@config_out, "organizationalUnitName_max = 64\n"); push (@config_out, "commonName = Common Name (eg, YOUR name)\n"); push (@config_out, "commonName_default = $keyname\n"); push (@config_out, "commonName_max = 64\n"); push (@config_out, "emailAddress = Email Address\n"); push (@config_out, "emailAddress_default = $email_address\n"); push (@config_out, "emailAddress_max = 40\n"); push (@config_out, "[ v3_req ]\n"); push (@config_out, "basicConstraints = CA:FALSE\n"); if (@altnames[0] ne "") { $altname=join(',', @altnames); push (@config_out, "subjectAltName=$altname\n"); } if ($mutual_tls eq "y") { push (@config_out, "extendedKeyUsage = clientAuth, serverAuth, msSGC, nsSGC\n"); push (@config_out, "nsCertType = client, server\n"); } push (@config_out, "nsComment = \"Custom openssl.cnf w/ subjectAltName\"\n"); push (@config_out, "keyUsage = digitalSignature, keyEncipherment\n"); push (@config_out, "# -------------- END OpsSec-provided openssl.cnf -----\n"); # Write shit to file print (CONFIG "@config_out"); close CONFIG; # Generate ssl stuff print ("\nAttempting openssl...\n"); system ("openssl req -batch -config $config_file -newkey rsa -out $home/${proc_base_name}_csr.pem"); print ("\nwriting csr to $home/${proc_base_name}_csr.pem...\n\n"); print ("Take the contents of $home/${proc_base_name}_csr.pem and go submit them to receive an SSL ID. When you receive your public key back, you 'should' name it something like '${proc_base_name}_server.pem'. "); `rm $config_file`;