Chocobozzz / PeerTube

ActivityPub-federated video streaming platform using P2P directly in your web browser
https://joinpeertube.org/
GNU Affero General Public License v3.0
13.07k stars 1.51k forks source link

Openssl v 3.0.0 support #4901

Closed GnunuX closed 2 years ago

GnunuX commented 2 years ago

Describe the current behavior

The "openssl genrsa" command generates certificates in pkcs8 since version 3.0.0 of openssl:

The command line utilities genrsa and rsa have been modified to use PKEY APIs. They now write PKCS#8 keys by default. These commands are now in maintenance mode and no new features will be added to them.

pem project support openssl 3.0.0 since 1.14.5: https://github.com/Dexus/pem/commit/7ec825744b9cf1a9cab37983302fa6d7da8a9b22

Unfortunately Peertube has dependency for "pem" version "^1.12.3"

The error message:

**2022-04-06 12:50:01.850 error: Cannot install application. {
"err": {
  "stack": "Error: RSA PRIVATE KEY not found from openssl output:\n---stdout---\n-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCpryg9sx4wBQkF\nVy2vUZRFB6vmtTVomtpC4/P3r1sc1/JsJ6buu0oOV5/N8vWryzKROBWfEk9AmThQ\nMTakjYU+9Iku5doOEseXdZiw7zxz4mCT7fnOnP954FMEJZTO/Y8xP6hcw9p7E/Py\nedjExwD2k/BxKxOvJqc3MuaYO34GKtsELgDiiJWCsxOQHyUDutkAcWY5ZZXQUFiR\nzepQyjh/RKy3yq8eVwmZ7BtrQ53gd+nJlzUhR6+PEPLOztGH1FmRrdXya6pRwhEf\n6e4NPdrceEhLLovGQgs7Yub/CyvAgVPKGj9lwzEHJkUkxKlfd0Jb97gmChFJYCeQ\nsrzvB/MvAgMBAAECggEAHSWu0mZlvZbn1UiD2vU29GKcdy1SZs0Ox+WzPy0aiApa\nJJTtGl4Gmw8XLr/YznFBlutc3228Olr3buL9QWfj5wwHUox+F+PG8C/tkhsONdPB\nhjefLQwP4GYVrIUuGh45DhsitwiXi4PCaDjg8PBELJb9+tqM743CtHskXCr/+1vy\nd6SMnSYSr8e6IrPvt17u3kjlXGSrha7lricjx1eO6z00M7omQcP8xMqhUSuL6FIq\nMvN1pSS/wH+tUZnBiG3yajknQhLUk8jwOMPYrfTBo3fPB3SgcgVCbOYYRA7gORwB\nkFN9awoRX2eQ1DvcE4g1PyyRncldWtbhKTF01mqiPQKBgQDEcGrANmkes7J0Cy99\nZNYBDk5sTjLsqTO8GDR0/M1jn9lzjfCUZ2fOUelZh1V9ql6epyiPmVrAokB3iCI9\nzoh2BxCCaboEOGPjKVpC8Gl8Eex0f4Qz4HbLgnvW8VoiMDjht9AhC0m8DYpYJQfg\nMvoopKXne4Lu+xcEQO6XgXP+tQKBgQDdIga3sOq4s8XUaj+oRe4a1O6UPWdaJ+6n\n711bZKA88uG/YP1LpHmg+zrpjdxi9Rf/MLiP5CD9CoyPZR8vg5hvwT8udkLGS/+/\n+X5AIonzPyvojTGpjhdIXCqzROnTOcvKSQZBc9pg5VlKhf4RA14ZVQowCACwh+1h\nZCZ5l9d00wKBgQDD6ubdK6cuAsUBrcJAithRl0YqCNgLZzn86Bsxbo0eRmgnMrJ2\nIKfMg697Jlniio/yJf9zMNztcSVg0fpssFczeBpEJ1RqLx+YiT7HsmVSY8Hz8tYU\ns1Nn54zBtFdT78pbfOtseYihsNOA0/YF6nHLJWN9+pZ86RpsmKIdovPgXQKBgDOw\nW2+enA8WfnybZUvZJMIC2c2u2LS7Ns7sZZ8SU15Ly7bnXe3LgV6+utOTBFvhPH27\nDrkbsxZS2+48zeBWW1BsFG+w1wTxCNP/5qHpMOO59XKDZoNiolCYsqZ6yklEbj1X\nem3cPcR+d7OyxGOHrPww45O8mySocTVdqpyrgIJfAoGAVR3xLYV9FYD14adgyHfF\nVbHk/zou3GjgONNhtIhBk5FUWj9VsMKb0uZcsI9yTprPpjwc2uhQztzCFoUwxqPw\nCTkfsCr6cZt/09+4FssdT7356e91b8s/xhkkwf82ZRgyMbvIl82V3d0hm1/gC0/U\nEA7ATpxmz5Yh16/tS5iHPIA=\n-----END PRIVATE KEY-----\n\n---stderr---\n\ncode: 0\n    at /usr/share/peertube/node_modules/pem/lib/openssl.js:80:23\n    at /usr/share/peertube/node_modules/pem/lib/openssl.js:224:7\n    at deleteSeries (/usr/share/peertube/node_modules/pem/lib/helper.js:130:14)\n    at Object.module.exports.deleteTempFiles (/usr/share/peertube/node_modules/pem/lib/helper.js:133:3)\n    at /usr/share/peertube/node_modules/pem/lib/openssl.js:223:12\n    at done (/usr/share/peertube/node_modules/pem/lib/openssl.js:162:18)\n    at ChildProcess.<anonymous> (/usr/share/peertube/node_modules/pem/lib/openssl.js:177:7)\n    at ChildProcess.emit (node:events:526:28)\n    at ChildProcess.emit (node:domain:475:12)\n    at maybeClose (node:internal/child_process:1092:16)\n    at Socket.<anonymous> (node:internal/child_process:451:11)\n    at Socket.emit (node:events:526:28)\n    at Socket.emit (node:domain:475:12)\n    at Pipe.<anonymous> (node:net:687:12)",
  "message": "RSA PRIVATE KEY not found from openssl output:\n---stdout---\n-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCpryg9sx4wBQkF\nVy2vUZRFB6vmtTVomtpC4/P3r1sc1/JsJ6buu0oOV5/N8vWryzKROBWfEk9AmThQ\nMTakjYU+9Iku5doOEseXdZiw7zxz4mCT7fnOnP954FMEJZTO/Y8xP6hcw9p7E/Py\nedjExwD2k/BxKxOvJqc3MuaYO34GKtsELgDiiJWCsxOQHyUDutkAcWY5ZZXQUFiR\nzepQyjh/RKy3yq8eVwmZ7BtrQ53gd+nJlzUhR6+PEPLOztGH1FmRrdXya6pRwhEf\n6e4NPdrceEhLLovGQgs7Yub/CyvAgVPKGj9lwzEHJkUkxKlfd0Jb97gmChFJYCeQ\nsrzvB/MvAgMBAAECggEAHSWu0mZlvZbn1UiD2vU29GKcdy1SZs0Ox+WzPy0aiApa\nJJTtGl4Gmw8XLr/YznFBlutc3228Olr3buL9QWfj5wwHUox+F+PG8C/tkhsONdPB\nhjefLQwP4GYVrIUuGh45DhsitwiXi4PCaDjg8PBELJb9+tqM743CtHskXCr/+1vy\nd6SMnSYSr8e6IrPvt17u3kjlXGSrha7lricjx1eO6z00M7omQcP8xMqhUSuL6FIq\nMvN1pSS/wH+tUZnBiG3yajknQhLUk8jwOMPYrfTBo3fPB3SgcgVCbOYYRA7gORwB\nkFN9awoRX2eQ1DvcE4g1PyyRncldWtbhKTF01mqiPQKBgQDEcGrANmkes7J0Cy99\nZNYBDk5sTjLsqTO8GDR0/M1jn9lzjfCUZ2fOUelZh1V9ql6epyiPmVrAokB3iCI9\nzoh2BxCCaboEOGPjKVpC8Gl8Eex0f4Qz4HbLgnvW8VoiMDjht9AhC0m8DYpYJQfg\nMvoopKXne4Lu+xcEQO6XgXP+tQKBgQDdIga3sOq4s8XUaj+oRe4a1O6UPWdaJ+6n\n711bZKA88uG/YP1LpHmg+zrpjdxi9Rf/MLiP5CD9CoyPZR8vg5hvwT8udkLGS/+/\n+X5AIonzPyvojTGpjhdIXCqzROnTOcvKSQZBc9pg5VlKhf4RA14ZVQowCACwh+1h\nZCZ5l9d00wKBgQDD6ubdK6cuAsUBrcJAithRl0YqCNgLZzn86Bsxbo0eRmgnMrJ2\nIKfMg697Jlniio/yJf9zMNztcSVg0fpssFczeBpEJ1RqLx+YiT7HsmVSY8Hz8tYU\ns1Nn54zBtFdT78pbfOtseYihsNOA0/YF6nHLJWN9+pZ86RpsmKIdovPgXQKBgDOw\nW2+enA8WfnybZUvZJMIC2c2u2LS7Ns7sZZ8SU15Ly7bnXe3LgV6+utOTBFvhPH27\nDrkbsxZS2+48zeBWW1BsFG+w1wTxCNP/5qHpMOO59XKDZoNiolCYsqZ6yklEbj1X\nem3cPcR+d7OyxGOHrPww45O8mySocTVdqpyrgIJfAoGAVR3xLYV9FYD14adgyHfF\nVbHk/zou3GjgONNhtIhBk5FUWj9VsMKb0uZcsI9yTprPpjwc2uhQztzCFoUwxqPw\nCTkfsCr6cZt/09+4FssdT7356e91b8s/xhkkwf82ZRgyMbvIl82V3d0hm1/gC0/U\nEA7ATpxmz5Yh16/tS5iHPIA=\n-----END PRIVATE KEY-----\n\n---stderr---\n\ncode: 0"
  }
}**

Steps to reproduce:

1.Install openssl v3.0.0

  1. Install peertube

Describe the expected behavior

Additional information

GnunuX commented 2 years ago

Same problem in isActorPrivateKeyValid function.

Chocobozzz commented 2 years ago

Hi,

PeerTube already uses latest pem dependency. :thinking: You can check your version in /usr/share/peertube/node_modules/pem/package.json

GnunuX commented 2 years ago

Ok... I'm not very familiar with npm dependencies.

If I undestand correctly https://github.com/Dexus/pem/issues/313 , the package 1.14.5 had OpenSSL v3 support, but this package was broken. So pem version 1.14.6 is, in fact the version 1.14.4 (without OpenSSLv3 support).

You might want to modify the isActorPrivateKeyValid function to support OpenSSL v3 when pem does.

Chocobozzz commented 2 years ago

It seems we'll have to wait a proper pem release containing openssl v3 support. Blocked by https://github.com/Dexus/pem/issues/316

BorisKourt commented 2 years ago

Is there a workaround for this at the moment?

GnunuX commented 2 years ago

Here are my patches while waiting for an official solution:

--- peertube/dist/server/helpers/custom-validators/activitypub/actor.js.ori 2022-04-06 13:58:17.752681849 +0000
+++ peertube/dist/server/helpers/custom-validators/activitypub/actor.js 2022-04-06 13:58:22.268682531 +0000
@@ -43,8 +43,8 @@
 function isActorPrivateKeyValid(privateKey) {
     return (0, misc_1.exists)(privateKey) &&
         typeof privateKey === 'string' &&
-        privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') &&
-        privateKey.includes('-----END RSA PRIVATE KEY-----') &&
+        privateKey.startsWith('-----BEGIN PRIVATE KEY-----') &&
+        privateKey.includes('-----END PRIVATE KEY-----') &&
         validator_1.default.isLength(privateKey, constants_1.CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY);
 }
 exports.isActorPrivateKeyValid = isActorPrivateKeyValid;
--- peertube/node_modules/pem/lib/pem.js.ori    2022-04-06 13:59:36.232693763 +0000
+++ peertube/node_modules/pem/lib/pem.js    2022-04-06 13:59:48.916695687 +0000
@@ -74,7 +74,7 @@

   params.push(keyBitsize)

-  openssl.exec(params, 'RSA PRIVATE KEY', function (sslErr, key) {
+  openssl.exec(params, 'PRIVATE KEY', function (sslErr, key) {
     function done (err) {
       if (err) {
         return callback(err)

Hope this will be useful to you.

nicfab commented 2 years ago

Hi,

PeerTube already uses latest pem dependency. 🤔 You can check your version in /usr/share/peertube/node_modules/pem/package.json

After the Peertube installation, there is no folder peertube under /usr/share Have I missed something?

nicfab commented 2 years ago

Here are my patches while waiting for an official solution:

--- peertube/dist/server/helpers/custom-validators/activitypub/actor.js.ori 2022-04-06 13:58:17.752681849 +0000
+++ peertube/dist/server/helpers/custom-validators/activitypub/actor.js 2022-04-06 13:58:22.268682531 +0000
@@ -43,8 +43,8 @@
 function isActorPrivateKeyValid(privateKey) {
     return (0, misc_1.exists)(privateKey) &&
         typeof privateKey === 'string' &&
-        privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') &&
-        privateKey.includes('-----END RSA PRIVATE KEY-----') &&
+        privateKey.startsWith('-----BEGIN PRIVATE KEY-----') &&
+        privateKey.includes('-----END PRIVATE KEY-----') &&
         validator_1.default.isLength(privateKey, constants_1.CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY);
 }
 exports.isActorPrivateKeyValid = isActorPrivateKeyValid;
--- peertube/node_modules/pem/lib/pem.js.ori    2022-04-06 13:59:36.232693763 +0000
+++ peertube/node_modules/pem/lib/pem.js    2022-04-06 13:59:48.916695687 +0000
@@ -74,7 +74,7 @@

   params.push(keyBitsize)

-  openssl.exec(params, 'RSA PRIVATE KEY', function (sslErr, key) {
+  openssl.exec(params, 'PRIVATE KEY', function (sslErr, key) {
     function done (err) {
       if (err) {
         return callback(err)

Hope this will be useful to you.

How can I set your workaround? I have the same issue

nicfab commented 2 years ago

Any news? I continue having the same issue with the error during the user account creation. I hope to have a reply from @GnunuX @Chocobozzz I don't have this resource /usr/share/peertube/node_modules/pem/package.json on my server, although I followed the installing instructions.

noellabo commented 2 years ago

@nicfab Find the file to be patched.

If you followed the installation instructions, you may find it here.

/var/www/peertube/peertube-latest/server/helpers/custom-validators/activitypub/actor.ts

/var/www/peertube/peertube-latest/node_modules/pem/lib/pem.js

Next, apply the changes listed. Simply remove the string 'RSA ' in three places.

nicfab commented 2 years ago

activitypub/

Thank you. Indeed, I was quite inattentive; the paths were in the previous message. I'm sorry.

However, I found only the second one (/var/www/peertube/peertube-latest/node_modules/pem/lib/pem.js). On my server, there isn't /var/www/peertube/peertube-latest/server/helpers/custom-validators/activitypub/actor.ts Should I worry about it?

Chocobozzz commented 2 years ago

Should I worry about it?

No, /var/www/peertube/peertube-latest/server/helpers/custom-validators/activitypub/actor.ts is a source file so it's normal that it's not available on prod servers.

nicfab commented 2 years ago

Should I worry about it?

No, /var/www/peertube/peertube-latest/server/helpers/custom-validators/activitypub/actor.ts is a source file so it's normal that it's not available on prod servers.

Thank you. Anyway, I remove 'RSA' from /var/www/peertube/peertube-latest/node_modules/pem/lib/pem.js but I continue receiving the same error. Here is that file. Can you tell if I did something wrong?

'use strict'

/**
 * pem module
 *
 * @module pem
 */

const { promisify } = require('es6-promisify')
var net = require('net')
var helper = require('./helper.js')
var openssl = require('./openssl.js')

module.exports.createPrivateKey = createPrivateKey
module.exports.createDhparam = createDhparam
module.exports.createEcparam = createEcparam
module.exports.createCSR = createCSR
module.exports.createCertificate = createCertificate
module.exports.readCertificateInfo = readCertificateInfo
module.exports.getPublicKey = getPublicKey
module.exports.getFingerprint = getFingerprint
module.exports.getModulus = getModulus
module.exports.getDhparamInfo = getDhparamInfo
module.exports.createPkcs12 = createPkcs12
module.exports.readPkcs12 = readPkcs12
module.exports.verifySigningChain = verifySigningChain
module.exports.checkCertificate = checkCertificate
module.exports.checkPkcs12 = checkPkcs12
module.exports.config = config

/**
 * quick access the convert module
 * @type {module:convert}
 */
module.exports.convert = require('./convert.js')

var KEY_START = '-----BEGIN PRIVATE KEY-----'
var KEY_END = '-----END PRIVATE KEY-----'
var RSA_KEY_START = '-----BEGIN RSA PRIVATE KEY-----'
var RSA_KEY_END = '-----END RSA PRIVATE KEY-----'
var ENCRYPTED_KEY_START = '-----BEGIN ENCRYPTED PRIVATE KEY-----'
var ENCRYPTED_KEY_END = '-----END ENCRYPTED PRIVATE KEY-----'
var CERT_START = '-----BEGIN CERTIFICATE-----'
var CERT_END = '-----END CERTIFICATE-----'

/**
 * Creates a private key
 *
 * @static
 * @param {Number} [keyBitsize=2048] Size of the key, defaults to 2048bit
 * @param {Object} [options] object of cipher and password {cipher:'aes128',password:'xxx'}, defaults empty object
 * @param {String} [options.cipher] string of the cipher for the encryption - needed with password
 * @param {String} [options.password] string of the cipher password for the encryption needed with cipher
 * @param {Function} callback Callback function with an error object and {key}
 */
function createPrivateKey (keyBitsize, options, callback) {
  if (!callback && !options && typeof keyBitsize === 'function') {
    callback = keyBitsize
    keyBitsize = undefined
    options = {}
  } else if (!callback && keyBitsize && typeof options === 'function') {
    callback = options
    options = {}
  }

  keyBitsize = Number(keyBitsize) || 2048

  var params = ['genrsa']
  var delTempPWFiles = []

  if (options && options.cipher && (Number(helper.ciphers.indexOf(options.cipher)) !== -1) && options.password) {
    helper.createPasswordFile({ cipher: options.cipher, password: options.password, passType: 'out' }, params, delTempPWFiles)
  }

  params.push(keyBitsize)

  openssl.exec(params, 'PRIVATE KEY', function (sslErr, key) {
    function done (err) {
      if (err) {
        return callback(err)
      }
      callback(null, {
        key: key
      })
    }
    helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
      done(sslErr || fsErr)
    })
  })
}

/**
 * Creates a dhparam key
 *
 * @static
 * @param {Number} [keyBitsize=512] Size of the key, defaults to 512bit
 * @param {Function} callback Callback function with an error object and {dhparam}
 */
function createDhparam (keyBitsize, callback) {
  if (!callback && typeof keyBitsize === 'function') {
    callback = keyBitsize
    keyBitsize = undefined
  }

  keyBitsize = Number(keyBitsize) || 512

  var params = ['dhparam',
    '-outform',
    'PEM',
    keyBitsize
  ]

  openssl.exec(params, 'DH PARAMETERS', function (error, dhparam) {
    if (error) {
      return callback(error)
    }
    return callback(null, {
      dhparam: dhparam
    })
  })
}

/**
 * Creates a ecparam key
 * @static
 * @param {String} [keyName=secp256k1] Name of the key, defaults to secp256k1
 * @param {String} [paramEnc=explicit] Encoding of the elliptic curve parameters, defaults to explicit
 * @param {Boolean} [noOut=false] This option inhibits the output of the encoded version of the parameters.
 * @param {Function} callback Callback function with an error object and {ecparam}
 */
function createEcparam (keyName, paramEnc, noOut, callback) {
  if (!callback && typeof noOut === 'undefined' && !paramEnc && typeof keyName === 'function') {
    callback = keyName
    keyName = undefined
  } else if (!callback && typeof noOut === 'undefined' && keyName && typeof paramEnc === 'function') {
    callback = paramEnc
    paramEnc = undefined
  } else if (!callback && typeof noOut === 'function' && keyName && paramEnc) {
    callback = noOut
    noOut = undefined
  }

  keyName = keyName || 'secp256k1'
  paramEnc = paramEnc || 'explicit'
  noOut = noOut || false

  var params = ['ecparam',
    '-name',
    keyName,
    '-genkey',
    '-param_enc',
    paramEnc
  ]

  var searchString = 'EC PARAMETERS'
  if (noOut) {
    params.push('-noout')
    searchString = 'EC PRIVATE KEY'
  }

  openssl.exec(params, searchString, function (error, ecparam) {
    if (error) {
      return callback(error)
    }
    return callback(null, {
      ecparam: ecparam
    })
  })
}

/**
 * Creates a Certificate Signing Request
 * If client key is undefined, a new key is created automatically. The used key is included
 * in the callback return as clientKey
 * @static
 * @param {Object} [options] Optional options object
 * @param {String} [options.clientKey] Optional client key to use
 * @param {Number} [options.keyBitsize] If clientKey is undefined, bit size to use for generating a new key (defaults to 2048)
 * @param {String} [options.hash] Hash function to use (either md5 sha1 or sha256, defaults to sha256)
 * @param {String} [options.country] CSR country field
 * @param {String} [options.state] CSR state field
 * @param {String} [options.locality] CSR locality field
 * @param {String} [options.organization] CSR organization field
 * @param {String} [options.organizationUnit] CSR organizational unit field
 * @param {String} [options.commonName='localhost'] CSR common name field
 * @param {String} [options.emailAddress] CSR email address field
 * @param {String} [options.csrConfigFile] CSR config file
 * @param {Array}  [options.altNames] is a list of subjectAltNames in the subjectAltName field
 * @param {Function} callback Callback function with an error object and {csr, clientKey}
 */
function createCSR (options, callback) {
  if (!callback && typeof options === 'function') {
    callback = options
    options = undefined
  }

  options = options || {}

  // http://stackoverflow.com/questions/14089872/why-does-node-js-accept-ip-addresses-in-certificates-only-for-san-not-for-cn
  if (options.commonName && (net.isIPv4(options.commonName) || net.isIPv6(options.commonName))) {
    if (!options.altNames) {
      options.altNames = [options.commonName]
    } else if (options.altNames.indexOf(options.commonName) === -1) {
      options.altNames = options.altNames.concat([options.commonName])
    }
  }

  if (!options.clientKey) {
    createPrivateKey(options.keyBitsize || 2048, function (error, keyData) {
      if (error) {
        return callback(error)
      }
      options.clientKey = keyData.key
      createCSR(options, callback)
    })
    return
  }

  var params = ['req',
    '-new',
    '-' + (options.hash || 'sha256')
  ]

  if (options.csrConfigFile) {
    params.push('-config')
    params.push(options.csrConfigFile)
  } else {
    params.push('-subj')
    params.push(generateCSRSubject(options))
  }

  params.push('-key')
  params.push('--TMPFILE--')

  var tmpfiles = [options.clientKey]
  var config = null

  if (options.altNames && Array.isArray(options.altNames) && options.altNames.length) {
    params.push('-extensions')
    params.push('v3_req')
    params.push('-config')
    params.push('--TMPFILE--')
    var altNamesRep = []
    for (var i = 0; i < options.altNames.length; i++) {
      altNamesRep.push((net.isIP(options.altNames[i]) ? 'IP' : 'DNS') + '.' + (i + 1) + ' = ' + options.altNames[i])
    }

    tmpfiles.push(config = [
      '[req]',
      'req_extensions = v3_req',
      'distinguished_name = req_distinguished_name',
      '[v3_req]',
      'subjectAltName = @alt_names',
      '[alt_names]',
      altNamesRep.join('\n'),
      '[req_distinguished_name]',
      'commonName = Common Name',
      'commonName_max = 64'
    ].join('\n'))
  } else if (options.config) {
    config = options.config
  }

  var delTempPWFiles = []
  if (options.clientKeyPassword) {
    helper.createPasswordFile({ cipher: '', password: options.clientKeyPassword, passType: 'in' }, params, delTempPWFiles)
  }

  openssl.exec(params, 'CERTIFICATE REQUEST', tmpfiles, function (sslErr, data) {
    function done (err) {
      if (err) {
        return callback(err)
      }
      callback(null, {
        csr: data,
        config: config,
        clientKey: options.clientKey
      })
    }
    helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
      done(sslErr || fsErr)
    })
  })
}

/**
 * Creates a certificate based on a CSR. If CSR is not defined, a new one
 * will be generated automatically. For CSR generation all the options values
 * can be used as with createCSR.
 * @static
 * @param {Object} [options] Optional options object
 * @param {String} [options.serviceCertificate] PEM encoded certificate
 * @param {String} [options.serviceKey] Private key for signing the certificate, if not defined a new one is generated
 * @param {String} [options.serviceKeyPassword] Password of the service key
 * @param {Boolean} [options.selfSigned] If set to true and serviceKey is not defined, use clientKey for signing
 * @param {String|Number} [options.serial] Set a serial max. 20 octets - only together with options.serviceCertificate
 * @param {String} [options.serialFile] Set the name of the serial file, without extension. - only together with options.serviceCertificate and never in tandem with options.serial
 * @param {String} [options.hash] Hash function to use (either md5 sha1 or sha256, defaults to sha256)
 * @param {String} [options.csr] CSR for the certificate, if not defined a new one is generated
 * @param {Number} [options.days] Certificate expire time in days
 * @param {String} [options.clientKeyPassword] Password of the client key
 * @param {String} [options.extFile] extension config file - without '-extensions v3_req'
 * @param {String} [options.config] extension config file - with '-extensions v3_req'
 * @param {String} [options.csrConfigFile] CSR config file - only used if no options.csr is provided
 * @param {Array}  [options.altNames] is a list of subjectAltNames in the subjectAltName field - only used if no options.csr is provided
 * @param {Function} callback Callback function with an error object and {certificate, csr, clientKey, serviceKey}
 */
function createCertificate (options, callback) {
  if (!callback && typeof options === 'function') {
    callback = options
    options = undefined
  }

  options = options || {}

  if (!options.csr) {
    createCSR(options, function (error, keyData) {
      if (error) {
        return callback(error)
      }
      options.csr = keyData.csr
      options.config = keyData.config
      options.clientKey = keyData.clientKey
      createCertificate(options, callback)
    })
    return
  }

  if (!options.clientKey) {
    options.clientKey = ''
  }

  if (!options.serviceKey) {
    if (options.selfSigned) {
      options.serviceKey = options.clientKey
    } else {
      createPrivateKey(options.keyBitsize || 2048, function (error, keyData) {
        if (error) {
          return callback(error)
        }
        options.serviceKey = keyData.key
        createCertificate(options, callback)
      })
      return
    }
  }

  readCertificateInfo(options.csr, function (error2, data2) {
    if (error2) {
      return callback(error2)
    }

    var params = ['x509',
      '-req',
      '-' + (options.hash || 'sha256'),
      '-days',
      Number(options.days) || '365',
      '-in',
      '--TMPFILE--'
    ]
    var tmpfiles = [options.csr]
    var delTempPWFiles = []

    if (options.serviceCertificate) {
      params.push('-CA')
      params.push('--TMPFILE--')
      params.push('-CAkey')
      params.push('--TMPFILE--')
      if (options.serial) {
        params.push('-set_serial')
        if (helper.isNumber(options.serial)) {
        // set the serial to the max lenth of 20 octets ()
        // A certificate serial number is not decimal conforming. That is the
        // bytes in a serial number do not necessarily map to a printable ASCII
        // character.
        // eg: 0x00 is a valid serial number and can not be represented in a
        // human readable format (atleast one that can be directly mapped to
        // the ACSII table).
          params.push('0x' + ('0000000000000000000000000000000000000000' + options.serial.toString(16)).slice(-40))
        } else {
          if (helper.isHex(options.serial)) {
            if (options.serial.startsWith('0x')) {
              options.serial = options.serial.substring(2, options.serial.length)
            }
            params.push('0x' + ('0000000000000000000000000000000000000000' + options.serial).slice(-40))
          } else {
            params.push('0x' + ('0000000000000000000000000000000000000000' + helper.toHex(options.serial)).slice(-40))
          }
        }
      } else {
        params.push('-CAcreateserial')
        if (options.serialFile) {
          params.push('-CAserial')
          params.push(options.serialFile + '.srl')
        }
      }
      if (options.serviceKeyPassword) {
        helper.createPasswordFile({ cipher: '', password: options.serviceKeyPassword, passType: 'in' }, params, delTempPWFiles)
      }
      tmpfiles.push(options.serviceCertificate)
      tmpfiles.push(options.serviceKey)
    } else {
      params.push('-signkey')
      params.push('--TMPFILE--')
      if (options.serviceKeyPassword) {
        helper.createPasswordFile({ cipher: '', password: options.serviceKeyPassword, passType: 'in' }, params, delTempPWFiles)
      }
      tmpfiles.push(options.serviceKey)
    }

    if (options.config) {
      params.push('-extensions')
      params.push('v3_req')
      params.push('-extfile')
      params.push('--TMPFILE--')
      tmpfiles.push(options.config)
    } else if (options.extFile) {
      params.push('-extfile')
      params.push(options.extFile)
    } else {
      var altNamesRep = []
      if (data2 && data2.san) {
        for (var i = 0; i < data2.san.dns.length; i++) {
          altNamesRep.push('DNS' + '.' + (i + 1) + ' = ' + data2.san.dns[i])
        }
        for (var i2 = 0; i2 < data2.san.ip.length; i2++) {
          altNamesRep.push('IP' + '.' + (i2 + 1) + ' = ' + data2.san.ip[i2])
        }
        for (var i3 = 0; i3 < data2.san.email.length; i3++) {
          altNamesRep.push('email' + '.' + (i3 + 1) + ' = ' + data2.san.email[i3])
        }
        params.push('-extensions')
        params.push('v3_req')
        params.push('-extfile')
        params.push('--TMPFILE--')
        tmpfiles.push([
          '[v3_req]',
          'subjectAltName = @alt_names',
          '[alt_names]',
          altNamesRep.join('\n')
        ].join('\n'))
      }
    }

    if (options.clientKeyPassword) {
      helper.createPasswordFile({ cipher: '', password: options.clientKeyPassword, passType: 'in' }, params, delTempPWFiles)
    }

    openssl.exec(params, 'CERTIFICATE', tmpfiles, function (sslErr, data) {
      function done (err) {
        if (err) {
          return callback(err)
        }
        var response = {
          csr: options.csr,
          clientKey: options.clientKey,
          certificate: data,
          serviceKey: options.serviceKey
        }
        return callback(null, response)
      }

      helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
        done(sslErr || fsErr)
      })
    })
  })
}

/**
 * Exports a public key from a private key, CSR or certificate
 * @static
 * @param {String} certificate PEM encoded private key, CSR or certificate
 * @param {Function} callback Callback function with an error object and {publicKey}
 */
function getPublicKey (certificate, callback) {
  if (!callback && typeof certificate === 'function') {
    callback = certificate
    certificate = undefined
  }

  certificate = (certificate || '').toString()

  var params

  if (certificate.match(/BEGIN(\sNEW)? CERTIFICATE REQUEST/)) {
    params = ['req',
      '-in',
      '--TMPFILE--',
      '-pubkey',
      '-noout'
    ]
  } else if (certificate.match(/BEGIN RSA PRIVATE KEY/) || certificate.match(/BEGIN PRIVATE KEY/)) {
    params = ['rsa',
      '-in',
      '--TMPFILE--',
      '-pubout'
    ]
  } else {
    params = ['x509',
      '-in',
      '--TMPFILE--',
      '-pubkey',
      '-noout'
    ]
  }

  openssl.exec(params, 'PUBLIC KEY', certificate, function (error, key) {
    if (error) {
      return callback(error)
    }
    return callback(null, {
      publicKey: key
    })
  })
}

/**
 * Reads subject data from a certificate or a CSR
 * @static
 * @param {String} certificate PEM encoded CSR or certificate
 * @param {Function} callback Callback function with an error object and {country, state, locality, organization, organizationUnit, commonName, emailAddress}
 */
function readCertificateInfo (certificate, callback) {
  if (!callback && typeof certificate === 'function') {
    callback = certificate
    certificate = undefined
  }

  certificate = (certificate || '').toString()
  var isMatch = certificate.match(/BEGIN(\sNEW)? CERTIFICATE REQUEST/)
  var type = isMatch ? 'req' : 'x509'
  var params = [type,
    '-noout',
    '-nameopt',
    'RFC2253,sep_multiline,space_eq,-esc_msb,utf8',
    '-text',
    '-in',
    '--TMPFILE--'
  ]
  openssl.spawnWrapper(params, certificate, function (err, code, stdout, stderr) {
    if (err) {
      return callback(err)
    } else if (stderr) {
      return callback(stderr)
    }
    return fetchCertificateData(stdout, callback)
  })
}

/**
 * get the modulus from a certificate, a CSR or a private key
 * @static
 * @param {String} certificate PEM encoded, CSR PEM encoded, or private key
 * @param {String} [password] password for the certificate
 * @param {String} [hash] hash function to use (up to now `md5` supported) (default: none)
 * @param {Function} callback Callback function with an error object and {modulus}
 */
function getModulus (certificate, password, hash, callback) {
  if (!callback && !hash && typeof password === 'function') {
    callback = password
    password = undefined
    hash = false
  } else if (!callback && hash && typeof hash === 'function') {
    callback = hash
    hash = false
    // password will be falsy if not provided
  }
  // adding hash function to params, is not supported by openssl.
  // process piping would be the right way (... | openssl md5)
  // No idea how this can be achieved in easy with the current build in methods
  // of pem.
  if (hash && hash !== 'md5') {
    hash = false
  }

  certificate = (Buffer.isBuffer(certificate) && certificate.toString()) || certificate

  var type = ''
  if (certificate.match(/BEGIN(\sNEW)? CERTIFICATE REQUEST/)) {
    type = 'req'
  } else if (certificate.match(/BEGIN RSA PRIVATE KEY/) || certificate.match(/BEGIN PRIVATE KEY/)) {
    type = 'rsa'
  } else {
    type = 'x509'
  }
  var params = [
    type,
    '-noout',
    '-modulus',
    '-in',
    '--TMPFILE--'
  ]
  var delTempPWFiles = []
  if (password) {
    helper.createPasswordFile({ cipher: '', password: password, passType: 'in' }, params, delTempPWFiles)
  }

  openssl.spawnWrapper(params, certificate, function (sslErr, code, stdout, stderr) {
    function done (err) {
      if (err) {
        return callback(err)
      }
      var match = stdout.match(/Modulus=([0-9a-fA-F]+)$/m)
      if (match) {
        return callback(null, {
          modulus: hash ? require(hash)(match[1]) : match[1]
        })
      } else {
        return callback(new Error('No modulus'))
      }
    }
    helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
      done(sslErr || fsErr || stderr)
    })
  })
}

/**
 * get the size and prime of DH parameters
 * @static
 * @param {String} DH parameters PEM encoded
 * @param {Function} callback Callback function with an error object and {size, prime}
 */
function getDhparamInfo (dh, callback) {
  dh = (Buffer.isBuffer(dh) && dh.toString()) || dh

  var params = [
    'dhparam',
    '-text',
    '-in',
    '--TMPFILE--'
  ]

  openssl.spawnWrapper(params, dh, function (err, code, stdout, stderr) {
    if (err) {
      return callback(err)
    } else if (stderr) {
      return callback(stderr)
    }

    var result = {}
    var match = stdout.match(/Parameters: \((\d+) bit\)/)

    if (match) {
      result.size = Number(match[1])
    }

    var prime = ''
    stdout.split('\n').forEach(function (line) {
      if (/\s+([0-9a-f][0-9a-f]:)+[0-9a-f]?[0-9a-f]?/g.test(line)) {
        prime += line.trim()
      }
    })

    if (prime) {
      result.prime = prime
    }

    if (!match && !prime) {
      return callback(new Error('No DH info found'))
    }

    return callback(null, result)
  })
}

/**
 * config the pem module
 * @static
 * @param {Object} options
 */
function config (options) {
  Object.keys(options).forEach(function (k) {
    openssl.set(k, options[k])
  })
}

/**
 * Gets the fingerprint for a certificate
 * @static
 * @param {String} PEM encoded certificate
 * @param {String} [hash] hash function to use (either `md5`, `sha1` or `sha256`, defaults to `sha1`)
 * @param {Function} callback Callback function with an error object and {fingerprint}
 */
function getFingerprint (certificate, hash, callback) {
  if (!callback && typeof hash === 'function') {
    callback = hash
    hash = undefined
  }

  hash = hash || 'sha1'

  var params = ['x509',
    '-in',
    '--TMPFILE--',
    '-fingerprint',
    '-noout',
    '-' + hash
  ]

  openssl.spawnWrapper(params, certificate, function (err, code, stdout, stderr) {
    if (err) {
      return callback(err)
    } else if (stderr) {
      return callback(stderr)
    }
    var match = stdout.match(/Fingerprint=([0-9a-fA-F:]+)$/m)
    if (match) {
      return callback(null, {
        fingerprint: match[1]
      })
    } else {
      return callback(new Error('No fingerprint'))
    }
  })
}

/**
 * Export private key and certificate to a PKCS12 keystore
 * @static
 * @param {String} PEM encoded private key
 * @param {String} PEM encoded certificate
 * @param {String} Password of the result PKCS12 file
 * @param {Object} [options] object of cipher and optional client key password {cipher:'aes128', clientKeyPassword: 'xxxx', certFiles: ['file1','file2']}
 * @param {Function} callback Callback function with an error object and {pkcs12}
 */
function createPkcs12 (key, certificate, password, options, callback) {
  if (!callback && typeof options === 'function') {
    callback = options
    options = {}
  }

  var params = ['pkcs12', '-export']
  var delTempPWFiles = []

  if (options.cipher && options.clientKeyPassword) {
    // NOTICE: The password field is needed! self if it is empty.
    // create password file for the import "-passin"
    helper.createPasswordFile({ cipher: options.cipher, password: options.clientKeyPassword, passType: 'in' }, params, delTempPWFiles)
  }
  // NOTICE: The password field is needed! self if it is empty.
  // create password file for the password "-password"
  helper.createPasswordFile({ cipher: '', password: password, passType: 'word' }, params, delTempPWFiles)

  params.push('-in')
  params.push('--TMPFILE--')
  params.push('-inkey')
  params.push('--TMPFILE--')

  var tmpfiles = [certificate, key]

  if (options.certFiles) {
    tmpfiles.push(options.certFiles.join(''))

    params.push('-certfile')
    params.push('--TMPFILE--')
  }

  openssl.execBinary(params, tmpfiles, function (sslErr, pkcs12) {
    function done (err) {
      if (err) {
        return callback(err)
      }
      return callback(null, {
        pkcs12: pkcs12
      })
    }
    helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
      done(sslErr || fsErr)
    })
  })
}

/**
 * read sslcert data from Pkcs12 file. Results are provided in callback response in object notation ({cert: .., ca:..., key:...})
 * @static
 * @param  {Buffer|String}   bufferOrPath Buffer or path to file
 * @param  {Object}   [options]      openssl options
 * @param  {Function} callback     Called with error object and sslcert bundle object
 */
function readPkcs12 (bufferOrPath, options, callback) {
  if (!callback && typeof options === 'function') {
    callback = options
    options = {}
  }

  options.p12Password = options.p12Password || ''

  var tmpfiles = []
  var delTempPWFiles = []
  var args = ['pkcs12', '-in', bufferOrPath]

  helper.createPasswordFile({ cipher: '', password: options.p12Password, passType: 'in' }, args, delTempPWFiles)

  if (Buffer.isBuffer(bufferOrPath)) {
    tmpfiles = [bufferOrPath]
    args[2] = '--TMPFILE--'
  }

  if (options.clientKeyPassword) {
    helper.createPasswordFile({ cipher: '', password: options.clientKeyPassword, passType: 'out' }, args, delTempPWFiles)
  } else {
    args.push('-nodes')
  }

  openssl.execBinary(args, tmpfiles, function (sslErr, stdout) {
    function done (err) {
      var keybundle = {}

      if (err && err.message.indexOf('No such file or directory') !== -1) {
        err.code = 'ENOENT'
      }

      if (!err) {
        var certs = readFromString(stdout, CERT_START, CERT_END)
        keybundle.cert = certs.shift()
        keybundle.ca = certs
        keybundle.key = readFromString(stdout, KEY_START, KEY_END).pop()

        if (keybundle.key) {
        // convert to RSA key
          return openssl.exec(['rsa', '-in', '--TMPFILE--'], 'RSA PRIVATE KEY', [keybundle.key], function (err, key) {
            keybundle.key = key

            return callback(err, keybundle)
          })
        }

        if (options.clientKeyPassword) {
          keybundle.key = readFromString(stdout, ENCRYPTED_KEY_START, ENCRYPTED_KEY_END).pop()
        } else {
          keybundle.key = readFromString(stdout, RSA_KEY_START, RSA_KEY_END).pop()
        }
      }

      return callback(err, keybundle)
    }
    helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
      done(sslErr || fsErr)
    })
  })
}

/**
 * Check a certificate
 * @static
 * @param {String} PEM encoded certificate
 * @param {String} [passphrase] password for the certificate
 * @param {Function} callback Callback function with an error object and a boolean valid
 */
function checkCertificate (certificate, passphrase, callback) {
  var params
  var delTempPWFiles = []

  if (!callback && typeof passphrase === 'function') {
    callback = passphrase
    passphrase = undefined
  }
  certificate = (certificate || '').toString()

  if (certificate.match(/BEGIN(\sNEW)? CERTIFICATE REQUEST/)) {
    params = ['req', '-text', '-noout', '-verify', '-in', '--TMPFILE--']
  } else if (certificate.match(/BEGIN RSA PRIVATE KEY/) || certificate.match(/BEGIN PRIVATE KEY/)) {
    params = ['rsa', '-noout', '-check', '-in', '--TMPFILE--']
  } else {
    params = ['x509', '-text', '-noout', '-in', '--TMPFILE--']
  }
  if (passphrase) {
    helper.createPasswordFile({ cipher: '', password: passphrase, passType: 'in' }, params, delTempPWFiles)
  }

  openssl.spawnWrapper(params, certificate, function (sslErr, code, stdout, stderr) {
    function done (err) {
      if (err && err.toString().trim() !== 'verify OK') {
        return callback(err)
      }
      var result
      switch (params[0]) {
        case 'rsa':
          result = /^Rsa key ok$/i.test(stdout.trim())
          break
        default:
          result = /Signature Algorithm/im.test(stdout)
          break
      }

      callback(null, result)
    }
    helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
      done(sslErr || fsErr || stderr)
    })
  })
}

/**
 * check a PKCS#12 file (.pfx or.p12)
 * @static
 * @param {Buffer|String} bufferOrPath PKCS#12 certificate
 * @param {String} [passphrase] optional passphrase which will be used to open the keystore
 * @param {Function} callback Callback function with an error object and a boolean valid
 */
function checkPkcs12 (bufferOrPath, passphrase, callback) {
  if (!callback && typeof passphrase === 'function') {
    callback = passphrase
    passphrase = ''
  }

  var tmpfiles = []
  var delTempPWFiles = []
  var args = ['pkcs12', '-info', '-in', bufferOrPath, '-noout', '-maciter', '-nodes']

  helper.createPasswordFile({ cipher: '', password: passphrase, passType: 'in' }, args, delTempPWFiles)

  if (Buffer.isBuffer(bufferOrPath)) {
    tmpfiles = [bufferOrPath]
    args[3] = '--TMPFILE--'
  }

  openssl.spawnWrapper(args, tmpfiles, function (sslErr, code, stdout, stderr) {
    function done (err) {
      if (err) {
        return callback(err)
      }
      callback(null, (/MAC verified OK/im.test(stderr) || (!(/MAC verified OK/im.test(stderr)) && !(/Mac verify error/im.test(stderr)))))
    }
    helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
      done(sslErr || fsErr)
    })
  })
}

/**
 * Verifies the signing chain of the passed certificate
 * @static
 * @param {String|Array} PEM encoded certificate include intermediate certificates
 * @param {String|Array} [List] of CA certificates
 * @param {Function} callback Callback function with an error object and a boolean valid
 */
function verifySigningChain (certificate, ca, callback) {
  if (!callback && typeof ca === 'function') {
    callback = ca
    ca = undefined
  }
  if (!Array.isArray(certificate)) {
    certificate = [certificate]
  }
  if (!Array.isArray(ca) && ca !== undefined) {
    if (ca !== '') {
      ca = [ca]
    }
  }

  var files = []

  if (ca !== undefined) {
    // ca certificates
    files.push(ca.join('\n'))
  }
  // certificate incl. intermediate certificates
  files.push(certificate.join('\n'))

  var params = ['verify']

  if (ca !== undefined) {
    // ca certificates
    params.push('-CAfile')
    params.push('--TMPFILE--')
  }
  // certificate incl. intermediate certificates
  params.push('--TMPFILE--')

  openssl.spawnWrapper(params, files, function (err, code, stdout, stderr) {
    if (err) {
      return callback(err)
    }

    callback(null, stdout.trim().slice(-4) === ': OK')
  })
}

// HELPER FUNCTIONS
function fetchCertificateData (certData, callback) {
  // try catch : if something will fail in parsing it won't crash the calling code
  try {
    certData = (certData || '').toString()

    var serial, subject, tmp, issuer
    var certValues = {
      issuer: {}
    }
    var validity = {}
    var san

    var ky, i

    // serial
    if ((serial = certData.match(/\s*Serial Number:\r?\n?\s*([^\r\n]*)\r?\n\s*\b/)) && serial.length > 1) {
      certValues.serial = serial[1]
    }

    if ((subject = certData.match(/\s*Subject:\r?\n(\s*(([a-zA-Z0-9.]+)\s=\s[^\r\n]+\r?\n))*\s*\b/)) && subject.length > 1) {
      subject = subject[0]
      tmp = matchAll(subject, /\s([a-zA-Z0-9.]+)\s=\s([^\r\n].*)/g)
      if (tmp) {
        for (i = 0; i < tmp.length; i++) {
          ky = tmp[i][1].trim()
          if (ky.match('(C|ST|L|O|OU|CN|emailAddress|DC)') || ky === '') {
            continue
          }
          certValues[ky] = tmp[i][2].trim()
        }
      }

      // country
      tmp = subject.match(/\sC\s=\s([^\r\n].*?)[\r\n]/)
      certValues.country = (tmp && tmp[1]) || ''

      // state
      tmp = subject.match(/\sST\s=\s([^\r\n].*?)[\r\n]/)
      certValues.state = (tmp && tmp[1]) || ''

      // locality
      tmp = subject.match(/\sL\s=\s([^\r\n].*?)[\r\n]/)
      certValues.locality = (tmp && tmp[1]) || ''

      // organization
      tmp = matchAll(subject, /\sO\s=\s([^\r\n].*)/g)
      certValues.organization = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
        var e = t[1].toUpperCase()
        var r = n[1].toUpperCase()
        return r > e ? -1 : e > r ? 1 : 0
      }).sort(function (t, n) {
        return t[1].length - n[1].length
      }).map(function (t) {
        return t[1]
      }) : tmp[0][1]) : ''

      // unit
      tmp = matchAll(subject, /\sOU\s=\s([^\r\n].*)/g)
      certValues.organizationUnit = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
        var e = t[1].toUpperCase()
        var r = n[1].toUpperCase()
        return r > e ? -1 : e > r ? 1 : 0
      }).sort(function (t, n) {
        return t[1].length - n[1].length
      }).map(function (t) {
        return t[1]
      }) : tmp[0][1]) : ''

      // common name
      tmp = matchAll(subject, /\sCN\s=\s([^\r\n].*)/g)
      certValues.commonName = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
        var e = t[1].toUpperCase()
        var r = n[1].toUpperCase()
        return r > e ? -1 : e > r ? 1 : 0
      }).sort(function (t, n) {
        return t[1].length - n[1].length
      }).map(function (t) {
        return t[1]
      }) : tmp[0][1]) : ''

      // email
      tmp = matchAll(subject, /emailAddress\s=\s([^\r\n].*)/g)
      certValues.emailAddress = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
        var e = t[1].toUpperCase()
        var r = n[1].toUpperCase()
        return r > e ? -1 : e > r ? 1 : 0
      }).sort(function (t, n) {
        return t[1].length - n[1].length
      }).map(function (t) {
        return t[1]
      }) : tmp[0][1]) : ''

      // DC name
      tmp = matchAll(subject, /\sDC\s=\s([^\r\n].*)/g)
      certValues.dc = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
        var e = t[1].toUpperCase()
        var r = n[1].toUpperCase()
        return r > e ? -1 : e > r ? 1 : 0
      }).sort(function (t, n) {
        return t[1].length - n[1].length
      }).map(function (t) {
        return t[1]
      }) : tmp[0][1]) : ''
    }

    if ((issuer = certData.match(/\s*Issuer:\r?\n(\s*([a-zA-Z0-9.]+)\s=\s[^\r\n].*\r?\n)*\s*\b/)) && issuer.length > 1) {
      issuer = issuer[0]
      tmp = matchAll(issuer, /\s([a-zA-Z0-9.]+)\s=\s([^\r\n].*)/g)
      for (i = 0; i < tmp.length; i++) {
        ky = tmp[i][1].toString()
        if (ky.match('(C|ST|L|O|OU|CN|emailAddress|DC)')) {
          continue
        }
        certValues.issuer[ky] = tmp[i][2].toString()
      }

      // country
      tmp = issuer.match(/\sC\s=\s([^\r\n].*?)[\r\n]/)
      certValues.issuer.country = (tmp && tmp[1]) || ''

      // state
      tmp = issuer.match(/\sST\s=\s([^\r\n].*?)[\r\n]/)
      certValues.issuer.state = (tmp && tmp[1]) || ''

      // locality
      tmp = issuer.match(/\sL\s=\s([^\r\n].*?)[\r\n]/)
      certValues.issuer.locality = (tmp && tmp[1]) || ''

      // organization
      tmp = matchAll(issuer, /\sO\s=\s([^\r\n].*)/g)
      certValues.issuer.organization = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
        var e = t[1].toUpperCase()
        var r = n[1].toUpperCase()
        return r > e ? -1 : e > r ? 1 : 0
      }).sort(function (t, n) {
        return t[1].length - n[1].length
      }).map(function (t) {
        return t[1]
      }) : tmp[0][1]) : ''

      // unit
      tmp = matchAll(issuer, /\sOU\s=\s([^\r\n].*)/g)
      certValues.issuer.organizationUnit = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
        var e = t[1].toUpperCase()
        var
          r = n[1].toUpperCase()
        return r > e ? -1 : e > r ? 1 : 0
      }).sort(function (t, n) {
        return t[1].length - n[1].length
      }).map(function (t) {
        return t[1]
      }) : tmp[0][1]) : ''

      // common name
      tmp = matchAll(issuer, /\sCN\s=\s([^\r\n].*)/g)
      certValues.issuer.commonName = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
        var e = t[1].toUpperCase()
        var
          r = n[1].toUpperCase()
        return r > e ? -1 : e > r ? 1 : 0
      }).sort(function (t, n) {
        return t[1].length - n[1].length
      }).map(function (t) {
        return t[1]
      }) : tmp[0][1]) : ''

      // DC name
      tmp = matchAll(issuer, /\sDC\s=\s([^\r\n].*)/g)
      certValues.issuer.dc = tmp ? (tmp.length > 1 ? tmp.sort(function (t, n) {
        var e = t[1].toUpperCase()
        var
          r = n[1].toUpperCase()
        return r > e ? -1 : e > r ? 1 : 0
      }).sort(function (t, n) {
        return t[1].length - n[1].length
      }).map(function (t) {
        return t[1]
      }) : tmp[0][1]) : ''
    }

    // SAN
    if ((san = certData.match(/X509v3 Subject Alternative Name: \r?\n([^\r\n]*)\r?\n/)) && san.length > 1) {
      san = san[1].trim() + '\n'
      certValues.san = {}

      // hostnames
      tmp = pregMatchAll('DNS:([^,\\r\\n].*?)[,\\r\\n\\s]', san)
      certValues.san.dns = tmp || ''

      // IP-Addresses IPv4 & IPv6
      tmp = pregMatchAll('IP Address:([^,\\r\\n].*?)[,\\r\\n\\s]', san)
      certValues.san.ip = tmp || ''

      // Email Addresses
      tmp = pregMatchAll('email:([^,\\r\\n].*?)[,\\r\\n\\s]', san)
      certValues.san.email = tmp || ''
    }

    // Validity
    if ((tmp = certData.match(/Not Before\s?:\s?([^\r\n]*)\r?\n/)) && tmp.length > 1) {
      validity.start = Date.parse((tmp && tmp[1]) || '')
    }

    if ((tmp = certData.match(/Not After\s?:\s?([^\r\n]*)\r?\n/)) && tmp.length > 1) {
      validity.end = Date.parse((tmp && tmp[1]) || '')
    }

    if (validity.start && validity.end) {
      certValues.validity = validity
    }
    // Validity end

    // Signature Algorithm
    if ((tmp = certData.match(/Signature Algorithm: ([^\r\n]*)\r?\n/)) && tmp.length > 1) {
      certValues.signatureAlgorithm = (tmp && tmp[1]) || ''
    }

    // Public Key
    if ((tmp = certData.match(/Public[ -]Key: ([^\r\n]*)\r?\n/)) && tmp.length > 1) {
      certValues.publicKeySize = ((tmp && tmp[1]) || '').replace(/[()]/g, '')
    }

    // Public Key Algorithm
    if ((tmp = certData.match(/Public Key Algorithm: ([^\r\n]*)\r?\n/)) && tmp.length > 1) {
      certValues.publicKeyAlgorithm = (tmp && tmp[1]) || ''
    }

    callback(null, certValues)
  } catch (err) {
    callback(err)
  }
}

function matchAll (str, regexp) {
  var matches = []
  str.replace(regexp, function () {
    var arr = ([]).slice.call(arguments, 0)
    var extras = arr.splice(-2)
    arr.index = extras[0]
    arr.input = extras[1]
    matches.push(arr)
  })
  return matches.length ? matches : null
}

function pregMatchAll (regex, haystack) {
  var globalRegex = new RegExp(regex, 'g')
  var globalMatch = haystack.match(globalRegex) || []
  var matchArray = []
  var nonGlobalRegex, nonGlobalMatch
  for (var i = 0; i < globalMatch.length; i++) {
    nonGlobalRegex = new RegExp(regex)
    nonGlobalMatch = globalMatch[i].match(nonGlobalRegex)
    matchArray.push(nonGlobalMatch[1])
  }
  return matchArray
}

function generateCSRSubject (options) {
  options = options || {}

  var csrData = {
    C: options.country || options.C,
    ST: options.state || options.ST,
    L: options.locality || options.L,
    O: options.organization || options.O,
    OU: options.organizationUnit || options.OU,
    CN: options.commonName || options.CN || 'localhost',
    DC: options.dc || options.DC || '',
    emailAddress: options.emailAddress
  }

  var csrBuilder = Object.keys(csrData).map(function (key) {
    if (csrData[key]) {
      if (typeof csrData[key] === 'object' && csrData[key].length >= 1) {
        var tmpStr = ''
        csrData[key].map(function (o) {
          tmpStr += '/' + key + '=' + o.replace(/[^\w .*\-,@']+/g, ' ').trim()
        })
        return tmpStr
      } else {
        return '/' + key + '=' + csrData[key].replace(/[^\w .*\-,@']+/g, ' ').trim()
      }
    }
  })

  return csrBuilder.join('')
}

function readFromString (string, start, end) {
  if (Buffer.isBuffer(string)) {
    string = string.toString('utf8')
  }

  var output = []

  if (!string) {
    return output
  }

  var offset = string.indexOf(start)

  while (offset !== -1) {
    string = string.substring(offset)

    var endOffset = string.indexOf(end)

    if (endOffset === -1) {
      break
    }

    endOffset += end.length

    output.push(string.substring(0, endOffset))
    offset = string.indexOf(start, endOffset)
  }

  return output
}

// promisify not tested yet
/**
 * Verifies the signing chain of the passed certificate
 * @namespace
 * @name promisified
 * @property {function}  createPrivateKey               @see createPrivateKey
 * @property {function}  createDhparam       - The default number of players.
 * @property {function}  createEcparam         - The default level for the party.
 * @property {function}  createCSR      - The default treasure.
 * @property {function}  createCertificate - How much gold the party starts with.
 */
module.exports.promisified = {
  createPrivateKey: promisify(createPrivateKey),
  createDhparam: promisify(createDhparam),
  createEcparam: promisify(createEcparam),
  createCSR: promisify(createCSR),
  createCertificate: promisify(createCertificate),
  readCertificateInfo: promisify(readCertificateInfo),
  getPublicKey: promisify(getPublicKey),
  getFingerprint: promisify(getFingerprint),
  getModulus: promisify(getModulus),
  getDhparamInfo: promisify(getDhparamInfo),
  createPkcs12: promisify(createPkcs12),
  readPkcs12: promisify(readPkcs12),
  verifySigningChain: promisify(verifySigningChain),
  checkCertificate: promisify(checkCertificate),
  checkPkcs12: promisify(checkPkcs12)
}
webbj74 commented 2 years ago

Patch by @GnunuX worked for me on an Ubuntu 22.04 installation

Chocobozzz commented 2 years ago

Hello,

We should now support openssl v3 with https://github.com/Chocobozzz/PeerTube/commit/5d7cb63ede7c4bba93954c0586f589ad9748d5ea

nicfab commented 2 years ago

Hello,

We should now support openssl v3 with 5d7cb63

Will it be available in the next release?

Chocobozzz commented 2 years ago

Will it be available in the next release?

Yes

arnaudbagnis commented 2 years ago

Hello, I just installed an instance and I have the same error. Do you know when you will release the next release?

Chocobozzz commented 2 years ago

No sorry we don't have an ETA

arnaudbagnis commented 2 years ago

What is the solution to install peertube before the release? change the version of ubuntu? change peertube version?

Chocobozzz commented 2 years ago

Try nightly builds: https://builds.joinpeertube.org/nightly/

arnaudbagnis commented 2 years ago

Good well always the same... even with the nightly version I'm waiting for the new version looking forward

Chocobozzz commented 2 years ago

what is your error?

arnaudbagnis commented 2 years ago
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.015 #033[33mwarn#033[39m: Directory of tmp should not be in the production directory of PeerTube. Please >
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.019 #033[33mwarn#033[39m: Directory of bin should not be in the production directory of PeerTube. Please >
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.020 #033[33mwarn#033[39m: Directory of avatars should not be in the production directory of PeerTube. Ple>
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.021 #033[33mwarn#033[39m: Directory of videos should not be in the production directory of PeerTube. Plea>
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.022 #033[33mwarn#033[39m: Directory of streaming_playlists should not be in the production directory of P>
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.023 #033[33mwarn#033[39m: Directory of redundancy should not be in the production directory of PeerTube. >
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.023 #033[33mwarn#033[39m: Directory of logs should not be in the production directory of PeerTube. Please>
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.024 #033[33mwarn#033[39m: Directory of previews should not be in the production directory of PeerTube. Pl>
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.025 #033[33mwarn#033[39m: Directory of thumbnails should not be in the production directory of PeerTube. >
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.026 #033[33mwarn#033[39m: Directory of torrents should not be in the production directory of PeerTube. Pl>
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.027 #033[33mwarn#033[39m: Directory of captions should not be in the production directory of PeerTube. Pl>
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.027 #033[33mwarn#033[39m: Directory of cache should not be in the production directory of PeerTube. Pleas>
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.028 #033[33mwarn#033[39m: Directory of plugins should not be in the production directory of PeerTube. Ple>
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.029 #033[33mwarn#033[39m: Directory of client_overrides should not be in the production directory of Peer>
Jul 20 21:31:22 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:22.994 #033[32minfo#033[39m: Database peertube_prod is ready.
Jul 20 21:31:24 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:24.711 #033[32minfo#033[39m: Creating application account.
Jul 20 21:31:24 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:24.723 #033[32minfo#033[39m: Creating a default OAuth Client.
Jul 20 21:31:24 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:24.733 #033[32minfo#033[39m: Creating the administrator.
Jul 20 21:31:24 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:24.760 #033[32minfo#033[39m: Client id: xxxxxxxx
Jul 20 21:31:24 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:24.761 #033[32minfo#033[39m: Client secret: xxxxxxxxxx
Jul 20 21:31:24 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:24.842 #033[32minfo#033[39m: Generating a RSA key...
Jul 20 21:31:25 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:25.096 #033[32minfo#033[39m: Generating a RSA key...
Jul 20 21:31:25 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:25.100 #033[32minfo#033[39m: Generating a RSA key...
Jul 20 21:31:25 server13 peertube[44958]: [server13.tk:9000] 2022-07-20 21:31:25.486 #033[31merror#033[39m: Cannot install application. {
Jul 20 21:31:25 server13 peertube[44958]:   "err": {
Jul 20 21:31:25 server13 peertube[44958]:     "stack": "Error: RSA PRIVATE KEY not found from openssl output:\n---stdout---\n-----BEGIN PRIVATE KEY-----\nxXXXXXXXXXXXXXXXXXXXXX>
Jul 20 21:31:25 server13 peertube[44958]:     "message": "RSA PRIVATE KEY not found from openssl output:\n---stdout---\n-----BEGIN PRIVATE KEY-----\nxXXXXXXXXXXXXXXXXXXXXXX>
Jul 20 21:31:25 server13 peertube[44958]:   }
Jul 20 21:31:25 server13 peertube[44958]: }
Jul 20 21:31:25 server13 systemd[1]: peertube.service: Main process exited, code=exited, status=255/EXCEPTION
Jul 20 21:31:25 server13 systemd[1]: peertube.service: Failed with result 'exit-code'.
Jul 20 21:31:25 server13 systemd[1]: peertube.service: Consumed 5.751s CPU time.
Jul 20 21:31:25 server13 systemd[1]: peertube.service: Scheduled restart job, restart counter is at 1.
Jul 20 21:31:25 server13 systemd[1]: Stopped PeerTube daemon.
Jul 20 21:31:25 server13 systemd[1]: peertube.service: Consumed 5.751s CPU time.
Jul 20 21:31:25 server13 systemd[1]: Started PeerTube daemon.r

After this I change root password with cli and i have another error when i try to connect to peertube

Jul 20 21:27:56 server13 peertube[40310]: [server13.tk:9000] 2022-07-20 21:27:56.557 #033[33mwarn#033[39m: Login error {
Jul 20 21:27:56 server13 peertube[40310]:   "err": {
Jul 20 21:27:56 server13 peertube[40310]:     "statusCode": 400,
Jul 20 21:27:56 server13 peertube[40310]:     "status": 400,
Jul 20 21:27:56 server13 peertube[40310]:     "code": 400,
Jul 20 21:27:56 server13 peertube[40310]:     "message": "Invalid client: client is invalid",
Jul 20 21:27:56 server13 peertube[40310]:     "name": "invalid_client",
Jul 20 21:27:56 server13 peertube[40310]:     "stack": "invalid_client: Invalid client: client is invalid\n    at new InvalidClientError (/var/www/peertube/versions/peertube-v4.2.2/nod>
Jul 20 21:27:56 server13 peertube[40310]:   }
Jul 20 21:27:56 server13 peertube[40310]: }
Chocobozzz commented 2 years ago

Are you sure you use the latest nightly build?

arnaudbagnis commented 2 years ago

Indeed that was it. I manage to generate the certificate and I see well in the root password logs. But I still get the same error when I try to login.

Jul 25 21:27:56 server13 peertube[40310]: [server13.tk:9000] 2022-07-20 21:27:56.557 #033[33mwarn#033[39m: Login error {
Jul 25 21:27:56 server13 peertube[40310]:   "err": {
Jul 25 21:27:56 server13 peertube[40310]:     "statusCode": 400,
Jul 25 21:27:56 server13 peertube[40310]:     "status": 400,
Jul 25 21:27:56 server13 peertube[40310]:     "code": 400,
Jul 25 21:27:56 server13 peertube[40310]:     "message": "Invalid client: client is invalid",
Jul 25 21:27:56 server13 peertube[40310]:     "name": "invalid_client",
Jul 25 21:27:56 server13 peertube[40310]:     "stack": "invalid_client: Invalid client: client is invalid\n    at new InvalidClientError (/var/www/peertube/versions/peertube-nightly-2022-07-25/nod>
Jul 25 21:27:56 server13 peertube[40310]:   }
Jul 25 21:27:56 server13 peertube[40310]: }
Chocobozzz commented 2 years ago

I don't think it's related. Please fill a new issue and fill the bug template