Add copy_extensions option to 'ca' utility.
diff --git a/CHANGES b/CHANGES
index c8db741..764f8f6 100644
--- a/CHANGES
+++ b/CHANGES
@@ -3,6 +3,10 @@
 
  Changes between 0.9.6 and 0.9.7  [xx XXX 2000]
 
+  *) Add a 'copy_extensions' option to the 'ca' utility. This copies
+     extensions from a certificate request to the certificate.
+     [Steve Henson]
+
   *) Allow multiple 'certopt' and 'nameopt' options to be separated
      by commas. Add 'namopt' and 'certopt' options to the 'ca' config
      file: this allows the display of the certificate about to be
diff --git a/apps/apps.c b/apps/apps.c
index d8d9140..4aeabdf 100644
--- a/apps/apps.c
+++ b/apps/apps.c
@@ -809,6 +809,62 @@
 	return set_multi_opts(flags, arg, ex_tbl);
 }
 
+int set_ext_copy(int *copy_type, const char *arg)
+{
+	if (!strcasecmp(arg, "none"))
+		*copy_type = EXT_COPY_NONE;
+	else if (!strcasecmp(arg, "copy"))
+		*copy_type = EXT_COPY_ADD;
+	else if (!strcasecmp(arg, "copyall"))
+		*copy_type = EXT_COPY_ALL;
+	else
+		return 0;
+	return 1;
+}
+
+int copy_extensions(X509 *x, X509_REQ *req, int copy_type)
+{
+	STACK_OF(X509_EXTENSION) *exts = NULL;
+	X509_EXTENSION *ext, *tmpext;
+	ASN1_OBJECT *obj;
+	int i, idx, ret = 0;
+	if (!x || !req || (copy_type == EXT_COPY_NONE))
+		return 1;
+	exts = X509_REQ_get_extensions(req);
+
+	for(i = 0; i < sk_X509_EXTENSION_num(exts); i++) {
+		ext = sk_X509_EXTENSION_value(exts, i);
+		obj = X509_EXTENSION_get_object(ext);
+		idx = X509_get_ext_by_OBJ(x, obj, -1);
+		/* Does extension exist? */
+		if (idx != -1) {
+			/* If normal copy don't override existing extension */
+			if (copy_type == EXT_COPY_ADD)
+				continue;
+			/* Delete all extensions of same type */
+			do {
+				tmpext = X509_get_ext(x, idx);
+				X509_delete_ext(x, idx);
+				X509_EXTENSION_free(tmpext);
+				idx = X509_get_ext_by_OBJ(x, obj, -1);
+			} while (idx != -1);
+		}
+		if (!X509_add_ext(x, ext, -1))
+			goto end;
+	}
+
+	ret = 1;
+
+	end:
+
+	sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
+
+	return ret;
+}
+		
+		
+			
+
 static int set_multi_opts(unsigned long *flags, const char *arg, const NAME_EX_TBL *in_tbl)
 {
 	STACK_OF(CONF_VALUE) *vals;
diff --git a/apps/apps.h b/apps/apps.h
index fb2e01b..96dafd9 100644
--- a/apps/apps.h
+++ b/apps/apps.h
@@ -153,6 +153,8 @@
 #endif
 int set_cert_ex(unsigned long *flags, const char *arg);
 int set_name_ex(unsigned long *flags, const char *arg);
+int set_ext_copy(int *copy_type, const char *arg);
+int copy_extensions(X509 *x, X509_REQ *req, int copy_type);
 int app_passwd(BIO *err, char *arg1, char *arg2, char **pass1, char **pass2);
 int add_oid_section(BIO *err, LHASH *conf);
 X509 *load_cert(BIO *err, char *file, int format);
@@ -170,6 +172,10 @@
 #define FORMAT_SMIME    6
 #define FORMAT_ENGINE   7
 
+#define EXT_COPY_NONE	0
+#define EXT_COPY_ADD	1
+#define EXT_COPY_ALL	2
+
 #define NETSCAPE_CERT_HDR	"certificate"
 
 #define APP_PASS_LEN	1024
diff --git a/apps/ca.c b/apps/ca.c
index 4185009..a37e333 100644
--- a/apps/ca.c
+++ b/apps/ca.c
@@ -134,6 +134,7 @@
 #define ENV_MSIE_HACK		"msie_hack"
 #define ENV_NAMEOPT		"name_opt"
 #define ENV_CERTOPT		"cert_opt"
+#define ENV_EXTCOPY		"copy_extensions"
 
 #define ENV_DATABASE		"database"
 
@@ -303,7 +304,7 @@
 	int notext=0;
 	unsigned long nameopt = 0, certopt = 0;
 	int default_op = 1;
-	int ext_copy = 0;
+	int ext_copy = EXT_COPY_NONE;
 	X509 *x509=NULL;
 	X509 *x=NULL;
 	BIO *in=NULL,*out=NULL,*Sout=NULL,*Cout=NULL;
@@ -798,6 +799,18 @@
 	else
 		ERR_clear_error();
 
+	f=CONF_get_string(conf,section,ENV_EXTCOPY);
+
+	if (f)
+		{
+		if (!set_ext_copy(&ext_copy, f))
+			{
+			BIO_printf(bio_err, "Invalid extension copy option: \"%s\"\n", f);
+			goto err;
+			}
+		}
+	else
+		ERR_clear_error();
 
 	/*****************************************************************/
 	/* lookup where to write new certificates */
@@ -1944,40 +1957,6 @@
 
 		if (default_op)
 			old_entry_print(bio_err, obj, str);
-#if 0
-		j=i2a_ASN1_OBJECT(bio_err,obj);
-		pbuf=buf;
-		for (j=22-j; j>0; j--)
-			*(pbuf++)=' ';
-		*(pbuf++)=':';
-		*(pbuf++)='\0';
-		BIO_puts(bio_err,buf);
-
-		if (str->type == V_ASN1_PRINTABLESTRING)
-			BIO_printf(bio_err,"PRINTABLE:'");
-		else if (str->type == V_ASN1_T61STRING)
-			BIO_printf(bio_err,"T61STRING:'");
-		else if (str->type == V_ASN1_IA5STRING)
-			BIO_printf(bio_err,"IA5STRING:'");
-		else if (str->type == V_ASN1_UNIVERSALSTRING)
-			BIO_printf(bio_err,"UNIVERSALSTRING:'");
-		else
-			BIO_printf(bio_err,"ASN.1 %2d:'",str->type);
-			
-		p=(char *)str->data;
-		for (j=str->length; j>0; j--)
-			{
-			if ((*p >= ' ') && (*p <= '~'))
-				BIO_printf(bio_err,"%c",*p);
-			else if (*p & 0x80)
-				BIO_printf(bio_err,"\\0x%02X",*p);
-			else if ((unsigned char)*p == 0xf7)
-				BIO_printf(bio_err,"^?");
-			else	BIO_printf(bio_err,"^%c",*p+'@');
-			p++;
-			}
-		BIO_printf(bio_err,"'\n");
-#endif
 		}
 
 	/* Ok, now we check the 'policy' stuff. */
@@ -2171,7 +2150,6 @@
 	if (!X509_set_issuer_name(ret,X509_get_subject_name(x509)))
 		goto err;
 
-	BIO_printf(bio_err,"Certificate is to be certified until ");
 	if (strcmp(startdate,"today") == 0)
 		X509_gmtime_adj(X509_get_notBefore(ret),0);
 	else ASN1_UTCTIME_set_string(X509_get_notBefore(ret),startdate);
@@ -2180,10 +2158,6 @@
 		X509_gmtime_adj(X509_get_notAfter(ret),(long)60*60*24*days);
 	else ASN1_UTCTIME_set_string(X509_get_notAfter(ret),enddate);
 
-	ASN1_UTCTIME_print(bio_err,X509_get_notAfter(ret));
-	if (days) BIO_printf(bio_err," (%d days)",days);
-	BIO_printf(bio_err, "\n");
-
 	if (!X509_set_subject_name(ret,subject)) goto err;
 
 	pktmp=X509_REQ_get_pubkey(req);
@@ -2251,16 +2225,32 @@
 			}
 		}
 
+	/* Copy extensions from request (if any) */
+
+	if (!copy_extensions(ret, req, ext_copy))
+		{
+		BIO_printf(bio_err, "ERROR: adding extensions from request\n");
+		ERR_print_errors(bio_err);
+		goto err;
+		}
+
+
+	if (!default_op)
+		{
+		BIO_printf(bio_err, "Certificate Details:\n");
+		/* Never print signature details because signature not present */
+		certopt |= X509_FLAG_NO_SIGDUMP | X509_FLAG_NO_SIGNAME;
+		X509_print_ex(bio_err, ret, nameopt, certopt); 
+		}
+
+	BIO_printf(bio_err,"Certificate is to be certified until ");
+	ASN1_UTCTIME_print(bio_err,X509_get_notAfter(ret));
+	if (days) BIO_printf(bio_err," (%d days)",days);
+	BIO_printf(bio_err, "\n");
 
 	if (!batch)
 		{
-		if (!default_op)
-			{
-			BIO_printf(bio_err, "Certificate Details:\n");
-			/* Never print signature details because signature not present */
-			certopt |= X509_FLAG_NO_SIGDUMP | X509_FLAG_NO_SIGNAME;
-			X509_print_ex(bio_err, ret, nameopt, certopt); 
-			}
+
 		BIO_printf(bio_err,"Sign the certificate? [y/n]:");
 		(void)BIO_flush(bio_err);
 		buf[0]='\0';
diff --git a/apps/openssl.cnf b/apps/openssl.cnf
index f02b2bd..5071d85 100644
--- a/apps/openssl.cnf
+++ b/apps/openssl.cnf
@@ -53,6 +53,9 @@
 name_opt 	= ca_default		# Subject Name options
 cert_opt 	= ca_default		# Certificate field options
 
+# Extension copying option: use with caution.
+# copy_extensions = copy
+
 # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
 # so this is commented out by default to leave a V1 CRL.
 # crl_extensions	= crl_ext
diff --git a/doc/apps/ca.pod b/doc/apps/ca.pod
index e441a62..420a0e3 100644
--- a/doc/apps/ca.pod
+++ b/doc/apps/ca.pod
@@ -334,6 +334,20 @@
 it only displays fields mentioned in the B<policy> section, mishandles
 multicharacter string types and does not display extensions.
 
+=item B<copy_extensions>
+
+determines how extensions in certificate requests should be handled.
+If set to B<none> or this option is not present then extensions are
+ignored and not copied to the certificate. If set to B<copy> then any
+extensions present in the request that are not already present are copied
+to the certificate. If set to B<copyall> then all extensions in the
+request are copied to the certificate: if the extension is already present
+in the certificate it is deleted first. See the B<WARNINGS> section before
+using this option.
+
+The main use of this option is to allow a certificate request to supply
+values for certain extensions such as subjectAltName.
+
 =back
 
 =head1 POLICY FORMAT
@@ -426,6 +440,7 @@
 
  nameopt	= default_ca		# Subject name display option
  certopt	= default_ca		# Certificate display option
+ copy_extensions = none			# Don't copy extensions from request
 
  [ policy_any ]
  countryName            = supplied
@@ -491,10 +506,6 @@
 numbers of certificates are present because, as the name implies
 the database has to be kept in memory.
 
-Certificate request extensions are ignored: some kind of "policy" should
-be included to use certain static extensions and certain extensions
-from the request.
-
 It is not possible to certify two certificates with the same DN: this
 is a side effect of how the text database is indexed and it cannot easily
 be fixed without introducing other problems. Some S/MIME clients can use
@@ -513,6 +524,30 @@
 Cancelling some commands by refusing to certify a certificate can
 create an empty file.
 
+=head1 WARNINGS
+
+The B<copy_extensions> option should be used with caution. If care is
+not taken then it can be a security risk. For example if a certificate
+request contains a basicConstraints extension with CA:TRUE and the
+B<copy_extensions> value is set to B<copyall> and the user does not spot
+this when the certificate is displayed then this will hand the requestor
+a valid CA certificate.
+
+This situation can be avoided by setting B<copy_extensions> to B<copy>
+and including basicConstraints with CA:FALSE in the configuration file.
+Then if the request contains a basicConstraints extension it will be
+ignored.
+
+It is advisable to also include values for other extensions such
+as B<keyUsage> to prevent a request supplying its own values.
+
+Additional restrictions can be placed on the CA certificate itself.
+For example if the CA certificate has:
+
+ basicConstraints = CA:TRUE, pathlen:0
+
+then even if a certificate is issued with CA:TRUE it will not be valid.
+
 =head1 SEE ALSO
 
 L<req(1)|req(1)>, L<spkac(1)|spkac(1)>, L<x509(1)|x509(1)>, L<CA.pl(1)|CA.pl(1)>,