util/pem.js

'use strict'

/**
 * @module util
 */

const ber = require('asn1').Ber

// Crypto-conditions always use the same RSA exponent, namely 65537
const RSA_EXPONENT = 65537

/**
 * Utilities for RSA-related DER/PEM encoding.
 */
class Pem {
  /**
   * Convert an RSA modulus to a PEM-encoded RSAPublicKey.
   *
   * Encodes the public using the RSAPublicKey format given in
   * RFC 3447, appendix C.
   *
   * This function assumes that the exponent is 65537.
   *
   * @param {Buffer} modulus RSA public modulus.
   * @return {String} PEM-encoded RSA public key.
   */
  static modulusToPem (modulus) {
    // We expect the modulus with no leading zeros
    if (modulus[0] === 0) {
      throw new Error('Modulus may not start with zero')
    }

    // If the high bit is set, we need to prefix a zero
    if (modulus[0] & 0x80) {
      modulus = Buffer.concat([new Buffer([0]), modulus])
    }

    const berWriter = new ber.Writer()
    berWriter.startSequence()
    berWriter.writeBuffer(modulus, 2)
    berWriter.writeInt(RSA_EXPONENT)
    berWriter.endSequence()

    return (
      '-----BEGIN RSA PUBLIC KEY-----\n' +
      berWriter.buffer.toString('base64').match(/.{1,64}/g).join('\n') + '\n' +
      '-----END RSA PUBLIC KEY-----\n'
    )
  }

  /**
   * Retrieve a modulus from a PEM-encoded private key.
   *
   * @param {String} privateKey PEM-encoded RSA private key.
   * @return {Buffer} modulus RSA public modulus.
   */
  static modulusFromPrivateKey (privateKey) {
    const pem = privateKey
      .replace('-----BEGIN RSA PRIVATE KEY-----', '')
      .replace('-----END RSA PRIVATE KEY-----', '')
      .replace(/\s+|\n\r|\n|\r$/gm, '')
    const buffer = new Buffer(pem, 'base64')

    const reader = new ber.Reader(buffer)
    reader.readSequence()
    reader.readString(2, true) // version
    const modulus = reader.readString(2, true) // modulus
    return modulus[0] === 0 ? modulus.slice(1) : modulus
  }
}

module.exports = Pem