Last active
March 13, 2023 11:23
-
-
Save trexx/7aa763b2432f0b0952e56a1822845aa0 to your computer and use it in GitHub Desktop.
The .ovpn to .onc converter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 | |
import argparse, json, re, sys, uuid, base64 | |
from os.path import basename, splitext | |
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'}, | |
'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.RemoteCertEKU', '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())} | |
}, | |
} | |
OPENVPN_KEYS = [ | |
'Auth', | |
'AuthRetry', | |
'AuthNoCache', | |
'Cipher', | |
'ClientCertRef', | |
'ClientCertPattern', | |
'ClientCertType', | |
'Compress', | |
'IgnoreDefaultRoute', | |
'KeyDirection', | |
'NsCertType', | |
'Password', | |
'Port', | |
'Proto', | |
'PushPeerInfo', | |
'RemoteCertEKU', | |
'RemoteCertKU', | |
'RemoteCertTLS', | |
'RenegSec', | |
'SaveCredentials', | |
'ServerCARefs', | |
'ServerCertRef', | |
'ServerPollTimeout', | |
'Shaper', | |
'StaticChallenge', | |
'TLSAuthContents', | |
'TLSRemote', | |
'Username', | |
'Verb', | |
'VerifyHash', | |
'VerifyX509', | |
] | |
def __init__(self): | |
self.openvpn = { | |
'IgnoreDefaultRoute': True, | |
'UserAuthenticationType': 'Password', | |
'ClientCertType': 'None' | |
} | |
self.vpn = { | |
'Type': 'OpenVPN', | |
'Host': None, | |
'OpenVPN': self.openvpn | |
} | |
self.network_config = { | |
'GUID': str(uuid.uuid4()), | |
'Name': None, | |
'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] | |
if command == 'remote' and len(args.split()) == 3: | |
args = args.split() | |
_lines.append('remote {}'.format(args[0])) | |
_lines.append('port {}'.format(args[1])) | |
_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['ServerCARefs'] = [_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: | |
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 | |
) | |
args = parser.parse_args() | |
infile = sys.stdin | |
outfile = sys.stdout | |
if args.infile: | |
infile = open(args.infile, 'r') | |
vpnname = splitext(basename(args.infile))[0] | |
if args.outfile: | |
outfile = open(args.outfile, 'w') | |
c = OpenVPNNetworkConfiguration() | |
c.init_with_file(infile) | |
c.network_config['Name'] = vpnname | |
c.to_json(outfile) | |
infile.close() | |
outfile.close() | |
if __name__ == '__main__': | |
main() |
I cant get the -----BEGIN OpenVPN Static key V1----- included in the onc file.
@kevdogg Its been a while since this was last used by me.
IIRC, you need to ensure the static key is in-line in the ovpn file like so. A path referring to the file will not work.
You also need to mention the key direction too when using this in-line format by using key-direction
Apologies if you're already doing this and I'm afraid I don't use this anymore to be able to investigate.
# Example section of in-line ovpn file
key-direction 1
<tls-auth>
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
073b0025464cdeaa6189247397d0f2f6
4c2cb415f7b662af421d3ea7c9d50c10
61ebd5ed93d04c2f863b4a6cc4ce6b32
b981297a1eb35d83e75b3051b162c286
653032398c3bc539bec746c778d67c16
dad74a45ce4e85e57bb04b3675f43ecc
e020210c3d252957e86b087804338c3a
2cec5f08306d276a54558cff885a7296
330ce026485ae88a0099430002a570f1
20b774bf64501ae28ed6650a2bc463ce
032a4c9495dd2849550ad09af18cb953
8aa516354e7a6f302fb7d9f66d1dad7f
9fe7683d84dd90d0985dff7dc2881b24
87884d98ffaafecff27d10d554e2f5a7
78226ee0561cb8f815a10b132b097579
9a9a92359aa0574a95715a1df0e51484
-----END OpenVPN Static key V1-----
</tls-auth>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I don't know if this script will support the module OpenSSL.