Skip to content

Commit f9e0990

Browse files
authored
Merge pull request #2255 from cdr/self-signed-3b2c
Fix self signed certificate for iPad
2 parents 37b87dd + c07296c commit f9e0990

File tree

8 files changed

+107
-28
lines changed

8 files changed

+107
-28
lines changed

ci/dev/fmt.sh

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ main() {
2626
doctoc --title '# Install' doc/install.md > /dev/null
2727
doctoc --title '# npm Install Requirements' doc/npm.md > /dev/null
2828
doctoc --title '# Contributing' doc/CONTRIBUTING.md > /dev/null
29+
doctoc --title '# iPad' doc/ipad.md > /dev/null
2930

3031
if [[ ${CI-} && $(git ls-files --other --modified --exclude-standard) ]]; then
3132
echo "Files need generation or are formatted incorrectly:"

doc/FAQ.md

+8-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# FAQ
44

55
- [Questions?](#questions)
6+
- [iPad Status?](#ipad-status)
67
- [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration)
78
- [Differences compared to VS Code?](#differences-compared-to-vs-code)
89
- [How can I request a missing extension?](#how-can-i-request-a-missing-extension)
@@ -21,7 +22,6 @@
2122
- [Heartbeat File](#heartbeat-file)
2223
- [Healthz endpoint](#healthz-endpoint)
2324
- [How does the config file work?](#how-does-the-config-file-work)
24-
- [Blank screen on iPad?](#blank-screen-on-ipad)
2525
- [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure)
2626
- [How do I make my keyboard shortcuts work?](#how-do-i-make-my-keyboard-shortcuts-work)
2727
- [Differences compared to Theia?](#differences-compared-to-theia)
@@ -33,6 +33,10 @@
3333

3434
Please file all questions and support requests at https://github.com/cdr/code-server/discussions.
3535

36+
## iPad Status?
37+
38+
Please see [./ipad.md](./ipad.md).
39+
3640
## How can I reuse my VS Code configuration?
3741

3842
The very popular [Settings Sync](https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync) extension works.
@@ -144,6 +148,9 @@ For HTTPS, you can use a self signed certificate by passing in just `--cert` or
144148
pass in an existing certificate by providing the path to `--cert` and the path to
145149
the key with `--cert-key`.
146150

151+
The self signed certificate will be generated into
152+
`~/.local/share/code-server/self-signed.crt`.
153+
147154
If `code-server` has been passed a certificate it will also respond to HTTPS
148155
requests and will redirect all HTTP requests to HTTPS.
149156

@@ -279,15 +286,6 @@ The `--config` flag or `$CODE_SERVER_CONFIG` can be used to change the config fi
279286

280287
The default location also respects `$XDG_CONFIG_HOME`.
281288

282-
## Blank screen on iPad?
283-
284-
Unfortunately at the moment self signed certificates cause a blank screen on iPadOS
285-
286-
There does seem to be a way to get it to work if you create your own CA and create a
287-
certificate using the CA and then import the CA onto your iPad.
288-
289-
See [#1566](https://github.com/cdr/code-server/issues/1566#issuecomment-623159434).
290-
291289
## Isn't an install script piped into sh insecure?
292290

293291
Please give

doc/guide.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,7 @@ Visit `https://<your-domain-name>` to access `code-server`. Congratulations!
251251

252252
### Self Signed Certificate
253253

254-
**note:** Self signed certificates do not work with iPad and will cause a blank page. You'll
255-
have to use [Let's Encrypt](#lets-encrypt) instead. See the [FAQ](./FAQ.md#blank-screen-on-ipad).
254+
**note:** Self signed certificates do not work with iPad normally. See [./ipad.md](./ipad.md) for details.
256255

257256
Recommended reading: https://security.stackexchange.com/a/8112.
258257

doc/ipad.md

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
2+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
3+
# iPad
4+
5+
- [iPad](#ipad)
6+
- [Known Issues](#known-issues)
7+
- [How to access code-server with a self signed certificate on iPad?](#how-to-access-code-server-with-a-self-signed-certificate-on-ipad)
8+
9+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
10+
11+
# iPad
12+
13+
## Known Issues
14+
15+
- Getting self signed certificates certificates to work is involved, see below.
16+
- Keyboard may disappear sometimes [#1313](https://github.com/cdr/code-server/issues/1313), [#979](https://github.com/cdr/code-server/issues/979)
17+
- Trackpad scrolling does not work [#1455](https://github.com/cdr/code-server/issues/1455)
18+
- See [issues tagged with the iPad label](https://github.com/cdr/code-server/issues?q=is%3Aopen+is%3Aissue+label%3AiPad) for more.
19+
20+
## How to access code-server with a self signed certificate on iPad?
21+
22+
Accessing a self signed certificate on iPad isn't as easy as accepting through all
23+
the security warnings. Safari will prevent WebSocket connections unless the certificate
24+
is installed as a profile on the device.
25+
26+
The below assumes you are using the self signed certificate that code-server
27+
generates for you. If not, that's fine but you'll have to make sure your certificate
28+
abides by the following guidelines from Apple: https://support.apple.com/en-us/HT210176
29+
30+
**note**: Another undocumented requirement we noticed is that the certificate has to have `basicConstraints=CA:true`.
31+
32+
The following instructions assume you have code-server installed and running
33+
with a self signed certificate. If not, please first go through [./guide.md](./guide.md)!
34+
35+
**warning**: Your iPad must access code-server via a domain name. It could be local
36+
DNS like `mymacbookpro.local` but it must be a domain name. Otherwise Safari will
37+
refuse to allow WebSockets to connect.
38+
39+
1. Your certificate **must** have a subject alt name that matches the hostname
40+
at which you will access code-server from your iPad. You can pass this to code-server
41+
so that it generates the certificate correctly with `--cert-host`.
42+
2. Share your self signed certificate with the iPad.
43+
- code-server will print the location of the certificate it has generated in the logs.
44+
45+
```
46+
[2020-10-30T08:55:45.139Z] info - Using generated certificate and key for HTTPS: ~/.local/share/code-server/mymbp_local.crt
47+
```
48+
49+
- You can mail it to yourself or if you have a Mac, it's easiest to just Airdrop to the iPad.
50+
51+
3. When opening the `*.crt` file, you'll be prompted to go into settings to install.
52+
4. Go to `Settings -> General -> Profile`, select the profile and then hit `Install`.
53+
- It should say the profile is verified.
54+
5. Go to `Settings -> About -> Certificate Trust Settings` and enable full trust for
55+
the certificate.
56+
6. Now you can access code-server! 🍻

src/node/cli.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface Args extends VsArgs {
2626
readonly auth?: AuthType
2727
readonly password?: string
2828
readonly cert?: OptionalString
29+
readonly "cert-host"?: string
2930
readonly "cert-key"?: string
3031
readonly "disable-telemetry"?: boolean
3132
readonly help?: boolean
@@ -101,7 +102,11 @@ const options: Options<Required<Args>> = {
101102
cert: {
102103
type: OptionalString,
103104
path: true,
104-
description: "Path to certificate. Generated if no path is provided.",
105+
description: "Path to certificate. A self signed certificate is generated if none is provided.",
106+
},
107+
"cert-host": {
108+
type: "string",
109+
description: "Hostname to use when generating a self signed certificate.",
105110
},
106111
"cert-key": { type: "string", path: true, description: "Path to certificate key when using non-generated cert." },
107112
"disable-telemetry": { type: "boolean", description: "Disable telemetry." },

src/node/entry.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ const main = async (args: Args, configArgs: Args): Promise<void> => {
160160
proxyDomains: args["proxy-domain"],
161161
socket: args.socket,
162162
...(args.cert && !args.cert.value
163-
? await generateCertificate()
163+
? await generateCertificate(args["cert-host"] || "localhost")
164164
: {
165165
cert: args.cert && args.cert.value,
166166
certKey: args["cert-key"],
@@ -209,7 +209,7 @@ const main = async (args: Args, configArgs: Args): Promise<void> => {
209209
logger.info(
210210
args.cert && args.cert.value
211211
? ` - Using provided certificate and key for HTTPS`
212-
: ` - Using generated certificate and key for HTTPS`,
212+
: ` - Using generated certificate and key for HTTPS: ${humanPath(options.cert)}`,
213213
)
214214
} else {
215215
logger.info(" - Not serving HTTPS")

src/node/util.ts

+32-12
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,45 @@ export function humanPath(p?: string): string {
5454
return p.replace(os.homedir(), "~")
5555
}
5656

57-
export const generateCertificate = async (): Promise<{ cert: string; certKey: string }> => {
58-
const paths = {
59-
cert: path.join(tmpdir, "self-signed.cert"),
60-
certKey: path.join(tmpdir, "self-signed.key"),
61-
}
62-
const checks = await Promise.all([fs.pathExists(paths.cert), fs.pathExists(paths.certKey)])
57+
export const generateCertificate = async (hostname: string): Promise<{ cert: string; certKey: string }> => {
58+
const certPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.crt`)
59+
const certKeyPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.key`)
60+
61+
const checks = await Promise.all([fs.pathExists(certPath), fs.pathExists(certKeyPath)])
6362
if (!checks[0] || !checks[1]) {
6463
// Require on demand so openssl isn't required if you aren't going to
6564
// generate certificates.
6665
const pem = require("pem") as typeof import("pem")
6766
const certs = await new Promise<import("pem").CertificateCreationResult>((resolve, reject): void => {
68-
pem.createCertificate({ selfSigned: true }, (error, result) => {
69-
return error ? reject(error) : resolve(result)
70-
})
67+
pem.createCertificate(
68+
{
69+
selfSigned: true,
70+
commonName: hostname,
71+
config: `
72+
[req]
73+
req_extensions = v3_req
74+
75+
[ v3_req ]
76+
basicConstraints = CA:true
77+
extendedKeyUsage = serverAuth
78+
subjectAltName = @alt_names
79+
80+
[alt_names]
81+
DNS.1 = ${hostname}
82+
`,
83+
},
84+
(error, result) => {
85+
return error ? reject(error) : resolve(result)
86+
},
87+
)
7188
})
72-
await fs.mkdirp(tmpdir)
73-
await Promise.all([fs.writeFile(paths.cert, certs.certificate), fs.writeFile(paths.certKey, certs.serviceKey)])
89+
await fs.mkdirp(paths.data)
90+
await Promise.all([fs.writeFile(certPath, certs.certificate), fs.writeFile(certKeyPath, certs.serviceKey)])
91+
}
92+
return {
93+
cert: certPath,
94+
certKey: certKeyPath,
7495
}
75-
return paths
7696
}
7797

7898
export const generatePassword = async (length = 24): Promise<string> => {

test/socket.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe("SocketProxyProvider", () => {
4545
}
4646

4747
before(async () => {
48-
const cert = await generateCertificate()
48+
const cert = await generateCertificate("localhost")
4949
const options = {
5050
cert: fs.readFileSync(cert.cert),
5151
key: fs.readFileSync(cert.certKey),

0 commit comments

Comments
 (0)