Complete refactoring
This commit is contained in:
parent
6684fb4d71
commit
771066ff5d
42
README.md
42
README.md
|
@ -1,24 +1,38 @@
|
||||||
# shellpki
|
# shellpki
|
||||||
|
|
||||||
This script is a wrapper around openssl to manage all the pki stuff
|
This script is a wrapper around openssl to manage a small PKI.
|
||||||
for openvpn.
|
|
||||||
|
|
||||||
# Usage
|
## Install
|
||||||
|
|
||||||
First create the directory, put the script in it and the openssl
|
~~~
|
||||||
configuration file. You may certainly need to edit the configuration.
|
mkdir /etc/shellpki
|
||||||
|
useradd shellpki --system -M --home-dir /etc/shellpki --shell /usr/sbin/nologin
|
||||||
|
install -m 0640 openssl.cnf /etc/shellpki/
|
||||||
|
install -m 0755 shellpki.sh /usr/local/sbin/shellpki
|
||||||
|
~~~
|
||||||
|
|
||||||
mkdir -p /etc/openvpn/ssl
|
## Usage
|
||||||
cp /path/to/shellpki.sh /etc/openvpn/ssl/
|
|
||||||
cp /path/to/openssl.cnf /etc/openvpn/ssl/
|
|
||||||
$EDITOR /etc/openvpn/ssl/openssl.cnf
|
|
||||||
|
|
||||||
Then you'll need to initialize the pki.
|
Initialize PKI creating CA key and certificate :
|
||||||
|
|
||||||
cd /etc/openvpn/ssl
|
~~~
|
||||||
sh shellpki.sh init
|
shellpki init
|
||||||
|
~~~
|
||||||
|
|
||||||
Once it's done, you can create all the certificates you need.
|
Create a certificate and key on the server :
|
||||||
|
|
||||||
sh shellpki.sh create
|
~~~
|
||||||
|
shellpki create
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Create a certificate without key from a CSR :
|
||||||
|
|
||||||
|
~~~
|
||||||
|
shellpki fromcsr
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Revoke a certificate :
|
||||||
|
|
||||||
|
~~~
|
||||||
|
shellpki revoke
|
||||||
|
~~~
|
||||||
|
|
10
openssl.cnf
10
openssl.cnf
|
@ -2,13 +2,13 @@
|
||||||
default_ca = CA_default
|
default_ca = CA_default
|
||||||
|
|
||||||
[ CA_default ]
|
[ CA_default ]
|
||||||
dir = /etc/openvpn/ssl/ca
|
dir = /etc/shellpki/ca
|
||||||
certs = /etc/openvpn/ssl/certs
|
certs = $dir/certs
|
||||||
new_certs_dir = /etc/openvpn/ssl/ca/tmp
|
new_certs_dir = $dir/tmp
|
||||||
database = $dir/index.txt
|
database = $dir/index.txt
|
||||||
certificate = $dir/cacert.pem
|
certificate = $dir/cacert.pem
|
||||||
serial = $dir/serial
|
serial = $dir/serial
|
||||||
crl = /etc/openvpn/ssl/crl.pem
|
crl = $dir/crl.pem
|
||||||
private_key = $dir/private.key
|
private_key = $dir/private.key
|
||||||
RANDFILE = $dir/.rand
|
RANDFILE = $dir/.rand
|
||||||
default_days = 365
|
default_days = 365
|
||||||
|
@ -51,5 +51,3 @@ commonName_max = 64
|
||||||
emailAddress = Email Address
|
emailAddress = Email Address
|
||||||
emailAddress_default = security@evolix.net
|
emailAddress_default = security@evolix.net
|
||||||
emailAddress_max = 40
|
emailAddress_max = 40
|
||||||
|
|
||||||
|
|
||||||
|
|
369
shellpki.sh
369
shellpki.sh
|
@ -1,239 +1,238 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
#
|
||||||
PREFIX=/etc/openvpn/ssl
|
# shellpki is a wrapper around openssl to manage a small PKI
|
||||||
CONFFILE=$PREFIX/openssl.cnf
|
#
|
||||||
OPENSSL=$(which openssl)
|
|
||||||
TIMESTAMP=$(/bin/date +"%s")
|
|
||||||
WWWDIR=/var/www/htdocs/vpn/ssl
|
|
||||||
|
|
||||||
|
|
||||||
if [ "$(id -u)" != "0" ]; then
|
|
||||||
echo "Please become root before running ${0##*/}!" >&2
|
|
||||||
echo >&2
|
|
||||||
echo "Press return to continue..." >&2
|
|
||||||
read REPLY
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
echo "Do you confirm ${0##*/} initialization?"
|
umask 0177
|
||||||
echo
|
|
||||||
echo "Press return to continue..."
|
|
||||||
read REPLY
|
|
||||||
echo
|
|
||||||
|
|
||||||
if [ ! -d $PREFIX/ca ]; then mkdir -p $PREFIX/ca; fi
|
if [ -f "${CADIR}/private.key" ]; then
|
||||||
if [ ! -d $PREFIX/ca/tmp ]; then mkdir -p $PREFIX/ca/tmp; fi
|
echo "${CADIR}/private.key already exists, do you really want to erase it ?\n"
|
||||||
if [ ! -d $PREFIX/certs ]; then mkdir -p $PREFIX/certs; fi
|
echo "Press return to continue..."
|
||||||
if [ ! -d $PREFIX/files ]; then mkdir -p $PREFIX/files; fi
|
read -r REPLY
|
||||||
if [ ! -f $PREFIX/ca/index.txt ]; then touch $PREFIX/ca/index.txt; fi
|
|
||||||
if [ ! -f $PREFIX/files/ca/serial ]; then echo 01 > $PREFIX/ca/serial; fi
|
|
||||||
|
|
||||||
if [ ! -e "$CONFFILE" ]; then
|
|
||||||
echo "$CONFFILE is missing" >&2
|
|
||||||
echo >&2
|
|
||||||
echo "Press return to continue..." >&2
|
|
||||||
read REPLY
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$OPENSSL dhparam -out $PREFIX/ca/dh2048.pem 2048
|
[ -d "${CADIR}" ] || mkdir -pm 0700 "${CADIR}"
|
||||||
$OPENSSL genrsa -out $PREFIX/ca/private.key 4096
|
[ -d "${CADIR}/certs" ] || mkdir -m 0777 "${CADIR}/certs"
|
||||||
|
[ -d "${CADIR}/tmp" ] || mkdir -m 0700 "${CADIR}/tmp"
|
||||||
|
[ -f "${CADIR}/index.txt" ] || touch "${CADIR}/index.txt"
|
||||||
|
[ -f "${CADIR}/serial" ] || echo "01" > "${CADIR}/serial"
|
||||||
|
|
||||||
$OPENSSL req \
|
"${OPENSSL}" req \
|
||||||
-config $CONFFILE \
|
-config "${CONFFILE}" \
|
||||||
-new -x509 -days 3650 \
|
-newkey rsa:4096 -sha512 \
|
||||||
-extensions v3_ca \
|
-x509 -days 3650 \
|
||||||
-keyout $PREFIX/ca/private.key \
|
-extensions v3_ca \
|
||||||
-out $PREFIX/ca/cacert.pem
|
-keyout "${CADIR}/private.key" \
|
||||||
|
-out "${CADIR}/cacert.pem"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_cn() {
|
||||||
|
cn="${1}"
|
||||||
|
if [ -f "${CADIR}/certs/${cn}.crt" ]; then
|
||||||
|
echo "Please revoke actual ${cn} cert before creating one"
|
||||||
|
echo
|
||||||
|
echo "Press return to continue..."
|
||||||
|
read -r REPLY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
umask 0137
|
||||||
echo "Please enter your CN (Common Name)"
|
echo "Please enter your CN (Common Name)"
|
||||||
read cn
|
read -r cn
|
||||||
echo
|
echo
|
||||||
echo "Your CN is '$cn'"
|
echo "Your CN is '${cn}'"
|
||||||
echo "Press return to continue..."
|
echo "Press return to continue..."
|
||||||
read REPLY
|
read -r REPLY
|
||||||
echo
|
echo
|
||||||
|
|
||||||
if [ -e $PREFIX/certs/$cn.crt ]; then
|
# check if CN already exist
|
||||||
echo "Please revoke actual $cn cert before creating one"
|
check_cn "${cn}"
|
||||||
echo
|
|
||||||
echo "Press return to continue..."
|
# generate private key
|
||||||
read REPLY
|
echo "Should private key be protected by a passphrase? [y/N] "
|
||||||
exit 1
|
read -r REPLY
|
||||||
|
if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then
|
||||||
|
"$OPENSSL" genrsa -aes256 -out "${KEYDIR}/${cn}-${TIMESTAMP}.key" 2048
|
||||||
|
else
|
||||||
|
"$OPENSSL" genrsa -out "${KEYDIR}/${cn}-${TIMESTAMP}.key" 2048
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DIR=$PREFIX/files/$cn-$TIMESTAMP
|
# generate csr req
|
||||||
mkdir $DIR
|
"$OPENSSL" req -batch \
|
||||||
|
-new \
|
||||||
|
-key "${KEYDIR}/${cn}-${TIMESTAMP}.key" \
|
||||||
|
-out "${CSRDIR}/${cn}-${TIMESTAMP}.csr" \
|
||||||
|
-config /dev/stdin <<EOF
|
||||||
|
$(cat "${CONFFILE}")
|
||||||
|
commonName_default = ${cn}
|
||||||
|
EOF
|
||||||
|
|
||||||
# generate private key
|
# ca sign and generate cert
|
||||||
echo -n "Should private key be protected by a passphrase? [y/N] "
|
"${OPENSSL}" ca \
|
||||||
read REPLY
|
-config "${CONFFILE}" \
|
||||||
if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then
|
-in "${CSRDIR}/${cn}-${TIMESTAMP}.csr" \
|
||||||
$OPENSSL genrsa -aes128 -out $DIR/$cn.key 2048
|
-out "${CADIR}/certs/${cn}.crt"
|
||||||
else
|
|
||||||
$OPENSSL genrsa -out $DIR/$cn.key 2048
|
|
||||||
fi
|
|
||||||
|
|
||||||
# generate csr req
|
# generate pem format
|
||||||
$OPENSSL req \
|
cat "${CADIR}/certs/${cn}.crt" "${CADIR}/cacert.pem" "${KEYDIR}/${cn}-${TIMESTAMP}.key" >> "${PEMDIR}/${cn}-${TIMESTAMP}.pem"
|
||||||
-new \
|
|
||||||
-key $DIR/$cn.key \
|
|
||||||
-config $CONFFILE \
|
|
||||||
-out $DIR/$cn.csr
|
|
||||||
|
|
||||||
# ca sign and generate cert
|
# generate pkcs12 format
|
||||||
$OPENSSL ca \
|
openssl pkcs12 -export -nodes -passout pass: -inkey "${KEYDIR}/${cn}-${TIMESTAMP}.key" -in "${CADIR}/certs/${cn}.crt" -out "${PKCS12DIR}/${cn}-${TIMESTAMP}.p12"
|
||||||
-config $CONFFILE \
|
|
||||||
-in $DIR/$cn.csr \
|
|
||||||
-out $DIR/$cn.crt
|
|
||||||
|
|
||||||
# pem cert style
|
|
||||||
cp $DIR/$cn.key $DIR/$cn.pem
|
|
||||||
cat $DIR/$cn.crt >> $DIR/$cn.pem
|
|
||||||
|
|
||||||
# copy to public certs dir
|
|
||||||
if [ -d "$WWWDIR" ]; then
|
|
||||||
echo
|
|
||||||
echo "copy cert to public certs dir"
|
|
||||||
echo
|
|
||||||
cp -i $DIR/$cn.crt $PREFIX/certs/
|
|
||||||
cp -i $DIR/$cn.crt $WWWDIR/
|
|
||||||
cp -i $DIR/$cn.key $WWWDIR/
|
|
||||||
chown -R root:www $WWWDIR
|
|
||||||
chmod -R u=rwX,g=rwX,o= $WWWDIR
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
|
|
||||||
# generate client configuration
|
|
||||||
|
|
||||||
if [ -e $PREFIX/template.conf ]; then
|
|
||||||
|
|
||||||
CA=$PREFIX/ca/cacert.pem
|
|
||||||
CERT=$WWWDIR/$cn.crt
|
|
||||||
KEY=$WWWDIR/$cn.key
|
|
||||||
REP=/tmp
|
|
||||||
|
|
||||||
cp $PREFIX/template.conf $REP/$cn.conf
|
|
||||||
echo "
|
|
||||||
|
|
||||||
|
# generate openvpn format
|
||||||
|
if [ -e "${PREFIX}/ovpn.conf" ]; then
|
||||||
|
cat "${PREFIX}/ovpn.conf" > "${OVPNDIR}/${cn}-${TIMESTAMP}.ovpn" <<EOF
|
||||||
<ca>
|
<ca>
|
||||||
$(cat $CA)
|
$(cat "${CADIR}/cacert.pem")
|
||||||
</ca>
|
</ca>
|
||||||
|
|
||||||
<cert>
|
<cert>
|
||||||
$(cat $CERT)
|
$(cat "${CADIR}/certs/${cn}.crt")
|
||||||
</cert>
|
</cert>
|
||||||
|
|
||||||
<key>
|
<key>
|
||||||
$(cat $KEY)
|
$(cat "${KEYDIR}/${cn}-${TIMESTAMP}.key")
|
||||||
</key>
|
</key>
|
||||||
" >> $REP/$cn.conf
|
EOF
|
||||||
|
echo "The configuration file is available in ${OVPNDIR}/${cn}.ovpn"
|
||||||
echo "The configuration file is available in $REP/$cn.conf"
|
fi
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
revoke() {
|
|
||||||
echo "Please enter CN (Common Name) to revoke"
|
|
||||||
read cn
|
|
||||||
echo
|
|
||||||
echo "CN '$cn' will be revoked"
|
|
||||||
echo "Press return to continue..."
|
|
||||||
read REPLY
|
|
||||||
echo
|
|
||||||
|
|
||||||
$OPENSSL ca \
|
|
||||||
-config $CONFFILE \
|
|
||||||
-revoke $PREFIX/certs/$cn.crt
|
|
||||||
|
|
||||||
rm -i $PREFIX/certs/$cn.crt
|
|
||||||
if [ -d "$WWWDIR" ]; then
|
|
||||||
rm -i $WWWDIR/$cn.crt
|
|
||||||
rm -i $WWWDIR/$cn.key
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fromcsr() {
|
fromcsr() {
|
||||||
echo "Please enter path for your CSR request file"
|
echo "Please enter path for your CSR request file"
|
||||||
read path
|
read -r path
|
||||||
echo
|
echo
|
||||||
|
|
||||||
if [ ! -e $path ]; then
|
if [ ! -e "${path}" ]; then
|
||||||
echo "Error in path..." >&2
|
echo "Error in path..." >&2
|
||||||
echo >&2
|
echo >&2
|
||||||
echo "Press return to continue..." >&2
|
echo "Press return to continue..." >&2
|
||||||
read REPLY
|
read -r REPLY
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Please enter the CN (Common Name)"
|
path=$(readlink -f "${path}")
|
||||||
read cn
|
|
||||||
|
# get CN from CSR
|
||||||
|
cn=$(openssl req -noout -subject -in "${path}"|grep -Eo "CN=[^/]*"|cut -d'=' -f2)
|
||||||
|
|
||||||
|
# check if CN already exist
|
||||||
|
check_cn "${cn}"
|
||||||
|
|
||||||
|
# copy CSR to CSRDIR
|
||||||
|
cp "$path" "${CSRDIR}/${cn}-${TIMESTAMP}.csr"
|
||||||
|
|
||||||
|
# ca sign and generate cert
|
||||||
|
"${OPENSSL}" ca \
|
||||||
|
-config "${CONFFILE}" \
|
||||||
|
-in "${CSRDIR}/${cn}-${TIMESTAMP}.csr" \
|
||||||
|
-out "${CADIR}/certs/${cn}.crt"
|
||||||
|
}
|
||||||
|
|
||||||
|
revoke() {
|
||||||
|
echo "Please enter CN (Common Name) to revoke"
|
||||||
|
read -r cn
|
||||||
echo
|
echo
|
||||||
echo "Your CN is '$cn'"
|
echo "CN '${cn}' will be revoked"
|
||||||
echo "Press return to continue..."
|
echo "Press return to continue..."
|
||||||
read REPLY
|
read -r REPLY
|
||||||
echo
|
echo
|
||||||
|
|
||||||
DIR=$PREFIX/files/req_$cn-$TIMESTAMP
|
[ ! -f "${CADIR}/certs/${cn}.crt" ] && echo "Unknow CN : ${cn}" >&2 && exit 1
|
||||||
mkdir $DIR
|
|
||||||
|
|
||||||
cp $path $DIR
|
echo "Revoke certificate ${CADIR}/certs/${cn}.crt :"
|
||||||
|
"$OPENSSL" ca \
|
||||||
# ca sign and generate cert
|
-config "${CONFFILE}" \
|
||||||
$OPENSSL ca \
|
-revoke "${CADIR}/certs/${cn}.crt" \
|
||||||
-config $CONFFILE \
|
&& rm "${CADIR}/certs/${cn}.crt"
|
||||||
-in $path \
|
|
||||||
-out $DIR/$cn.crt
|
|
||||||
|
|
||||||
# copy to public certs dir
|
|
||||||
echo
|
|
||||||
echo "copy cert to public certs dir"
|
|
||||||
echo
|
|
||||||
cp -i $DIR/$cn.crt $PREFIX/certs/
|
|
||||||
echo
|
|
||||||
|
|
||||||
|
echo "Update CRL :"
|
||||||
|
"$OPENSSL" ca \
|
||||||
|
-config "${CONFFILE}" \
|
||||||
|
-gencrl -out "${CADIR}/crl.pem"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list() {
|
||||||
crl() {
|
echo "* List of allowed CN :"
|
||||||
|
ls -1 "${CADIR}/certs"
|
||||||
$OPENSSL ca -gencrl \
|
|
||||||
-config $CONFFILE \
|
|
||||||
-out crl.pem
|
|
||||||
|
|
||||||
# TODO : a voir pour l'importation pdts Mozilla, Apple et Microsoft
|
|
||||||
#openssl crl2pkcs7 -in crl.pem -certfile /etc/ssl/certs/cacert.pem -out p7.pem
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "$1" in
|
main() {
|
||||||
init)
|
if [ "$(id -u)" != "0" ]; then
|
||||||
init
|
echo "Please become root before running ${0##*/}!" >&2
|
||||||
;;
|
echo >&2
|
||||||
|
echo "Press return to continue..." >&2
|
||||||
|
read -r REPLY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
create)
|
# main vars
|
||||||
create
|
PREFIX="/etc/shellpki"
|
||||||
;;
|
PKIUSER="shellpki"
|
||||||
|
CONFFILE="${PREFIX}/openssl.cnf"
|
||||||
|
CADIR=$(grep -E "^dir" "${CONFFILE}" | cut -d'=' -f2|xargs -n1)
|
||||||
|
OPENSSL=$(command -v openssl)
|
||||||
|
TIMESTAMP=$(/bin/date +"%s")
|
||||||
|
# directories for clients key, csr, crt
|
||||||
|
KEYDIR="${PREFIX}/private"
|
||||||
|
CSRDIR="${PREFIX}/requests"
|
||||||
|
PEMDIR="${PREFIX}/pem"
|
||||||
|
PKCS12DIR="${PREFIX}/pkcs12"
|
||||||
|
OVPNDIR="${PREFIX}/openvpn"
|
||||||
|
|
||||||
fromcsr)
|
if ! getent passwd "${PKIUSER}" >/dev/null || ! getent group "${PKIUSER}" >/dev/null; then
|
||||||
fromcsr
|
echo "You must create ${PKIUSER} user and group !" >&2
|
||||||
;;
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
revoke)
|
if [ ! -e "${CONFFILE}" ]; then
|
||||||
revoke
|
echo "${CONFFILE} is missing" >&2
|
||||||
;;
|
>&2
|
||||||
|
echo "Press return to continue..." >&2
|
||||||
|
read -r REPLY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
crl)
|
# create needed dir
|
||||||
crl
|
[ -d "${PREFIX}" ] || mkdir -p "${PREFIX}"
|
||||||
;;
|
[ -d "${KEYDIR}" ] || mkdir -m 0750 "${KEYDIR}"
|
||||||
|
[ -d "${CSRDIR}" ] || mkdir -m 0755 "${CSRDIR}"
|
||||||
|
[ -d "${PEMDIR}" ] || mkdir -m 0750 "${PEMDIR}"
|
||||||
|
[ -d "${PKCS12DIR}" ] || mkdir -m 0750 "${PKCS12DIR}"
|
||||||
|
[ -d "${OVPNDIR}" ] || mkdir -m 0750 "${OVPNDIR}"
|
||||||
|
|
||||||
*)
|
# fix right
|
||||||
echo "Usage: ${0##*/} {init|create|fromcsr|revoke|crl}" >&2
|
find "${PREFIX}" ! -path "${CADIR}" -exec chown "${PKIUSER}":"${PKIUSER}" {} \; -exec chmod u=rwX,g=rX,o= {} \;
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
init)
|
||||||
|
init
|
||||||
|
;;
|
||||||
|
|
||||||
|
create)
|
||||||
|
create
|
||||||
|
;;
|
||||||
|
|
||||||
|
fromcsr)
|
||||||
|
fromcsr
|
||||||
|
;;
|
||||||
|
|
||||||
|
revoke)
|
||||||
|
revoke
|
||||||
|
;;
|
||||||
|
|
||||||
|
list)
|
||||||
|
list
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Usage: ${0} {init|create|fromcsr|revoke|list}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|
Loading…
Reference in a new issue