From 10b3028196192607dab139ea01b58b94a2336eae Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 30 Oct 2020 04:13:22 -0400 Subject: [PATCH 1/6] util: Generate self signed certificate into data directory Closes #1778 --- doc/FAQ.md | 3 +++ src/node/entry.ts | 2 +- src/node/util.ts | 18 ++++++++++-------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/doc/FAQ.md b/doc/FAQ.md index 370dd666..694a0722 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -144,6 +144,9 @@ For HTTPS, you can use a self signed certificate by passing in just `--cert` or pass in an existing certificate by providing the path to `--cert` and the path to the key with `--cert-key`. +The self signed certificate will be generated into +`~/.local/share/code-server/self-signed.cert`. + If `code-server` has been passed a certificate it will also respond to HTTPS requests and will redirect all HTTP requests to HTTPS. diff --git a/src/node/entry.ts b/src/node/entry.ts index 96db046e..3fbbb4cf 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -209,7 +209,7 @@ const main = async (args: Args, configArgs: Args): Promise => { logger.info( args.cert && args.cert.value ? ` - Using provided certificate and key for HTTPS` - : ` - Using generated certificate and key for HTTPS`, + : ` - Using generated certificate and key for HTTPS: ${humanPath(options.cert)}`, ) } else { logger.info(" - Not serving HTTPS") diff --git a/src/node/util.ts b/src/node/util.ts index 75122fe7..ee1e85be 100644 --- a/src/node/util.ts +++ b/src/node/util.ts @@ -55,11 +55,10 @@ export function humanPath(p?: string): string { } export const generateCertificate = async (): Promise<{ cert: string; certKey: string }> => { - const paths = { - cert: path.join(tmpdir, "self-signed.cert"), - certKey: path.join(tmpdir, "self-signed.key"), - } - const checks = await Promise.all([fs.pathExists(paths.cert), fs.pathExists(paths.certKey)]) + const certPath = path.join(paths.data, "self-signed.cert") + const certKeyPath = path.join(paths.data, "self-signed.key") + + const checks = await Promise.all([fs.pathExists(certPath), fs.pathExists(certKeyPath)]) if (!checks[0] || !checks[1]) { // Require on demand so openssl isn't required if you aren't going to // generate certificates. @@ -69,10 +68,13 @@ export const generateCertificate = async (): Promise<{ cert: string; certKey: st return error ? reject(error) : resolve(result) }) }) - await fs.mkdirp(tmpdir) - await Promise.all([fs.writeFile(paths.cert, certs.certificate), fs.writeFile(paths.certKey, certs.serviceKey)]) + await fs.mkdirp(paths.data) + await Promise.all([fs.writeFile(certPath, certs.certificate), fs.writeFile(certKeyPath, certs.serviceKey)]) + } + return { + cert: certPath, + certKey: certKeyPath, } - return paths } export const generatePassword = async (length = 24): Promise => { From 8b85006996847580b10092c4b57697f2c3eef5e4 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 30 Oct 2020 04:35:08 -0400 Subject: [PATCH 2/6] src/node/util.ts: Make certificate generation "modern" Now we add a subject alt name, set extendedKeyUsage and use the correct certificate extension. The above allow it to be properly trusted by iOS. See https://support.apple.com/en-us/HT210176 *.cert isn't a real extension for certificates, *.crt is correct for it to be recognized by e.g. keychain or when importing as a profile into iOS. Updates #1566 I've been able to successfully connect from my iPad Pro now to my code-server instance with a self signed certificate! Next commit will be docs. --- doc/FAQ.md | 2 +- src/node/util.ts | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/doc/FAQ.md b/doc/FAQ.md index 694a0722..5d1407d1 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -145,7 +145,7 @@ pass in an existing certificate by providing the path to `--cert` and the path t the key with `--cert-key`. The self signed certificate will be generated into -`~/.local/share/code-server/self-signed.cert`. +`~/.local/share/code-server/self-signed.crt`. If `code-server` has been passed a certificate it will also respond to HTTPS requests and will redirect all HTTP requests to HTTPS. diff --git a/src/node/util.ts b/src/node/util.ts index ee1e85be..20880913 100644 --- a/src/node/util.ts +++ b/src/node/util.ts @@ -55,7 +55,7 @@ export function humanPath(p?: string): string { } export const generateCertificate = async (): Promise<{ cert: string; certKey: string }> => { - const certPath = path.join(paths.data, "self-signed.cert") + const certPath = path.join(paths.data, "self-signed.crt") const certKeyPath = path.join(paths.data, "self-signed.key") const checks = await Promise.all([fs.pathExists(certPath), fs.pathExists(certKeyPath)]) @@ -64,9 +64,25 @@ export const generateCertificate = async (): Promise<{ cert: string; certKey: st // generate certificates. const pem = require("pem") as typeof import("pem") const certs = await new Promise((resolve, reject): void => { - pem.createCertificate({ selfSigned: true }, (error, result) => { - return error ? reject(error) : resolve(result) - }) + pem.createCertificate( + { + selfSigned: true, + config: ` +[req] +req_extensions = v3_req + +[ v3_req ] +extendedKeyUsage = serverAuth +subjectAltName = @alt_names + +[alt_names] +DNS.1 = localhost +`, + }, + (error, result) => { + return error ? reject(error) : resolve(result) + }, + ) }) await fs.mkdirp(paths.data) await Promise.all([fs.writeFile(certPath, certs.certificate), fs.writeFile(certKeyPath, certs.serviceKey)]) From bae28727bd00e02601b618fe3efe720a675a3e3a Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 30 Oct 2020 05:26:40 -0400 Subject: [PATCH 3/6] src/node/cli.ts: Add --cert-host to configure generated certificate hostname --- src/node/cli.ts | 7 ++++++- src/node/entry.ts | 2 +- src/node/util.ts | 9 +++++---- test/socket.test.ts | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/node/cli.ts b/src/node/cli.ts index 1403d892..4ff35b44 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -26,6 +26,7 @@ export interface Args extends VsArgs { readonly auth?: AuthType readonly password?: string readonly cert?: OptionalString + readonly "cert-host"?: string readonly "cert-key"?: string readonly "disable-telemetry"?: boolean readonly help?: boolean @@ -101,7 +102,11 @@ const options: Options> = { cert: { type: OptionalString, path: true, - description: "Path to certificate. Generated if no path is provided.", + description: "Path to certificate. A self signed certificate is generated if none is provided.", + }, + "cert-host": { + type: "string", + description: "Hostname to use when generating a self signed certificate.", }, "cert-key": { type: "string", path: true, description: "Path to certificate key when using non-generated cert." }, "disable-telemetry": { type: "boolean", description: "Disable telemetry." }, diff --git a/src/node/entry.ts b/src/node/entry.ts index 3fbbb4cf..5184c434 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -160,7 +160,7 @@ const main = async (args: Args, configArgs: Args): Promise => { proxyDomains: args["proxy-domain"], socket: args.socket, ...(args.cert && !args.cert.value - ? await generateCertificate() + ? await generateCertificate(args["cert-host"] || "localhost") : { cert: args.cert && args.cert.value, certKey: args["cert-key"], diff --git a/src/node/util.ts b/src/node/util.ts index 20880913..b4e175a3 100644 --- a/src/node/util.ts +++ b/src/node/util.ts @@ -54,9 +54,9 @@ export function humanPath(p?: string): string { return p.replace(os.homedir(), "~") } -export const generateCertificate = async (): Promise<{ cert: string; certKey: string }> => { - const certPath = path.join(paths.data, "self-signed.crt") - const certKeyPath = path.join(paths.data, "self-signed.key") +export const generateCertificate = async (hostname: string): Promise<{ cert: string; certKey: string }> => { + const certPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.crt`) + const certKeyPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.key`) const checks = await Promise.all([fs.pathExists(certPath), fs.pathExists(certKeyPath)]) if (!checks[0] || !checks[1]) { @@ -67,6 +67,7 @@ export const generateCertificate = async (): Promise<{ cert: string; certKey: st pem.createCertificate( { selfSigned: true, + commonName: hostname, config: ` [req] req_extensions = v3_req @@ -76,7 +77,7 @@ extendedKeyUsage = serverAuth subjectAltName = @alt_names [alt_names] -DNS.1 = localhost +DNS.1 = ${hostname} `, }, (error, result) => { diff --git a/test/socket.test.ts b/test/socket.test.ts index 7d4de985..b1e974ad 100644 --- a/test/socket.test.ts +++ b/test/socket.test.ts @@ -45,7 +45,7 @@ describe("SocketProxyProvider", () => { } before(async () => { - const cert = await generateCertificate() + const cert = await generateCertificate("localhost") const options = { cert: fs.readFileSync(cert.cert), key: fs.readFileSync(cert.certKey), From a1b61d165935e647e16570c304feb7b3df801afd Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 30 Oct 2020 05:42:42 -0400 Subject: [PATCH 4/6] src/node/util.ts: Mark generated certificates as CA Required for access under iPad. --- src/node/util.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node/util.ts b/src/node/util.ts index b4e175a3..b50916b5 100644 --- a/src/node/util.ts +++ b/src/node/util.ts @@ -73,6 +73,7 @@ export const generateCertificate = async (hostname: string): Promise<{ cert: str req_extensions = v3_req [ v3_req ] +basicConstraints = CA:true extendedKeyUsage = serverAuth subjectAltName = @alt_names From 31306f7fddda269a35da2beef78468a92725cb24 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 30 Oct 2020 04:48:51 -0400 Subject: [PATCH 5/6] docs: Add iPad self signed certificate documentation Closes #1816 Closes #1566 --- ci/dev/fmt.sh | 1 + doc/FAQ.md | 5 +++++ doc/ipad.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 doc/ipad.md diff --git a/ci/dev/fmt.sh b/ci/dev/fmt.sh index d3bd4191..47911aad 100755 --- a/ci/dev/fmt.sh +++ b/ci/dev/fmt.sh @@ -26,6 +26,7 @@ main() { doctoc --title '# Install' doc/install.md > /dev/null doctoc --title '# npm Install Requirements' doc/npm.md > /dev/null doctoc --title '# Contributing' doc/CONTRIBUTING.md > /dev/null + doctoc --title '# iPad' doc/ipad.md > /dev/null if [[ ${CI-} && $(git ls-files --other --modified --exclude-standard) ]]; then echo "Files need generation or are formatted incorrectly:" diff --git a/doc/FAQ.md b/doc/FAQ.md index 5d1407d1..25e7a13d 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -3,6 +3,7 @@ # FAQ - [Questions?](#questions) +- [iPad Status?](#ipad-status) - [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration) - [Differences compared to VS Code?](#differences-compared-to-vs-code) - [How can I request a missing extension?](#how-can-i-request-a-missing-extension) @@ -33,6 +34,10 @@ Please file all questions and support requests at https://github.com/cdr/code-server/discussions. +## iPad Status? + +Please see [./ipad.md](./ipad.md). + ## How can I reuse my VS Code configuration? The very popular [Settings Sync](https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync) extension works. diff --git a/doc/ipad.md b/doc/ipad.md new file mode 100644 index 00000000..e190000e --- /dev/null +++ b/doc/ipad.md @@ -0,0 +1,48 @@ + + +# iPad + +- [iPad](#ipad) + - [How to access code-server with a self signed certificate on iPad](#how-to-access-code-server-with-a-self-signed-certificate-on-ipad) + + + +# iPad + +## How to access code-server with a self signed certificate on iPad + +Accessing a self signed certificate on iPad isn't as easy as accepting through all +the security warnings. Safari will prevent WebSocket connections unless the certificate +is installed as a profile on the device. + +The below assumes you are using the self signed certificate that code-server +generates for you. If not, that's fine but you'll have to make sure your certificate +abides by the following guidelines from Apple: https://support.apple.com/en-us/HT210176 + +**note**: Another undocumented requirement we noticed is that the certificate has to have `basicConstraints=CA:true`. + +The following instructions assume you have code-server installed and running +with a self signed certificate. If not, please first go through [./guide.md](./guide.md)! + +**warning**: Your iPad must access code-server via a domain name. It could be local +DNS like `mymacbookpro.local` but it must be a domain name. Otherwise Safari will +refuse to allow WebSockets to connect. + +1. Your certificate **must** have a subject alt name that matches the hostname + at which you will access code-server from your iPad. You can pass this to code-server + so that it generates the certificate correctly with `--cert-host`. +2. Share your self signed certificate with the iPad. + - code-server will print the location of the certificate it has generated in the logs. + +``` +[2020-10-30T08:55:45.139Z] info - Using generated certificate and key for HTTPS: ~/.local/share/code-server/mymbp_local.crt +``` + +- You can mail it to yourself or if you have a Mac, it's easiest to just Airdrop to the iPad. + +3. When opening the `*.crt` file, you'll be prompted to go into settings to install. +4. Go to `Settings -> General -> Profile`, select the profile and then hit `Install`. + - It should say the profile is verified. +5. Go to `Settings -> About -> Certificate Trust Settings` and enable full trust for + the certificate. +6. Now you can access code-server! 🍻 From c07296cce0e448803491aa72a1ceeb2219eee387 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Fri, 30 Oct 2020 06:08:42 -0400 Subject: [PATCH 6/6] docs: Add known issues to iPad docs and add more links to iPad docs Closes #1816 --- doc/FAQ.md | 10 ---------- doc/guide.md | 3 +-- doc/ipad.md | 12 ++++++++++-- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/doc/FAQ.md b/doc/FAQ.md index 25e7a13d..1a6a217d 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -22,7 +22,6 @@ - [Heartbeat File](#heartbeat-file) - [Healthz endpoint](#healthz-endpoint) - [How does the config file work?](#how-does-the-config-file-work) -- [Blank screen on iPad?](#blank-screen-on-ipad) - [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure) - [How do I make my keyboard shortcuts work?](#how-do-i-make-my-keyboard-shortcuts-work) - [Differences compared to Theia?](#differences-compared-to-theia) @@ -287,15 +286,6 @@ The `--config` flag or `$CODE_SERVER_CONFIG` can be used to change the config fi The default location also respects `$XDG_CONFIG_HOME`. -## Blank screen on iPad? - -Unfortunately at the moment self signed certificates cause a blank screen on iPadOS - -There does seem to be a way to get it to work if you create your own CA and create a -certificate using the CA and then import the CA onto your iPad. - -See [#1566](https://github.com/cdr/code-server/issues/1566#issuecomment-623159434). - ## Isn't an install script piped into sh insecure? Please give diff --git a/doc/guide.md b/doc/guide.md index 8782f57f..ce17a361 100644 --- a/doc/guide.md +++ b/doc/guide.md @@ -251,8 +251,7 @@ Visit `https://` to access `code-server`. Congratulations! ### Self Signed Certificate -**note:** Self signed certificates do not work with iPad and will cause a blank page. You'll -have to use [Let's Encrypt](#lets-encrypt) instead. See the [FAQ](./FAQ.md#blank-screen-on-ipad). +**note:** Self signed certificates do not work with iPad normally. See [./ipad.md](./ipad.md) for details. Recommended reading: https://security.stackexchange.com/a/8112. diff --git a/doc/ipad.md b/doc/ipad.md index e190000e..1bda0bd4 100644 --- a/doc/ipad.md +++ b/doc/ipad.md @@ -3,13 +3,21 @@ # iPad - [iPad](#ipad) - - [How to access code-server with a self signed certificate on iPad](#how-to-access-code-server-with-a-self-signed-certificate-on-ipad) + - [Known Issues](#known-issues) + - [How to access code-server with a self signed certificate on iPad?](#how-to-access-code-server-with-a-self-signed-certificate-on-ipad) # iPad -## How to access code-server with a self signed certificate on iPad +## Known Issues + +- Getting self signed certificates certificates to work is involved, see below. +- Keyboard may disappear sometimes [#1313](https://github.com/cdr/code-server/issues/1313), [#979](https://github.com/cdr/code-server/issues/979) +- Trackpad scrolling does not work [#1455](https://github.com/cdr/code-server/issues/1455) +- See [issues tagged with the iPad label](https://github.com/cdr/code-server/issues?q=is%3Aopen+is%3Aissue+label%3AiPad) for more. + +## How to access code-server with a self signed certificate on iPad? Accessing a self signed certificate on iPad isn't as easy as accepting through all the security warnings. Safari will prevent WebSocket connections unless the certificate