ca-signing.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. var { assert } = require('chai');
  2. var crypto = require('crypto');
  3. describe('CA signing', function () {
  4. var generate = require('../index').generate;
  5. it('should generate certificate signed by provided CA', async function () {
  6. // First generate a self-signed CA certificate
  7. const ca = await generate([
  8. { name: 'commonName', value: 'Test CA' },
  9. { name: 'organizationName', value: 'Test Organization' }
  10. ], {
  11. algorithm: 'sha256'
  12. });
  13. // Generate a certificate signed by the CA
  14. const pems = await generate([
  15. { name: 'commonName', value: 'localhost' }
  16. ], {
  17. algorithm: 'sha256',
  18. ca: {
  19. key: ca.private,
  20. cert: ca.cert
  21. }
  22. });
  23. assert.ok(!!pems.private, 'has a private key');
  24. assert.ok(!!pems.public, 'has a public key');
  25. assert.ok(!!pems.cert, 'has a certificate');
  26. assert.ok(!!pems.fingerprint, 'has fingerprint');
  27. const cert = new crypto.X509Certificate(pems.cert);
  28. const caCert = new crypto.X509Certificate(ca.cert);
  29. // Verify issuer is the CA, not self-signed
  30. assert.include(cert.issuer, 'CN=Test CA', 'issuer should be the CA');
  31. assert.include(cert.subject, 'CN=localhost', 'subject should be localhost');
  32. assert.notEqual(cert.issuer, cert.subject, 'should not be self-signed');
  33. // Verify the certificate is signed by the CA
  34. assert.isTrue(cert.verify(caCert.publicKey), 'certificate should be verified by CA public key');
  35. });
  36. it('should include Subject Alternative Name extension', async function () {
  37. const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
  38. algorithm: 'sha256'
  39. });
  40. const pems = await generate([
  41. { name: 'commonName', value: 'example.com' }
  42. ], {
  43. algorithm: 'sha256',
  44. ca: { key: ca.private, cert: ca.cert }
  45. });
  46. const cert = new crypto.X509Certificate(pems.cert);
  47. assert.include(cert.subjectAltName, 'DNS:example.com', 'should have DNS SAN matching CN');
  48. });
  49. it('should include IP SAN for localhost', async function () {
  50. const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
  51. algorithm: 'sha256'
  52. });
  53. const pems = await generate([
  54. { name: 'commonName', value: 'localhost' }
  55. ], {
  56. algorithm: 'sha256',
  57. ca: { key: ca.private, cert: ca.cert }
  58. });
  59. const cert = new crypto.X509Certificate(pems.cert);
  60. assert.include(cert.subjectAltName, 'DNS:localhost', 'should have DNS SAN');
  61. assert.include(cert.subjectAltName, 'IP Address:127.0.0.1', 'should have IP SAN for localhost');
  62. });
  63. it('should support different hash algorithms with CA signing', async function () {
  64. const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
  65. algorithm: 'sha256'
  66. });
  67. // Test sha384
  68. const pems384 = await generate([{ name: 'commonName', value: 'test384.local' }], {
  69. algorithm: 'sha384',
  70. ca: { key: ca.private, cert: ca.cert }
  71. });
  72. const cert384 = new crypto.X509Certificate(pems384.cert);
  73. assert.ok(cert384.publicKey, 'should generate sha384 CA-signed cert');
  74. // Test sha512
  75. const pems512 = await generate([{ name: 'commonName', value: 'test512.local' }], {
  76. algorithm: 'sha512',
  77. ca: { key: ca.private, cert: ca.cert }
  78. });
  79. const cert512 = new crypto.X509Certificate(pems512.cert);
  80. assert.ok(cert512.publicKey, 'should generate sha512 CA-signed cert');
  81. });
  82. it('should respect notAfterDate option with CA signing', async function () {
  83. const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
  84. algorithm: 'sha256'
  85. });
  86. const notBefore = new Date('2025-01-01T00:00:00Z');
  87. const notAfter = new Date('2025-01-31T00:00:00Z'); // 30 days validity
  88. const pems = await generate([{ name: 'commonName', value: 'short-lived.local' }], {
  89. algorithm: 'sha256',
  90. notBeforeDate: notBefore,
  91. notAfterDate: notAfter,
  92. ca: { key: ca.private, cert: ca.cert }
  93. });
  94. const cert = new crypto.X509Certificate(pems.cert);
  95. const validFrom = new Date(cert.validFrom);
  96. const validTo = new Date(cert.validTo);
  97. assert.approximately(validFrom.getTime(), notBefore.getTime(), 5000, 'should use custom notBeforeDate');
  98. assert.approximately(validTo.getTime(), notAfter.getTime(), 5000, 'should use custom notAfterDate');
  99. });
  100. it('should generate unique certificates with same CA', async function () {
  101. const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
  102. algorithm: 'sha256'
  103. });
  104. const pems1 = await generate([{ name: 'commonName', value: 'test1.local' }], {
  105. algorithm: 'sha256',
  106. ca: { key: ca.private, cert: ca.cert }
  107. });
  108. const pems2 = await generate([{ name: 'commonName', value: 'test2.local' }], {
  109. algorithm: 'sha256',
  110. ca: { key: ca.private, cert: ca.cert }
  111. });
  112. const cert1 = new crypto.X509Certificate(pems1.cert);
  113. const cert2 = new crypto.X509Certificate(pems2.cert);
  114. assert.notEqual(cert1.serialNumber, cert2.serialNumber, 'serial numbers should be unique');
  115. assert.notEqual(pems1.private, pems2.private, 'private keys should be different');
  116. });
  117. it('should work with custom keySize and CA signing', async function () {
  118. const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
  119. algorithm: 'sha256',
  120. keySize: 4096
  121. });
  122. const pems = await generate([{ name: 'commonName', value: 'bigkey.local' }], {
  123. algorithm: 'sha256',
  124. keySize: 4096,
  125. ca: { key: ca.private, cert: ca.cert }
  126. });
  127. const privateKey = crypto.createPrivateKey(pems.private);
  128. assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 4096, 'should use custom key size');
  129. const cert = new crypto.X509Certificate(pems.cert);
  130. const caCert = new crypto.X509Certificate(ca.cert);
  131. assert.isTrue(cert.verify(caCert.publicKey), 'certificate should verify with CA');
  132. });
  133. it('should support existing keyPair with CA signing', async function () {
  134. const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
  135. algorithm: 'sha256'
  136. });
  137. // Generate a key pair first
  138. const keyPair = await generate([{ name: 'commonName', value: 'keypair.local' }], {
  139. algorithm: 'sha256'
  140. });
  141. // Use existing key pair with CA signing
  142. const pems = await generate([{ name: 'commonName', value: 'reused.local' }], {
  143. algorithm: 'sha256',
  144. keyPair: {
  145. privateKey: keyPair.private,
  146. publicKey: keyPair.public
  147. },
  148. ca: { key: ca.private, cert: ca.cert }
  149. });
  150. assert.strictEqual(pems.private, keyPair.private, 'should use provided private key');
  151. assert.strictEqual(pems.public, keyPair.public, 'should use provided public key');
  152. const cert = new crypto.X509Certificate(pems.cert);
  153. const caCert = new crypto.X509Certificate(ca.cert);
  154. assert.isTrue(cert.verify(caCert.publicKey), 'certificate should verify with CA');
  155. });
  156. it('should include proper extended key usage extensions', async function () {
  157. const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
  158. algorithm: 'sha256'
  159. });
  160. const pems = await generate([{ name: 'commonName', value: 'server.local' }], {
  161. algorithm: 'sha256',
  162. ca: { key: ca.private, cert: ca.cert }
  163. });
  164. const cert = new crypto.X509Certificate(pems.cert);
  165. // Check extended key usage (OIDs)
  166. // 1.3.6.1.5.5.7.3.1 = serverAuth
  167. // 1.3.6.1.5.5.7.3.2 = clientAuth
  168. assert.include(cert.keyUsage, '1.3.6.1.5.5.7.3.1', 'should have serverAuth extended key usage');
  169. assert.include(cert.keyUsage, '1.3.6.1.5.5.7.3.2', 'should have clientAuth extended key usage');
  170. });
  171. it('should work with PKCS#1 RSA key format', async function () {
  172. // Generate a CA with PKCS#1 format key (like mkcert uses)
  173. const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
  174. modulusLength: 2048,
  175. privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
  176. publicKeyEncoding: { type: 'spki', format: 'pem' }
  177. });
  178. // Create a self-signed CA cert using the PKCS#1 key
  179. const ca = await generate([{ name: 'commonName', value: 'PKCS1 CA' }], {
  180. algorithm: 'sha256'
  181. });
  182. // Now test that we can use a PKCS#1 formatted key as CA
  183. // Convert our generated key to PKCS#1 for testing
  184. const caKeyObject = crypto.createPrivateKey(ca.private);
  185. const pkcs1Key = caKeyObject.export({ type: 'pkcs1', format: 'pem' });
  186. const pems = await generate([{ name: 'commonName', value: 'pkcs1-test.local' }], {
  187. algorithm: 'sha256',
  188. ca: {
  189. key: pkcs1Key,
  190. cert: ca.cert
  191. }
  192. });
  193. const cert = new crypto.X509Certificate(pems.cert);
  194. const caCert = new crypto.X509Certificate(ca.cert);
  195. assert.isTrue(cert.verify(caCert.publicKey), 'should work with PKCS#1 key format');
  196. });
  197. });