Skip to content

Commit

Permalink
chore: use forge partially to generate certificates (#6325)
Browse files Browse the repository at this point in the history
> watching The Matrix while hacking these certificates

Alternative to #6173

Since sharing same certificate for all users is not the best approach
I suggest to try decompose node-forge which is used internally by
selfsigned package. We can just use part of the package which already
gives a good result.

Eventually we will deprecate this feature but would be good to have less
impact on current major too.

```
du -sk dist/node
```
before: 10760
after: 9944
  • Loading branch information
TrySound committed Dec 30, 2021
1 parent aa47549 commit dd8869b
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 106 deletions.
29 changes: 0 additions & 29 deletions packages/vite/LICENSE.md
Expand Up @@ -3439,35 +3439,6 @@ Repository: git://github.com/feross/safe-buffer.git
---------------------------------------

## selfsigned
License: MIT
By: José F. Romaniello, Paolo Fragomeni, Charles Bushong
Repository: git://github.com/jfromaniello/selfsigned.git

> MIT License
>
> Copyright (c) 2013 José F. Romaniello
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
---------------------------------------

## shebang-command
License: MIT
By: Kevin Mårtensson
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/package.json
Expand Up @@ -99,6 +99,7 @@
"magic-string": "^0.25.7",
"micromatch": "^4.0.4",
"mrmime": "^1.0.0",
"node-forge": "^0.10.0",
"okie": "^1.0.1",
"open": "^8.4.0",
"periscopic": "^2.0.3",
Expand All @@ -108,7 +109,6 @@
"postcss-modules": "^4.3.0",
"resolve.exports": "^1.1.0",
"rollup-plugin-license": "^2.6.0",
"selfsigned": "^1.10.11",
"sirv": "^1.0.19",
"source-map": "^0.6.1",
"source-map-support": "^0.5.21",
Expand Down
145 changes: 145 additions & 0 deletions packages/vite/src/node/certificate.ts
@@ -0,0 +1,145 @@
// simplified fork of
// https://github.com/jfromaniello/selfsigned/blob/da38146f8d02183c35f49f91659a744a243e8707/index.js
// with inlined options and partial node-forge usage
// to achieve smaller bundle
//
// this utility create untrusted certificate which still
// allows to access page after proceeding a wall with warning
//
// should be deprecated eventually and replaced with recipes
// about generating secure trusted certificates

// @ts-ignore
import forge from 'node-forge/lib/forge'
// @ts-ignore
import 'node-forge/lib/pki'

// a hexString is considered negative if it's most significant bit is 1
// because serial numbers use ones' complement notation
// this RFC in section 4.1.2.2 requires serial numbers to be positive
// http://www.ietf.org/rfc/rfc5280.txt
function toPositiveHex(hexString: string) {
let mostSiginficativeHexAsInt = parseInt(hexString[0], 16)
if (mostSiginficativeHexAsInt < 8) {
return hexString
}

mostSiginficativeHexAsInt -= 8
return mostSiginficativeHexAsInt.toString() + hexString.substring(1)
}

export function createCertificate(): string {
const days = 30
const keySize = 2048

const extensions = [
// {
// name: 'basicConstraints',
// cA: true,
// },
{
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
},
{
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
timeStamping: true
},
{
name: 'subjectAltName',
altNames: [
{
// type 2 is DNS
type: 2,
value: 'localhost'
},
{
type: 2,
value: 'localhost.localdomain'
},
{
type: 2,
value: 'lvh.me'
},
{
type: 2,
value: '*.lvh.me'
},
{
type: 2,
value: '[::1]'
},
{
// type 7 is IP
type: 7,
ip: '127.0.0.1'
},
{
type: 7,
ip: 'fe80::1'
}
]
}
]

const attrs = [
{
name: 'commonName',
value: 'example.org'
},
{
name: 'countryName',
value: 'US'
},
{
shortName: 'ST',
value: 'Virginia'
},
{
name: 'localityName',
value: 'Blacksburg'
},
{
name: 'organizationName',
value: 'Test'
},
{
shortName: 'OU',
value: 'Test'
}
]

const keyPair = forge.pki.rsa.generateKeyPair(keySize)

const cert = forge.pki.createCertificate()

cert.serialNumber = toPositiveHex(
forge.util.bytesToHex(forge.random.getBytesSync(9))
) // the serial number can be decimal or hex (if preceded by 0x)

cert.validity.notBefore = new Date()
cert.validity.notAfter = new Date()
cert.validity.notAfter.setDate(cert.validity.notBefore.getDate() + days)

cert.setSubject(attrs)
cert.setIssuer(attrs)

cert.publicKey = keyPair.publicKey

cert.setExtensions(extensions)

const algorithm = forge.md.sha256.create()
cert.sign(keyPair.privateKey, algorithm)

const privateKeyPem = forge.pki.privateKeyToPem(keyPair.privateKey)
const certPem = forge.pki.certificateToPem(cert)

return privateKeyPem + certPem
}
79 changes: 5 additions & 74 deletions packages/vite/src/node/http.ts
Expand Up @@ -152,82 +152,13 @@ function readFileIfExists(value?: string | Buffer | any[]) {
return value
}

/**
* https://github.com/webpack/webpack-dev-server/blob/master/lib/utils/createCertificate.js
*
* Copyright JS Foundation and other contributors
* This source code is licensed under the MIT license found in the
* LICENSE file at
* https://github.com/webpack/webpack-dev-server/blob/master/LICENSE
*/
async function createCertificate() {
const { generate } = await import('selfsigned')
const pems = generate(null, {
algorithm: 'sha256',
days: 30,
keySize: 2048,
extensions: [
// {
// name: 'basicConstraints',
// cA: true,
// },
{
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
},
{
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
timeStamping: true
},
{
name: 'subjectAltName',
altNames: [
{
// type 2 is DNS
type: 2,
value: 'localhost'
},
{
type: 2,
value: 'localhost.localdomain'
},
{
type: 2,
value: 'lvh.me'
},
{
type: 2,
value: '*.lvh.me'
},
{
type: 2,
value: '[::1]'
},
{
// type 7 is IP
type: 7,
ip: '127.0.0.1'
},
{
type: 7,
ip: 'fe80::1'
}
]
}
]
})
return pems.private + pems.cert
async function createCertificateLazily() {
const { createCertificate } = await import('./certificate')
return createCertificate()
}

async function getCertificate(cacheDir?: string) {
if (!cacheDir) return await createCertificate()
if (!cacheDir) return await createCertificateLazily()

const cachePath = path.join(cacheDir, '_cert.pem')

Expand All @@ -243,7 +174,7 @@ async function getCertificate(cacheDir?: string) {

return content
} catch {
const content = await createCertificate()
const content = await createCertificateLazily()
fsp
.mkdir(cacheDir, { recursive: true })
.then(() => fsp.writeFile(cachePath, content))
Expand Down
4 changes: 2 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit dd8869b

Please sign in to comment.