1
0
mirror of https://github.com/laurivosandi/certidude synced 2024-12-22 08:15:18 +00:00

Several improvements

* Add EC support
* Make token form toggleable
* Make client certificates compatible with iOS native IKEv2
* Fix OU for self-enroll
* Improved sample scripts in web UI
This commit is contained in:
Lauri Võsandi 2018-04-09 13:08:12 +00:00
parent 9c6872a949
commit 577962e09b
11 changed files with 253 additions and 184 deletions

View File

@ -10,7 +10,7 @@ from xattr import listxattr, getxattr
from certidude.auth import login_required from certidude.auth import login_required
from certidude.user import User from certidude.user import User
from certidude.decorators import serialize, csrf_protection from certidude.decorators import serialize, csrf_protection
from certidude import const, config from certidude import const, config, authority
from .utils import AuthorityHandler from .utils import AuthorityHandler
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -140,8 +140,11 @@ class SessionResource(AuthorityHandler):
offline = 600, # Seconds from last seen activity to consider lease offline, OpenVPN reneg-sec option offline = 600, # Seconds from last seen activity to consider lease offline, OpenVPN reneg-sec option
dead = 604800 # Seconds from last activity to consider lease dead, X509 chain broken or machine discarded dead = 604800 # Seconds from last activity to consider lease dead, X509 chain broken or machine discarded
), ),
common_name = const.FQDN, certificate = dict(
title = self.authority.certificate.subject.native["common_name"], algorithm = authority.public_key.algorithm,
common_name = self.authority.certificate.subject.native["common_name"],
blob = self.authority.certificate_buf.decode("ascii"),
),
mailer = dict( mailer = dict(
name = config.MAILER_NAME, name = config.MAILER_NAME,
address = config.MAILER_ADDRESS address = config.MAILER_ADDRESS
@ -164,6 +167,7 @@ class SessionResource(AuthorityHandler):
) )
) if req.context.get("user").is_admin() else None, ) if req.context.get("user").is_admin() else None,
features=dict( features=dict(
token=bool(config.TOKEN_URL),
tagging=True, tagging=True,
leases=True, leases=True,
logging=config.LOGGING_BACKEND)) logging=config.LOGGING_BACKEND))

View File

@ -41,7 +41,8 @@ class TokenResource(AuthorityHandler):
common_name = csr["certification_request_info"]["subject"].native["common_name"] common_name = csr["certification_request_info"]["subject"].native["common_name"]
assert common_name == username or common_name.startswith(username + "@"), "Invalid common name %s" % common_name assert common_name == username or common_name.startswith(username + "@"), "Invalid common name %s" % common_name
try: try:
_, resp.body = self.authority._sign(csr, body, profile="default") _, resp.body = self.authority._sign(csr, body, profile="default",
overwrite=config.TOKEN_OVERWRITE_PERMITTED)
resp.set_header("Content-Type", "application/x-pem-file") resp.set_header("Content-Type", "application/x-pem-file")
logger.info("Autosigned %s as proven by token ownership", common_name) logger.info("Autosigned %s as proven by token ownership", common_name)
except FileExistsError: except FileExistsError:

View File

@ -47,11 +47,16 @@ def self_enroll():
try: try:
path, buf, cert, signed, expires = get_signed(common_name) path, buf, cert, signed, expires = get_signed(common_name)
public_key = asymmetric.load_public_key(path) self_public_key = asymmetric.load_public_key(path)
private_key = asymmetric.load_private_key(self_key_path) private_key = asymmetric.load_private_key(self_key_path)
except FileNotFoundError: # certificate or private key not found except FileNotFoundError: # certificate or private key not found
with open(self_key_path, 'wb') as fh: with open(self_key_path, 'wb') as fh:
public_key, private_key = asymmetric.generate_pair('rsa', bit_size=2048) if public_key.algorithm == "ec":
self_public_key, private_key = asymmetric.generate_pair("ec", curve=public_key.curve)
elif public_key.algorithm == "rsa":
self_public_key, private_key = asymmetric.generate_pair("rsa", bit_size=public_key.bit_size)
else:
NotImplemented
fh.write(asymmetric.dump_private_key(private_key, None)) fh.write(asymmetric.dump_private_key(private_key, None))
else: else:
now = datetime.utcnow() now = datetime.utcnow()
@ -59,7 +64,7 @@ def self_enroll():
click.echo("Certificate %s still valid, delete to self-enroll again" % path) click.echo("Certificate %s still valid, delete to self-enroll again" % path)
return return
builder = CSRBuilder({"common_name": common_name}, public_key) builder = CSRBuilder({"common_name": common_name}, self_public_key)
request = builder.build(private_key) request = builder.build(private_key)
with open(os.path.join(directory, "requests", common_name + ".pem"), "wb") as fh: with open(os.path.join(directory, "requests", common_name + ".pem"), "wb") as fh:
fh.write(pem_armor_csr(request)) fh.write(pem_armor_csr(request))
@ -389,6 +394,7 @@ def _sign(csr, buf, skip_notify=False, skip_push=False, overwrite=False, profile
builder.subject_alt_domains = [common_name] # OpenVPN uses CN while StrongSwan uses SAN to match hostname of the server builder.subject_alt_domains = [common_name] # OpenVPN uses CN while StrongSwan uses SAN to match hostname of the server
builder.extended_key_usage = set(["server_auth", "1.3.6.1.5.5.8.2.2", "client_auth"]) builder.extended_key_usage = set(["server_auth", "1.3.6.1.5.5.8.2.2", "client_auth"])
else: else:
builder.subject_alt_domains = [common_name] # iOS demands SAN also for clients
builder.extended_key_usage = set(["client_auth"]) builder.extended_key_usage = set(["client_auth"])
end_entity_cert = builder.build(private_key) end_entity_cert = builder.build(private_key)

View File

@ -287,7 +287,17 @@ def certidude_enroll(fork, renew, no_wait, kerberos, skip_self):
if not os.path.exists(request_path): if not os.path.exists(request_path):
key_partial = key_path + ".part" key_partial = key_path + ".part"
request_partial = request_path + ".part" request_partial = request_path + ".part"
public_key, private_key = asymmetric.generate_pair('rsa', bit_size=2048)
certificate = x509.Certificate.load(certificate_der_bytes)
public_key = asymmetric.load_public_key(certificate["tbs_certificate"]["subject_public_key_info"])
if public_key.algorithm == "ec":
self_public_key, private_key = asymmetric.generate_pair("ec", curve=public_key.curve)
elif public_key.algorithm == "rsa":
self_public_key, private_key = asymmetric.generate_pair("rsa", bit_size=public_key.bit_size)
else:
NotImplemented
builder = CSRBuilder({"common_name": common_name}, public_key) builder = CSRBuilder({"common_name": common_name}, public_key)
request = builder.build(private_key) request = builder.build(private_key)
with open(key_partial, 'wb') as f: with open(key_partial, 'wb') as f:
@ -945,8 +955,9 @@ def certidude_setup_openvpn_networkmanager(authority, remote, common_name, **pat
@click.option("--server-flags", is_flag=True, help="Add TLS Server and IKE Intermediate extended key usage flags") @click.option("--server-flags", is_flag=True, help="Add TLS Server and IKE Intermediate extended key usage flags")
@click.option("--outbox", default="smtp://smtp.%s" % const.DOMAIN, help="SMTP server, smtp://smtp.%s by default" % const.DOMAIN) @click.option("--outbox", default="smtp://smtp.%s" % const.DOMAIN, help="SMTP server, smtp://smtp.%s by default" % const.DOMAIN)
@click.option("--skip-packages", is_flag=True, help="Don't attempt to install apt/pip/npm packages") @click.option("--skip-packages", is_flag=True, help="Don't attempt to install apt/pip/npm packages")
@click.option("--elliptic-curve", "-e", is_flag=True, help="Generate EC instead of RSA keypair")
@fqdn_required @fqdn_required
def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, state, locality, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, server_flags, title, skip_packages): def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, state, locality, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, server_flags, title, skip_packages, elliptic_curve):
assert os.getuid() == 0 and os.getgid() == 0, "Authority can be set up only by root" assert os.getuid() == 0 and os.getgid() == 0, "Authority can be set up only by root"
import pwd import pwd
@ -1160,9 +1171,12 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country,
# Generate and sign CA key # Generate and sign CA key
if not os.path.exists(ca_key): if not os.path.exists(ca_key):
click.echo("Generating %d-bit RSA key for CA ..." % const.KEY_SIZE) if elliptic_curve:
click.echo("Generating %s EC key for CA ..." % const.CURVE_NAME)
public_key, private_key = asymmetric.generate_pair('rsa', bit_size=const.KEY_SIZE) public_key, private_key = asymmetric.generate_pair("ec", curve=const.CURVE_NAME)
else:
click.echo("Generating %d-bit RSA key for CA ..." % const.KEY_SIZE)
public_key, private_key = asymmetric.generate_pair("rsa", bit_size=const.KEY_SIZE)
names = ( names = (
("country_name", country), ("country_name", country),

View File

@ -99,3 +99,6 @@ PROFILES = OrderedDict([[i, [j.strip() for j in cp.get("profile", i).split(",")]
cp2 = configparser.RawConfigParser() cp2 = configparser.RawConfigParser()
cp2.readfp(open(const.BUILDER_CONFIG_PATH, "r")) cp2.readfp(open(const.BUILDER_CONFIG_PATH, "r"))
IMAGE_BUILDER_PROFILES = [(j, cp2.get(j, "title"), cp2.get(j, "rename")) for j in cp2.sections()] IMAGE_BUILDER_PROFILES = [(j, cp2.get(j, "title"), cp2.get(j, "rename")) for j in cp2.sections()]
TOKEN_OVERWRITE_PERMITTED=True

View File

@ -5,6 +5,8 @@ import socket
import sys import sys
KEY_SIZE = 1024 if os.getenv("TRAVIS") else 4096 KEY_SIZE = 1024 if os.getenv("TRAVIS") else 4096
CURVE_NAME = "secp384r1"
RUN_DIR = "/run/certidude" RUN_DIR = "/run/certidude"
CONFIG_DIR = "/etc/certidude" CONFIG_DIR = "/etc/certidude"
SERVER_CONFIG_PATH = os.path.join(CONFIG_DIR, "server.conf") SERVER_CONFIG_PATH = os.path.join(CONFIG_DIR, "server.conf")

View File

@ -13,7 +13,7 @@
<div class="highlight"> <div class="highlight">
<pre><code>easy_install pip; <pre><code>easy_install pip;
pip3 install certidude; pip3 install certidude;
certidude bootstrap {{session.authority.common_name}} certidude bootstrap {{ window.location.hostname }}
</code></pre> </code></pre>
</div> </div>
@ -30,14 +30,16 @@ Signature="$Windows NT$
[NewRequest] [NewRequest]
Subject = "CN=$hostname" Subject = "CN=$hostname"
Exportable = FALSE Exportable = FALSE
KeyLength = 2048
KeySpec = 1 KeySpec = 1
KeyUsage = 0xA0 KeyUsage = 0xA0
MachineKeySet = True MachineKeySet = True
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12 ProviderType = 12
RequestType = PKCS10 RequestType = PKCS10
"@ {% if session.authority.certificate.algorithm == "ec" %}ProviderName = "Microsoft Software Key Storage Provider"
KeyAlgorithm = ECDSA_P384
{% else %}ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
KeyLength = 2048
{% endif %}"@
$templ | Out-File req.inf $templ | Out-File req.inf
@ -54,8 +56,23 @@ Import-Certificate -FilePath client_cert.pem -CertStoreLocation Cert:\LocalMachi
# Set up IPSec VPN tunnel # Set up IPSec VPN tunnel
Remove-VpnConnection -AllUserConnection -Force k-space Remove-VpnConnection -AllUserConnection -Force k-space
Add-VpnConnection -Name k-space -ServerAddress guests.k-space.ee -SplitTunneling -PassThru -TunnelType ikev2 -AllUserConnection -AuthenticationMethod MachineCertificate Add-VpnConnection `
Set-VpnConnectionIPsecConfiguration -ConnectionName k-space -AuthenticationTransformConstants SHA256128 -CipherTransformConstants AES256 -EncryptionMethod AES256 -IntegrityCheckMethod SHA384 -PfsGroup PFS24 -DHGroup Group24 -PassThru -AllUserConnection -Force</code></pre> -Name k-space `
-ServerAddress guests.k-space.ee `
-AuthenticationMethod MachineCertificate `
-SplitTunneling `
-TunnelType ikev2 `
-PassThru -AllUserConnection
# Security hardening
Set-VpnConnectionIPsecConfiguration `
-ConnectionName k-space `
-AuthenticationTransformConstants GCMAES128 `
-CipherTransformConstants GCMAES128 `
-EncryptionMethod AES128 `
-IntegrityCheckMethod none `
-PfsGroup {% if session.authority.certificate.algorithm == "ec" %}ECP384 -DHGroup ECP384{% else %}PFS2048 -DHGroup Group14{% endif %} `
-PassThru -AllUserConnection -Force</code></pre>
</div> </div>
<h5>UNIX & UNIX-like</h5> <h5>UNIX & UNIX-like</h5>
@ -63,58 +80,110 @@ Set-VpnConnectionIPsecConfiguration -ConnectionName k-space -AuthenticationTrans
<p>On other UNIX-like machines generate key pair and submit the signing request using OpenSSL and cURL:</p> <p>On other UNIX-like machines generate key pair and submit the signing request using OpenSSL and cURL:</p>
<div class="highlight"> <div class="highlight">
<pre class="code"><code>NAME=$(hostname); <pre class="code"><code>NAME=$(hostname);
openssl genrsa -out client_key.pem 2048; {% if session.authority.certificate.algorithm == "ec" %}openssl ecparam -name secp384r1 -genkey -noout -out client_key.pem{% else %}openssl genrsa -out client_key.pem 2048;{% endif %}
openssl req -new -sha256 -key client_key.pem -out client_req.pem -subj "/CN=$NAME"; openssl req -new -sha384 -key client_key.pem -out client_req.pem -subj "/CN=$NAME";
curl -f -L -H "Content-type: application/pkcs10" --data-binary @client_req.pem \ curl -f -L -H "Content-type: application/pkcs10" --data-binary @client_req.pem \
http://{{ window.location.hostname }}/api/request/?wait=yes > client_cert.pem</code></pre> http://{{ window.location.hostname }}/api/request/?wait=yes > client_cert.pem</code></pre>
</div> </div>
<h5>OpenVPN gateway on OpenWrt/LEDE router</h5> <h5>StrongSwan as client</h5>
<p>First enroll certificates:</p>
<div class="highlight">
<pre class="code"><code>
FQDN=$(cat /etc/hostname)
curl -f http://{{ window.location.hostname }}/api/certificate/ -o /etc/ipsec.d/cacerts/ca.pem; \
test -e /etc/ipsec.d/private/client.pem \
|| openssl ecparam -name secp384r1 -genkey -noout -out /etc/ipsec.d/private/client.pem; \
test -e /etc/ipsec.d/reqs/client.pem \
|| openssl req -new -sha384 \
-key /etc/ipsec.d/private/client.pem \
-out /etc/ipsec.d/reqs/client.pem -subj "/CN=$FQDN"; \
curl -f -L -H "Content-type: application/pkcs10" \
--data-binary @/etc/ipsec.d/reqs/client.pem \
-o /etc/ipsec.d/certs/client.pem \
http://{{ window.location.hostname }}/api/request/?wait=yes</code></pre>
</div>
<p>Then configure StrongSwan</p>
<div class="highlight">
<pre class="code"><code>
cat > /etc/ipsec.conf << EOF
conn c2s
auto=start
right=router.k-space.ee
dpdaction=restart
closeaction=restart
left=%defaultroute
rightsubnet=0.0.0.0/0
keyingtries=%forever
rightid=%any
leftsourceip=%config
leftcert=client.pem
ike=aes128-sha384-{% if session.authority.certificate.algorithm == "ec" %}ecp384{% else %}modp2048{% endif %}!
esp=aes128gcm16!
EOF
echo ": {% if session.authority.certificate.algorithm == "ec" %}ECDSA{% else %}RSA{% endif %} client.pem" > /etc/ipsec.secrets
ipsec restart</code></pre>
</div>
<h5>OpenWrt/LEDE as VPN gateway</h5>
<p>First enroll certificates:</p> <p>First enroll certificates:</p>
<div class="highlight"> <div class="highlight">
<pre class="code"><code># Derive FQDN from WAN interface's reverse DNS record <pre class="code"><code># Derive FQDN from WAN interface's reverse DNS record
FQDN=$(nslookup $(uci get network.wan.ipaddr) | grep "name =" | head -n1 | cut -d "=" -f 2 | xargs) FQDN=$(nslookup $(uci get network.wan.ipaddr) | grep "name =" | head -n1 | cut -d "=" -f 2 | xargs)
mkdir -p /etc/certidude/authority/{{ window.location.hostname }}; \ mkdir -p /etc/certidude/authority/{{ window.location.hostname }}
grep -c certidude /etc/sysupgrade.conf || echo /etc/certidude >> /etc/sysupgrade.conf; \ grep -c certidude /etc/sysupgrade.conf || echo /etc/certidude >> /etc/sysupgrade.conf
curl -f http://{{ window.location.hostname }}/api/certificate/ -o /etc/certidude/authority/{{ window.location.hostname }}/ca_cert.pem; \ cat << EOF > /etc/certidude/authority/{{ window.location.hostname }}/ca_cert.pem
{{ session.authority.certificate.blob }}
EOF
test -e /etc/certidude/authority/{{ window.location.hostname }}/server_key.pem \ test -e /etc/certidude/authority/{{ window.location.hostname }}/server_key.pem \
|| openssl genrsa -out /etc/certidude/authority/{{ window.location.hostname }}/server_key.pem 2048; \ || openssl {% if session.authority.certificate.algorithm == "ec" %}ecparam -name secp384r1 -genkey -noout -out /etc/certidude/authority/{{ window.location.hostname }}/server_key.pem{% else %}genrsa -out /etc/certidude/authority/{{ window.location.hostname }}/server_key.pem 2048{% endif %}
test -e /etc/certidude/authority/{{ window.location.hostname }}/server_req.pem \ test -e /etc/certidude/authority/{{ window.location.hostname }}/server_req.pem \
|| openssl req -new -sha256 \ || openssl req -new -sha384 \
-key /etc/certidude/authority/{{ window.location.hostname }}/server_key.pem \ -key /etc/certidude/authority/{{ window.location.hostname }}/server_key.pem \
-out /etc/certidude/authority/{{ window.location.hostname }}/server_req.pem -subj "/CN=$FQDN"; \ -out /etc/certidude/authority/{{ window.location.hostname }}/server_req.pem -subj "/CN=$FQDN"
cat /etc/certidude/authority/{{ window.location.hostname }}/server_req.pem
curl -f -L -H "Content-type: application/pkcs10" \ curl -f -L -H "Content-type: application/pkcs10" \
--data-binary @/etc/certidude/authority/{{ window.location.hostname }}/server_req.pem \ --data-binary @/etc/certidude/authority/{{ window.location.hostname }}/server_req.pem \
-o /etc/certidude/authority/{{ window.location.hostname }}/server_cert.pem \ -o /etc/certidude/authority/{{ window.location.hostname }}/server_cert.pem \
http://{{ window.location.hostname }}/api/request/?wait=yes</code></pre> http://{{ window.location.hostname }}/api/request/?wait=yes
</div>
<p>Then set up service:</p> # Create VPN gateway up/down script for reporting client IP addresses to CA
<div class="highlight"> cat <<\EOF > /etc/certidude/authority/{{ window.location.hostname }}/updown
<pre class="code"><code># Create VPN gateway up/down script for reporting client IP addresses to CA
cat <<\EOF > /etc/certidude/updown
#!/bin/sh #!/bin/sh
CURL="curl -f --key /etc/certidude/authority/{{ window.location.hostname }}/server_key.pem --cert /etc/certidude/authority/{{ window.location.hostname }}/server_cert.pem --cacert /etc/certidude/authority/{{ window.location.hostname }}/ca_cert.pem https://{{ window.location.hostname }}:8443/api/lease/"
CURL="curl -m 3 -f --key /etc/certidude/authority/{{ window.location.hostname }}/server_key.pem --cert /etc/certidude/authority/{{ window.location.hostname }}/server_cert.pem --cacert /etc/certidude/authority/{{ window.location.hostname }}/ca_cert.pem https://{{ window.location.hostname }}:8443/api/lease/"
case $PLUTO_VERB in case $PLUTO_VERB in
up-client|down-client) $CURL --data "outer_address=$PLUTO_PEER&inner_address=$PLUTO_PEER_SOURCEIP&client=$PLUTO_PEER_ID" ;; up-client) $CURL --data-urlencode "outer_address=$PLUTO_PEER" --data-urlencode "inner_address=$PLUTO_PEER_SOURCEIP" --data-urlencode "client=$PLUTO_PEER_ID" ;;
*) $CURL --data "client=$X509_0_CN&outer_address=$untrusted_ip&inner_address=$ifconfig_pool_remote_ip&serial=$tls_serial_0" ;; *) ;;
esac
case $script_type in
client-connect) $CURL --data-urlencode client=$X509_0_CN --data-urlencode serial=$tls_serial_0 --data-urlencode outer_address=$untrusted_ip --data-urlencode inner_address=$ifconfig_pool_remote_ip ;;
*) ;;
esac esac
EOF EOF
chmod +x /etc/certidude/updown chmod +x /etc/certidude/authority/{{ window.location.hostname }}/updown</code></pre>
</div>
<p>Then either set up OpenVPN service:</p>
# Generate Diffie-Hellman parameters file for OpenVPN <div class="highlight">
<pre class="code"><code># Generate Diffie-Hellman parameters file for OpenVPN
test -e /etc/certidude/dh.pem \ test -e /etc/certidude/dh.pem \
|| openssl dhparam 2048 -out /etc/certidude/dh.pem || openssl dhparam 2048 -out /etc/certidude/dh.pem
# Create interface definition for tunnel # Create interface definition for tunnel
uci set network.vpn=interface uci set network.vpn=interface
uci set network.vpn.name='vpn' uci set network.vpn.name='vpn'
uci set network.vpn.ifname=tun_s2c uci set network.vpn.ifname=tun_s2c_udp tun_s2c_tcp
uci set network.vpn.proto='none' uci set network.vpn.proto='none'
# Create zone definition for VPN interface # Create zone definition for VPN interface
@ -133,6 +202,14 @@ uci set firewall.openvpn.dest_port=1194
uci set firewall.openvpn.proto='udp' uci set firewall.openvpn.proto='udp'
uci set firewall.openvpn.target='ACCEPT' uci set firewall.openvpn.target='ACCEPT'
# Allow TCP 443 on WAN interface
uci set firewall.openvpn=rule
uci set firewall.openvpn.name='Allow OpenVPN over TCP'
uci set firewall.openvpn.src='wan'
uci set firewall.openvpn.dest_port=443
uci set firewall.openvpn.proto='tcp'
uci set firewall.openvpn.target='ACCEPT'
# Forward traffic from VPN to LAN # Forward traffic from VPN to LAN
uci set firewall.c2s=forwarding uci set firewall.c2s=forwarding
uci set firewall.c2s.src='vpn' uci set firewall.c2s.src='vpn'
@ -142,31 +219,83 @@ uci set firewall.c2s.dest='lan'
uci set dhcp.@dnsmasq[0].localservice='0' uci set dhcp.@dnsmasq[0].localservice='0'
touch /etc/config/openvpn touch /etc/config/openvpn
uci set openvpn.s2c=openvpn
uci set openvpn.s2c.local=$(uci get network.wan.ipaddr) # Configure OpenVPN over TCP
uci set openvpn.s2c.script_security=2 uci set openvpn.s2c_tcp=openvpn
uci set openvpn.s2c.client_connect='/etc/certidude/updown' uci set openvpn.s2c_tcp.local=$(uci get network.wan.ipaddr)
uci set openvpn.s2c.tls_version_min='1.2' uci set openvpn.s2c_tcp.server='10.179.43.0 255.255.255.128'
uci set openvpn.s2c.tls_cipher='TLS-DHE-RSA-WITH-AES-256-GCM-SHA384' uci set openvpn.s2c_tcp.proto='tcp-server'
uci set openvpn.s2c.cipher='AES-256-CBC' uci set openvpn.s2c_tcp.port='443'
uci set openvpn.s2c.auth='SHA384' uci set openvpn.s2c_tcp.dev=tun_s2c_tcp
uci set openvpn.s2c.dev=tun_s2c
uci set openvpn.s2c.server='10.179.43.0 255.255.255.0' # Configure OpenVPN over UDP
uci set openvpn.s2c.key='/etc/certidude/authority/{{ window.location.hostname }}/server_key.pem' uci set openvpn.s2c_udp=openvpn
uci set openvpn.s2c.cert='/etc/certidude/authority/{{ window.location.hostname }}/server_cert.pem' uci set openvpn.s2c_udp.local=$(uci get network.wan.ipaddr)
uci set openvpn.s2c.ca='/etc/certidude/authority/{{ window.location.hostname }}/ca_cert.pem' uci set openvpn.s2c_udp.server='10.179.43.128 255.255.255.128'
uci set openvpn.s2c.dh='/etc/certidude/dh.pem' uci set openvpn.s2c_tcp.dev=tun_s2c_udp
uci set openvpn.s2c.enabled=1
uci set openvpn.s2c.comp_lzo=yes for section in s2c_tcp s2c_udp; do
uci add_list openvpn.s2c.push="route-metric 1000"
uci add_list openvpn.s2c.push="route $(uci get network.lan.ipaddr) $(uci get network.lan.netmask)" # Common paths
uci add_list openvpn.s2c.push="dhcp-option DNS $(uci get network.lan.ipaddr)" uci set openvpn.$section.script_security=2
uci add_list openvpn.s2c.push="dhcp-option DOMAIN $(uci get dhcp.@dnsmasq[0].domain)" uci set openvpn.$section.client_connect='/etc/certidude/updown'
uci set openvpn.$section.key='/etc/certidude/authority/{{ window.location.hostname }}/server_key.pem'
uci set openvpn.$section.cert='/etc/certidude/authority/{{ window.location.hostname }}/server_cert.pem'
uci set openvpn.$section.ca='/etc/certidude/authority/{{ window.location.hostname }}/ca_cert.pem'
uci set openvpn.$section.dh='/etc/certidude/dh.pem'
uci set openvpn.$section.enabled=1
# DNS and routes
uci add_list openvpn.$section.push="route-metric 1000"
uci add_list openvpn.$section.push="route $(uci get network.lan.ipaddr) $(uci get network.lan.netmask)"
uci add_list openvpn.$section.push="dhcp-option DNS $(uci get network.lan.ipaddr)"
uci add_list openvpn.$section.push="dhcp-option DOMAIN $(uci get dhcp.@dnsmasq[0].domain)"
# Security hardening
uci set openvpn.$section.tls_version_min='1.2'
uci set openvpn.$section.tls_cipher='TLS-{% if session.authority.certificate.algorithm == "ec" %}ECDHE-ECDSA{% else %}DHE-RSA{% endif %}-WITH-AES-128-GCM-SHA384'
uci set openvpn.$section.cipher='AES-128-GCM'
uci set openvpn.$section.auth='SHA384'
done
/etc/init.d/openvpn restart /etc/init.d/openvpn restart
/etc/init.d/firewall restart</code></pre> /etc/init.d/firewall restart</code></pre>
</div> </div>
<p>Alternatively or additionally set up StrongSwan:</p>
<div class="highlight">
<pre class="code"><code># Generate StrongSwan config
cat > /etc/ipsec.conf << EOF
config setup
strictcrlpolicy=yes
uniqueids = yes
ca {{ window.location.hostname }}
auto=add
cacert = /etc/certidude/authority/{{ window.location.hostname }}/ca_cert.pem
crluri = http://{{ window.location.hostname }}/api/revoked
ocspuri = http://{{ window.location.hostname }}/api/ocsp/
conn s2c
auto=add
dpdaction=clear
closeaction=clear
leftdns=$(uci get network.lan.ipaddr)
rightsourceip=172.21.0.0/24
left=$(uci get network.wan.ipaddr)
leftsubnet=$(uci get network.lan.ipaddr | cut -d . -f 1-3).0/24
leftcert=/etc/certidude/authority/{{ window.location.hostname }}/server_cert.pem
ike=aes128-sha384-{% if session.authority.certificate.algorithm == "ec" %}ecp384{% else %}modp2048{% endif %}!
esp=aes128gcm16!
leftupdown=/etc/certidude/{{ window.location.hostname }}/updown
EOF
echo ": {% if session.authority.certificate.algorithm == "ec" %}ECDSA{% else %}RSA{% endif %} /etc/certidude/authority/{{ window.location.hostname }}/server_key.pem" > /etc/ipsec.secrets
ipsec restart</code></pre>
</div>
{% if session.authority.builder %} {% if session.authority.builder %}
<h5>OpenWrt/LEDE image builder</h5> <h5>OpenWrt/LEDE image builder</h5>
@ -337,28 +466,30 @@ forbidden
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<h1>Tokens</h1>
<p>Tokens allow enrolling smartphones and third party devices.</p>
<ul>
<li>You can issue yourself a token to be used on a mobile device</li>
<li>Enter username to issue a token to issue a token for another user</li>
<li>Enter e-mail address to issue a token to guest users outside domain</li>
</ul>
<p>
<div class="input-group">
<input id="token_username" name="username" type="text" class="form-control" placeholder="Username" aria-describedby="sizing-addon2">
<input id="token_mail" name="mail" type="mail" class="form-control" placeholder="Optional e-mail" aria-describedby="sizing-addon2">
<span class="input-group-btn">
<button class="btn btn-secondary" type="button" onClick="onSendToken();"><i class="fa fa-send"></i> Send token</button>
</span>
</div>
</p>
<div id="token_qrcode"></div>
{% if session.authority %} {% if session.authority %}
{% if session.features.token %}
<h1>Tokens</h1>
<p>Tokens allow enrolling smartphones and third party devices.</p>
<ul>
<li>You can issue yourself a token to be used on a mobile device</li>
<li>Enter username to issue a token to issue a token for another user</li>
<li>Enter e-mail address to issue a token to guest users outside domain</li>
</ul>
<p>
<div class="input-group">
<input id="token_username" name="username" type="text" class="form-control" placeholder="Username" aria-describedby="sizing-addon2">
<input id="token_mail" name="mail" type="mail" class="form-control" placeholder="Optional e-mail" aria-describedby="sizing-addon2">
<span class="input-group-btn">
<button class="btn btn-secondary" type="button" onClick="onSendToken();"><i class="fa fa-send"></i> Send token</button>
</span>
</div>
</p>
<div id="token_qrcode"></div>
{% endif %}
<h1>Pending requests</h1> <h1>Pending requests</h1>
<p>Use Certidude client to apply for a certificate. <p>Use Certidude client to apply for a certificate.

View File

@ -39,7 +39,7 @@ class PosixUserManager(object):
_, _, _, _, gecos, _, _ = pwd.getpwnam(username) _, _, _, _, gecos, _, _ = pwd.getpwnam(username)
gecos = gecos.split(",") gecos = gecos.split(",")
full_name = gecos[0] full_name = gecos[0]
mail = "%s@%s" % (username, const.DOMAIN) mail = "%s@%s" % ("tteearu", "k-space.ee") # username, "k-space.ee") # const.DOMAIN)
if full_name and " " in full_name: if full_name and " " in full_name:
given_name, surname = full_name.split(" ", 1) given_name, surname = full_name.split(" ", 1)
return User(username, mail, given_name, surname) return User(username, mail, given_name, surname)

37
doc/overlay/etc/hotplug.d/iface/50-certidude Normal file → Executable file
View File

@ -5,12 +5,13 @@
# TODO: renewal # TODO: renewal
AUTHORITY=certidude.@authority[0]
[ $ACTION == "ifup" ] || exit 0 [ $ACTION == "ifup" ] || exit 0
[ $INTERFACE == "wan" ] || exit 0 [ $INTERFACE == "$(uci get $AUTHORITY.trigger)" ] || exit 0
# TODO: iterate over all authorities # TODO: iterate over all authorities
AUTHORITY=certidude.@authority[0]
URL=$(uci get $AUTHORITY.url) URL=$(uci get $AUTHORITY.url)
GATEWAY=$(uci get $AUTHORITY.gateway) GATEWAY=$(uci get $AUTHORITY.gateway)
@ -40,8 +41,9 @@ logger -t certidude -s "Time is now: $(date)"
# If certificate file is there assume everything's set up # If certificate file is there assume everything's set up
if [ -f $CERTIFICATE_PATH ]; then if [ -f $CERTIFICATE_PATH ]; then
SERIAL=$(openssl x509 -in $CERTIFICATE_PATH -noout -serial | cut -d "=" -f 2 | tr [A-F] [a-f]) SERIAL=$(openssl x509 -in $CERTIFICATE_PATH -noout -serial | cut -d "=" -f 2 | tr [A-F] [a-f])
logger -t certidude -s "Certificate with serial $SERIAL already exists, attempting to bring up IPsec tunnel..." logger -t certidude -s "Certificate with serial $SERIAL already exists in $CERTIFICATE_PATH, attempting to bring up VPN tunnel..."
ipsec up client-to-site /etc/init.d/openvpn start
/etc/init.d/ipsec start
exit 0 exit 0
fi fi
@ -60,7 +62,7 @@ fi
if [ ! -f $KEY_PATH ]; then if [ ! -f $KEY_PATH ]; then
KEY_TEMP=$(mktemp -u) KEY_TEMP=$(mktemp -u)
logger -t certidude -s "Generating RSA key for IPsec..." logger -t certidude -s "Generating RSA key for VPN..."
if [ -d $GREEN_LED ]; then if [ -d $GREEN_LED ]; then
echo 250 | tee $GREEN_LED/delay_* echo 250 | tee $GREEN_LED/delay_*
fi fi
@ -167,31 +169,10 @@ fi
logger -t certidude -s "Certificate md5sum: $(md5sum -b $CERTIFICATE_TEMP)" logger -t certidude -s "Certificate md5sum: $(md5sum -b $CERTIFICATE_TEMP)"
###################################
### Generate /etc/ipsec.secrets ###
###################################
SECRETS_TEMP=$(mktemp -u)
for filename in /etc/ipsec.d/private/*.pem; do
echo ": RSA $filename" >> $SECRETS_TEMP
done
uci commit uci commit
mv $SECRETS_TEMP /etc/ipsec.secrets
mv $IPSEC_TEMP /etc/ipsec.conf
mv $CERTIFICATE_TEMP $CERTIFICATE_PATH mv $CERTIFICATE_TEMP $CERTIFICATE_PATH
# Enable services
/etc/init.d/ipsec enable
# Restart services # Restart services
/etc/init.d/ipsec restart /etc/init.d/ipsec start
/etc/init.d/openvpn start
sleep 2
ipsec up client-to-site

View File

@ -14,18 +14,11 @@ export PS1='\u@\h:\w\$ '
[ -x /usr/bin/ldd ] || ldd() { LD_TRACE_LOADED_OBJECTS=1 $*; } [ -x /usr/bin/ldd ] || ldd() { LD_TRACE_LOADED_OBJECTS=1 $*; }
HOSTNAME=$(uci get system.@system[0].hostname) HOSTNAME=$(uci get system.@system[0].hostname)
DOMAIN=$(uci -q get dhcp.@dnsmasq[0].domain)
if [ $? -eq 0 ]; then export PS1='\[\033[01;31m\]$HOSTNAME\[\033[01;34m\] \W #\[\033[00m\] '
FQDN=$HOSTNAME.$DOMAIN
else
FQDN=$HOSTNAME
fi
export PS1='\[\033[01;31m\]$FQDN\[\033[01;34m\] \W #\[\033[00m\] '
case "$TERM" in case "$TERM" in
xterm*|rxvt*) xterm*|rxvt*)
echo -ne "\033]0;${USER}@${FQDN}:${PWD}\007" echo -ne "\033]0;${USER}@${HOSTNAME}:${PWD}\007"
;; ;;
*) *)
;; ;;

View File

@ -1,66 +0,0 @@
# Disable DHCP servers
/etc/init.d/odhcpd disable
/etc/init.d/dnsmasq disable
# Remove firewall rules since AP bridges ethernet to wireless anyway
uci delete firewall.@zone[1]
uci delete firewall.@zone[0]
uci delete firewall.@forwarding[0]
for j in $(seq 0 10); do uci delete firewall.@rule[0]; done
# Remove WAN interface
uci delete network.wan
uci delete network.wan6
# Reconfigure DHCP client for bridge over LAN and WAN ports
uci delete network.lan.ipaddr
uci delete network.lan.netmask
uci delete network.lan.ip6assign
uci delete network.globals.ula_prefix
uci delete network.@switch_vlan[1]
uci delete dhcp.@dnsmasq[0].domain
uci set network.lan.proto=dhcp
uci set network.lan.ipv6=0
uci set network.lan.ifname='eth0'
uci set network.lan.stp=1
# Radio ordering differs among models
case $(uci get wireless.radio0.hwmode) in
11a) uci rename wireless.radio0=radio5ghz;;
11g) uci rename wireless.radio0=radio2ghz;;
esac
case $(uci get wireless.radio1.hwmode) in
11a) uci rename wireless.radio1=radio5ghz;;
11g) uci rename wireless.radio1=radio2ghz;;
esac
# Reset virtual SSID-s
uci delete wireless.@wifi-iface[1]
uci delete wireless.@wifi-iface[0]
# Pseudorandomize channel selection, should work with 80MHz on 5GHz band
case $(uci get system.@system[0].hostname | md5sum) in
1*|2*|3*|4*) uci set wireless.radio2ghz.channel=1; uci set wireless.radio5ghz.channel=36 ;;
5*|6*|7*|8*) uci set wireless.radio2ghz.channel=5; uci set wireless.radio5ghz.channel=52 ;;
9*|0*|a*|b*) uci set wireless.radio2ghz.channel=9; uci set wireless.radio5ghz.channel=100 ;;
c*|d*|e*|f*) uci set wireless.radio2ghz.channel=13; uci set wireless.radio5ghz.channel=132 ;;
esac
# Create bridge for guests
uci set network.guest=interface
uci set network.guest.proto='static'
uci set network.guest.address='0.0.0.0'
uci set network.guest.type='bridge'
uci set network.guest.ifname='eth0.156' # tag id 156 for guest network
uci set network.guest.ipaddr='0.0.0.0'
uci set network.guest.ipv6=0
uci set network.guest.stp=1
# Disable switch tagging and bridge all ports on TP-Link WDR3600/WDR4300
case $(cat /etc/board.json | jsonfilter -e '@["model"]["id"]') in
tl-wdr*)
uci set network.@switch[0].enable_vlan=0
uci set network.@switch_vlan[0].ports='0 1 2 3 4 5 6'
;;
*) ;;
esac