Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Certificate Length 825 days Mac Catalina #39

Closed
c10ckw0rk opened this issue Oct 11, 2019 · 6 comments · Fixed by #45
Closed

Certificate Length 825 days Mac Catalina #39

c10ckw0rk opened this issue Oct 11, 2019 · 6 comments · Fixed by #45

Comments

@c10ckw0rk
Copy link

Heya,

Since upgrading to catalina max cert length has been changed to 825 days. (https://support.apple.com/en-us/HT210176). Currently the certs this package generates are 7000 days.

Happy to make a pull request to change these values, just wanted to log an issue see how you wanted to approach it.

Cheers!

@ThibaultAndreis
Copy link

ThibaultAndreis commented Oct 16, 2019

hi,
i've done a quick fix for myself but i'm not sure it's the best way to do it,
if you have a better solution i'll be happy to know it !
(at least it fix the firefox SEC_ERROR_REUSED_ISSUER_AND_SERIAL error and google NET::ERR_CERT_REVOKED error)

import path from 'path';
import {
  unlinkSync as rm,
  readFileSync as readFile,
  writeFileSync as writeFile,
  existsSync as exists
} from 'fs';
import { sync as rimraf } from 'rimraf';
import createDebug from 'debug';

import {
  rootCAKeyPath,
  rootCACertPath,
  caSelfSignConfig,
  opensslSerialFilePath,
  opensslDatabaseFilePath,
  isWindows,
  isLinux,
/////// i've added this line////////
  isMac,
///////////////////////////////////
  caVersionFile
} from './constants';
import currentPlatform from './platforms';
import { openssl, mktmp } from './utils';
import { generateKey } from './certificates';
import { Options } from './index';

const debug = createDebug('devcert:certificate-authority');

/**
 * Install the once-per-machine trusted root CA. We'll use this CA to sign
 * per-app certs.
 */
export default async function installCertificateAuthority(options: Options = {}): Promise<void> {
  debug(`Checking if older devcert install is present`);
  scrubOldInsecureVersions();

  debug(`Generating a root certificate authority`);
  let rootKeyPath = mktmp();
  let rootCertPath = mktmp();

  debug(`Generating the OpenSSL configuration needed to setup the certificate authority`);
  seedConfigFiles();

  debug(`Generating a private key`);
  generateKey(rootKeyPath);

  debug(`Generating a CA certificate`);

/////// i've added thoses lines////////
  if (isMac){
    openssl(`req -new -x509 -config "${ caSelfSignConfig }" -key "${ rootKeyPath }" -out "${ rootCertPath }" -days 825`);

  } else
  {
    openssl(`req -new -x509 -config "${ caSelfSignConfig }" -key "${ rootKeyPath }" -out "${ rootCertPath }" -days 7000`);
  }
/////////////////////////////////////////////////////

  debug('Saving certificate authority credentials');
  await saveCertificateAuthorityCredentials(rootKeyPath, rootCertPath);

  debug(`Adding the root certificate authority to trust stores`);
  await currentPlatform.addToTrustStores(rootCertPath, options);
}

/**
 * Older versions of devcert left the root certificate keys unguarded and
 * accessible by userland processes. Here, we check for evidence of this older
 * version, and if found, we delete the root certificate keys to remove the
 * attack vector.
 */
function scrubOldInsecureVersions() {
  // Use the old verion's logic for determining config directory
  let configDir: string;
  if (isWindows && process.env.LOCALAPPDATA) {
    configDir = path.join(process.env.LOCALAPPDATA, 'devcert', 'config');
  } else {
    let uid = process.getuid && process.getuid();
    let userHome = (isLinux && uid === 0) ? path.resolve('/usr/local/share') : require('os').homedir();
    configDir = path.join(userHome, '.config', 'devcert');
  }

  // Delete the root certificate keys, as well as the generated app certificates
  debug(`Checking ${ configDir } for legacy files ...`);
  [
    path.join(configDir, 'openssl.conf'),
    path.join(configDir, 'devcert-ca-root.key'),
    path.join(configDir, 'devcert-ca-root.crt'),
    path.join(configDir, 'devcert-ca-version'),
    path.join(configDir, 'certs')
  ].forEach((filepath) => {
    if (exists(filepath)) {
      debug(`Removing legacy file: ${ filepath }`)
      rimraf(filepath);
    }
  });
}

/**
 * Initializes the files OpenSSL needs to sign certificates as a certificate
 * authority, as well as our CA setup version
 */
function seedConfigFiles() {
  // This is v2 of the devcert certificate authority setup
  writeFile(caVersionFile, '2');
  // OpenSSL CA files
  writeFile(opensslDatabaseFilePath, '');
  writeFile(opensslSerialFilePath, '01');
}

export async function withCertificateAuthorityCredentials(cb: ({ caKeyPath, caCertPath }: { caKeyPath: string, caCertPath: string }) => Promise<void> | void) {
  debug(`Retrieving devcert's certificate authority credentials`);
  let tmpCAKeyPath = mktmp();
  let tmpCACertPath = mktmp();
  let caKey = await currentPlatform.readProtectedFile(rootCAKeyPath);
  let caCert = await currentPlatform.readProtectedFile(rootCACertPath);
  writeFile(tmpCAKeyPath, caKey);
  writeFile(tmpCACertPath, caCert);
  await cb({ caKeyPath: tmpCAKeyPath, caCertPath: tmpCACertPath });
  rm(tmpCAKeyPath);
  rm(tmpCACertPath);
}

async function saveCertificateAuthorityCredentials(keypath: string, certpath: string) {
  debug(`Saving devcert's certificate authority credentials`);
  let key = readFile(keypath, 'utf-8');
  let cert = readFile(certpath, 'utf-8');
  await currentPlatform.writeProtectedFile(rootCAKeyPath, key);
  await currentPlatform.writeProtectedFile(rootCACertPath, cert);
}

@Js-Brecht
Copy link
Contributor

IMHO, there should be a CA renewal process. All that needs to be done is check the certificate to ensure it's not expired, and if it then renew it with the same private key. Then each trust store needs to have its trust removed, and the renewed certificate installed.

Then, the expiration date could be set for a static 825, period, and nobody would need to worry too much about when it expires.

I feel the CA should be created with the -create_serial flag, too. That would just about eliminate any serial number collisions. I've had the same SEC_ERROR_REUSED_ISSUER_AND_SERIAL error in Firefox, and I ended up just wiping out Firefox's nssdb, letting it rebuild it, then re-importing the CA cert.

@deasems
Copy link

deasems commented Oct 28, 2019

Just upgraded to Catalina and I'm seeing my devcerts revoked in Chrome/Safari due to this issue

@webdeb
Copy link

webdeb commented Nov 8, 2019

@webdeb
Copy link

webdeb commented Nov 8, 2019

I solved it, by changing the -days parameter to 700 in the node_modules/devcert/dist/certificates.js file. That hack just works for me right now. Surely its just a temporary solution, waiting for the official fix.

@Js-Brecht
Copy link
Contributor

Could be waiting a while. There's not much activity in this repo. I wound up forking it, and I'm maintaining a releases branch with fixes until this one gets updated. That way I can just do an install using the github branch, and not have to think about hacking around in the script in node_modules; and I don't have to worry about maintaining a published version on npm.

I've opened PR #45 to fix this. We'll see how it goes 🤷‍♂

Btw, an alternative to maintaining a release on github is forking and making your changes on your local computer. Then just link to it instead of npm. I did the github release because I needed it to be publicly accessible.

Eventually, I may have to publish my own version on NPM, because there are PRs in other projects that are dependent on PRs in this, but I would very much like to avoid that, just out of respect for the author of this library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants