git » kxd » commit 5198634

Use our own tool to generate certificates

author Alberto Bertogli
2020-08-21 01:46:14 UTC
committer Alberto Bertogli
2020-08-21 15:53:37 UTC
parent 2ec7193cf143884db2f7a7017424284d329afbf6

Use our own tool to generate certificates

This patch introduces an kxgencert tool that can be used to generate
self-signed certificates.

All scripts and tests are adjusted to use it, removing the dependency on
openssl for certificate manipulation.

Unfortunately, the CA tests are removed, although the functionality
continues to be transparently supported.

The main reason is that the way we were generating the self-signed
certificates is not compatible with Go >= 1.15, since the usage of the
CommonName field is deprecated:
https://golang.org/doc/go1.15#commonname.

Because of that, also most clients built with Go 1.15 will no longer
talk to servers with old certificates. This can be worked around by
setting the GODEBUG=x509ignoreCN=0 environment variable.

.travis.yml +2 -2
Makefile +12 -4
README.md +2 -3
doc/quick_start.md +4 -2
kxgencert/kxgencert.go +104 -0
scripts/create-kxd-config +10 -12
scripts/kxc-add-key +4 -12
tests/openssl.cnf +0 -276
tests/run_tests +13 -159

diff --git a/.travis.yml b/.travis.yml
index 69da01f..fc16647 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,13 +7,13 @@ go_import_path: blitiri.com.ar/go/kxd
 
 go:
     - 1.11  # Debian stable.
-    - tip
+    - stable
+    - master
 
 addons:
     apt:
         packages:
             - python3
-            - openssl
 
 # Nothing to do for install, the tests will build the binaries anyway.
 install: true
diff --git a/Makefile b/Makefile
index 882f635..649b94d 100644
--- a/Makefile
+++ b/Makefile
@@ -2,11 +2,14 @@
 GO = go
 OUTDIR = ./out
 
-default: kxd kxc
+default: kxd kxc kxgencert
 
 kxd:
 	$(GO) build -o $(OUTDIR)/kxd ./kxd
 
+kxgencert:
+	$(GO) build -o $(OUTDIR)/kxgencert ./kxgencert
+
 # For the client, because it can be run in a very limited environment without
 # glibc (like initramfs), we build it using the native go networking so it can
 # work even when glibc's resolvers are missing.
@@ -19,7 +22,7 @@ fmt:
 vet:
 	$(GO) vet ./...
 
-test: kxd kxc
+test: kxd kxc kxgencert
 	tests/run_tests -b
 
 tests: test
@@ -33,7 +36,8 @@ SYSTEMDDIR=$(shell pkg-config systemd --variable=systemdsystemunitdir)
 # Install utility, we assume it's GNU/BSD compatible.
 INSTALL=install
 
-install-all: install-kxd install-init.d install-kxc install-initramfs
+install-all: install-kxd install-kxc install-kxgencert \
+	install-init.d install-initramfs
 
 install-kxd: kxd
 	$(INSTALL) -d $(PREFIX)/bin
@@ -41,6 +45,10 @@ install-kxd: kxd
 	$(INSTALL) -m 0755 scripts/create-kxd-config $(PREFIX)/bin/
 	$(INSTALL) -m 0755 scripts/kxd-add-client-key $(PREFIX)/bin/
 
+install-kxgencert: kxgencert
+	$(INSTALL) -d $(PREFIX)/bin
+	$(INSTALL) -m 0755 out/kxgencert $(PREFIX)/bin/
+
 install-init.d: install-kxd
 	$(INSTALL) -m 0755 scripts/init.d/kxd $(ETCDIR)/init.d/kxd
 	$(INSTALL) -m 0644 scripts/default/kxd $(ETCDIR)/default/kxd
@@ -67,7 +75,7 @@ install-initramfs: install-kxc
 		$(PREFIX)/share/initramfs-tools/scripts/init-premount/
 
 
-.PHONY: kxd kxc
+.PHONY: kxd kxc kxgencert
 .PHONY: install-all install-kxd install-init.d install-kxc install-initramfs
 .PHONY: test tests
 
diff --git a/README.md b/README.md
index 70ce4a1..3f61e13 100644
--- a/README.md
+++ b/README.md
@@ -75,10 +75,9 @@ There are no runtime dependencies for the kxd and kxc binaries.
 Building requires Go 1.11.
 
 The configuration helper scripts (`create-kxd-config`, `kxc-add-key`, etc.)
-depend on: `bash`, `openssl` (the binary), and core utilities (`mkdir`, `dd`,
-etc.).
+depend on: `bash` and core utilities (`mkdir`, `dd`, etc.).
 
-Testing needs Python 3, and openssl (the binary).
+Testing needs Python 3.
 
 
 ## Bugs and contact
diff --git a/doc/quick_start.md b/doc/quick_start.md
index 353094e..a64337c 100644
--- a/doc/quick_start.md
+++ b/doc/quick_start.md
@@ -19,10 +19,12 @@ be similar but may differ on some of the details (specially on the
 Install [kxd] on the server, via your distribution packages (e.g. `apt install
 kxd`), or directly from source.
 
-Then, run `create-kxd-config`, which will create the configuration
+Then, run `create-kxd-config <hostname>`, which will create the configuration
 directories, and generate a [self-signed] key/cert pair for the server (valid
 for 10 years).
-Everything is in `/etc/kxd/`.
+
+For the hostname, use the name or IP that the clients will use to reach the
+server. You can use more than one, separated with commas.
 
 
 ## Client setup
diff --git a/kxgencert/kxgencert.go b/kxgencert/kxgencert.go
new file mode 100644
index 0000000..e1f40a7
--- /dev/null
+++ b/kxgencert/kxgencert.go
@@ -0,0 +1,104 @@
+// Utility to generate self-signed certificates.
+// It generates a self-signed x509 certificate and key pair.
+package main
+
+import (
+	crand "crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"flag"
+	"fmt"
+	"math/big"
+	"net"
+	"os"
+	"strings"
+	"time"
+)
+
+var (
+	host = flag.String("host", "*",
+		"Hostnames/IPs to generate the certificate for (comma separated)")
+	validFor = flag.Duration("validfor", 24*time.Hour*365*10,
+		"How long will the certificate be valid for (default: 10y)")
+	orgName = flag.String("organization", "",
+		"Organization to use in the certificate, useful for debugging")
+
+	certPath = flag.String("cert", "cert.pem",
+		"Where to write the generated certificate")
+	keyPath = flag.String("key", "key.pem",
+		"Where to write the generated key")
+)
+
+func fatalf(f string, a ...interface{}) {
+	fmt.Printf(f, a...)
+	os.Exit(1)
+}
+
+func main() {
+	flag.Parse()
+
+	// Build the certificate template.
+	serial, err := crand.Int(crand.Reader, big.NewInt(1<<62))
+	if err != nil {
+		fatalf("Error generating serial number: %v\n", err)
+	}
+	tmpl := x509.Certificate{
+		SerialNumber: serial,
+		Subject:      pkix.Name{Organization: []string{*orgName}},
+
+		NotBefore: time.Now(),
+		NotAfter:  time.Now().Add(*validFor),
+
+		KeyUsage: x509.KeyUsageKeyEncipherment |
+			x509.KeyUsageDigitalSignature |
+			x509.KeyUsageCertSign,
+		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+
+		BasicConstraintsValid: true,
+	}
+
+	hosts := strings.Split(*host, ",")
+	for _, h := range hosts {
+		if ip := net.ParseIP(h); ip != nil {
+			tmpl.IPAddresses = append(tmpl.IPAddresses, ip)
+		} else {
+			tmpl.DNSNames = append(tmpl.DNSNames, h)
+		}
+	}
+
+	// Generate a private key (RSA 2048).
+	privK, err := rsa.GenerateKey(crand.Reader, 2048)
+	if err != nil {
+		fatalf("Error generating key: %v\n", err)
+	}
+
+	// Write the certificate.
+	{
+		derBytes, err := x509.CreateCertificate(
+			crand.Reader, &tmpl, &tmpl, &privK.PublicKey, privK)
+		if err != nil {
+			fatalf("Failed to create certificate: %v\n", err)
+		}
+
+		fullchain, err := os.Create(*certPath)
+		if err != nil {
+			fatalf("Failed to open %q: %v\n", *certPath, err)
+		}
+		pem.Encode(fullchain, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
+		fullchain.Close()
+	}
+
+	// Write the private key.
+	{
+		privkey, err := os.Create(*keyPath)
+		if err != nil {
+			fatalf("failed to open %q: %v\n", *keyPath, err)
+		}
+		block := &pem.Block{Type: "RSA PRIVATE KEY",
+			Bytes: x509.MarshalPKCS1PrivateKey(privK)}
+		pem.Encode(privkey, block)
+		privkey.Close()
+	}
+}
diff --git a/scripts/create-kxd-config b/scripts/create-kxd-config
index 3f1c6f0..6861eef 100755
--- a/scripts/create-kxd-config
+++ b/scripts/create-kxd-config
@@ -7,6 +7,11 @@
 #
 # It should be run under the same user as kxd itself.
 
+if [ "$1" == "" ]; then
+	echo "Usage: $0 <hostname>"
+	exit 1
+fi
+
 set -e
 
 # Create the base configuration directory.
@@ -18,19 +23,12 @@ mkdir -p /etc/kxd/data
 
 # Create a private key for the server.
 if ! [ -e /etc/kxd/key.pem ]; then
-	echo "Generating private key (/etc/kxd/key.pem)"
-	openssl genrsa -out /etc/kxd/key.pem 2048
+	kxgencert \
+		-host "${1?}" \
+		-organization "kxd@$HOSTNAME" \
+		-key /etc/kxd/key.pem \
+		-cert /etc/kxd/cert.pem
 	chmod 400 /etc/kxd/key.pem
 else
 	echo "Private key already exists (/etc/kxd/key.pem)"
 fi
-
-# And a self-signed certificate.
-if ! [ -e /etc/kxd/cert.pem ]; then
-	echo "Generating certificate (/etc/kxd/cert.pem)"
-	openssl req -new -x509 -batch -days 3650 \
-		-subj "/commonName=*/organizationalUnitName=kxd@$HOSTNAME/" \
-		-key /etc/kxd/key.pem -out /etc/kxd/cert.pem
-else
-	echo "Certificate already exists (/etc/kxd/cert.pem)"
-fi
diff --git a/scripts/kxc-add-key b/scripts/kxc-add-key
index 0e62e43..bc84ecd 100755
--- a/scripts/kxc-add-key
+++ b/scripts/kxc-add-key
@@ -29,23 +29,15 @@ mkdir -p /etc/kxc/
 
 # Create a private key for the client.
 if ! [ -e /etc/kxc/key.pem ]; then
-	echo "Generating private key (/etc/kxc/key.pem)"
-	openssl genrsa -out /etc/kxc/key.pem 2048
+	kxgencert \
+		-organization "kxc@$HOSTNAME" \
+		-key /etc/kxc/key.pem \
+		-cert /etc/kxc/cert.pem
 	chmod 400 /etc/kxc/key.pem
 else
 	echo "Private key already exists (/etc/kxc/key.pem)"
 fi
 
-# And a self-signed certificate.
-if ! [ -e /etc/kxc/cert.pem ]; then
-	echo "Generating certificate (/etc/kxc/cert.pem)"
-	openssl req -new -x509 -batch -days 3650 \
-		-subj "/commonName=*/organizationalUnitName=kxc@$HOSTNAME/" \
-		-key /etc/kxc/key.pem -out /etc/kxc/cert.pem
-else
-	echo "Certificate already exists (/etc/kxc/cert.pem)"
-fi
-
 echo "Setting URL to kxd://$SERVER/$HOSTNAME/$KEYNAME"
 echo "kxd://$SERVER/$HOSTNAME/$KEYNAME" > "/etc/kxc/${KEYNAME}.url"
 
diff --git a/tests/openssl.cnf b/tests/openssl.cnf
deleted file mode 100644
index 9c8b4f3..0000000
--- a/tests/openssl.cnf
+++ /dev/null
@@ -1,276 +0,0 @@
-# OpenSSL configuration for kxd tests.
-#
-# This file is used in some of the kxd tests, to avoid depending on the local
-# configuration which can vary between different systems.
-#
-# It's only used for CA operations, so it focuses on that.
-# It is based on Debian's default configuration.
-#############################################################################
-
-# This definition stops the following lines choking if HOME isn't
-# defined.
-HOME			= .
-RANDFILE		= $ENV::HOME/.rnd
-
-[ ca ]
-default_ca	= CA_default
-
-[ CA_default ]
-
-dir		= ./kxd-ca		# Where everything is kept
-certs		= $dir/certs		# Where the issued certs are kept
-crl_dir		= $dir/crl		# Where the issued crl are kept
-database	= $dir/index.txt	# database index file.
-#unique_subject	= no			# Set to 'no' to allow creation of
-					# several ctificates with same subject.
-new_certs_dir	= $dir/newcerts		# default place for new certs.
-
-certificate	= $dir/cacert.pem 	# The CA certificate
-serial		= $dir/serial 		# The current serial number
-crlnumber	= $dir/crlnumber	# the current crl number
-					# must be commented out to leave a V1 CRL
-crl		= $dir/crl.pem 		# The current CRL
-private_key	= $dir/private/cakey.pem # The private key
-RANDFILE	= $dir/private/.rand	# private random number file
-
-x509_extensions	= usr_cert		# The extentions to add to the cert
-
-# Comment out the following two lines for the "traditional"
-# (and highly broken) format.
-name_opt 	= ca_default		# Subject Name options
-cert_opt 	= ca_default		# Certificate field options
-
-default_days	= 365			# how long to certify for
-default_crl_days= 30			# how long before next CRL
-default_md	= default		# use public key default MD
-preserve	= no			# keep passed DN ordering
-
-policy		= policy_anything
-
-[ policy_anything ]
-countryName		= optional
-stateOrProvinceName	= optional
-localityName		= optional
-organizationName	= optional
-organizationalUnitName	= optional
-commonName		= supplied
-emailAddress		= optional
-
-####################################################################
-[ req ]
-default_bits		= 2048
-default_keyfile 	= privkey.pem
-distinguished_name	= req_distinguished_name
-attributes		= req_attributes
-x509_extensions	= v3_ca	# The extentions to add to the self signed cert
-
-# Passwords for private keys if not present they will be prompted for
-# input_password = secret
-# output_password = secret
-
-# This sets a mask for permitted string types. There are several options. 
-# default: PrintableString, T61String, BMPString.
-# pkix	 : PrintableString, BMPString (PKIX recommendation before 2004)
-# utf8only: only UTF8Strings (PKIX recommendation after 2004).
-# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
-# MASK:XXXX a literal mask value.
-# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
-string_mask = utf8only
-
-# req_extensions = v3_req # The extensions to add to a certificate request
-
-[ req_distinguished_name ]
-countryName			= Country Name (2 letter code)
-countryName_default		= AU
-countryName_min			= 2
-countryName_max			= 2
-
-stateOrProvinceName		= State or Province Name (full name)
-stateOrProvinceName_default	= Some-State
-
-localityName			= Locality Name (eg, city)
-
-0.organizationName		= Organization Name (eg, company)
-0.organizationName_default	= Internet Widgits Pty Ltd
-
-# we can do this but it is not needed normally :-)
-#1.organizationName		= Second Organization Name (eg, company)
-#1.organizationName_default	= World Wide Web Pty Ltd
-
-organizationalUnitName		= Organizational Unit Name (eg, section)
-#organizationalUnitName_default	=
-
-commonName			= Common Name (e.g. server FQDN or YOUR name)
-commonName_max			= 64
-
-emailAddress			= Email Address
-emailAddress_max		= 64
-
-# SET-ex3			= SET extension number 3
-
-[ req_attributes ]
-challengePassword		= A challenge password
-challengePassword_min		= 4
-challengePassword_max		= 20
-
-unstructuredName		= An optional company name
-
-[ usr_cert ]
-
-# These extensions are added when 'ca' signs a request.
-
-# This goes against PKIX guidelines but some CAs do it and some software
-# requires this to avoid interpreting an end user certificate as a CA.
-
-basicConstraints=CA:FALSE
-
-# Here are some examples of the usage of nsCertType. If it is omitted
-# the certificate can be used for anything *except* object signing.
-
-# This is OK for an SSL server.
-# nsCertType			= server
-
-# For an object signing certificate this would be used.
-# nsCertType = objsign
-
-# For normal client use this is typical
-# nsCertType = client, email
-
-# and for everything including object signing:
-# nsCertType = client, email, objsign
-
-# This is typical in keyUsage for a client certificate.
-# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
-
-# This will be displayed in Netscape's comment listbox.
-nsComment			= "OpenSSL Generated Certificate"
-
-# PKIX recommendations harmless if included in all certificates.
-subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid,issuer
-
-# This stuff is for subjectAltName and issuerAltname.
-# Import the email address.
-# subjectAltName=email:copy
-# An alternative to produce certificates that aren't
-# deprecated according to PKIX.
-# subjectAltName=email:move
-
-# Copy subject details
-# issuerAltName=issuer:copy
-
-#nsCaRevocationUrl		= http://www.domain.dom/ca-crl.pem
-#nsBaseUrl
-#nsRevocationUrl
-#nsRenewalUrl
-#nsCaPolicyUrl
-#nsSslServerName
-
-# This is required for TSA certificates.
-# extendedKeyUsage = critical,timeStamping
-
-[ v3_req ]
-
-# Extensions to add to a certificate request
-
-basicConstraints = CA:FALSE
-keyUsage = nonRepudiation, digitalSignature, keyEncipherment
-
-[ v3_ca ]
-
-
-# Extensions for a typical CA
-
-
-# PKIX recommendation.
-
-subjectKeyIdentifier=hash
-
-authorityKeyIdentifier=keyid:always,issuer
-
-# This is what PKIX recommends but some broken software chokes on critical
-# extensions.
-#basicConstraints = critical,CA:true
-# So we do this instead.
-basicConstraints = CA:true
-
-# Key usage: this is typical for a CA certificate. However since it will
-# prevent it being used as an test self-signed certificate it is best
-# left out by default.
-# keyUsage = cRLSign, keyCertSign
-
-# Some might want this also
-# nsCertType = sslCA, emailCA
-
-# Include email address in subject alt name: another PKIX recommendation
-# subjectAltName=email:copy
-# Copy issuer details
-# issuerAltName=issuer:copy
-
-# DER hex encoding of an extension: beware experts only!
-# obj=DER:02:03
-# Where 'obj' is a standard or added object
-# You can even override a supported extension:
-# basicConstraints= critical, DER:30:03:01:01:FF
-
-[ crl_ext ]
-
-# CRL extensions.
-# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
-
-# issuerAltName=issuer:copy
-authorityKeyIdentifier=keyid:always
-
-[ proxy_cert_ext ]
-# These extensions should be added when creating a proxy certificate
-
-# This goes against PKIX guidelines but some CAs do it and some software
-# requires this to avoid interpreting an end user certificate as a CA.
-
-basicConstraints=CA:FALSE
-
-# Here are some examples of the usage of nsCertType. If it is omitted
-# the certificate can be used for anything *except* object signing.
-
-# This is OK for an SSL server.
-# nsCertType			= server
-
-# For an object signing certificate this would be used.
-# nsCertType = objsign
-
-# For normal client use this is typical
-# nsCertType = client, email
-
-# and for everything including object signing:
-# nsCertType = client, email, objsign
-
-# This is typical in keyUsage for a client certificate.
-# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
-
-# This will be displayed in Netscape's comment listbox.
-nsComment			= "OpenSSL Generated Certificate"
-
-# PKIX recommendations harmless if included in all certificates.
-subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid,issuer
-
-# This stuff is for subjectAltName and issuerAltname.
-# Import the email address.
-# subjectAltName=email:copy
-# An alternative to produce certificates that aren't
-# deprecated according to PKIX.
-# subjectAltName=email:move
-
-# Copy subject details
-# issuerAltName=issuer:copy
-
-#nsCaRevocationUrl		= http://www.domain.dom/ca-crl.pem
-#nsBaseUrl
-#nsRevocationUrl
-#nsRenewalUrl
-#nsCaPolicyUrl
-#nsSslServerName
-
-# This really needs to be in place for it to be a proxy certificate.
-proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
-
diff --git a/tests/run_tests b/tests/run_tests
index 9499bfe..62079fb 100755
--- a/tests/run_tests
+++ b/tests/run_tests
@@ -42,10 +42,6 @@ tracemalloc.start()
 BINS = os.path.abspath(
     os.path.dirname(os.path.realpath(__file__)) + "/../out")
 
-# Path to the test OpenSSL configuration.
-OPENSSL_CONF = os.path.abspath(
-    os.path.dirname(os.path.realpath(__file__)) + "/openssl.cnf")
-
 TEMPDIR = "/does/not/exist"
 
 # User the script is running as. Just informational, for troubleshooting
@@ -58,6 +54,8 @@ def setUpModule():    # pylint: disable=invalid-name
         raise RuntimeError("kxd not found at " + BINS + "/kxd")
     if not os.path.isfile(BINS + "/kxc"):
         raise RuntimeError("kxc not found at " + BINS + "/kxc")
+    if not os.path.isfile(BINS + "/kxgencert"):
+        raise RuntimeError("kxgencert not found at " + BINS + "/kxgencert")
 
     global TEMPDIR    # pylint: disable=global-statement
     TEMPDIR = tempfile.mkdtemp(prefix="kxdtest-")
@@ -86,29 +84,15 @@ class Config:
         self.path = tempfile.mkdtemp(prefix="config-%s-" % name, dir=TEMPDIR)
         self.name = name
 
-    def gen_certs(self, self_sign=True):
+    def gen_cert(self):
         try:
-            cmd = ["openssl", "genrsa",
-                   "-out", "%s/key.pem" % self.path, "2048"]
+            cmd = [BINS + "/kxgencert",
+                   "-organization=kxd-tests-%s" % self.name,
+                   "-key=" + self.key_path(),
+                   "-cert=" + self.cert_path()]
             subprocess.check_output(cmd, stderr=subprocess.STDOUT)
         except subprocess.CalledProcessError as err:
-            print("openssl call failed, output: %r" % err.output)
-            raise
-
-        ouname = "kxd-tests-%s" % self.name
-        req_args = ["openssl", "req", "-new", "-batch",
-                    "-subj", ("/commonName=*" +
-                              "/organizationalUnitName=%s" % ouname),
-                    "-key", "%s/key.pem" % self.path]
-        if self_sign:
-            req_args.extend(["-x509", "-out", "%s/cert.pem" % self.path])
-        else:
-            req_args.extend(["-out", "%s/cert.csr" % self.path])
-
-        try:
-            subprocess.check_output(req_args, stderr=subprocess.STDOUT)
-        except subprocess.CalledProcessError as err:
-            print("openssl call failed, output: %r" % err.output)
+            print("kxgencert call failed, output: %r" % err.output)
             raise
 
     def cert_path(self):
@@ -117,61 +101,15 @@ class Config:
     def key_path(self):
         return self.path + "/key.pem"
 
-    def csr_path(self):
-        return self.path + "/cert.csr"
-
     def cert(self):
         return read_all(self.path + "/cert.pem")
 
 
-class CA:
-    def __init__(self):
-        self.path = tempfile.mkdtemp(prefix="config-ca-", dir=TEMPDIR)
-        os.makedirs(self.path + "/kxd-ca/newcerts/")
-
-        try:
-            # We need to run the CA commands from within the path.
-            with pushd(self.path):
-                open("kxd-ca/index.txt", "w").close()
-                with open("kxd-ca/serial", "w") as serial:
-                    serial.write("1000\n")
-                subprocess.check_output(
-                    ["openssl", "req", "-new", "-x509", "-batch",
-                     "-config", OPENSSL_CONF,
-                     "-subj", ("/commonName=*" +
-                               "/organizationalUnitName=kxd-tests-ca"),
-                     "-extensions", "v3_ca", "-nodes",
-                     "-keyout", "cakey.pem",
-                     "-out", "cacert.pem"],
-                    stderr=subprocess.STDOUT)
-        except subprocess.CalledProcessError as err:
-            print("openssl call failed, output: %r" % err.output)
-            raise
-
-    def sign(self, csr):
-        try:
-            with pushd(self.path):
-                subprocess.check_output(
-                    ["openssl", "ca", "-batch", "-config", OPENSSL_CONF,
-                     "-keyfile", "cakey.pem", "-cert", "cacert.pem",
-                     "-in", csr, "-out", "%s.pem" % os.path.splitext(csr)[0]],
-                    stderr=subprocess.STDOUT)
-        except subprocess.CalledProcessError as err:
-            print("openssl call failed, output: %r" % err.output)
-            raise
-
-    def cert_path(self):
-        return self.path + "/cacert.pem"
-
-    def cert(self):
-        return read_all(self.path + "/cacert.pem")
-
-
 class ServerConfig(Config):
-    def __init__(self, self_sign=True, name="server"):
+    def __init__(self, name="server"):
         Config.__init__(self, name)
         self.keys = {}
-        self.gen_certs(self_sign)
+        self.gen_cert()
 
     def new_key(self, name, allowed_clients=None, allowed_hosts=None):
         self.keys[name] = os.urandom(1024)
@@ -193,9 +131,9 @@ class ServerConfig(Config):
 
 
 class ClientConfig(Config):
-    def __init__(self, self_sign=True, name="client"):
+    def __init__(self, name="client"):
         Config.__init__(self, name)
-        self.gen_certs(self_sign)
+        self.gen_cert()
 
     def call(self, server_cert, url):
         args = [BINS + "/kxc",
@@ -469,90 +407,6 @@ class BrokenServerConfig(TestCase):
         self.assertClientFails("kxd://localhost/k1", "404 Not Found")
 
 
-class Delegation(TestCase):
-    """Tests for CA delegations."""
-    def setUp(self):
-        # For these tests, we don't have a common setup, as each will create
-        # server and clients in a slightly different way.
-        self.daemon = None
-
-    def prepare(self, server_self_sign=True, client_self_sign=True,
-                ca_sign_server=None, ca_sign_client=None):
-        self.server = ServerConfig(self_sign=server_self_sign)
-        self.client = ClientConfig(self_sign=client_self_sign)
-
-        self.ca = CA()
-        if ca_sign_server is None:
-            ca_sign_server = not server_self_sign
-        if ca_sign_client is None:
-            ca_sign_client = not client_self_sign
-
-        if ca_sign_server:
-            self.ca.sign(self.server.csr_path())
-        if ca_sign_client:
-            self.ca.sign(self.client.csr_path())
-
-        self.launch_server(self.server)
-
-    def test_server_delegated(self):
-        self.prepare(server_self_sign=False)
-
-        self.server.new_key("k1",
-                            allowed_clients=[self.client.cert()],
-                            allowed_hosts=["localhost"])
-
-        # Successful request.
-        key = self.client.call(self.ca.cert_path(), "kxd://localhost/k1")
-        self.assertEqual(key, self.server.keys["k1"])
-
-        # The server is signed by the CA, but the CA is unknown to the client.
-        # But the client knows the server directly, so it's allowed.
-        key = self.client.call(self.server.cert_path(), "kxd://localhost/k1")
-        self.assertEqual(key, self.server.keys["k1"])
-
-        # Same as above, but give the wrong CA.
-        ca2 = CA()
-        self.assertClientFails("kxd://localhost/k1",
-                               "certificate signed by unknown authority",
-                               cert_path=ca2.cert_path())
-
-    def test_client_delegated(self):
-        self.prepare(client_self_sign=False)
-
-        # Successful request.
-        self.server.new_key("k1",
-                            allowed_clients=[self.ca.cert()],
-                            allowed_hosts=["localhost"])
-        key = self.client.call(self.server.cert_path(), "kxd://localhost/k1")
-        self.assertEqual(key, self.server.keys["k1"])
-
-        # The CA signing the client is unknown to the server.
-        ca2 = CA()
-        self.server.new_key("k2",
-                            allowed_clients=[ca2.cert()],
-                            allowed_hosts=["localhost"])
-        self.assertClientFails("kxd://localhost/k2",
-                               "403 Forbidden.*No allowed certificate found",
-                               cert_path=self.server.cert_path())
-
-        # The client is signed by the CA, but the CA is unknown to the server.
-        # But the server it knows the client directly, so it's allowed.
-        self.server.new_key("k3",
-                            allowed_clients=[self.client.cert()],
-                            allowed_hosts=["localhost"])
-        key = self.client.call(self.server.cert_path(), "kxd://localhost/k3")
-        self.assertEqual(key, self.server.keys["k3"])
-
-    def test_both_delegated(self):
-        self.prepare(server_self_sign=False, client_self_sign=False)
-        self.server.new_key("k1",
-                            allowed_clients=[self.ca.cert()],
-                            allowed_hosts=["localhost"])
-
-        key = self.client.call(self.ca.cert_path(), "kxd://localhost/k1")
-        self.assertEqual(key, self.server.keys["k1"])
-
-
 class Hook(TestCase):
     """Test cases for hook support."""
 
@@ -582,7 +436,7 @@ class Hook(TestCase):
         self.assertEqual(key, self.server.keys["k1"])
 
         hook_out = read_all(self.server.path + "/data/hook-output")
-        self.assertIn("CLIENT_CERT_SUBJECT=OU=kxd-tests-client", hook_out)
+        self.assertIn("CLIENT_CERT_SUBJECT=O=kxd-tests-client", hook_out)
 
         # Failure caused by the hook exiting with error.
         self.write_hook(exit_code=1)