#!/bin/bash
#
#  Generate a root CA cert for signing, and then a subject cert.
#  Usage: make-certs.sh hostname [user[@domain]] [more ...]
#  For testing only, probably still has some bugs in it.
#
set -e

DOMAIN=example.com
DAYS=3650
KEYTYPE=RSA
CRLHOURS=24
CRLDAYS=

# Cleanup temporary files at exit.
touch openssl.cnf
cleanup() {
	test -f openssl.cnf && rm -f openssl.cnf
	test -f host.csr    && rm -f host.csr
	test -f ca.txt      && rm -f ca.txt
}
#trap cleanup EXIT

# Choose a user name part for email attributes.
GIVENUSER=$2
test x"$GIVENUSER" = x && GIVENUSER=$USER
echo "$GIVENUSER" | grep -q @ || GIVENUSER="$GIVENUSER"@$DOMAIN
DOMAIN=`echo "$GIVENUSER" | cut -f2- -d@`

# Default to generating a key/cert pair for the current host.
commonname="$1"
if test "x$commonname" = x ; then
	echo Usage: `basename $0` 'commonname' user'[@domain]' '[more [...]]'
	echo More:
	echo -e \\tKey usage: "[sign|signing|encrypt|encryption|all]"
	echo -e \\tAuthority Access Info OCSP responder: "ocsp:URI"
	echo -e \\tCRL distribution point: "crl:URI"
	echo -e \\tSubject Alternative Name:
	echo -e \\t\\tHostname: "*"
	echo -e \\t\\tIP address: w.x.y.z
	echo -e \\t\\tEmail address: "*@*.com/edu/net/org/local"
	echo -e \\t\\tKerberos principal name: "*@*.COM/EDU/NET/ORG/LOCAL"
	echo -e \\tExtended key usage:
	echo -e \\t\\t1....
	echo -e \\t\\t2....
	echo -e \\t\\tid-kp-server-auth \| tls-server
	echo -e \\t\\tid-kp-client-auth \| tls-client
	echo -e \\t\\tid-kp-email-protection \| email
	echo -e \\t\\tid-ms-kp-sc-logon \| id-ms-sc-logon
	echo -e \\t\\tid-pkinit-kp-client-auth \| id-pkinit-client
	echo -e \\t\\tid-pkinit-kp-kdc \| id-pkinit-kdc
	echo -e \\t\\tca \| CA
	exit 1
fi

shift || true
shift || true

# Done already?
done=:

ocsp() {
	request_file=`mktemp -t ocsp.request.XXXXXX`
	if test -n "$request_file" ; then
		case "$REQUEST_METHOD" in
		GET)
			echo "$@" > "$request_file"
			;;
		POST)
			dd bs=1 of="$request_file" status=noxfer
			;;
		esac
	fi
}

keygen() {
	case "$KEYTYPE" in
	DSA)
		openssl dsaparam -out "$1".param 1024
		openssl gendsa "$1".param
		;;
	RSA|*)
		openssl genrsa 2048
		;;
	esac
}

# Generate a CA key.
if ! test -s ca.key ; then
	umask=`umask -p`
	umask 077
	keygen ca > ca.key 2> /dev/null
	$umask
else
	echo "You already have a ca.key file; not replacing."
	done=echo
fi

# Create a new CA serial number.
if ! test -s ca.srl ; then
	(dd if=/dev/urandom bs=8 count=1 2> /dev/null) | od -t x1c | head -n 1 | awk '{OFS="";$1="00";print}' > ca.srl
else
	echo "You already have a ca.srl file; not replacing."
fi

# Create a new CA "database".
if ! test -s ca.db ; then
	touch ca.db
else
	echo "You already have a ca.db file; not replacing."
fi

# Parse the arguments which indicate what sort of information we want.
CA=FALSE
while test $# -gt 0 ; do
	type=
	value="$1"
	case "$value" in
	RSA|rsa)
		KEYTYPE=RSA
		;;
	DSA|dsa)
		KEYTYPE=DSA
		;;
	OCSP:*|ocsp:*)
		aiaval=`echo "$value" | cut -f2- -d:`
		aia="OCSP;URI:$aiaval"
		;;
	CRL:*|crl:*)
		crlval=`echo "$value" | cut -f2- -d:`
		crl="URI:$crlval"
		;;
	signing|sign)
		keyusage="${keyusage:+${keyusage},}nonRepudiation,digitalSignature"
		;;
	encryption|encrypt)
		keyusage="${keyusage:+${keyusage},}keyEncipherment,dataEncipherment"
		;;
	all)
		keyusage="digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment,keyAgreement,keyCertSign,cRLSign,encipherOnly,decipherOnly"
		;;
	ca|CA)
		CA=TRUE
		keyusage="${keyusage:+${keyusage},}nonRepudiation,digitalSignature,keyEncipherment,dataEncipherment,keyAgreement,keyCertSign,cRLSign"
		;;
	1.*|2.*|id-*|tls-*|email|mail)
		ekuval=`echo "$value" | tr '[A-Z]' '[a-z]' | sed 's,\-,,g'`
		case "$ekuval" in
		idkpserverauth|tlsserver) ekuval=1.3.6.1.5.5.7.3.1;;
		idkpclientauth|tlsclient) ekuval=1.3.6.1.5.5.7.3.2;;
		idkpemailprotection|email|mail) ekuval=1.3.6.1.5.5.7.3.4;;
		idmskpsclogon|idmssclogon) ekuval=1.3.6.1.4.1.311.20.2.2;;
		idpkinitkpclientauth|idpkinitclient) ekuval=1.3.6.1.5.2.3.4;;
		idpkinitkpkdc|idpkinitkdc) ekuval=1.3.6.1.5.2.3.5;;
		esac
		if test -z "$eku" ; then
			eku="$ekuval"
		else
			eku="$eku,$ekuval"
		fi
		;;
	*@*.COM|*@*.EDU|*@*.NET|*@*.ORG|*@*.LOCAL)
		luser=`echo "$value" | tr '[A-Z]' '[a-z]'`
		if test "$luser" = "$value" ; then
			luser=
		fi
		type="otherName:1.3.6.1.5.2.2;SEQUENCE:$value,${luser:+otherName:1.3.6.1.4.1.311.20.2.3;UTF8:${luser},}otherName:1.3.6.1.4.1.311.20.2.3;UTF8"
		unset luser
		principals="$principals $value"
		;;
	*@*.com|*@*.edu|*@*.net|*@*.org|*@*.local)            type=email;;
	[0-9]*.[0-9]*.[0-9]*.[0-9]*)                          type=IP;;
	*)                                                    type=DNS;;
	esac
	if test -n "$type" ; then
		newvalue="${type}:$value"
		if test -z "$altnames" ; then
			altnames="${newvalue}"
		else
			altnames="${altnames},${newvalue}"
		fi
	fi
	shift
done

# Generate the CA cert.  Set the X.509v3 basic constraints to mark it as a CA
# certificate.
if ! test -s ca.crt ; then
	cat > openssl.cnf <<- EOF
	[req]
	x509_extensions = v3_ca
	distinguished_name = req_ca_name
	oid_section = new_oids
	prompt = no
database = `pwd`/ca.db
serial = `pwd`/ca.srl

	[req_ca_name]
	EOF
	echo $DOMAIN | awk 'BEGIN {FS="."}{for(i=NF;i>0;i--){print NF-i ".domainComponent="$i;}}' >> openssl.cnf
	cat >> openssl.cnf <<- EOF
	commonName = Test Certifying CA

	[new_oids]
	domainComponent = 0.9.2342.19200300.100.1.25

[ policy_anything ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

	[v3_ca]
	subjectKeyIdentifier=hash
	authorityKeyIdentifier=keyid:always,issuer:always
	keyUsage=nonRepudiation,digitalSignature,keyEncipherment,dataEncipherment,keyAgreement,keyCertSign,cRLSign
	basicConstraints=critical,CA:TRUE
	nsComment="Testing CA Certificate"
	EOF
	if test -n "$aia" ; then
		echo "authorityInfoAccess = ${aia}" >> openssl.cnf
	fi
	if test -n "$crl" ; then
		echo "crlDistributionPoints = ${crl}" >> openssl.cnf
	fi
	echo "" >> openssl.cnf
	
	openssl req -config `pwd`/openssl.cnf -new -key ca.key > ca.csr 2> /dev/null
	openssl ca -config `pwd`/openssl.cnf -days $DAYS -policy policy_anything -md sha1 -selfsign -keyfile ca.key -extensions v3_ca -in ca.csr -out ca.crt -name req -outdir `pwd` -batch
	openssl x509 -noout -text -in ca.crt > ca.txt
	cat ca.crt >> ca.txt
	cat ca.txt > ca.crt
	rm ca.txt
	cat ca.crt > ca.chain.crt
else
	echo "You already have a ca.crt file; not replacing."
	done=echo
fi

# Generate an OCSP key.
if ! test -s ocsp.key ; then
	umask=`umask -p`
	umask 077
	keygen ocsp > ocsp.key 2> /dev/null
	$umask
else
	echo "You already have an ocsp.key file; not replacing."
	done=echo
fi

# Generate the OCSP signing cert.  Set the X.509v3 basic constraints and EKU.
if ! test -s ocsp.crt ; then
	cat > openssl.cnf <<- EOF
	[req]
	x509_extensions = v3_ocsp
	distinguished_name = req_ocsp_name
	oid_section = new_oids
	prompt = no
database = `pwd`/ca.db
serial = `pwd`/ca.srl

	[req_ocsp_name]
	EOF
	echo $DOMAIN | awk 'BEGIN {FS="."}{for(i=NF;i>0;i--){print NF-i ".domainComponent="$i;}}' >> openssl.cnf
	cat >> openssl.cnf <<- EOF
	commonName = OCSP Signer for Test Certifying CA

	[new_oids]
	domainComponent = 0.9.2342.19200300.100.1.25
	ocspNoCheck = 1.3.6.1.5.5.7.48.1.5

[ policy_anything ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

	[v3_ocsp]
	subjectKeyIdentifier=hash
	authorityKeyIdentifier=keyid:always,issuer:always
	keyUsage=digitalSignature,keyEncipherment,dataEncipherment,keyAgreement,keyCertSign,cRLSign
	basicConstraints=CA:FALSE
	extendedKeyUsage=1.3.6.1.5.5.7.3.9
	nsComment="Testing OCSP Certificate"
	1.3.6.1.5.5.7.48.1.5=ASN1:NULL
	EOF
	if test -n "$aia" ; then
		echo "authorityInfoAccess = ${aia}" >> openssl.cnf
	fi
	if test -n "$crl" ; then
		echo "crlDistributionPoints = ${crl}" >> openssl.cnf
	fi
	echo "" >> openssl.cnf
	
	openssl req -config `pwd`/openssl.cnf -new -key ocsp.key > ocsp.csr 2> /dev/null
	openssl ca -config `pwd`/openssl.cnf -days $DAYS -policy policy_anything -md sha1 -selfsign -keyfile ocsp.key -extensions v3_ocsp -in ocsp.csr -out ocsp.crt -name req -outdir `pwd` -batch
	openssl x509 -noout -text -in ocsp.crt > ocsp.txt
	cat ocsp.crt >> ocsp.txt
	cat ocsp.txt >  ocsp.crt
	rm ocsp.txt
else
	echo "You already have an ocsp.crt file; not replacing."
	done=echo
fi

# We always generate a fresh, empty, CRL.  Might as well do it now.
cat > openssl.cnf <<- EOF
[ca]
default_ca = issuer

[issuer]
private_key = `pwd`/ca.key
certificate = `pwd`/ca.crt
database = `pwd`/ca.db
serial = `pwd`/ca.srl
default_md = sha1
EOF
openssl ca -config `pwd`/openssl.cnf -gencrl ${CRLHOURS:+-crlhours ${CRLHOURS}} ${CRLDAYS:+-crldays ${CRLDAYS}} -out ca.crl.pem
openssl crl -in ca.crl.pem -outform der -out ca.crl

# Create a new serial number if this is a new CA.
if test "x$CA" = xTRUE ; then
	if ! test -d "$commonname" ; then
		mkdir "$commonname"
	fi
	if ! test -s "$commonname/ca.srl" ; then
		(dd if=/dev/urandom bs=8 count=1 2> /dev/null) | od -t x1c | head -n 1 | awk '{$1="00";OFS="";print}' > "$commonname/ca.srl"
	else
		echo "You already have a $commonname/ca.srl file; not replacing."
	fi
	cert="$commonname/ca.crt"
	csr="$commonname/ca.csr"
	key="$commonname/ca.key"
	pem="$commonname/ca.pem"
	pfx="$commonname/ca.p12"
	ln -s ../`basename $0` "$commonname"/
else
	cert="$commonname.crt"
	csr="$commonname.csr"
	key="$commonname.key"
	pem="$commonname.pem"
	pfx="$commonname.p12"
fi

# Generate the subject's certificate.  Set the X.509v3 basic constraints.
if ! test -s "$cert" ; then
	cat >> openssl.cnf <<- EOF

	[req]
	distinguished_name = req_issued_name
	prompt = no

[ policy_anything ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

	[x509]
	x509_extensions = v3_issued

	[req_issued_name]
	EOF
	echo $DOMAIN | awk 'BEGIN {FS="."}{for(i=NF;i>0;i--){print NF-i ".domainComponent="$i;}}' >> openssl.cnf
	cat >> openssl.cnf <<- EOF
	mail = $GIVENUSER
	commonName = $commonname

	[v3_issued]
	certificatePolicies=2.5.29.32.0${eku:+,${eku}}
	subjectKeyIdentifier=hash
	authorityKeyIdentifier=keyid,issuer
	EOF
	if test -n "$aia" ; then
		echo "authorityInfoAccess = ${aia}" >> openssl.cnf
	fi
	if test -n "$crl" ; then
		echo "crlDistributionPoints = ${crl}" >> openssl.cnf
	fi
	if test -n "$keyusage" ; then
		echo "keyUsage = critical,${keyusage}" >> openssl.cnf
	fi
	if test -n "$altnames" ; then
		echo "subjectAltName = ${altnames}" >> openssl.cnf
	fi
	if test -n "$eku" ; then
		echo "extendedKeyUsage = ${eku}" >> openssl.cnf
	fi
	echo "basicConstraints=critical,CA:$CA" >> openssl.cnf
	if test "x$CA" = xTRUE ; then
		echo 'nsComment="Testing CA Certificate for '"$commonname"'"' >> openssl.cnf
	else
		echo 'nsComment="Testing Certificate for '"$commonname"'"' >> openssl.cnf
	fi
	for value in $principals; do
		user=`echo "$value" | cut -f1 -d@`
		realm=`echo "$value" | cut -f2- -d@`
		echo "" >> openssl.cnf
		echo "[$value]" >> openssl.cnf
		echo "realm=EXPLICIT:0,GeneralString:$realm" >> openssl.cnf
		echo "kerberosname=EXPLICIT:1,SEQUENCE:krb5$user" >> openssl.cnf

		echo "" >> openssl.cnf
		echo "[krb5$user]" >> openssl.cnf
		echo "nametype=EXPLICIT:0,INTEGER:1" >> openssl.cnf
		echo "namelist=EXPLICIT:1,SEQUENCE:krb5basic$user" >> openssl.cnf

		echo "[krb5basic$user]" >> openssl.cnf
		count=0
		for part in `echo "$user" | sed 's,/, ,g'` ; do
			echo "$count.part=GeneralString:$part" >> openssl.cnf
			count=`expr "$count" + 1`
		done
	done
	
	# Generate another key, unless we have a key or CSR.
	if ! test -s "$key" && ! test -s "$csr" ; then
		umask=`umask -p`
		umask 077
		keygen "$commonname" > "$key" 2> /dev/null
		$umask
	else
		echo "You already have a $key or $csr file; not replacing."
		done=echo
	fi

	if ! test -s "$csr" ; then
		openssl req -config `pwd`/openssl.cnf -new -key "$key" > "$csr" 2> /dev/null
	fi
	openssl ca -config `pwd`/openssl.cnf -extensions v3_issued -cert ca.crt -keyfile ca.key -in "$csr" -days $DAYS -out "$cert" -outdir `pwd` -policy policy_anything -batch
	openssl x509 -noout -text -in "$cert" > "$cert.txt"
	cat "$cert" >> "$cert.txt"
	cat "$cert.txt" > "$cert"
	rm -f "$cert.txt"
else
  echo "You already have a $cert file; not replacing."
fi

if test -s ca.chain.crt ; then
	chain=ca.chain.crt
else
	chain=ca.crt
fi
if test "x$CA" = xTRUE ; then
	cat "$chain" "$cert" > "$commonname/ca.chain.crt"
fi

# Create ca.pem and the subject's name.pem for the benefit of applications
# which expect both the private key and the certificate in one file.
if ! test -s ca.pem ; then
	cat ca.key ca.crt > ca.pem
else
	echo "You already have a ca.pem file; not replacing."
	done=echo
fi
if ! test -s "$pem" ; then
	cat "$key" "$cert" > "$pem"
else
	echo "You already have a $pem file; not replacing."
	done=echo
fi
if ! test -s "$pfx" ; then
        # default SUN JSSE implementation fails with empty password
	openssl pkcs12 -export -inkey "$key" -in "$cert" -name "$commonname" -out "$pfx" -nodes -passout pass:weneedthatforjava
else
	echo "You already have a $pfx file; not replacing."
	done=echo
fi
$umask
$done

echo CA certificate:
openssl x509 -noout -issuer  -in ca.crt | sed s,=\ ,\ ,g
openssl x509 -noout -subject -in ca.crt | sed s,=\ ,\ ,g
echo
echo End entity certificate:
openssl x509 -noout -issuer  -in "$cert" | sed s,=\ ,\ ,g
openssl x509 -noout -subject -in "$cert" | sed s,=\ ,\ ,g
openssl x509 -noout -serial  -in "$cert" | sed s,=,\ ,g
echo
echo PKCS12 bag:
openssl pkcs12 -in "$pfx" -nodes -nokeys -nocerts -info -passin pass:weneedthatforjava
echo
echo Verifying:
echo + openssl verify -CAfile "$chain" "$cert"
openssl verify -CAfile "$chain" "$cert"
echo
echo Add *.key read permissions for group and others
chmod 644 *.key
