These are some tips I’ve put together on how to create a certificate using acme.sh
then import it into a FortiGate firewall for use on the SSL-VPN or similar.
Getting the Certificate and Key file
I won’t go into too much detail on this – just use the acme.sh documentation to get a key+certificate: https://acme.sh
Convert the Certificate and Key into a p12 file
To make this work we need need to first convert the certificate provided by acme.sh into a p12 file for the FortiGate:
# Create the p12 file - run this on the server that has
# the acme.sh tool and the files:
root@acmeserver #~/tftp> pwd
/root/tftp
root@acmeserver #~/tftp>
openssl pkcs12 -passout pass:abc123 -export -in '/root/.acme.sh/example.org_ecc/fullchain.cer' -inkey '/root/.acme.sh/example.org_ecc/example.org.key' -out cert.p12
root@acmeserver #~/tftp> ls
cert.p12
We now have a p12 file saved, with the password set to “abc123” – obviously use a different password for your own file
Creating a temporary tftp server to allow FortiGate to fetch the file
The fortigate wants to download the cert from a tftp server. There are lots of ways to do this, but what I’ve done for my uses is installed the tftpy
module in python and then use that to transfer the file. This allows me to run the tftp server only while transferring the file, then get rid of it once done.
# make sure we have the module installed
# I'm using a virtualenv called tftp-venv
(tftp-venv) root@acmeserver #~> pip list
Package Version
------------- -------
pip 20.0.2
pkg-resources 0.0.0
setuptools 44.0.0
tftpy 0.8.0
# run the tftp server:
(tftp-venv) root@acmeserver #~> python -c 'import tftpy; tftpy.TftpServer("/root/tftp").listen("0.0.0.0", 69)'
# This will then just wait, listening for tftp connections
Import the cert+key into the FortiGate
Now run the command to import the p12 into the fortigate:
# 10.1.1.11 == the ip of acmeserver
# cert.p12 == the filename we chose
# abc123 == obviously replace with your own p12 password
fortiwall # execute vpn certificate local import tftp cert.p12 10.1.1.11 p12 abc123
Done.
fortiwall #
You can now use this cert+key for your VPN or other uses. Hit <Ctrl+C> on your TFTP server prompt to kill it.
As a big script to run automatically:
This can be executed in a python script, run from the server holding the cert+key, to upload a new certificate whenever it changes:
#!/usr/bin/env python3
import paramiko
import time
import subprocess
import tempfile
import uuid
from pathlib import Path
# Config
__version__ = "1.0.0"
hostname = '10.1.1.19'
my_ip = '10.1.1.11'
username = 'admin'
fg_password_file = '/root/.ssh/fortigate_pass'
cert_file = '/root/.acme.sh/*.h.jaytuckey.name_ecc/fullchain.cer'
key_file = '/root/.acme.sh/*.h.jaytuckey.name_ecc/*.h.jaytuckey.name.key'
# read in the fortigate password
password = Path(fg_password_file).read_text().strip()
# temp dir to store the p12 file
tmpdir = tempfile.TemporaryDirectory()
# generate a temporary password for the p12
p12pass = str(uuid.uuid1())
print(f'generating p12 file from {cert_file} and {key_file} to {tmpdir.name}/cert.p12')
subprocess.run(['openssl','pkcs12','-passout',f'pass:{p12pass}','-export','-in',cert_file,'-inkey',key_file,'-out',f'{tmpdir.name}/cert.p12'], check=True)
print(f'starting up tftp server on dir {tmpdir.name}')
# Set up the virtualenv variables
env = { 'VIRTUAL_ENV': '/root/tftp-venv' } # Virtualenv with tftpy installed
p = subprocess.Popen(['/root/tftp-venv/bin/python', '-c', f'import tftpy; tftpy.TftpServer("{tmpdir.name}").listen("0.0.0.0", 69)'], env=env)
try:
client = paramiko.SSHClient() # create an SSH client
# Set the policy to auto-connect to hosts not in known-hosts file
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
print(f"Connecting to fortigate on {hostname}")
client.connect(hostname, username=username, password=password, look_for_keys=False, allow_agent=False)
channel = client.invoke_shell(); time.sleep(5)
command = f'execute vpn certificate local import tftp cert.p12 {my_ip} p12 {p12pass}\n'
print('running command on fortigate:')
print(command)
channel.send(command); time.sleep(5)
response = channel.recv(999999).decode('utf-8'); time.sleep(5)
print('got response:')
print(response)
client.close()
finally:
# kill the tftp server
p.terminate()
p.wait(timeout=5)
tmpdir.cleanup() # clean up and delete the temp dir