Source: security/v2/certificate-v2.js

  1. /**
  2. * Copyright (C) 2017-2018 Regents of the University of California.
  3. * @author: Jeff Thompson <jefft0@remap.ucla.edu>
  4. * @author: From ndn-cxx security https://github.com/named-data/ndn-cxx/blob/master/src/security/v2/certificate.hpp
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Lesser General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. * A copy of the GNU Lesser General Public License is in the file COPYING.
  19. */
  20. /** @ignore */
  21. var Name = require('../../name.js').Name; /** @ignore */
  22. var Data = require('../../data.js').Data; /** @ignore */
  23. var KeyLocator = require('../../key-locator.js').KeyLocator; /** @ignore */
  24. var KeyLocatorType = require('../../key-locator.js').KeyLocatorType; /** @ignore */
  25. var Sha256WithRsaSignature = require('../../sha256-with-rsa-signature.js').Sha256WithRsaSignature; /** @ignore */
  26. var Sha256WithEcdsaSignature = require('../../sha256-with-ecdsa-signature.js').Sha256WithEcdsaSignature; /** @ignore */
  27. var ContentType = require('../../meta-info.js').ContentType; /** @ignore */
  28. var WireFormat = require('../../encoding/wire-format.js').WireFormat; /** @ignore */
  29. var Schedule = require('../../encrypt/schedule.js').Schedule; /** @ignore */
  30. var ValidityPeriod = require('../validity-period.js').ValidityPeriod; /** @ignore */
  31. var InvalidArgumentException = require('../security-exception.js').InvalidArgumentException;
  32. /**
  33. * CertificateV2 represents a certificate following the certificate format
  34. * naming convention.
  35. *
  36. * Overview of the NDN certificate format:
  37. *
  38. * CertificateV2 ::= DATA-TLV TLV-LENGTH
  39. * Name (= /<NameSpace>/KEY/[KeyId]/[IssuerId]/[Version])
  40. * MetaInfo (.ContentType = KEY)
  41. * Content (= X509PublicKeyContent)
  42. * SignatureInfo (= CertificateV2SignatureInfo)
  43. * SignatureValue
  44. *
  45. * X509PublicKeyContent ::= CONTENT-TLV TLV-LENGTH
  46. * BYTE+ (= public key bits in PKCS#8 format)
  47. *
  48. * CertificateV2SignatureInfo ::= SIGNATURE-INFO-TYPE TLV-LENGTH
  49. * SignatureType
  50. * KeyLocator
  51. * ValidityPeriod
  52. * ... optional critical or non-critical extension blocks ...
  53. *
  54. * An example of NDN certificate name:
  55. *
  56. * /edu/ucla/cs/yingdi/KEY/%03%CD...%F1/%9F%D3...%B7/%FD%d2...%8E
  57. * \_________________/ \___________/ \___________/\___________/
  58. * Certificate Namespace Key Id Issuer Id Version
  59. * (Identity)
  60. * \__________________________________/
  61. * Key Name
  62. *
  63. * Notes:
  64. *
  65. * - `Key Id` is an opaque name component to identify the instance of the public
  66. * key for the certificate namespace. The value of `Key ID` is controlled by
  67. * the namespace owner. The library includes helpers for generating key IDs
  68. * using an 8-byte random number, SHA-256 digest of the public key, timestamp,
  69. * and the specified numerical identifiers.
  70. *
  71. * - `Issuer Id` is sn opaque name component to identify the issuer of the
  72. * certificate. The value is controlled by the issuer. The library includes
  73. * helpers to set issuer the ID to an 8-byte random number, SHA-256 digest of
  74. * the issuer's public key, and the specified numerical identifiers.
  75. *
  76. * - `Key Name` is a logical name of the key used for management purposes. the
  77. * Key Name includes the certificate namespace, keyword `KEY`, and `KeyId`
  78. * components.
  79. *
  80. * @see https://github.com/named-data/ndn-cxx/blob/master/docs/specs/certificate-format.rst
  81. *
  82. * Create a CertificateV2 from the content in the Data packet (if not omitted).
  83. * @param {Data} data (optional) The data packet with the content to copy.
  84. * If omitted, create a CertificateV2 with content type KEY and default or
  85. * unspecified values.
  86. * @constructor
  87. */
  88. var CertificateV2 = function CertificateV2(data)
  89. {
  90. // Call the base constructor.
  91. if (data != undefined) {
  92. Data.call(this, data);
  93. this.checkFormat_();
  94. }
  95. else {
  96. Data.call(this);
  97. this.getMetaInfo().setType(ContentType.KEY);
  98. }
  99. };
  100. CertificateV2.prototype = new Data();
  101. CertificateV2.prototype.name = "CertificateV2";
  102. exports.CertificateV2 = CertificateV2;
  103. /**
  104. * Create a new CertificateV2.Error to report an error for not complying with
  105. * the certificate format.
  106. * Call with: throw new CertificateV2.Error(new Error("message")).
  107. * @constructor
  108. * @param {Error} error The exception created with new Error.
  109. */
  110. CertificateV2.Error = function CertificateV2Error(error)
  111. {
  112. if (error) {
  113. error.__proto__ = CertificateV2.Error.prototype;
  114. return error;
  115. }
  116. };
  117. CertificateV2.Error.prototype = new Error();
  118. CertificateV2.Error.prototype.name = "CertificateV2Error";
  119. CertificateV2.prototype.checkFormat_ = function()
  120. {
  121. if (!CertificateV2.isValidName(this.getName()))
  122. throw new CertificateV2.Error(new Error
  123. ("The Data Name does not follow the certificate naming convention"));
  124. if (this.getMetaInfo().getType() != ContentType.KEY)
  125. throw new CertificateV2.Error(new Error("The Data ContentType is not KEY"));
  126. if (this.getMetaInfo().getFreshnessPeriod() < 0.0)
  127. throw new CertificateV2.Error(new Error
  128. ("The Data FreshnessPeriod is not set"));
  129. if (this.getContent().size() == 0)
  130. throw new CertificateV2.Error(new Error("The Data Content is empty"));
  131. };
  132. /**
  133. * Get key name from the certificate name.
  134. * @return {Name} The key name as a new Name.
  135. */
  136. CertificateV2.prototype.getKeyName = function()
  137. {
  138. return this.getName().getPrefix(CertificateV2.KEY_ID_OFFSET + 1);
  139. };
  140. /**
  141. * Get the identity name from the certificate name.
  142. * @return {Name} The identity name as a new Name.
  143. */
  144. CertificateV2.prototype.getIdentity = function()
  145. {
  146. return this.getName().getPrefix(CertificateV2.KEY_COMPONENT_OFFSET);
  147. };
  148. /**
  149. * Get the key ID component from the certificate name.
  150. * @return {Name.Component} The key ID name component.
  151. */
  152. CertificateV2.prototype.getKeyId = function()
  153. {
  154. return this.getName().get(CertificateV2.KEY_ID_OFFSET);
  155. };
  156. /**
  157. * Get the issuer ID component from the certificate name.
  158. * @return {Name.Component} The issuer ID component.
  159. */
  160. CertificateV2.prototype.getIssuerId = function()
  161. {
  162. return this.getName().get(CertificateV2.ISSUER_ID_OFFSET);
  163. };
  164. /**
  165. * Get the public key DER encoding.
  166. * @return {Blob} The DER encoding Blob.
  167. * @throws CertificateV2.Error If the public key is not set.
  168. */
  169. CertificateV2.prototype.getPublicKey = function()
  170. {
  171. if (this.getContent().size() == 0)
  172. throw new CertificateV2.Error(new Error
  173. ("The public key is not set (the Data content is empty)"));
  174. return this.getContent();
  175. };
  176. /**
  177. * Get the certificate validity period from the SignatureInfo.
  178. * @return {ValidityPeriod} The ValidityPeriod object.
  179. * @throws InvalidArgumentException If the SignatureInfo doesn't have a
  180. * ValidityPeriod.
  181. */
  182. CertificateV2.prototype.getValidityPeriod = function()
  183. {
  184. if (!ValidityPeriod.canGetFromSignature(this.getSignature()))
  185. throw new InvalidArgumentException(new Error
  186. ("The SignatureInfo does not have a ValidityPeriod"));
  187. return ValidityPeriod.getFromSignature(this.getSignature());
  188. };
  189. /**
  190. * Check if the time falls within the validity period.
  191. * @param {number} time (optional) The time to check as milliseconds since
  192. * Jan 1, 1970 UTC. If omitted, use the current time.
  193. * @return {boolean} True if the beginning of the validity period is less than
  194. * or equal to time and time is less than or equal to the end of the validity
  195. * period.
  196. * @throws InvalidArgumentException If the SignatureInfo doesn't have a
  197. * ValidityPeriod.
  198. */
  199. CertificateV2.prototype.isValid = function(time)
  200. {
  201. return this.getValidityPeriod().isValid(time);
  202. };
  203. // TODO: getExtension
  204. /**
  205. * Override to call the base class wireDecode then check the certificate format.
  206. * @param {Blob|Buffer} input The buffer with the bytes to decode.
  207. * @param {WireFormat} wireFormat (optional) A WireFormat object used to decode
  208. * this object. If omitted, use WireFormat.getDefaultWireFormat().
  209. */
  210. CertificateV2.prototype.wireDecode = function(input, wireFormat)
  211. {
  212. wireFormat = (wireFormat || WireFormat.getDefaultWireFormat());
  213. Data.prototype.wireDecode.call(this, input, wireFormat);
  214. this.checkFormat_();
  215. };
  216. /**
  217. * Get a string representation of this certificate.
  218. * @return {string} The string representation.
  219. */
  220. CertificateV2.prototype.toString = function()
  221. {
  222. var result = "";
  223. result += "Certificate name:\n";
  224. result += " " + this.getName().toUri() + "\n";
  225. result += "Validity:\n";
  226. result += " NotBefore: " + Schedule.toIsoString
  227. (this.getValidityPeriod().getNotBefore()) + "\n";
  228. result += " NotAfter: " + Schedule.toIsoString
  229. (this.getValidityPeriod().getNotAfter()) + "\n";
  230. // TODO: Print the extension.
  231. result += "Public key bits:\n";
  232. try {
  233. var keyBase64 = this.getPublicKey().buf().toString('base64');
  234. for (var i = 0; i < keyBase64.length; i += 64)
  235. result += (keyBase64.substr(i, 64) + "\n");
  236. } catch (ex) {
  237. // No public key.
  238. }
  239. result += "Signature Information:\n";
  240. result += " Signature Type: ";
  241. if (this.getSignature() instanceof Sha256WithEcdsaSignature)
  242. result += "SignatureSha256WithEcdsa\n";
  243. else if (this.getSignature() instanceof Sha256WithRsaSignature)
  244. result += "SignatureSha256WithRsa\n";
  245. else
  246. result += "<unknown>\n";
  247. if (KeyLocator.canGetFromSignature(this.getSignature())) {
  248. result += " Key Locator: ";
  249. var keyLocator = KeyLocator.getFromSignature(this.getSignature());
  250. if (keyLocator.getType() == KeyLocatorType.KEYNAME) {
  251. if (keyLocator.getKeyName().equals(this.getKeyName()))
  252. result += "Self-Signed ";
  253. result += "Name=" + keyLocator.getKeyName().toUri() + "\n";
  254. }
  255. else
  256. result += "<no KeyLocator key name>\n";
  257. }
  258. return result;
  259. };
  260. /**
  261. * Check if certificateName follows the naming convention for a certificate.
  262. * @param {Name} certificateName The name of the certificate.
  263. * @return {boolean} True if certificateName follows the naming convention.
  264. */
  265. CertificateV2.isValidName = function(certificateName)
  266. {
  267. // /<NameSpace>/KEY/[KeyId]/[IssuerId]/[Version]
  268. return (certificateName.size() >= CertificateV2.MIN_CERT_NAME_LENGTH &&
  269. certificateName.get(CertificateV2.KEY_COMPONENT_OFFSET).equals
  270. (CertificateV2.KEY_COMPONENT));
  271. };
  272. /**
  273. * Extract the identity namespace from certificateName.
  274. * @param {Name} certificateName The name of the certificate.
  275. * @return {Name} The identity namespace as a new Name.
  276. */
  277. CertificateV2.extractIdentityFromCertName = function(certificateName)
  278. {
  279. if (!CertificateV2.isValidName(certificateName))
  280. throw new InvalidArgumentException(new Error
  281. ("Certificate name `" + certificateName.toUri() +
  282. "` does not follow the naming conventions"));
  283. return certificateName.getPrefix(CertificateV2.KEY_COMPONENT_OFFSET);
  284. };
  285. /**
  286. * Extract key name from certificateName.
  287. * @param {Name} certificateName The name of the certificate.
  288. * @return {Name} The key name as a new Name.
  289. */
  290. CertificateV2.extractKeyNameFromCertName = function(certificateName)
  291. {
  292. if (!CertificateV2.isValidName(certificateName)) {
  293. throw new InvalidArgumentException(new Error
  294. ("Certificate name `" + certificateName.toUri() +
  295. "` does not follow the naming conventions"));
  296. }
  297. // Trim everything after the key ID.
  298. return certificateName.getPrefix(CertificateV2.KEY_ID_OFFSET + 1);
  299. };
  300. CertificateV2.VERSION_OFFSET = -1;
  301. CertificateV2.ISSUER_ID_OFFSET = -2;
  302. CertificateV2.KEY_ID_OFFSET = -3;
  303. CertificateV2.KEY_COMPONENT_OFFSET = -4;
  304. CertificateV2.MIN_CERT_NAME_LENGTH = 4;
  305. CertificateV2.MIN_KEY_NAME_LENGTH = 2;
  306. CertificateV2.KEY_COMPONENT = new Name.Component("KEY");