generate.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.SSLGenerateCommand = void 0;
  4. const tslib_1 = require("tslib");
  5. const utils_fs_1 = require("@ionic/utils-fs");
  6. const utils_terminal_1 = require("@ionic/utils-terminal");
  7. const lodash = tslib_1.__importStar(require("lodash"));
  8. const path = tslib_1.__importStar(require("path"));
  9. const color_1 = require("../../lib/color");
  10. const errors_1 = require("../../lib/errors");
  11. const base_1 = require("./base");
  12. const DEFAULT_BITS = '2048';
  13. const DEFAULT_COUNTRY_NAME = 'US';
  14. const DEFAULT_STATE_OR_PROVINCE_NAME = 'Wisconsin';
  15. const DEFAULT_LOCALITY_NAME = 'Madison';
  16. const DEFAULT_ORGANIZATION_NAME = 'Ionic';
  17. const DEFAULT_COMMON_NAME = 'localhost';
  18. const DEFAULT_KEY_FILE = '.ionic/ssl/key.pem';
  19. const DEFAULT_CERT_FILE = '.ionic/ssl/cert.pem';
  20. class SSLGenerateCommand extends base_1.SSLBaseCommand {
  21. getDefaultKeyPath() {
  22. return path.resolve(this.project ? this.project.directory : '', DEFAULT_KEY_FILE);
  23. }
  24. getDefaultCertPath() {
  25. return path.resolve(this.project ? this.project.directory : '', DEFAULT_CERT_FILE);
  26. }
  27. async getMetadata() {
  28. const defaultKeyPath = (0, utils_terminal_1.prettyPath)(this.getDefaultKeyPath());
  29. const defaultCertPath = (0, utils_terminal_1.prettyPath)(this.getDefaultCertPath());
  30. return {
  31. name: 'generate',
  32. type: 'project',
  33. summary: 'Generates an SSL key & certificate',
  34. // TODO: document how to add trusted certs
  35. description: `
  36. Uses OpenSSL to create a self-signed certificate for ${(0, color_1.strong)('localhost')} (by default).
  37. After the certificate is generated, you will still need to add it to your system or browser as a trusted certificate.
  38. The default directory for ${(0, color_1.input)('--key-path')} and ${(0, color_1.input)('--cert-path')} is ${(0, color_1.input)('.ionic/ssl/')}.
  39. Deprecated. Developers should generate an SSL certificate locally and then configure it using their project tooling such as Vite or Angular CLI.
  40. `,
  41. options: [
  42. {
  43. name: 'key-path',
  44. summary: 'Destination of private key file',
  45. default: defaultKeyPath,
  46. spec: { value: 'path' },
  47. },
  48. {
  49. name: 'cert-path',
  50. summary: 'Destination of certificate file',
  51. default: defaultCertPath,
  52. spec: { value: 'path' },
  53. },
  54. {
  55. name: 'country-name',
  56. summary: 'The country name (C) of the SSL certificate',
  57. default: DEFAULT_COUNTRY_NAME,
  58. groups: ["advanced" /* MetadataGroup.ADVANCED */],
  59. spec: { value: 'C' },
  60. },
  61. {
  62. name: 'state-or-province-name',
  63. summary: 'The state or province name (ST) of the SSL certificate',
  64. default: DEFAULT_STATE_OR_PROVINCE_NAME,
  65. groups: ["advanced" /* MetadataGroup.ADVANCED */],
  66. spec: { value: 'ST' },
  67. },
  68. {
  69. name: 'locality-name',
  70. summary: 'The locality name (L) of the SSL certificate',
  71. default: DEFAULT_LOCALITY_NAME,
  72. groups: ["advanced" /* MetadataGroup.ADVANCED */],
  73. spec: { value: 'L' },
  74. },
  75. {
  76. name: 'organization-name',
  77. summary: 'The organization name (O) of the SSL certificate',
  78. default: DEFAULT_ORGANIZATION_NAME,
  79. groups: ["advanced" /* MetadataGroup.ADVANCED */],
  80. spec: { value: 'O' },
  81. },
  82. {
  83. name: 'common-name',
  84. summary: 'The common name (CN) of the SSL certificate',
  85. default: DEFAULT_COMMON_NAME,
  86. groups: ["advanced" /* MetadataGroup.ADVANCED */],
  87. spec: { value: 'CN' },
  88. },
  89. {
  90. name: 'bits',
  91. summary: 'Number of bits in the key',
  92. aliases: ['b'],
  93. default: DEFAULT_BITS,
  94. groups: ["advanced" /* MetadataGroup.ADVANCED */],
  95. },
  96. ],
  97. groups: ["deprecated" /* MetadataGroup.DEPRECATED */],
  98. };
  99. }
  100. async preRun(inputs, options) {
  101. await this.checkForOpenSSL();
  102. }
  103. async run(inputs, options) {
  104. if (!this.project) {
  105. throw new errors_1.FatalException(`Cannot run ${(0, color_1.input)('ionic ssl generate')} outside a project directory.`);
  106. }
  107. const keyPath = path.resolve(options['key-path'] ? String(options['key-path']) : this.getDefaultKeyPath());
  108. const keyPathDir = path.dirname(keyPath);
  109. const certPath = path.resolve(options['cert-path'] ? String(options['cert-path']) : this.getDefaultCertPath());
  110. const certPathDir = path.dirname(certPath);
  111. const bits = options['bits'] ? String(options['bits']) : DEFAULT_BITS;
  112. const countryName = options['country-name'] ? String(options['country-name']) : DEFAULT_COUNTRY_NAME;
  113. const stateOrProvinceName = options['state-or-province-name'] ? String(options['state-or-province-name']) : DEFAULT_STATE_OR_PROVINCE_NAME;
  114. const localityName = options['locality-name'] ? String(options['locality-name']) : DEFAULT_LOCALITY_NAME;
  115. const organizationName = options['organization-name'] ? String(options['organization-name']) : DEFAULT_ORGANIZATION_NAME;
  116. const commonName = options['common-name'] ? String(options['common-name']) : DEFAULT_COMMON_NAME;
  117. await this.ensureDirectory(keyPathDir);
  118. await this.ensureDirectory(certPathDir);
  119. const overwriteKeyPath = await this.checkExistingFile(keyPath);
  120. const overwriteCertPath = await this.checkExistingFile(certPath);
  121. if (overwriteKeyPath) {
  122. await (0, utils_fs_1.unlink)(keyPath);
  123. }
  124. if (overwriteCertPath) {
  125. await (0, utils_fs_1.unlink)(certPath);
  126. }
  127. const cnf = { bits, countryName, stateOrProvinceName, localityName, organizationName, commonName };
  128. const cnfPath = await this.writeConfig(cnf);
  129. await this.env.shell.run('openssl', ['req', '-x509', '-newkey', `rsa:${bits}`, '-nodes', '-subj', this.formatSubj(cnf), '-reqexts', 'SAN', '-extensions', 'SAN', '-config', cnfPath, '-days', '365', '-keyout', keyPath, '-out', certPath], {});
  130. this.env.log.nl();
  131. this.env.log.rawmsg(`Key: ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(keyPath))}\n` +
  132. `Cert: ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(certPath))}\n\n`);
  133. this.env.log.ok('Generated key & certificate!');
  134. }
  135. formatSubj(cnf) {
  136. const subjNames = new Map([
  137. ['countryName', 'C'],
  138. ['stateOrProvinceName', 'ST'],
  139. ['localityName', 'L'],
  140. ['organizationName', 'O'],
  141. ['commonName', 'CN'],
  142. ]);
  143. return '/' + lodash.toPairs(cnf).filter(([k]) => subjNames.has(k)).map(([k, v]) => `${subjNames.get(k)}=${v}`).join('/');
  144. }
  145. async ensureDirectory(p) {
  146. if (!(await (0, utils_fs_1.pathExists)(p))) {
  147. await (0, utils_fs_1.mkdirp)(p, 0o700);
  148. this.env.log.msg(`Created ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(p))} directory for you.`);
  149. }
  150. }
  151. async checkExistingFile(p) {
  152. if (await (0, utils_fs_1.pathExists)(p)) {
  153. const confirm = await this.env.prompt({
  154. type: 'confirm',
  155. name: 'confirm',
  156. message: `Key ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(p))} exists. Overwrite?`,
  157. });
  158. if (confirm) {
  159. return true;
  160. }
  161. else {
  162. throw new errors_1.FatalException(`Not overwriting ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(p))}.`);
  163. }
  164. }
  165. }
  166. async writeConfig({ bits, countryName, stateOrProvinceName, localityName, organizationName, commonName }) {
  167. const cnf = `
  168. [req]
  169. default_bits = ${bits}
  170. distinguished_name = req_distinguished_name
  171. [req_distinguished_name]
  172. countryName = ${countryName}
  173. stateOrProvinceName = ${stateOrProvinceName}
  174. localityName = ${localityName}
  175. organizationName = ${organizationName}
  176. commonName = ${commonName}
  177. [SAN]
  178. subjectAltName=DNS:${commonName}
  179. `.trim();
  180. const p = (0, utils_fs_1.tmpfilepath)('ionic-ssl');
  181. await (0, utils_fs_1.writeFile)(p, cnf, { encoding: 'utf8' });
  182. return p;
  183. }
  184. }
  185. exports.SSLGenerateCommand = SSLGenerateCommand;