From 140fa1f6d290bb0e9a3b7d968cf284821f11c5d0 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Fri, 27 Feb 2026 08:08:40 +0000 Subject: [PATCH] Update readme, add certs dir --- .gitignore | 5 + README.md | 71 +++++++---- certs/.gitkeep | 0 generate.js | 275 ----------------------------------------- package.json | 6 +- src-languages/01-en.md | 83 ++++++++----- 6 files changed, 109 insertions(+), 331 deletions(-) create mode 100644 .gitignore create mode 100644 certs/.gitkeep delete mode 100644 generate.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..457fe79 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +certs/cert.pem +certs/chain.pem +certs/fullchain.pem +certs/privkey.pem +certs/README diff --git a/README.md b/README.md index 5613e02..1e35f4c 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,7 @@ English | [įŽ€äŊ“中文](https://github.com/paulmillr/encrypted-dns/blob/master/ # encrypted-dns-configs -Configuration profiles for [DNS over HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS) and [DNS over TLS](https://en.wikipedia.org/wiki/DNS_over_TLS). Check out the article for more info: [paulmillr.com/posts/encrypted-dns/](https://paulmillr.com/posts/encrypted-dns/). To add a new provider, or edit an existing one, edit json files in `src` directory. - -## Known issues - -1. Some apps and protocols will ignore encrypted-dns: - - Firefox in specific regions, App Store in all regions. [More info](https://github.com/paulmillr/encrypted-dns/issues/22) - - iCloud Private Relay, VPN clients - - Little Snitch, LuLu - - DNS-related CLI tools: `host`, `dig`, `nslookup` etc. -2. [Wi-Fi captive portals](https://en.wikipedia.org/wiki/Captive_portal) in cafes, hotels, airports are exempted by Apple from eDNS rules; to simplify authentication - this is ok -3. TLS DNS is easier for providers to block, because it uses non-standard port 853. - [More info](https://security.googleblog.com/2022/07/dns-over-http3-in-android.html) -4. e-dns over TOR could be better privacy-wise, but we don't have this for now. +Configuration profiles for [DNS over HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS) and [DNS over TLS](https://en.wikipedia.org/wiki/DNS_over_TLS). Check out the article for more info: [paulmillr.com/posts/encrypted-dns/](https://paulmillr.com/posts/encrypted-dns/). To add a new provider, or edit an existing one: see [#contributing](#contributing). ## Usage @@ -22,20 +10,20 @@ Install / download profile (`.mobileconfig` file) from a table below. After that iPhones, iPads: -1. Open the mobileconfig file in GitHub by using Safari (other browsers will just download the file and won't ask for installation) +1. Open the file by using Safari (other browsers will just download the file and won't ask for installation) 2. Tap on "Allow" button. The profile should download. 3. Go to **System Settings => General => VPN, DNS & Device Management**, select downloaded profile and tap the "Install" button. Mac: 1. Ensure the downloaded file has proper extension: NAME.mobileconfig, not NAME.mobileconfig.txt. -2. Choose Apple menu > System Settings, click Privacy and Security in the sidebar, then click Profiles on the right. (You may need to scroll down.) -3. You may be asked to supply your password or other information during installation. -4. In the Downloaded section, double-click the profile. Review the profile contents then click Continue, Install or Enroll to install the profile. If an earlier version of a profile is already installed on your Mac, the settings in the updated version replace the previous ones. +2. Choose Apple menu > System Settings, click Privacy and Security in the sidebar, then click Profiles on the right. + You may need to scroll down. You may be asked to supply your password or other information during installation. +3. In the Downloaded section, double-click the profile. Review the profile contents then click Continue, Install or Enroll to install the profile. If an earlier version of a profile is already installed on your Mac, the settings in the updated version replace the previous ones. ## Providers -`Censorship=yes` (also known as "filtering") means the profile will not send true information about `hostname=IP` relation for some hosts. +Censorship (also known as "filtering") means the profile will not send true information about `hostname=IP` relation for some hosts. | Name | Region | Censorship | Notes | Install | Install (unsigned) | | ------------------------------------------------------------------------------------ | ------ | ---------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | @@ -77,17 +65,52 @@ Mac: | [Quad9 Unfiltered][quad9] | 🇨🇭 | No | Operated by Quad9 Foundation. | [HTTPS][quad9-profile-unfiltered-https-signed], [TLS][quad9-profile-unfiltered-tls-signed] | [HTTPS][quad9-profile-unfiltered-https], [TLS][quad9-profile-unfiltered-tls] | | [Tiarap][tiarap] | 🇸đŸ‡Ŧ đŸ‡ē🇸 | Yes | Operated by Tiarap Inc. Blocks ads, tracking, phising & malware | [HTTPS][tiarap-profile-https-signed], [TLS][tiarap-profile-tls-signed] | [HTTPS][tiarap-profile-https], [TLS][tiarap-profile-tls] | -## Signed Profiles +## Known issues -To verify resolver IPs and hostnames, compare mobileconfig files to their documentation URLs. Internal workings of the profiles are described on [developer.apple.com](https://developer.apple.com/documentation/devicemanagement/dnssettings). In order to verify signed mobileconfigs, you will need to download them to your computer and open them in a text editor, because signing profiles makes GitHub think that they are binary files. +1. Some apps and protocols will ignore encrypted-dns: + - Firefox in specific regions, App Store in all regions. [More info](https://github.com/paulmillr/encrypted-dns/issues/22) + - iCloud Private Relay, VPN clients + - Little Snitch, LuLu + - DNS-related CLI tools: `host`, `dig`, `nslookup` etc. +2. [Wi-Fi captive portals](https://en.wikipedia.org/wiki/Captive_portal) in cafes, hotels, airports are exempted by Apple from eDNS rules; to simplify authentication - this is ok +3. TLS DNS is easier for providers to block, because it uses non-standard port 853. + [More info](https://security.googleblog.com/2022/07/dns-over-http3-in-android.html) +4. e-dns over TOR could be better privacy-wise, but we don't have this for now. -## On demand activation +## Contributing -You can optionally exclude some trusted Wi-Fi networks where you don't want to use encrypted DNS. To do so, add your SSIDs in the [OnDemandRules](https://github.com/paulmillr/encrypted-dns/blob/master/profiles/template-on-demand.mobileconfig#L22-L38) section inside the `PayloadContent` dictionary of a profile. Note: you can't edit signed profiles. +- **To add / edit a profile:** edit json files in `src` directory. +- **To verify resolver IPs / hostnames:** compare mobileconfig files to their original websites (open files in a text editor). +- Check out [developer.apple.com](https://developer.apple.com/documentation/devicemanagement/dnssettings) for more docs. +- **On demand activation:** You can optionally exclude some trusted Wi-Fi networks where you don't want to use encrypted DNS. To do so, add your SSIDs in the [OnDemandRules](https://github.com/paulmillr/encrypted-dns/blob/master/profiles/template-on-demand.mobileconfig#L22-L38) section inside the `PayloadContent` dictionary of a profile. -## Contributing a new profile +### Scripts -To add a new provider, or edit an existing one, edit json files in `src` directory. +- `npm run build` - re-build profiles, signed profiles, READMEs +- `npm run sign` - re-sign all profiles (updates `signature` field) using an ECC SSL certificate. + - Signing is done using [key-producer](https://github.com/paulmillr/micro-key-producer) + - Let's Encrypt free certificates are OK, but [expire in 45 days](https://letsencrypt.org/2026/02/24/rate-limits-45-day-certs). + - Expects following files to be present in `certs` subdirectory: + + ``` + `privkey.pem` : the private key for your certificate. + `fullchain.pem`: the certificate file used in most server software. + `chain.pem` : used for OCSP stapling in Nginx >=1.3.7. + `cert.pem` + ``` + +- `npm run new` - interactively creates new profile from CLI options. Can also be ran with flags. + - `scripts/new.test.ts` includes CLI snapshot tests and a PTY interactive flow test. + - PTY test runs by default; set `NEW_TEST_PTY=0` to opt out. +- `node scripts/sign-single.ts --ca cert.pem --priv_key key.pem [--chain chain.pem] path.mobileconfig` - sings single mobileconfig +- `node scripts/sign-single-openssl.ts --ca cert.pem --priv_key key.pem [--chain chain.pem] path.mobileconfig` Sign one `.mobileconfig` using OpenSSL. + - Uses `-nosmimecap` to match local CMS signing policy. +- `node scripts/detach.ts signed.mobileconfig` - detach CMS signature from signed profile and print PEM to stdout. +- `node test/sign-single.test.ts` - Parity check for `sign-single.ts` vs `sign-single-openssl.sh`. + - Runs under `npm run test`. + - Generates temporary test root/signer certificates and keys via OpenSSL. + - Signs the same profile with `scripts/sign.ts` and `scripts/sign_openssl.sh`. + - Verifies detached content and embedded certificate set parity. [360-dns]: https://sdns.360.net/dnsPublic.html [360-dns-profile-https]: https://github.com/paulmillr/encrypted-dns/raw/master/profiles/360-https.mobileconfig diff --git a/certs/.gitkeep b/certs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/generate.js b/generate.js deleted file mode 100644 index 7b322b6..0000000 --- a/generate.js +++ /dev/null @@ -1,275 +0,0 @@ -#!/usr/bin/env node -const fs = require('node:fs'); -const path = require('node:path'); - -const LANGUAGES_DIR = path.join(__dirname, 'src-languages'); -const PROVIDERS_PATH = path.join(__dirname, 'src'); -const DEFAULT_LANG = 'en'; -const OUTPUT_DIR = __dirname; -const REPO_RAW = 'https://github.com/paulmillr/encrypted-dns/raw/master'; - -const REGIONS = { - US: 'đŸ‡ē🇸', - CN: 'đŸ‡¨đŸ‡ŗ', - RU: '🇷đŸ‡ē', - NL: 'đŸ‡ŗđŸ‡ą', - DE: '🇩đŸ‡Ē', - SG: '🇸đŸ‡Ŧ', - CA: '🇨đŸ‡Ļ', - FR: 'đŸ‡Ģ🇷', - CH: '🇨🇭', - SE: '🇸đŸ‡Ē', - CZ: '🇨đŸ‡ŋ', -}; - -const providerFile = (p, https, signed) => { - const postfix = (p, https) => { - if (https) { - if (p.doh && p.doh !== true) return `doh${p.doh}`; - if (p.doh) return 'doh'; - return 'https'; - } else { - if (p.doh && p.doh !== true) return `dot${p.doh}`; - if (p.doh) return 'dot'; - return 'tls'; - } - }; - const name = p.name || p.id; - return `${signed ? 'signed' : 'profiles'}/${name}-${postfix(p, https)}.mobileconfig`; -}; - -const languages = fs - .readdirSync(LANGUAGES_DIR) - .filter((f) => f.endsWith('.json')) - .sort() - .map((f) => { - const p = path.join(LANGUAGES_DIR, f); - const data = JSON.parse(fs.readFileSync(p, 'utf8')); - return { - code: data.code, - name: data.name, - mdFile: p.replace('.json', '.md'), - jsonFile: p, - data: data, - }; - }); - -const providers = fs - .readdirSync(PROVIDERS_PATH) - .sort() - .map((i) => JSON.parse(fs.readFileSync(`${PROVIDERS_PATH}/${i}`))) - .map((i) => { - const unsigned = { - https: !!i.https || fs.existsSync(providerFile(i, true)), - tls: !!i.tls || fs.existsSync(providerFile(i, false)), - }; - const signed = { - https: unsigned.https && fs.existsSync(providerFile(i, true, true)), - tls: unsigned.tls && fs.existsSync(providerFile(i, false, true)), - }; - return { ...i, formats: { unsigned, signed } }; - }); - -const FULLWIDTH_PATTERN = - /[\u1100-\u115F\u2329\u232A\u2E80-\u303E\u3040-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE6F\uFF00-\uFF60\uFFE0-\uFFE6]/u; -function chrWidth(str) { - let width = 0; - for (const char of str) width += FULLWIDTH_PATTERN.test(char) || REGIONS[char] ? 2 : 1; - return width; -} -const padEnd = (s, len, chr) => `${s}${chr.repeat(Math.max(0, len - chrWidth(s)))}`; - -const genTable = (rows) => { - // first row is header - const widths = rows[0].map((i) => 0); - // Collect cell widths - for (const r of rows) { - for (let i = 0; i < r.length; i++) widths[i] = Math.max(widths[i], chrWidth(r[i])); - } - let table = ''; - rows.forEach((r, i) => { - const cells = r.map((c, j) => padEnd(c, widths[j], ' ')).join(' | '); - table += `| ${cells} |\n`; - if (i === 0) table += `| ${r.map((c, j) => padEnd('', widths[j], '-')).join(' | ')} |\n`; - }); - return table; -}; - -const TAGS = { - // Language selection header - LANGUAGES: (currentLang) => { - return languages - .map((lang) => { - if (lang.code === currentLang.code) return lang.name; - return `[${lang.name}](https://github.com/paulmillr/encrypted-dns/${ - lang.code === DEFAULT_LANG ? '' : `blob/master/README.${lang.code}.md` - })`; - }) - .join(' | '); - }, - PROVIDERS_TABLE: (currentLang) => { - const rows = [ - // header - [ - currentLang.data.table_columns.name, - currentLang.data.table_columns.region, - currentLang.data.table_columns.censorship, - currentLang.data.table_columns.notes, - currentLang.data.table_columns.install_signed, - currentLang.data.table_columns.install_unsigned, - ], - ]; - const sorted = Array.from(providers).sort((a, b) => a.id.localeCompare(b.id)); - for (const provider of sorted) { - const name = provider.names[currentLang.code] || provider.names[DEFAULT_LANG]; - const note = provider.notes[currentLang.code] || provider.notes[DEFAULT_LANG]; - const censorship = provider.censorship ? currentLang.data.yes : currentLang.data.no; - const regionEmoji = (Array.isArray(provider.region) ? provider.region : [provider.region]) - .map((r) => REGIONS[r]) - .join(' '); - const unsignedLinks = []; - if (provider.formats.unsigned && provider.formats.unsigned.https) - unsignedLinks.push(`[HTTPS][${provider.profile}-https]`); - if (provider.formats.unsigned && provider.formats.unsigned.tls) - unsignedLinks.push(`[TLS][${provider.profile}-tls]`); - const signedLinks = []; - if (provider.formats.signed && provider.formats.signed.https) - signedLinks.push(`[HTTPS][${provider.profile}-https-signed]`); - if (provider.formats.signed && provider.formats.signed.tls) - signedLinks.push(`[TLS][${provider.profile}-tls-signed]`); - rows.push([ - `[${name}][${provider.id}]`, - regionEmoji, - censorship, - note, - signedLinks.join(', '), - unsignedLinks.join(', '), - ]); - } - return genTable(rows).trim(); - }, - PROVIDERS_LINKS: (currentLang) => { - let res = ''; - const addLink = (p, https, signed) => { - const file = providerFile(p, https, signed); - if (!fs.existsSync(`./${file}`)) throw new Error('missing: ' + file); - res += `[${p.profile}-${https ? 'https' : 'tls'}${ - signed ? '-signed' : '' - }]: ${REPO_RAW}/${file}\n`; - }; - for (const p of providers) { - if (p.website) res += `[${p.id}]: ${p.website}\n`; - if (p.formats.unsigned.https) addLink(p, true); - if (p.formats.unsigned.tls) addLink(p, false); - } - // signed - for (const p of providers) { - if (p.formats.signed.https) addLink(p, true, true); - if (p.formats.signed.tls) addLink(p, false, true); - } - return res; - }, -}; - -function processTemplate(templateContent, lang) { - let content = templateContent; - for (const [tag, handler] of Object.entries(TAGS)) { - const tagPattern = new RegExp(`<%${tag}%>`, 'g'); - if (content.match(tagPattern)) content = content.replace(tagPattern, handler(lang)); - } - return content; -} - -function generateReadmes() { - for (const lang of languages) { - if (!fs.existsSync(lang.mdFile)) throw new Error(`Template file not found: ${lang.mdFile}`); - const tpl = fs.readFileSync(lang.mdFile, 'utf8'); - const processed = processTemplate(tpl, lang); - const out = lang.code === DEFAULT_LANG ? 'README.md' : `README.${lang.code}.md`; - fs.writeFileSync(path.join(OUTPUT_DIR, out), processed, 'utf8'); - console.log(`Generated ${out}`); - } -} - -function generateSingle(x) { - return ` - - - - PayloadContent - - - DNSSettings - - DNSProtocol - ${x.DNSProtocol} - ServerAddresses - -${x.ServerAddresses.map((i) => `\t\t\t\t\t${i}`).join('\n')} - - ${!x.ServerURLOrName.startsWith('https://') ? 'ServerName' : 'ServerURL'} - ${x.ServerURLOrName} - - PayloadDescription - Configures device to use ${x.name} - PayloadDisplayName - ${x.PayloadDisplayName} - PayloadIdentifier - ${x.PayloadIdentifier} - PayloadType - com.apple.dnsSettings.managed - PayloadUUID - ${x.PayloadUUID} - PayloadVersion - 1 - ProhibitDisablement - - - - PayloadDescription - Adds the ${x.fullName} to Big Sur and iOS 14 based systems - PayloadDisplayName - ${x.topName} - PayloadIdentifier - com.paulmillr.apple-dns - PayloadRemovalDisallowed - - PayloadScope - System - PayloadType - Configuration - PayloadUUID - ${x.TopPayloadUUID} - PayloadVersion - 1 - - -`; -} - -function generateConfigs() { - function generate(file, parsed) { - if (!parsed) return; - fs.writeFileSync(file, generateSingle(parsed)); - console.log(`Generated ${file}`); - } - for (const p of providers) { - if (p.formats.unsigned.https) generate(providerFile(p, true), p.https); - if (p.formats.unsigned.tls) generate(providerFile(p, false), p.tls); - } -} -// Small utility to rewrite config structure -function patchConfigs() { - for (const f of fs.readdirSync(`./src/`)) { - const path = `./src/${f}`; - const json = JSON.parse(fs.readFileSync(path, 'utf8')); - fs.writeFileSync(path, JSON.stringify(json, null, 4)); - } -} - -function main() { - //patchConfigs(); - generateReadmes(); - generateConfigs(); -} -main(); diff --git a/package.json b/package.json index 6fb797c..e293805 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,10 @@ "type": "module", "scripts": { "build:clean": "rm -f signed/*.mobileconfig profiles/*.mobileconfig README*.md", - "build": "npm run build:clean && node scripts/generate.ts", - "test": "node --experimental-strip-types --test scripts/new.test.ts scripts/sign.test.ts", + "build": "npm run build:clean && node scripts/build.ts", + "sign": "node scripts/sign.ts", + "new": "node scripts/new.ts", + "test": "node --experimental-strip-types --test scripts/new.test.ts scripts/sign-single.test.ts", "format": "prettier --write \"src/*.json\" scripts/*.ts *.ts" }, "dependencies": { diff --git a/src-languages/01-en.md b/src-languages/01-en.md index bf5319e..c87033a 100644 --- a/src-languages/01-en.md +++ b/src-languages/01-en.md @@ -2,7 +2,30 @@ # encrypted-dns-configs -Configuration profiles for [DNS over HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS) and [DNS over TLS](https://en.wikipedia.org/wiki/DNS_over_TLS). Check out the article for more info: [paulmillr.com/posts/encrypted-dns/](https://paulmillr.com/posts/encrypted-dns/). To add a new provider, or edit an existing one, edit json files in `src` directory. +Configuration profiles for [DNS over HTTPS](https://en.wikipedia.org/wiki/DNS_over_HTTPS) and [DNS over TLS](https://en.wikipedia.org/wiki/DNS_over_TLS). Check out the article for more info: [paulmillr.com/posts/encrypted-dns/](https://paulmillr.com/posts/encrypted-dns/). To add a new provider, or edit an existing one: see [#contributing](#contributing). + +## Usage + +Install / download profile (`.mobileconfig` file) from a table below. After that: + +iPhones, iPads: + +1. Open the file by using Safari (other browsers will just download the file and won't ask for installation) +2. Tap on "Allow" button. The profile should download. +3. Go to **System Settings => General => VPN, DNS & Device Management**, select downloaded profile and tap the "Install" button. + +Mac: + +1. Ensure the downloaded file has proper extension: NAME.mobileconfig, not NAME.mobileconfig.txt. +2. Choose Apple menu > System Settings, click Privacy and Security in the sidebar, then click Profiles on the right. + You may need to scroll down. You may be asked to supply your password or other information during installation. +3. In the Downloaded section, double-click the profile. Review the profile contents then click Continue, Install or Enroll to install the profile. If an earlier version of a profile is already installed on your Mac, the settings in the updated version replace the previous ones. + +## Providers + +Censorship (also known as "filtering") means the profile will not send true information about `hostname=IP` relation for some hosts. + +<%PROVIDERS_TABLE%> ## Known issues @@ -16,39 +39,39 @@ Configuration profiles for [DNS over HTTPS](https://en.wikipedia.org/wiki/DNS_ov [More info](https://security.googleblog.com/2022/07/dns-over-http3-in-android.html) 4. e-dns over TOR could be better privacy-wise, but we don't have this for now. -## Usage +## Contributing -Install / download profile (`.mobileconfig` file) from a table below. After that: +- **To add / edit a profile:** edit json files in `src` directory. +- **To verify resolver IPs / hostnames:** compare mobileconfig files to their original websites (open files in a text editor). +- Check out [developer.apple.com](https://developer.apple.com/documentation/devicemanagement/dnssettings) for more docs. +- **On demand activation:** You can optionally exclude some trusted Wi-Fi networks where you don't want to use encrypted DNS. To do so, add your SSIDs in the [OnDemandRules](https://github.com/paulmillr/encrypted-dns/blob/master/profiles/template-on-demand.mobileconfig#L22-L38) section inside the `PayloadContent` dictionary of a profile. -iPhones, iPads: +### Scripts -1. Open the mobileconfig file in GitHub by using Safari (other browsers will just download the file and won't ask for installation) -2. Tap on "Allow" button. The profile should download. -3. Go to **System Settings => General => VPN, DNS & Device Management**, select downloaded profile and tap the "Install" button. +- `npm run build` - re-build profiles, signed profiles, READMEs +- `npm run sign` - re-sign all profiles (updates `signature` field) using an ECC SSL certificate. + - Signing is done using [key-producer](https://github.com/paulmillr/micro-key-producer) + - Let's Encrypt free certificates are OK, but [expire in 45 days](https://letsencrypt.org/2026/02/24/rate-limits-45-day-certs). + - Expects following files to be present in `certs` subdirectory: -Mac: + ``` + `privkey.pem` : the private key for your certificate. + `fullchain.pem`: the certificate file used in most server software. + `chain.pem` : used for OCSP stapling in Nginx >=1.3.7. + `cert.pem` + ``` -1. Ensure the downloaded file has proper extension: NAME.mobileconfig, not NAME.mobileconfig.txt. -2. Choose Apple menu > System Settings, click Privacy and Security in the sidebar, then click Profiles on the right. (You may need to scroll down.) -3. You may be asked to supply your password or other information during installation. -4. In the Downloaded section, double-click the profile. Review the profile contents then click Continue, Install or Enroll to install the profile. If an earlier version of a profile is already installed on your Mac, the settings in the updated version replace the previous ones. - -## Providers - -`Censorship=yes` (also known as "filtering") means the profile will not send true information about `hostname=IP` relation for some hosts. - -<%PROVIDERS_TABLE%> - -## Signed Profiles - -To verify resolver IPs and hostnames, compare mobileconfig files to their documentation URLs. Internal workings of the profiles are described on [developer.apple.com](https://developer.apple.com/documentation/devicemanagement/dnssettings). In order to verify signed mobileconfigs, you will need to download them to your computer and open them in a text editor, because signing profiles makes GitHub think that they are binary files. - -## On demand activation - -You can optionally exclude some trusted Wi-Fi networks where you don't want to use encrypted DNS. To do so, add your SSIDs in the [OnDemandRules](https://github.com/paulmillr/encrypted-dns/blob/master/profiles/template-on-demand.mobileconfig#L22-L38) section inside the `PayloadContent` dictionary of a profile. Note: you can't edit signed profiles. - -## Contributing a new profile - -To add a new provider, or edit an existing one, edit json files in `src` directory. +- `npm run new` - interactively creates new profile from CLI options. Can also be ran with flags. + - `scripts/new.test.ts` includes CLI snapshot tests and a PTY interactive flow test. + - PTY test runs by default; set `NEW_TEST_PTY=0` to opt out. +- `node scripts/sign-single.ts --ca cert.pem --priv_key key.pem [--chain chain.pem] path.mobileconfig` - sings single mobileconfig +- `node scripts/sign-single-openssl.ts --ca cert.pem --priv_key key.pem [--chain chain.pem] path.mobileconfig` Sign one `.mobileconfig` using OpenSSL. + - Uses `-nosmimecap` to match local CMS signing policy. +- `node scripts/detach.ts signed.mobileconfig` - detach CMS signature from signed profile and print PEM to stdout. +- `node test/sign-single.test.ts` - Parity check for `sign-single.ts` vs `sign-single-openssl.sh`. + - Runs under `npm run test`. + - Generates temporary test root/signer certificates and keys via OpenSSL. + - Signs the same profile with `scripts/sign.ts` and `scripts/sign_openssl.sh`. + - Verifies detached content and embedded certificate set parity. <%PROVIDERS_LINKS%>