openssl.js

- Jump To … +
  lib/helper.js lib/openssl.js lib/pem.js

- # openssl.js

- ¶

      var helper = require('./helper.js')
      var cpspawn = require('child_process').spawn
      var pathlib = require('path')
      var fs = require('fs')
      var osTmpdir = require('os-tmpdir')
      var crypto = require('crypto')
      var which = require('which')
      var settings = {}
      var tempDir = process.env.PEMJS_TMPDIR || osTmpdir()

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

      /**
       * configue this openssl module
       *
       * @static
       * @param {String} option name e.g. pathOpenSSL, openSslVersion; TODO rethink nomenclature
       * @param {*} value value
       */
      function set (option, value) {
        settings[option] = value
      }

      /**
       * get configuration setting value
       *
       * @static
       * @param {String} option name
       */
      function get (option) {
        return settings[option] || null
      }

      /**
       * Spawn an openssl command
       *
       * @static
       * @param {Array} params Array of openssl command line parameters
       * @param {String} searchStr String to use to find data
       * @param {Array} [tmpfiles] list of temporary files
       * @param {Function} callback Called with (error, stdout-substring)
       */
      function exec (params, searchStr, tmpfiles, callback) {
        if (!callback && typeof tmpfiles === 'function') {
          callback = tmpfiles
          tmpfiles = false
        }

        spawnWrapper(params, tmpfiles, function (err, code, stdout, stderr) {
          var start, end

          if (err) {
            return callback(err)
          }

          if ((start = stdout.match(new RegExp('\\-+BEGIN ' + searchStr + '\\-+$', 'm')))) {
            start = start.index
          } else {
            start = -1
          }

- ¶

  To get the full EC key with parameters and private key

          if (searchStr === 'EC PARAMETERS') {
            searchStr = 'EC PRIVATE KEY'
          }

          if ((end = stdout.match(new RegExp('^\\-+END ' + searchStr + '\\-+', 'm')))) {
            end = end.index + end[0].length
          } else {
            end = -1
          }

          if (start >= 0 && end >= 0) {
            return callback(null, stdout.substring(start, end))
          } else {
            return callback(new Error(searchStr + ' not found from openssl output:\n---stdout---\n' + stdout + '\n---stderr---\n' + stderr + '\ncode: ' + code))
          }
        })
      }

      /**
       *  Spawn an openssl command and get binary output
       *
       * @static
       * @param {Array} params Array of openssl command line parameters
       * @param {Array} [tmpfiles] list of temporary files
       * @param {Function} callback Called with (error, stdout)
      */
      function execBinary (params, tmpfiles, callback) {
        if (!callback && typeof tmpfiles === 'function') {
          callback = tmpfiles
          tmpfiles = false
        }
        spawnWrapper(params, tmpfiles, true, function (err, code, stdout, stderr) {
          if (err) {
            return callback(err)
          }
          return callback(null, stdout)
        })
      }

      /**
       * Generically spawn openSSL, without processing the result
       *
       * @static
       * @param {Array}        params   The parameters to pass to openssl
       * @param {Boolean}      binary   Output of openssl is binary or text
       * @param {Function}     callback Called with (error, exitCode, stdout, stderr)
       */
      function spawn (params, binary, callback) {
        var pathBin = get('pathOpenSSL') || process.env.OPENSSL_BIN || 'openssl'

        testOpenSSLPath(pathBin, function (err) {
          if (err) {
            return callback(err)
          }
          var openssl = cpspawn(pathBin, params)
          var stderr = ''

          var stdout = (binary ? Buffer.alloc(0) : '')
          openssl.stdout.on('data', function (data) {
            if (!binary) {
              stdout += data.toString('binary')
            } else {
              stdout = Buffer.concat([stdout, data])
            }
          })

          openssl.stderr.on('data', function (data) {
            stderr += data.toString('binary')
          })

- ¶

  We need both the return code and access to all of stdout. Stdout isn’t really available until the close event fires; the timing nuance was making this fail periodically.

          var needed = 2 // wait for both exit and close.
          var code = -1
          var finished = false
          var done = function (err) {
            if (finished) {
              return
            }

            if (err) {
              finished = true
              return callback(err)
            }

            if (--needed < 1) {
              finished = true
              if (code) {
                if (code === 2 && (stderr === '' || /depth lookup: unable to/.test(stderr))) {
                  return callback(null, code, stdout, stderr)
                }
                return callback(new Error('Invalid openssl exit code: ' + code + '\n% openssl ' + params.join(' ') + '\n' + stderr), code)
              } else {
                return callback(null, code, stdout, stderr)
              }
            }
          }

          openssl.on('error', done)

          openssl.on('exit', function (ret) {
            code = ret
            done()
          })

          openssl.on('close', function () {
            stdout = (binary ? stdout : Buffer.from(stdout, 'binary').toString('utf-8'))
            stderr = Buffer.from(stderr, 'binary').toString('utf-8')
            done()
          })
        })
      }

      /**
       * Wrapper for spawn method
       *
       * @static
       * @param {Array} params The parameters to pass to openssl
       * @param {Array} [tmpfiles] list of temporary files
       * @param {Boolean} [binary] Output of openssl is binary or text
       * @param {Function} callback Called with (error, exitCode, stdout, stderr)
       */
      function spawnWrapper (params, tmpfiles, binary, callback) {
        if (!callback && typeof binary === 'function') {
          callback = binary
          binary = false
        }

        var files = []
        var delTempPWFiles = []

        if (tmpfiles) {
          tmpfiles = [].concat(tmpfiles)
          var fpath, i
          for (i = 0; i < params.length; i++) {
            if (params[i] === '--TMPFILE--') {
              fpath = pathlib.join(tempDir, crypto.randomBytes(20).toString('hex'))
              files.push({
                path: fpath,
                contents: tmpfiles.shift()
              })
              params[i] = fpath
              delTempPWFiles.push(fpath)
            }
          }
        }

        var file
        for (i = 0; i < files.length; i++) {
          file = files[i]
          fs.writeFileSync(file.path, file.contents)
        }

        spawn(params, binary, function (err, code, stdout, stderr) {
          helper.deleteTempFiles(delTempPWFiles, function (fsErr) {
            callback(err || fsErr, code, stdout, stderr)
          })
        })
      }

      /**
       * Validates the pathBin for the openssl command
       *
       * @private
       * @param {String} pathBin The path to OpenSSL Bin
       * @param {Function} callback Callback function with an error object
       */
      function testOpenSSLPath (pathBin, callback) {
        which(pathBin, function (error) {
          if (error) {
            return callback(new Error('Could not find openssl on your system on this path: ' + pathBin))
          }
          callback()
        })
      }

      /* Once PEM is imported, the openSslVersion is set with this function. */
      spawn(['version'], false, function (err, code, stdout, stderr) {
        var text = String(stdout) + '\n' + String(stderr) + '\n' + String(err)
        var tmp = text.match(/^LibreSSL/i)
        set('openSslVersion', (tmp && tmp[0] ? 'LibreSSL' : 'openssl').toUpperCase())
      })

      module.exports = {
        exec: exec,
        execBinary: execBinary,
        spawn: spawn,
        spawnWrapper: spawnWrapper,
        set: set,
        get: get
      }