1
0
mirror of https://github.com/ArcticFoxes-net/ONC-Converter synced 2024-11-17 17:21:34 -05:00

Convert to dark theme

Signed-off-by: Tommy <contact@tommytran.io>
This commit is contained in:
Tommy 2022-11-21 22:29:41 -05:00
parent 2b0babf76c
commit 1ba8ababf9
No known key found for this signature in database
GPG Key ID: 060B29EB996BD9F2
5 changed files with 577 additions and 536 deletions

View File

@ -1,8 +1,10 @@
# ovpn2onc
Convert OpenVPN config files to the ONC ChromeOS network config files.
# ONC Converter
Convert OpenVPN configuration files to ONC configuration files.
## How to use
Download the `ovpn2onc.html` file and open it in Chrome (or any other modern browser). Follow the instructions there.
Download the `index.html` file and open it in Chrome (or any other modern browser). Follow the instructions there.
You can also use the hosted copy at [https://thomkeh.github.io/ovpn2onc/ovpn2onc.html](https://thomkeh.github.io/ovpn2onc/ovpn2onc.html)
but be aware that in this case your configuration (with keys) will be sent over the Internet and you have to trust the website.
You can also use the hosted copy at [https://onc.arcticfoxes.net](https://onc.tommytran.io). If you do, you need to trust the website (hosted on [Netlify](https://netlify.app/)) to not exfiltrate your password, certificates, and keys.
## Attribution
This is a fork of [thomkeh/ovpn2onc](https://github.com/thomkeh/ovpn2onc) with the dark theme.

45
css/style.css Normal file
View File

@ -0,0 +1,45 @@
body {
background-color: #1F2125;
color: #DADADB;
}
a{
color: inherit;
}
#output {
background-color: #26282D;
}
#log {
width: 50em;
min-height: 7em;
border: 1px solid grey;
overflow-x: auto;
background-color: #26282D;
}
#log>p {
margin: 0px;
}
input::file-selector-button {
color: #DADADB;
background-color: #26282D;
border: thin solid grey;
border-radius: 3px;
}
.input_field {
color: #DADADB;
background-color: #26282D;
border: thin solid grey;
border-radius: 3px;
}
.button {
color: #DADADB;
background-color: #26282D;
border: thin solid grey;
border-radius: 3px;
}

53
index.html Normal file
View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>OpenVPN to ONC Converter</title>
<meta name="description" content="Convert OpenVPN config files to ONC files">
<link rel="stylesheet" href="css/style.css">
<script src="js/conversion.js"></script>
</head>
<body onload="setHandler()">
<h1>OpenVPN to ONC Converter</h1>
<div>
<p><b>Input</b></p>
<ul>
<li>
<label for="connname">Name for connection:</label>
<input type="text" id="connname" class="input_field">
</li>
<li>
<label for="inputopenvpn">OpenVPN configuration file:</label>
<input type="file" id="inputopenvpn">
</li>
<li>
<label for="inputcertificates">Certificates and keys (can be multiple files):</label>
<input type="file" id="inputcertificates" multiple>
</li>
</ul>
<button id="convertbutton" type="button" class="button">Convert</button>
</div>
<div>
<p><b>Output</b> (save this as an .onc file)</p>
<textarea readonly id="output" wrap="off" rows="20" cols="98"></textarea>
</div>
<div>
<p>Debugging info:</p>
<div id="log"></div>
</div>
<div>
<p><b>Notes</b></p>
<ul>
<li>This converter can only put 1 IP address in the .onc file.</li>
<li>Import the .onc file in <a href="chrome://network">chrome://network</a></li>
<li><a href="https://github.com/tommytran732/ONC-Converter">Source Code</a></li>
</ul>
</div>
</body>
</html>

472
js/conversion.js Normal file
View File

@ -0,0 +1,472 @@
/**
* Register the function `handler` to be called when the `Convert` button is
* pressed.
*/
function setHandler() {
let convertButton = document.getElementById('convertbutton')
convertButton.addEventListener('click', handler, false)
}
/**
* Read parameters and pass them to the `main` function. This function is
* called when the `Convert` button is clicked.
*/
function handler() {
let selectedFile = document.getElementById('inputopenvpn').files[0]
let certificates = document.getElementById('inputcertificates').files
let connName = document.getElementById('connname').value
let output = document.getElementById('output')
main(connName, selectedFile, certificates, output)
}
/**
* Return a function that logs text to the log output.
*/
function getLogger() {
let logOutput = document.getElementById('log')
let logger = function (text) {
logOutput.innerHTML += `<p>${text}</p>`
}
return logger
}
/**
* Read, convert and print result. This function calls other functions
* to first read everything, then convert it and finally print the result.
*
* The function is `async` because it uses `await` when reading files.
*
* @param {String} connName Name of the connection
* @param {File} ovpnFile File object for the ovpn file
* @param {Array} certificateFiles List of file objects for the certificates
* @param {Object} output HTML element where the output should go
*/
async function main(connName, ovpnFile, certificateFiles, output) {
log = getLogger()
if (connName === '') {
log('connName is empty')
alert('Please specify a name for the connection.')
return
}
log(`Size of OVPN file: ${ovpnFile.size} bytes`)
let ovpnContent = await readFile(ovpnFile)
let ovpn
let keys
try {
[ovpn, keys] = parseOvpn(ovpnContent)
} catch (err) {
log(err)
return
}
log("Parsed config:")
log(JSON.stringify(ovpn))
for (const certificateFile of certificateFiles) {
keys[certificateFile.name] = await readFile(certificateFile)
}
let onc
try {
onc = constructOnc(connName, ovpn, keys)
} catch (err) {
log(err)
return
}
output.value = JSON.stringify(onc, null, 2)
log('All done!')
}
/**
* Return a promise to read a file as text.
*
* @param {File} file A file object
*
* @return {Promise} A promise with the contents of the file
*/
function readFile(file) {
return new Promise(resolve => {
let reader = new FileReader()
reader.onload = e => {
// callback and remove windows-style newlines
resolve(e.target.result.replace(/\r/g, ''))
}
// start reading
reader.readAsText(file)
})
}
/**
* Parse an OVPN file. Extract all the key-value pairs and keys
* that are written inside XML tags.
*
* The key-value pairs are written into an object. The keys are also
* written into an object with the the XML tag name as the key.
*
* @param {String} str The contents of the ovpn file as a string.
*
* @return {Array} An array that contains the key-value pairs and
* the keys.
*/
function parseOvpn(str) {
log = getLogger()
let ovpn = {}
let keys = {}
// define regexes for properties, opening xml tag and closing xml tag
const reProperty = /^([^ ]+)( (.*))?$/i
const reXmlOpen = /^<([^\/].*)>$/i
const reXmlClose = /^<\/(.*)>$/i
// temporary variables for handling xml tags
let xmlTag = ''
let inXml = false
let xmlContent = ''
let lines = str.split(/[\r\n]+/g)
for (let line of lines) {
// skip line if it is empty or begins with '#' or ';'
if (!line || line.match(/^\s*[;#]/)) {
log(`Skipped line: "${line}"`)
continue
}
if (inXml) { // an XML tag was opened and hasn't been closed yet
const xmlMatch = line.match(reXmlClose)
if (!xmlMatch) {
// no closing tag -> add content to `xmlContent`
xmlContent += line + '\n'
continue
}
const tag = xmlMatch[1]
if (tag !== xmlTag) {
alert('Cannot parse ovpn file.')
throw 'bad xml tag'
}
// closing tag was found
// make sure the tag name and the contents are safe
const name = makeSafe(xmlTag)
const value = xmlContent
// store everything and reset the xml variables
keys[name] = value
ovpn[name] = name
xmlContent = ''
inXml = false
continue
}
const xmlMatch = line.match(reXmlOpen)
if (xmlMatch) {
// an xml tag was opened
inXml = true
xmlTag = xmlMatch[1]
log(`XML tag was opened: "${xmlTag}"`)
continue
}
// check if the line contains a property
const match = line.match(reProperty)
if (!match) continue
// make sure everything is safe and then store it
const key = makeSafe(match[1])
const value = match[2] ? (match[3] || '') : true
ovpn[key] = value
log(`Found: ${key} = ${value}`)
}
log('Finished parsing')
return [ovpn, keys]
}
/**
* Check if string is quoted
*/
function isQuoted(val) {
return ((val.charAt(0) === '"' && val.slice(-1) === '"') ||
(val.charAt(0) === "'" && val.slice(-1) === "'"))
}
/**
* This function is supposed to prevent any exploits via the object keys
*
* It's probably complete overkill.
*/
function makeSafe(val, doUnesc) {
val = (val || '').trim()
if (isQuoted(val)) {
// remove the single quotes before calling JSON.parse
if (val.charAt(0) === "'") {
val = val.substr(1, val.length - 2)
}
try { val = JSON.parse(val) } catch (_) { }
} else {
// walk the val to find the first not-escaped ; character
var esc = false
var unesc = ''
for (var i = 0, l = val.length; i < l; i++) {
var c = val.charAt(i)
if (esc) {
if ('\\;#'.indexOf(c) !== -1) {
unesc += c
} else {
unesc += '\\' + c
}
esc = false
} else if (';#'.indexOf(c) !== -1) {
break
} else if (c === '\\') {
esc = true
} else {
unesc += c
}
}
if (esc) {
unesc += '\\'
}
return unesc
}
return val
}
/**
* Convert the keys from the parsed OVPN file into ONC keys
*
* @param {Object} keys Strings with keys, indexed by key name
* @param {Object} keynames Object with the key names
* @return {Object} ONC parameters and a list of converted certificates
*/
function convertKeys(keys, keyNames) {
let params = {}
// Add certificates
let certs = []
// Server certificate
// TODO: confirm that the type should be 'Authority'
let [cas, caGuids] = constructCerts(keys, keyNames.certificateAuthorities,
'Authority')
params['ServerCARefs'] = caGuids
certs = certs.concat(cas)
// Client certificate
if (keyNames.clientCertificates) {
// TODO: handle other types of client certificates
let [clientCerts, clientCertGuids] = constructCerts(
keys, keyNames.clientCertificates, 'Authority')
params['ClientCertType'] = 'Pattern'
params['ClientCertPattern'] = {
'IssuerCARef': clientCertGuids
}
certs = certs.concat(clientCerts)
} else {
params['ClientCertType'] = 'None'
}
// TLS auth
if (keyNames.tlsAuth) {
let authKey = keyNames.tlsAuth.split(' ')
let keyString = keys[authKey[0]]
if (!keyString) {
alert(`Please provide the file '${authKey[0]}' in 'Certificates and keys'`)
throw `Couldn't find the key for ${authKey[0]}`
}
params['TLSAuthContents'] = convertKey(keyString)
if (authKey[1]) params['KeyDirection'] = authKey[1]
}
return [params, certs]
}
/**
* Convert the parsed ovpn file into the ONC structure
*
* @param {Object} ovpn The parsed OVPN file
* @return {Array} An array with the host and an object with the parameters
*/
function convertToOnc(ovpn) {
if (!ovpn.client) {
console.warn('Is this a server file?')
}
let params = {}
// Add parameters
let remote = ovpn.remote.split(' ')
const host = remote[0]
if (remote[1]) params['Port'] = Number(remote[1])
if (ovpn['auth-user-pass']) params['UserAuthenticationType'] = 'Password'
if (ovpn['comp-lzo'] && ovpn['comp-lzo'] !== 'no') {
params['CompLZO'] = 'true'
} else {
params['CompLZO'] = 'false'
}
if (ovpn['persist-key']) params['SaveCredentials'] = true
if (ovpn['verify-x509-name']) {
const x509String = ovpn['verify-x509-name']
let x509 = {}
if (x509String.includes("'")) {
// the name is quoted with '
const parts = x509String.split("'")
x509['Name'] = parts[1]
if (parts[2]) {
x509['Type'] = parts[2].trim()
}
} else {
const parts = x509String.split(' ')
x509['Name'] = parts[0]
if (parts[1]) {
x509['Type'] = parts[1]
}
}
params['VerifyX509'] = x509
}
// set parameters if they exist in the ovpn config
let conditionalSet = (ovpnName, oncName, type = 'str') => {
if (ovpn[ovpnName]) {
const raw = ovpn[ovpnName]
let value
switch (type) {
case 'int':
value = Number(raw)
break
default:
value = raw
}
params[oncName] = value
} else {
log(`Value for '${ovpnName}' not specified.`)
}
}
conditionalSet('port', 'Port', 'int')
conditionalSet('proto', 'Proto')
conditionalSet('key-direction', 'KeyDirection')
conditionalSet('remote-cert-tls', 'RemoteCertTLS')
conditionalSet('cipher', 'Cipher')
conditionalSet('auth', 'Auth')
conditionalSet('auth-retry', 'AuthRetry')
conditionalSet('reneg-sec', 'RenegSec', 'int')
const keyNames = {
'certificateAuthorities': ovpn['ca'],
'clientCertificates': ovpn['cert'],
'tlsAuth': ovpn['tls-auth'],
}
return [host, params, keyNames]
}
/**
* Construct the ONC structure from the name, the parsed ovpn file and the keys
*
* @param {string} name Name of the connection
* @param {Object} ovpn The parsed OVPN file
* @param {Object} keys Strings with keys, indexed by key name
* @return {Object} The converted ONC structure
*/
function constructOnc(name, ovpn, keys) {
let [host, params, keyNames] = convertToOnc(ovpn)
let [certParams, certificates] = convertKeys(keys, keyNames)
// merge parameters
params = Object.assign({}, params, certParams)
// Put together network configuration
let networkConfiguration = {
'GUID': `{${uuidv4()}}`,
'Name': name,
'Type': 'VPN',
'VPN': {
'Type': 'OpenVPN',
'Host': host,
'OpenVPN': params
}
}
// Put everything together
return {
'Type': 'UnencryptedConfiguration',
'Certificates': certificates,
'NetworkConfigurations': [networkConfiguration]
}
}
/**
* Create UUID (from Stackoverflow).
*/
function uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
)
}
/**
* Replace newlines with explicit `\n` and filter out comments
*/
function convertKey(key) {
let lines = key.split(/\n/g)
let out = ''
for (let line of lines) {
// filter out empty lines and lines with comments
if (!line || line.match(/^\s*[;#]/)) continue
out += line + '\n'
}
return out
}
/**
* Find all certificates in a string and extract them
*/
function extractCas(str) {
log = getLogger()
let splits = str.replace(/\n/g, '').split('-----BEGIN CERTIFICATE-----')
let cas = []
for (const s of splits) {
if (s.includes('-----END CERTIFICATE-----')) {
let extractedCa = s.split('-----END CERTIFICATE-----')[0]
log("Extracted CA:")
log(extractedCa)
cas.push(extractedCa)
}
}
return cas
}
/**
* Construct certificates in the ONC format
*
* @param {Object} keys Strings with keys, indexed by key name
* @param {string} certName The index for the keys object
* @param {string} certType Type of the certificate: 'Authority', 'Client' or
* 'Server'
* @return {Array} An array of certificates and an array of corresponding IDs
*/
function constructCerts(keys, certName, certType) {
let certs = []
let certGuids = []
if (certName) {
let cert = keys[certName]
if (!cert) {
alert(`Please provide the file '${certName}' in 'Certificates and keys'`)
throw `Couldn't find a certificate for ${certName}`
}
let rawCerts = extractCas(cert)
const format = (certType === 'Authority') ? 'X509' : 'PKCS12'
for (const cert of rawCerts) {
const guid = `{${uuidv4()}}`
certGuids.push(guid)
let oncCert = {
'GUID': guid,
'Type': certType
}
oncCert[format] = cert
certs.push(oncCert)
}
}
return [certs, certGuids]
}

View File

@ -1,531 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>OpenVPN to ONC</title>
<meta name="description" content="Convert OpenVPN config files to ONC files">
<style>
#output {
background-color: #ddd;
}
#log {
width: 40em;
min-height: 7em;
border: 1px solid black;
overflow-x: auto;
background-color: #eee;
}
#log>p {
margin: 0px;
}
</style>
<script>
/**
* Register the function `handler` to be called when the `Convert` button is
* pressed.
*/
function setHandler () {
let convertButton = document.getElementById('convertbutton')
convertButton.addEventListener('click', handler, false)
}
/**
* Read parameters and pass them to the `main` function. This function is
* called when the `Convert` button is clicked.
*/
function handler () {
let selectedFile = document.getElementById('inputopenvpn').files[0]
let certificates = document.getElementById('inputcertificates').files
let connName = document.getElementById('connname').value
let output = document.getElementById('output')
main(connName, selectedFile, certificates, output)
}
/**
* Return a function that logs text to the log output.
*/
function getLogger () {
let logOutput = document.getElementById('log')
let logger = function (text) {
logOutput.innerHTML += `<p>${text}</p>`
}
return logger
}
/**
* Read, convert and print result. This function calls other functions
* to first read everything, then convert it and finally print the result.
*
* The function is `async` because it uses `await` when reading files.
*
* @param {String} connName Name of the connection
* @param {File} ovpnFile File object for the ovpn file
* @param {Array} certificateFiles List of file objects for the certificates
* @param {Object} output HTML element where the output should go
*/
async function main (connName, ovpnFile, certificateFiles, output) {
log = getLogger()
if (connName === '') {
log('connName is empty')
alert('Please specify a name for the connection.')
return
}
log(`Size of OVPN file: ${ovpnFile.size} bytes`)
let ovpnContent = await readFile(ovpnFile)
let ovpn
let keys
try {
[ovpn, keys] = parseOvpn(ovpnContent)
} catch (err) {
log(err)
return
}
log("Parsed config:")
log(JSON.stringify(ovpn))
for (const certificateFile of certificateFiles) {
keys[certificateFile.name] = await readFile(certificateFile)
}
let onc
try {
onc = constructOnc(connName, ovpn, keys)
} catch (err) {
log(err)
return
}
output.value = JSON.stringify(onc, null, 2)
log('All done!')
}
/**
* Return a promise to read a file as text.
*
* @param {File} file A file object
*
* @return {Promise} A promise with the contents of the file
*/
function readFile (file) {
return new Promise(resolve => {
let reader = new FileReader()
reader.onload = e => {
// callback and remove windows-style newlines
resolve(e.target.result.replace(/\r/g, ''))
}
// start reading
reader.readAsText(file)
})
}
/**
* Parse an OVPN file. Extract all the key-value pairs and keys
* that are written inside XML tags.
*
* The key-value pairs are written into an object. The keys are also
* written into an object with the the XML tag name as the key.
*
* @param {String} str The contents of the ovpn file as a string.
*
* @return {Array} An array that contains the key-value pairs and
* the keys.
*/
function parseOvpn (str) {
log = getLogger()
let ovpn = {}
let keys = {}
// define regexes for properties, opening xml tag and closing xml tag
const reProperty = /^([^ ]+)( (.*))?$/i
const reXmlOpen = /^<([^\/].*)>$/i
const reXmlClose = /^<\/(.*)>$/i
// temporary variables for handling xml tags
let xmlTag = ''
let inXml = false
let xmlContent = ''
let lines = str.split(/[\r\n]+/g)
for (let line of lines) {
// skip line if it is empty or begins with '#' or ';'
if (!line || line.match(/^\s*[;#]/)) {
log(`Skipped line: "${line}"`)
continue
}
if (inXml) { // an XML tag was opened and hasn't been closed yet
const xmlMatch = line.match(reXmlClose)
if (!xmlMatch) {
// no closing tag -> add content to `xmlContent`
xmlContent += line + '\n'
continue
}
const tag = xmlMatch[1]
if (tag !== xmlTag) {
alert('Cannot parse ovpn file.')
throw 'bad xml tag'
}
// closing tag was found
// make sure the tag name and the contents are safe
const name = makeSafe(xmlTag)
const value = xmlContent
// store everything and reset the xml variables
keys[name] = value
ovpn[name] = name
xmlContent = ''
inXml = false
continue
}
const xmlMatch = line.match(reXmlOpen)
if (xmlMatch) {
// an xml tag was opened
inXml = true
xmlTag = xmlMatch[1]
log(`XML tag was opened: "${xmlTag}"`)
continue
}
// check if the line contains a property
const match = line.match(reProperty)
if (!match) continue
// make sure everything is safe and then store it
const key = makeSafe(match[1])
const value = match[2] ? (match[3] || '') : true
ovpn[key] = value
log(`Found: ${key} = ${value}`)
}
log('Finished parsing')
return [ovpn, keys]
}
/**
* Check if string is quoted
*/
function isQuoted (val) {
return ((val.charAt(0) === '"' && val.slice(-1) === '"') ||
(val.charAt(0) === "'" && val.slice(-1) === "'"))
}
/**
* This function is supposed to prevent any exploits via the object keys
*
* It's probably complete overkill.
*/
function makeSafe (val, doUnesc) {
val = (val || '').trim()
if (isQuoted(val)) {
// remove the single quotes before calling JSON.parse
if (val.charAt(0) === "'") {
val = val.substr(1, val.length - 2)
}
try { val = JSON.parse(val) } catch (_) {}
} else {
// walk the val to find the first not-escaped ; character
var esc = false
var unesc = ''
for (var i = 0, l = val.length; i < l; i++) {
var c = val.charAt(i)
if (esc) {
if ('\\;#'.indexOf(c) !== -1) {
unesc += c
} else {
unesc += '\\' + c
}
esc = false
} else if (';#'.indexOf(c) !== -1) {
break
} else if (c === '\\') {
esc = true
} else {
unesc += c
}
}
if (esc) {
unesc += '\\'
}
return unesc
}
return val
}
/**
* Convert the keys from the parsed OVPN file into ONC keys
*
* @param {Object} keys Strings with keys, indexed by key name
* @param {Object} keynames Object with the key names
* @return {Object} ONC parameters and a list of converted certificates
*/
function convertKeys (keys, keyNames) {
let params = {}
// Add certificates
let certs = []
// Server certificate
// TODO: confirm that the type should be 'Authority'
let [cas, caGuids] = constructCerts(keys, keyNames.certificateAuthorities,
'Authority')
params['ServerCARefs'] = caGuids
certs = certs.concat(cas)
// Client certificate
if (keyNames.clientCertificates) {
// TODO: handle other types of client certificates
let [clientCerts, clientCertGuids] = constructCerts(
keys, keyNames.clientCertificates, 'Authority')
params['ClientCertType'] = 'Pattern'
params['ClientCertPattern'] = {
'IssuerCARef': clientCertGuids
}
certs = certs.concat(clientCerts)
} else {
params['ClientCertType'] = 'None'
}
// TLS auth
if (keyNames.tlsAuth) {
let authKey = keyNames.tlsAuth.split(' ')
let keyString = keys[authKey[0]]
if (!keyString) {
alert(`Please provide the file '${authKey[0]}' in 'Certificates and keys'`)
throw `Couldn't find the key for ${authKey[0]}`
}
params['TLSAuthContents'] = convertKey(keyString)
if (authKey[1]) params['KeyDirection'] = authKey[1]
}
return [params, certs]
}
/**
* Convert the parsed ovpn file into the ONC structure
*
* @param {Object} ovpn The parsed OVPN file
* @return {Array} An array with the host and an object with the parameters
*/
function convertToOnc (ovpn) {
if (!ovpn.client) {
console.warn('Is this a server file?')
}
let params = {}
// Add parameters
let remote = ovpn.remote.split(' ')
const host = remote[0]
if (remote[1]) params['Port'] = Number(remote[1])
if (ovpn['auth-user-pass']) params['UserAuthenticationType'] = 'Password'
if (ovpn['comp-lzo'] && ovpn['comp-lzo'] !== 'no') {
params['CompLZO'] = 'true'
} else {
params['CompLZO'] = 'false'
}
if (ovpn['persist-key']) params['SaveCredentials'] = true
if (ovpn['verify-x509-name']) {
const x509String = ovpn['verify-x509-name']
let x509 = {}
if (x509String.includes("'")) {
// the name is quoted with '
const parts = x509String.split("'")
x509['Name'] = parts[1]
if (parts[2]) {
x509['Type'] = parts[2].trim()
}
} else {
const parts = x509String.split(' ')
x509['Name'] = parts[0]
if (parts[1]) {
x509['Type'] = parts[1]
}
}
params['VerifyX509'] = x509
}
// set parameters if they exist in the ovpn config
let conditionalSet = (ovpnName, oncName, type = 'str') => {
if (ovpn[ovpnName]) {
const raw = ovpn[ovpnName]
let value
switch (type) {
case 'int':
value = Number(raw)
break
default:
value = raw
}
params[oncName] = value
} else {
log(`Value for '${ovpnName}' not specified.`)
}
}
conditionalSet('port', 'Port', 'int')
conditionalSet('proto', 'Proto')
conditionalSet('key-direction', 'KeyDirection')
conditionalSet('remote-cert-tls', 'RemoteCertTLS')
conditionalSet('cipher', 'Cipher')
conditionalSet('auth', 'Auth')
conditionalSet('auth-retry', 'AuthRetry')
conditionalSet('reneg-sec', 'RenegSec', 'int')
const keyNames = {
'certificateAuthorities': ovpn['ca'],
'clientCertificates': ovpn['cert'],
'tlsAuth': ovpn['tls-auth'],
}
return [host, params, keyNames]
}
/**
* Construct the ONC structure from the name, the parsed ovpn file and the keys
*
* @param {string} name Name of the connection
* @param {Object} ovpn The parsed OVPN file
* @param {Object} keys Strings with keys, indexed by key name
* @return {Object} The converted ONC structure
*/
function constructOnc (name, ovpn, keys) {
let [host, params, keyNames] = convertToOnc(ovpn)
let [certParams, certificates] = convertKeys(keys, keyNames)
// merge parameters
params = Object.assign({}, params, certParams)
// Put together network configuration
let networkConfiguration = {
'GUID': `{${uuidv4()}}`,
'Name': name,
'Type': 'VPN',
'VPN': {
'Type': 'OpenVPN',
'Host': host,
'OpenVPN': params
}
}
// Put everything together
return {
'Type': 'UnencryptedConfiguration',
'Certificates': certificates,
'NetworkConfigurations': [networkConfiguration]
}
}
/**
* Create UUID (from Stackoverflow).
*/
function uuidv4 () {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
)
}
/**
* Replace newlines with explicit `\n` and filter out comments
*/
function convertKey (key) {
let lines = key.split(/\n/g)
let out = ''
for (let line of lines) {
// filter out empty lines and lines with comments
if (!line || line.match(/^\s*[;#]/)) continue
out += line + '\n'
}
return out
}
/**
* Find all certificates in a string and extract them
*/
function extractCas (str) {
log = getLogger()
let splits = str.replace(/\n/g, '').split('-----BEGIN CERTIFICATE-----')
let cas = []
for (const s of splits) {
if (s.includes('-----END CERTIFICATE-----')) {
let extractedCa = s.split('-----END CERTIFICATE-----')[0]
log("Extracted CA:")
log(extractedCa)
cas.push(extractedCa)
}
}
return cas
}
/**
* Construct certificates in the ONC format
*
* @param {Object} keys Strings with keys, indexed by key name
* @param {string} certName The index for the keys object
* @param {string} certType Type of the certificate: 'Authority', 'Client' or
* 'Server'
* @return {Array} An array of certificates and an array of corresponding IDs
*/
function constructCerts (keys, certName, certType) {
let certs = []
let certGuids = []
if (certName) {
let cert = keys[certName]
if (!cert) {
alert(`Please provide the file '${certName}' in 'Certificates and keys'`)
throw `Couldn't find a certificate for ${certName}`
}
let rawCerts = extractCas(cert)
const format = (certType === 'Authority') ? 'X509' : 'PKCS12'
for (const cert of rawCerts) {
const guid = `{${uuidv4()}}`
certGuids.push(guid)
let oncCert = {
'GUID': guid,
'Type': certType
}
oncCert[format] = cert
certs.push(oncCert)
}
}
return [certs, certGuids]
}
</script>
</head>
<body onload="setHandler()">
<div>
<h1>ovpn2onc</h1>
<ul>
<li>
<label for="connname">Name for connection (can be chosen freely):</label>
<input type="text" id="connname">
</li>
<li>
<label for="inputopenvpn">OpenVPN config file (*.ovpn):</label>
<input type="file" id="inputopenvpn">
</li>
<li>
<label for="inputcertificates">Certificates and keys (can be multiple files):</label>
<input type="file" id="inputcertificates" multiple>
</li>
</ul>
<button id="convertbutton" type="button">Convert</button>
</div>
<div>
<p><b>Output</b> (copy this into a new file and load it in ChromeOS)</p>
<textarea readonly id="output" wrap="off" rows="40" cols="100"></textarea>
</div>
<div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>Debugging info:</p>
<div id="log"></div>
</div>
</body>
</html>