Skip to content

Commit

Permalink
feat: show hosts in cert in CLI (#19317)
Browse files Browse the repository at this point in the history
  • Loading branch information
sapphi-red authored Feb 5, 2025
1 parent 4d88f6c commit a5e306f
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 5 deletions.
55 changes: 55 additions & 0 deletions packages/vite/src/node/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import fs from 'node:fs'
import path from 'node:path'
import crypto from 'node:crypto'
import { describe, expect, test } from 'vitest'
import {
asyncFlatten,
bareImportRE,
extractHostnamesFromSubjectAltName,
flattenId,
generateCodeFrame,
getHash,
Expand Down Expand Up @@ -165,6 +167,59 @@ describe('resolveHostname', () => {
})
})

describe('extractHostnamesFromSubjectAltName', () => {
const testCases = [
['DNS:localhost', ['localhost']],
['DNS:localhost, DNS:foo.localhost', ['localhost', 'foo.localhost']],
['DNS:*.localhost', ['vite.localhost']],
['DNS:[::1]', []], // [::1] is skipped
['othername:"foo,bar", DNS:localhost', ['localhost']], // handle quoted correctly
] as const

for (const [input, expected] of testCases) {
test(`should extract names from subjectAltName: ${input}`, () => {
expect(extractHostnamesFromSubjectAltName(input)).toStrictEqual(expected)
})
}

test('should extract names from actual certificate', () => {
const certText = `
-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIJS9D2rIN7tA8mMA0GCSqGSIb3DQEBCwUAMGkxFDASBgNV
BAMTC2V4YW1wbGUub3JnMQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWEx
EzARBgNVBAcTCkJsYWNrc2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRl
c3QwHhcNMjUwMTMwMDQxNTI1WhcNMjUwMzAxMDQxNTI1WjBpMRQwEgYDVQQDEwtl
eGFtcGxlLm9yZzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYD
VQQHEwpCbGFja3NidXJnMQ0wCwYDVQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxNPlCqTmUZ7/F7GyFWDopqZ6
w19Y7/98B10JEeFGTAQIj/RP2UgZNcTABQDUvtkF7y+bOeoVJW7Zz8ozQYhRaDp8
CN2gXMcYeTUku/pKLXyCzHHVrOPAXTeU7sMRgLvPCrrJtx5OjvndW+O/PhohPRi3
iEpPvpM8gi7MVRGhnWVSx0/Ynx5c0+/vqyBTzrM2OX7Ufg8Nv7LaTXpCAnmIQp+f
Sqq7HZ7t6Y7laS4RApityvlnFHZ4f2cEibAKv/vXLED7bgAlGb8R1viPRdMtAPuI
MYvHBgGFjyX1fmq6Mz3aqlAscJILtbQlwty1oYyaENE0lq8+nZXQ+t6I+CIVLQID
AQABo4GZMIGWMAsGA1UdDwQEAwIC9DAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYB
BQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDCDBUBgNVHREETTBLgglsb2NhbGhvc3SC
DWZvby5sb2NhbGhvc3SCECoudml0ZS5sb2NhbGhvc3SCBVs6OjFdhwR/AAABhxD+
gAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQBi302qLCgxWsUalgc2
olFxVKob1xCciS8yUVX6HX0vza0WJ7oGW6qZsBbQtfgDwB/dHv7rwsfpjRWvFhmq
gEUrewa1h0TIC+PPTYYz4M0LOwcLIWZLZr4am1eI7YP9NDgRdhfAfM4hw20vjf2a
kYLKyRTC5+3/ly5opMq+CGLQ8/gnFxhP3ho8JYrRnqLeh3KCTGen3kmbAhD4IOJ9
lxMwFPTTWLFFjxbXjXmt5cEiL2mpcq13VCF2HmheCen37CyYIkrwK9IfLhBd5QQh
WEIBLwjKCAscrtyayXWp6zUTmgvb8PQf//3Mh2DiEngAi3WI/nL+8Y0RkqbvxBar
X2JN
-----END CERTIFICATE-----
`.trim()
const cert = new crypto.X509Certificate(certText)
expect(
extractHostnamesFromSubjectAltName(cert.subjectAltName ?? ''),
).toStrictEqual([
'localhost',
'foo.localhost',
'vite.vite.localhost', // *.vite.localhost
])
})
})

describe('posToNumber', () => {
test('simple', () => {
const actual = posToNumber('a\nb', { line: 2, column: 0 })
Expand Down
8 changes: 3 additions & 5 deletions packages/vite/src/node/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,9 @@ export async function preview(
)
}

const httpsOptions = await resolveHttpsConfig(config.server.https)
const app = connect() as Connect.Server
const httpServer = await resolveHttpServer(
config.preview,
app,
await resolveHttpsConfig(config.preview.https),
)
const httpServer = await resolveHttpServer(config.preview, app, httpsOptions)
setClientErrorHandler(httpServer, config.logger)

const options = config.preview
Expand Down Expand Up @@ -274,6 +271,7 @@ export async function preview(
server.resolvedUrls = await resolveServerUrls(
httpServer,
config.preview,
httpsOptions,
config,
)

Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,7 @@ export async function _createServer(
server.resolvedUrls = await resolveServerUrls(
httpServer,
config.server,
httpsOptions,
config,
)
if (!isRestart && config.server.open) server.openBrowser()
Expand Down
53 changes: 53 additions & 0 deletions packages/vite/src/node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from 'node:path'
import { exec } from 'node:child_process'
import crypto from 'node:crypto'
import { URL, fileURLToPath } from 'node:url'
import type { ServerOptions as HttpsServerOptions } from 'node:https'
import { builtinModules, createRequire } from 'node:module'
import { promises as dns } from 'node:dns'
import { performance } from 'node:perf_hooks'
Expand Down Expand Up @@ -982,6 +983,7 @@ export async function resolveHostname(
export async function resolveServerUrls(
server: Server,
options: CommonServerOptions,
httpsOptions: HttpsServerOptions | undefined,
config: ResolvedConfig,
): Promise<ResolvedServerUrls> {
const address = server.address()
Expand Down Expand Up @@ -1035,9 +1037,60 @@ export async function resolveServerUrls(
}
})
}

const cert =
httpsOptions?.cert && !Array.isArray(httpsOptions.cert)
? new crypto.X509Certificate(httpsOptions.cert)
: undefined
const hostnameFromCert = cert?.subjectAltName
? extractHostnamesFromSubjectAltName(cert.subjectAltName)
: []

if (hostnameFromCert.length > 0) {
const existings = new Set([...local, ...network])
local.push(
...hostnameFromCert
.map((hostname) => `https://${hostname}:${port}${base}`)
.filter((url) => !existings.has(url)),
)
}

return { local, network }
}

export function extractHostnamesFromSubjectAltName(
subjectAltName: string,
): string[] {
const hostnames: string[] = []
let remaining = subjectAltName
while (remaining) {
const nameEndIndex = remaining.indexOf(':')
const name = remaining.slice(0, nameEndIndex)
remaining = remaining.slice(nameEndIndex + 1)
if (!remaining) break

const isQuoted = remaining[0] === '"'
let value: string
if (isQuoted) {
const endQuoteIndex = remaining.indexOf('"', 1)
value = JSON.parse(remaining.slice(0, endQuoteIndex + 1))
remaining = remaining.slice(endQuoteIndex + 1)
} else {
const maybeEndIndex = remaining.indexOf(',')
const endIndex = maybeEndIndex === -1 ? remaining.length : maybeEndIndex
value = remaining.slice(0, endIndex)
remaining = remaining.slice(endIndex)
}
remaining = remaining.slice(/* for , */ 1).trimStart()

// [::1] might be included but skip it as it's already included as a local address
if (name === 'DNS' && value !== '[::1]') {
hostnames.push(value.replace('*', 'vite'))
}
}
return hostnames
}

export function arraify<T>(target: T | T[]): T[] {
return Array.isArray(target) ? target : [target]
}
Expand Down

0 comments on commit a5e306f

Please sign in to comment.