Skip to content

Instantly share code, notes, and snippets.

@ArturKlauser
Forked from trexx/onc_converter.py
Last active July 9, 2024 13:43
Show Gist options
  • Save ArturKlauser/ed5af568ed9ccf9be1c0a1ffb833587d to your computer and use it in GitHub Desktop.
Save ArturKlauser/ed5af568ed9ccf9be1c0a1ffb833587d to your computer and use it in GitHub Desktop.
The .ovpn to .onc converter
#!/usr/bin/python
# The .ovpn to .onc converter
# This tool parses an 'inline' OpenVPN profile (certs and keys are included within the .ovpn file)
## and spits out a Open Network Configuration file which can be imported in to ChromeOS.
# Open Network Configuration specs can be found here
## https://chromium.googlesource.com/chromium/src/+/master/components/onc/docs/onc_spec.md
# Original credit goes to Steve Woodrow (https://github.com/woodrow)
# Modified from: https://gist.github.com/woodrow/155b6af97fdbd217ddca3f9f919ee02a
# Modified by: Turan Asikoglu
#
# Adapted from https://www.snip2code.com/Snippet/3217054/The--ovpn-to--onc-converter
# (same as https://gist.github.com/trexx/7aa763b2432f0b0952e56a1822845aa0)
# Modified by: [email protected]
import argparse
import base64
import json
import re
import sys
import uuid
from OpenSSL.crypto import PKCS12, FILETYPE_PEM, load_certificate, load_privatekey
class OpenVPNNetworkConfiguration(object):
KNOWN_CONFIG_KEYS = {
'name': {'key': 'Name'},
'startonopen': {'key': 'VPN.AutoConnect', 'value': lambda x: bool(x)},
'remote': {'key': 'VPN.Host', 'value': lambda x: x.split()[0]},
'auth': {'key': 'VPN.OpenVPN.Auth'},
'auth-retry': {'key': 'VPN.OpenVPN.AuthRetry'},
'auth-nocache': {'key': 'VPN.OpenVPN.AuthNoCache', 'value': True},
'cipher': {'key': 'VPN.OpenVPN.Cipher'},
'comp-lzo': {'key': 'VPN.OpenVPN.CompLZO'},
'comp-noadapt': {'key': 'VPN.OpenVPN.CompNoAdapt', 'value': True},
'key-direction': {'key': 'VPN.OpenVPN.KeyDirection'},
'ns-cert-type': {'key': 'VPN.OpenVPN.NsCertType'},
'port': {'key': 'VPN.OpenVPN.Port', 'value': lambda x: int(x)},
'proto': {'key': 'VPN.OpenVPN.Proto'},
'push-peer-info': {'key': 'VPN.OpenVPN.PushPeerInfo', 'value': True},
'remote-cert-eku': {'key': 'VPN.OpenVPN.RemoteCertEKU'},
'remote-cert-ku': {'key': 'VPN.OpenVPN.RemoteCertKU', 'value': lambda x: x.split()},
'remote-cert-tls': {'key': 'VPN.OpenVPN.RemoteCertTLS'},
'reneg-sec': {'key': 'VPN.OpenVPN.RenegSec', 'value': lambda x: int(x)},
'server-poll-timeout': {'key': 'VPN.OpenVPN.ServerPollTimeout', 'value': lambda x: int(x)},
'shaper': {'key': 'VPN.OpenVPN.Shaper', 'value': lambda x: int(x)},
'static-challenge': {'key': 'VPN.OpenVPN.StaticChallenge', 'value': lambda x: x.rsplit(maxsplit=1)},
'tls-remote': {'key': 'VPN.OpenVPN.TLSRemote'},
'username': {'key': 'VPN.OpenVPN.Username'},
'verb': {'key': 'VPN.OpenVPN.Verb'},
'verify-hash': {'key': 'VPN.OpenVPN.VerifyHash'},
'verify-x509-name': {
'key': 'VPN.OpenVPN.VerifyX509', 'value': lambda x: {i[0]: i[1] for i in zip(['Name', 'Type'], x.split())}
},
}
def __init__(self):
self.openvpn = {}
self.vpn = {
'Type': 'OpenVPN',
'AutoConnect': False,
'Host': None,
'OpenVPN': self.openvpn
}
self.network_config = {
'GUID': str(uuid.uuid4()),
'Type': 'VPN',
'VPN': self.vpn
}
self.onc = {
'Type': 'UnencryptedConfiguration',
'NetworkConfigurations': [self.network_config],
'Certificates': []
}
def init_with_file(self, f):
lines = f.readlines()
lines = self.transform_config(lines)
self.parse_openvpn_config(lines)
@staticmethod
def transform_config(lines):
_lines = []
multiline = None
for line in lines:
if not multiline and re.match(r'\A\s*<[a-z-]+>', line):
multiline = line.strip()
elif multiline and re.match(r'\A\s*</[a-z-]+>', line):
_lines.append(multiline + '\n' + line.strip())
multiline = None
elif multiline:
multiline += ('\n' + line.strip())
else:
parts = line.strip().split(' ', 1)
command = parts[0]
args = None
if len(parts) > 1:
args = parts[1].split()
if command == 'remote' and len(args) >= 2:
_lines.append('remote {}'.format(args[0]))
_lines.append('port {}'.format(args[1]))
if len(args) >= 3:
_lines.append('proto {}'.format(args[2]))
else:
_lines.append(line.strip())
return _lines
def parse_openvpn_config(self, lines):
clientx509_cert = None
clientx509_key = None
for line in lines:
if re.match(r'\A\s*#viscosity', line):
line = re.match(r'\A\s*#viscosity(.*)', line).group(1)
elif re.match(r'\A\s*#', line):
continue
parts = line.strip().split(' ', 1)
command = parts[0]
args = None
if len(parts) > 1:
args = parts[1]
if command in self.KNOWN_CONFIG_KEYS:
spec = self.KNOWN_CONFIG_KEYS[command]
value = spec.get('value', args)
if callable(value):
value = value(args)
_dict = self.network_config
key_parts = spec['key'].split('.')
for k in key_parts[:-1]:
_dict = _dict[k]
_dict[key_parts[-1]] = value
elif command.startswith('<tls-auth>'):
self.openvpn['TLSAuthContents'] = '\n'.join(
re.search('<tls-auth>(.*?)</tls-auth>', line, re.DOTALL).group(1).strip().split('\n'))
elif command.startswith('<ca>'):
_cas = []
_ca_uuids = []
for ca in re.findall(r'-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----', line, re.DOTALL):
guid = str(uuid.uuid4())
_ca = ''.join(ca.strip().split())
_cas.append(_ca)
_ca_uuids.append(guid)
self.onc['Certificates'].append({
'GUID': guid,
'Type': 'Authority',
'X509': _ca
})
self.openvpn['ServerCARef'] = _ca_uuids[0]
elif command.startswith('<cert>'):
clientx509_cert = load_certificate(FILETYPE_PEM, re.search(r'-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----', line, re.DOTALL).group(0))
elif command.startswith('<key>'):
clientx509_key = load_privatekey(FILETYPE_PEM, re.search(r'-----BEGIN PRIVATE KEY-----(.*?)-----END PRIVATE KEY-----', line, re.DOTALL).group(0))
if clientx509_cert and clientx509_key:
# cat client.key client.crt | openssl pkcs12 -export -name foobar -passout pass: | base64 -w0
clientp12 = PKCS12()
clientp12.set_certificate(clientx509_cert)
clientp12.set_privatekey(clientx509_key)
clientp12_b64 = base64.b64encode(clientp12.export()).decode('ascii')
guid = str(uuid.uuid4())
self.onc['Certificates'].append({
'GUID': guid,
'Type': 'Client',
'PKCS12': clientp12_b64
})
self.openvpn['ClientCertType'] = 'Ref'
self.openvpn['ClientCertRef'] = guid
def to_json(self, outfile=None):
if outfile:
json.dump(self.onc, outfile, sort_keys=True, indent=2, separators=(',', ': '))
else:
return json.dumps(self.onc, sort_keys=True, indent=2, separators=(',', ': '))
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'--infile',
metavar='OPENVPN_CONFIG_FILE',
help='OpenVPN config file to be converted. If not present, stdin is used.',
default=None
)
parser.add_argument(
'--outfile',
metavar='ONC_FILE',
help='Path to output ONC file. If not present, stdout is used.',
default=None
)
parser.add_argument(
'--name',
help='OpenVPN client config name. Required.',
required=True,
default=None
)
args = parser.parse_args()
infile = sys.stdin
outfile = sys.stdout
if args.infile:
infile = open(args.infile, 'r')
if args.outfile:
outfile = open(args.outfile, 'w')
c = OpenVPNNetworkConfiguration()
c.init_with_file(infile)
c.network_config['Name'] = args.name
c.openvpn['Username'] = 'not_used'
c.openvpn['Password'] = 'not_used'
c.openvpn['UserAuthenticationType'] = 'None'
c.openvpn['SaveCredentials'] = True
c.to_json(outfile)
infile.close()
outfile.close()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment