UNPKG

ndn-js

Version:

A JavaScript client library for Named Data Networking

342 lines (306 loc) 12.4 kB
/** * Copyright (C) 2017-2019 Regents of the University of California. * @author: Jeff Thompson <jefft0@remap.ucla.edu> * @author: From ndn-cxx security https://github.com/named-data/ndn-cxx/blob/master/ndn-cxx/security/v2/certificate.hpp * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * A copy of the GNU Lesser General Public License is in the file COPYING. */ /** @ignore */ var Name = require('../../name.js').Name; /** @ignore */ var Data = require('../../data.js').Data; /** @ignore */ var KeyLocator = require('../../key-locator.js').KeyLocator; /** @ignore */ var KeyLocatorType = require('../../key-locator.js').KeyLocatorType; /** @ignore */ var Sha256WithRsaSignature = require('../../sha256-with-rsa-signature.js').Sha256WithRsaSignature; /** @ignore */ var Sha256WithEcdsaSignature = require('../../sha256-with-ecdsa-signature.js').Sha256WithEcdsaSignature; /** @ignore */ var ContentType = require('../../meta-info.js').ContentType; /** @ignore */ var WireFormat = require('../../encoding/wire-format.js').WireFormat; /** @ignore */ var Schedule = require('../../encrypt/schedule.js').Schedule; /** @ignore */ var ValidityPeriod = require('../validity-period.js').ValidityPeriod; /** @ignore */ var InvalidArgumentException = require('../security-exception.js').InvalidArgumentException; /** * CertificateV2 represents a certificate following the certificate format * naming convention. * * Overview of the NDN certificate format: * * CertificateV2 ::= DATA-TLV TLV-LENGTH * Name (= /<NameSpace>/KEY/[KeyId]/[IssuerId]/[Version]) * MetaInfo (.ContentType = KEY) * Content (= X509PublicKeyContent) * SignatureInfo (= CertificateV2SignatureInfo) * SignatureValue * * X509PublicKeyContent ::= CONTENT-TLV TLV-LENGTH * BYTE+ (= public key bits in PKCS#8 format) * * CertificateV2SignatureInfo ::= SIGNATURE-INFO-TYPE TLV-LENGTH * SignatureType * KeyLocator * ValidityPeriod * ... optional critical or non-critical extension blocks ... * * An example of NDN certificate name: * * /edu/ucla/cs/yingdi/KEY/%03%CD...%F1/%9F%D3...%B7/%FD%d2...%8E * \_________________/ \___________/ \___________/\___________/ * Certificate Namespace Key Id Issuer Id Version * (Identity) * \__________________________________/ * Key Name * * Notes: * * - `Key Id` is an opaque name component to identify the instance of the public * key for the certificate namespace. The value of `Key ID` is controlled by * the namespace owner. The library includes helpers for generating key IDs * using an 8-byte random number, SHA-256 digest of the public key, timestamp, * and the specified numerical identifiers. * * - `Issuer Id` is sn opaque name component to identify the issuer of the * certificate. The value is controlled by the issuer. The library includes * helpers to set issuer the ID to an 8-byte random number, SHA-256 digest of * the issuer's public key, and the specified numerical identifiers. * * - `Key Name` is a logical name of the key used for management purposes. the * Key Name includes the certificate namespace, keyword `KEY`, and `KeyId` * components. * * @see https://github.com/named-data/ndn-cxx/blob/master/docs/specs/certificate-format.rst * * Create a CertificateV2 from the content in the Data packet (if not omitted). * @param {Data} data (optional) The data packet with the content to copy. * If omitted, create a CertificateV2 with content type KEY and default or * unspecified values. * @constructor */ var CertificateV2 = function CertificateV2(data) { // Call the base constructor. if (data != undefined) { Data.call(this, data); this.checkFormat_(); } else { Data.call(this); this.getMetaInfo().setType(ContentType.KEY); } }; CertificateV2.prototype = new Data(); CertificateV2.prototype.name = "CertificateV2"; exports.CertificateV2 = CertificateV2; /** * Create a new CertificateV2.Error to report an error for not complying with * the certificate format. * Call with: throw new CertificateV2.Error(new Error("message")). * @constructor * @param {Error} error The exception created with new Error. */ CertificateV2.Error = function CertificateV2Error(error) { if (error) { error.__proto__ = CertificateV2.Error.prototype; return error; } }; CertificateV2.Error.prototype = new Error(); CertificateV2.Error.prototype.name = "CertificateV2Error"; CertificateV2.prototype.checkFormat_ = function() { if (!CertificateV2.isValidName(this.getName())) throw new CertificateV2.Error(new Error ("The Data Name does not follow the certificate naming convention")); if (this.getMetaInfo().getType() != ContentType.KEY) throw new CertificateV2.Error(new Error("The Data ContentType is not KEY")); if (this.getMetaInfo().getFreshnessPeriod() < 0.0) throw new CertificateV2.Error(new Error ("The Data FreshnessPeriod is not set")); if (this.getContent().size() == 0) throw new CertificateV2.Error(new Error("The Data Content is empty")); }; /** * Get key name from the certificate name. * @return {Name} The key name as a new Name. */ CertificateV2.prototype.getKeyName = function() { return this.getName().getPrefix(CertificateV2.KEY_ID_OFFSET + 1); }; /** * Get the identity name from the certificate name. * @return {Name} The identity name as a new Name. */ CertificateV2.prototype.getIdentity = function() { return this.getName().getPrefix(CertificateV2.KEY_COMPONENT_OFFSET); }; /** * Get the key ID component from the certificate name. * @return {Name.Component} The key ID name component. */ CertificateV2.prototype.getKeyId = function() { return this.getName().get(CertificateV2.KEY_ID_OFFSET); }; /** * Get the issuer ID component from the certificate name. * @return {Name.Component} The issuer ID component. */ CertificateV2.prototype.getIssuerId = function() { return this.getName().get(CertificateV2.ISSUER_ID_OFFSET); }; /** * Get the public key DER encoding. * @return {Blob} The DER encoding Blob. * @throws CertificateV2.Error If the public key is not set. */ CertificateV2.prototype.getPublicKey = function() { if (this.getContent().size() == 0) throw new CertificateV2.Error(new Error ("The public key is not set (the Data content is empty)")); return this.getContent(); }; /** * Get the certificate validity period from the SignatureInfo. * @return {ValidityPeriod} The ValidityPeriod object. * @throws InvalidArgumentException If the SignatureInfo doesn't have a * ValidityPeriod. */ CertificateV2.prototype.getValidityPeriod = function() { if (!ValidityPeriod.canGetFromSignature(this.getSignature())) throw new InvalidArgumentException(new Error ("The SignatureInfo does not have a ValidityPeriod")); return ValidityPeriod.getFromSignature(this.getSignature()); }; /** * Check if the time falls within the validity period. * @param {number} time (optional) The time to check as milliseconds since * Jan 1, 1970 UTC. If omitted, use the current time. * @return {boolean} True if the beginning of the validity period is less than * or equal to time and time is less than or equal to the end of the validity * period. * @throws InvalidArgumentException If the SignatureInfo doesn't have a * ValidityPeriod. */ CertificateV2.prototype.isValid = function(time) { return this.getValidityPeriod().isValid(time); }; // TODO: getExtension /** * Override to call the base class wireDecode then check the certificate format. * @param {Blob|Buffer} input The buffer with the bytes to decode. * @param {WireFormat} wireFormat (optional) A WireFormat object used to decode * this object. If omitted, use WireFormat.getDefaultWireFormat(). */ CertificateV2.prototype.wireDecode = function(input, wireFormat) { wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); Data.prototype.wireDecode.call(this, input, wireFormat); this.checkFormat_(); }; /** * Get a string representation of this certificate. * @return {string} The string representation. */ CertificateV2.prototype.toString = function() { var result = ""; result += "Certificate name:\n"; result += " " + this.getName().toUri() + "\n"; result += "Validity:\n"; result += " NotBefore: " + Schedule.toIsoString (this.getValidityPeriod().getNotBefore()) + "\n"; result += " NotAfter: " + Schedule.toIsoString (this.getValidityPeriod().getNotAfter()) + "\n"; // TODO: Print the extension. result += "Public key bits:\n"; try { var keyBase64 = this.getPublicKey().buf().toString('base64'); for (var i = 0; i < keyBase64.length; i += 64) result += (keyBase64.substr(i, 64) + "\n"); } catch (ex) { // No public key. } result += "Signature Information:\n"; result += " Signature Type: "; if (this.getSignature() instanceof Sha256WithEcdsaSignature) result += "SignatureSha256WithEcdsa\n"; else if (this.getSignature() instanceof Sha256WithRsaSignature) result += "SignatureSha256WithRsa\n"; else result += "<unknown>\n"; if (KeyLocator.canGetFromSignature(this.getSignature())) { result += " Key Locator: "; var keyLocator = KeyLocator.getFromSignature(this.getSignature()); if (keyLocator.getType() == KeyLocatorType.KEYNAME) { if (keyLocator.getKeyName().equals(this.getKeyName())) result += "Self-Signed "; result += "Name=" + keyLocator.getKeyName().toUri() + "\n"; } else result += "<no KeyLocator key name>\n"; } return result; }; /** * Check if certificateName follows the naming convention for a certificate. * @param {Name} certificateName The name of the certificate. * @return {boolean} True if certificateName follows the naming convention. */ CertificateV2.isValidName = function(certificateName) { // /<NameSpace>/KEY/[KeyId]/[IssuerId]/[Version] return (certificateName.size() >= CertificateV2.MIN_CERT_NAME_LENGTH && certificateName.get(CertificateV2.KEY_COMPONENT_OFFSET).equals (CertificateV2.KEY_COMPONENT)); }; /** * Extract the identity namespace from certificateName. * @param {Name} certificateName The name of the certificate. * @return {Name} The identity namespace as a new Name. */ CertificateV2.extractIdentityFromCertName = function(certificateName) { if (!CertificateV2.isValidName(certificateName)) throw new InvalidArgumentException(new Error ("Certificate name `" + certificateName.toUri() + "` does not follow the naming conventions")); return certificateName.getPrefix(CertificateV2.KEY_COMPONENT_OFFSET); }; /** * Extract key name from certificateName. * @param {Name} certificateName The name of the certificate. * @return {Name} The key name as a new Name. */ CertificateV2.extractKeyNameFromCertName = function(certificateName) { if (!CertificateV2.isValidName(certificateName)) { throw new InvalidArgumentException(new Error ("Certificate name `" + certificateName.toUri() + "` does not follow the naming conventions")); } // Trim everything after the key ID. return certificateName.getPrefix(CertificateV2.KEY_ID_OFFSET + 1); }; CertificateV2.VERSION_OFFSET = -1; CertificateV2.ISSUER_ID_OFFSET = -2; CertificateV2.KEY_ID_OFFSET = -3; CertificateV2.KEY_COMPONENT_OFFSET = -4; CertificateV2.MIN_CERT_NAME_LENGTH = 4; CertificateV2.MIN_KEY_NAME_LENGTH = 2; CertificateV2.KEY_COMPONENT = new Name.Component("KEY");