bip70: comments.

This commit is contained in:
Christopher Jeffrey 2017-02-01 19:56:22 -08:00
parent 91af611866
commit 5b1966a6c3
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
5 changed files with 465 additions and 45 deletions

View File

@ -15,6 +15,16 @@ var PaymentDetails = require('./paymentdetails');
var ProtoReader = protobuf.ProtoReader;
var ProtoWriter = protobuf.ProtoWriter;
/**
* Represents a BIP70 payment.
* @constructor
* @param {Object?} options
* @property {Buffer} merchantData
* @property {TX[]} transactions
* @property {Output[]} refundTo
* @property {String|null} memo
*/
function Payment(options) {
if (!(this instanceof Payment))
return new Payment(options);
@ -28,6 +38,13 @@ function Payment(options) {
this.fromOptions(options);
}
/**
* Inject properties from options.
* @private
* @param {Object} options
* @returns {Payment}
*/
Payment.prototype.fromOptions = function fromOptions(options) {
var i, tx, output;
@ -58,13 +75,43 @@ Payment.prototype.fromOptions = function fromOptions(options) {
return this;
};
/**
* Instantiate payment from options.
* @param {Object} options
* @returns {Payment}
*/
Payment.fromOptions = function fromOptions(options) {
return new Payment().fromOptions(options);
};
/**
* Set payment details.
* @method
* @alias Payment#setData
* @param {Object} data
* @param {String?} enc
*/
Payment.prototype.setData = PaymentDetails.prototype.setData;
/**
* Get payment details.
* @method
* @alias Payment#getData
* @param {String?} enc
* @returns {String|Object|null}
*/
Payment.prototype.getData = PaymentDetails.prototype.getData;
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
* @returns {Payment}
*/
Payment.prototype.fromRaw = function fromRaw(data) {
var br = new ProtoReader(data);
var tx, op, output;
@ -89,12 +136,23 @@ Payment.prototype.fromRaw = function fromRaw(data) {
return this;
};
/**
* Instantiate payment from serialized data.
* @param {Buffer} data
* @returns {Payment}
*/
Payment.fromRaw = function fromRaw(data, enc) {
if (typeof data === 'string')
data = new Buffer(data, enc);
return new Payment().fromRaw(data);
};
/**
* Serialize the payment (protobuf).
* @returns {Buffer}
*/
Payment.prototype.toRaw = function toRaw() {
var bw = new ProtoWriter();
var i, tx, op, output;
@ -121,4 +179,8 @@ Payment.prototype.toRaw = function toRaw() {
return bw.render();
};
/*
* Expose
*/
module.exports = Payment;

View File

@ -12,6 +12,13 @@ var Payment = require('./payment');
var ProtoReader = protobuf.ProtoReader;
var ProtoWriter = protobuf.ProtoWriter;
/**
* Represents a BIP70 payment ack.
* @param {Object?} options
* @property {Payment} payment
* @property {String|null} memo
*/
function PaymentACK(options) {
if (!(this instanceof PaymentACK))
return new PaymentACK(options);
@ -23,6 +30,13 @@ function PaymentACK(options) {
this.fromOptions(options);
}
/**
* Inject properties from options.
* @private
* @param {Object} options
* @returns {PaymentACK}
*/
PaymentACK.prototype.fromOptions = function fromOptions(options) {
if (options.payment)
this.payment.fromOptions(options.payment);
@ -35,10 +49,23 @@ PaymentACK.prototype.fromOptions = function fromOptions(options) {
return this;
};
/**
* Instantiate payment ack from options.
* @param {Object} options
* @returns {PaymentACK}
*/
PaymentACK.fromOptions = function fromOptions(options) {
return new PaymentACK().fromOptions(options);
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
* @returns {PaymentACK}
*/
PaymentACK.prototype.fromRaw = function fromRaw(data) {
var br = new ProtoReader(data);
@ -48,12 +75,23 @@ PaymentACK.prototype.fromRaw = function fromRaw(data) {
return this;
};
/**
* Instantiate payment ack from serialized data.
* @param {Buffer} data
* @returns {PaymentACK}
*/
PaymentACK.fromRaw = function fromRaw(data, enc) {
if (typeof data === 'string')
data = new Buffer(data, enc);
return new PaymentACK().fromRaw(data);
};
/**
* Serialize the payment ack (protobuf).
* @returns {Buffer}
*/
PaymentACK.prototype.toRaw = function toRaw() {
var bw = new ProtoWriter();
@ -65,4 +103,8 @@ PaymentACK.prototype.toRaw = function toRaw() {
return bw.render();
};
/*
* Expose
*/
module.exports = PaymentACK;

View File

@ -13,6 +13,18 @@ var protobuf = require('../utils/protobuf');
var ProtoReader = protobuf.ProtoReader;
var ProtoWriter = protobuf.ProtoWriter;
/**
* Represents BIP70 payment details.
* @param {Object?} options
* @property {String|null} network
* @property {Output[]} outputs
* @property {Number} time
* @property {Number} expires
* @property {String|null} memo
* @property {String|null} paymentUrl
* @property {Buffer|null} merchantData
*/
function PaymentDetails(options) {
if (!(this instanceof PaymentDetails))
return new PaymentDetails(options);
@ -29,6 +41,13 @@ function PaymentDetails(options) {
this.fromOptions(options);
}
/**
* Inject properties from options.
* @private
* @param {Object} options
* @returns {PaymentDetails}
*/
PaymentDetails.prototype.fromOptions = function fromOptions(options) {
var i, output;
@ -71,16 +90,33 @@ PaymentDetails.prototype.fromOptions = function fromOptions(options) {
return this;
};
/**
* Instantiate payment details from options.
* @param {Object} options
* @returns {PaymentDetails}
*/
PaymentDetails.fromOptions = function fromOptions(options) {
return new PaymentDetails().fromOptions(options);
};
/**
* Test whether the payment is expired.
* @returns {Boolean}
*/
PaymentDetails.prototype.isExpired = function isExpired() {
if (this.expires === -1)
return false;
return util.now() > this.expires;
};
/**
* Set payment details.
* @param {Object} data
* @param {String?} enc
*/
PaymentDetails.prototype.setData = function setData(data, enc) {
if (data == null || Buffer.isBuffer(data)) {
this.merchantData = data;
@ -96,6 +132,12 @@ PaymentDetails.prototype.setData = function setData(data, enc) {
this.merchantData = new Buffer(data, enc);
};
/**
* Get payment details.
* @param {String?} enc
* @returns {String|Object|null}
*/
PaymentDetails.prototype.getData = function getData(enc) {
var data = this.merchantData;
@ -118,6 +160,13 @@ PaymentDetails.prototype.getData = function getData(enc) {
return data.toString(enc);
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
* @returns {PaymentDetails}
*/
PaymentDetails.prototype.fromRaw = function fromRaw(data) {
var br = new ProtoReader(data);
var op, output;
@ -141,12 +190,23 @@ PaymentDetails.prototype.fromRaw = function fromRaw(data) {
return this;
};
/**
* Instantiate payment details from serialized data.
* @param {Buffer} data
* @returns {PaymentDetails}
*/
PaymentDetails.fromRaw = function fromRaw(data, enc) {
if (typeof data === 'string')
data = new Buffer(data, enc);
return new PaymentDetails().fromRaw(data);
};
/**
* Serialize the payment details (protobuf).
* @returns {Buffer}
*/
PaymentDetails.prototype.toRaw = function toRaw() {
var bw = new ProtoWriter();
var i, op, output;
@ -179,4 +239,8 @@ PaymentDetails.prototype.toRaw = function toRaw() {
return bw.render();
};
/*
* Expose
*/
module.exports = PaymentDetails;

View File

@ -16,6 +16,16 @@ var PaymentDetails = require('./paymentdetails');
var ProtoReader = protobuf.ProtoReader;
var ProtoWriter = protobuf.ProtoWriter;
/**
* Represents a BIP70 payment request.
* @param {Object?} options
* @property {Number} version
* @property {String|null} pkiType
* @property {Buffer|null} pkiData
* @property {PaymentDetails} paymentDetails
* @property {Buffer|null} signature
*/
function PaymentRequest(options) {
if (!(this instanceof PaymentRequest))
return new PaymentRequest(options);
@ -30,6 +40,13 @@ function PaymentRequest(options) {
this.fromOptions(options);
}
/**
* Inject properties from options.
* @private
* @param {Object} options
* @returns {PaymentRequest}
*/
PaymentRequest.prototype.fromOptions = function fromOptions(options) {
if (options.version != null) {
assert(util.isNumber(options.version));
@ -60,10 +77,23 @@ PaymentRequest.prototype.fromOptions = function fromOptions(options) {
return this;
};
/**
* Instantiate payment request from options.
* @param {Object} options
* @returns {PaymentRequest}
*/
PaymentRequest.fromOptions = function fromOptions(options) {
return new PaymentRequest().fromOptions(options);
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
* @returns {PaymentRequest}
*/
PaymentRequest.prototype.fromRaw = function fromRaw(data) {
var br = new ProtoReader(data);
@ -76,12 +106,23 @@ PaymentRequest.prototype.fromRaw = function fromRaw(data) {
return this;
};
/**
* Instantiate payment request from serialized data.
* @param {Buffer} data
* @returns {PaymentRequest}
*/
PaymentRequest.fromRaw = function fromRaw(data, enc) {
if (typeof data === 'string')
data = new Buffer(data, enc);
return new PaymentRequest().fromRaw(data);
};
/**
* Serialize the payment request (protobuf).
* @returns {Buffer}
*/
PaymentRequest.prototype.toRaw = function toRaw() {
var bw = new ProtoWriter();
@ -102,6 +143,11 @@ PaymentRequest.prototype.toRaw = function toRaw() {
return bw.render();
};
/**
* Get payment request signature algorithm.
* @returns {Object|null}
*/
PaymentRequest.prototype.getAlgorithm = function getAlgorithm() {
var parts;
@ -119,9 +165,14 @@ PaymentRequest.prototype.getAlgorithm = function getAlgorithm() {
if (parts[1] !== 'sha1' && parts[1] !== 'sha256')
return;
return { key: parts[0], hash: parts[1] };
return new Algorithm(parts[0], parts[1]);
};
/**
* Serialize payment request for sighash.
* @returns {Buffer}
*/
PaymentRequest.prototype.signatureData = function signatureData() {
var signature = this.signature;
var data;
@ -135,12 +186,22 @@ PaymentRequest.prototype.signatureData = function signatureData() {
return data;
};
/**
* Get signature hash.
* @returns {Hash}
*/
PaymentRequest.prototype.signatureHash = function signatureHash() {
var alg = this.getAlgorithm();
assert(alg, 'No hash algorithm available.');
return crypto.hash(alg.hash, this.signatureData());
};
/**
* Set x509 certificate chain.
* @param {Buffer[]} chain
*/
PaymentRequest.prototype.setChain = function setChain(chain) {
var bw = new ProtoWriter();
var i, cert, pem;
@ -162,6 +223,11 @@ PaymentRequest.prototype.setChain = function setChain(chain) {
this.pkiData = bw.render();
};
/**
* Get x509 certificate chain.
* @returns {Buffer[]}
*/
PaymentRequest.prototype.getChain = function getChain() {
var chain = [];
var br;
@ -177,6 +243,12 @@ PaymentRequest.prototype.getChain = function getChain() {
return chain;
};
/**
* Sign payment request (chain must be set).
* @param {Buffer} key
* @param {Buffer[]?} chain
*/
PaymentRequest.prototype.sign = function sign(key, chain) {
var alg, msg;
@ -195,6 +267,11 @@ PaymentRequest.prototype.sign = function sign(key, chain) {
this.signature = x509.signSubject(alg.hash, msg, key, chain);
};
/**
* Verify payment request signature.
* @returns {Boolean}
*/
PaymentRequest.prototype.verify = function verify() {
var alg, msg, sig, chain;
@ -216,6 +293,11 @@ PaymentRequest.prototype.verify = function verify() {
return x509.verifySubject(alg.hash, msg, sig, chain);
};
/**
* Verify x509 certificate chain.
* @returns {Boolean}
*/
PaymentRequest.prototype.verifyChain = function verifyChain() {
if (!this.pkiType || this.pkiType === 'none')
return true;
@ -223,6 +305,11 @@ PaymentRequest.prototype.verifyChain = function verifyChain() {
return x509.verifyChain(this.getChain());
};
/**
* Get root certificate authority.
* @returns {Object|null}
*/
PaymentRequest.prototype.getCA = function getCA() {
var chain, root;
@ -239,11 +326,32 @@ PaymentRequest.prototype.getCA = function getCA() {
if (!root)
return;
return {
name: x509.getCAName(root),
trusted: x509.isTrusted(root),
cert: root
};
return new CA(root);
};
/**
* Algorithm
* @constructor
*/
function Algorithm(key, hash) {
this.key = key;
this.hash = hash;
}
/**
* CA
* @constructor
*/
function CA(root) {
this.name = x509.getCAName(root);
this.trusted = x509.isTrusted(root);
this.cert = root;
}
/*
* Expose
*/
module.exports = PaymentRequest;

View File

@ -14,6 +14,71 @@ var crypto = require('../crypto/crypto');
var pk = require('./pk');
var x509 = exports;
/**
* Map of trusted root certs.
* @type {Object}
*/
x509.trusted = {};
/**
* Whether to allow untrusted root
* certs during verification.
* @type {Boolean}
*/
x509.allowUntrusted = false;
/**
* OID to algorithm map for PKI.
* @const {Object}
* @see https://www.ietf.org/rfc/rfc2459.txt
* @see https://tools.ietf.org/html/rfc3279
* @see http://oid-info.com/get/1.2.840.10040.4
* @see http://oid-info.com/get/1.2.840.113549.1.1
* @see http://oid-info.com/get/1.2.840.10045.4.3
*/
x509.oid = {
'1.2.840.10040.4.1' : { key: 'dsa', hash: null },
'1.2.840.10040.4.2' : { key: 'dsa', hash: null },
'1.2.840.10040.4.3' : { key: 'dsa', hash: 'sha1' },
'1.2.840.113549.1.1.1' : { key: 'rsa', hash: null },
'1.2.840.113549.1.1.2' : { key: 'rsa', hash: 'md2' },
'1.2.840.113549.1.1.3' : { key: 'rsa', hash: 'md4' },
'1.2.840.113549.1.1.4' : { key: 'rsa', hash: 'md5' },
'1.2.840.113549.1.1.5' : { key: 'rsa', hash: 'sha1' },
'1.2.840.113549.1.1.11': { key: 'rsa', hash: 'sha256' },
'1.2.840.113549.1.1.12': { key: 'rsa', hash: 'sha384' },
'1.2.840.113549.1.1.13': { key: 'rsa', hash: 'sha512' },
'1.2.840.113549.1.1.14': { key: 'rsa', hash: 'sha224' },
'1.2.840.10045.2.1' : { key: 'ecdsa', hash: null },
'1.2.840.10045.4.1' : { key: 'ecdsa', hash: 'sha1' },
'1.2.840.10045.4.3.1' : { key: 'ecdsa', hash: 'sha224' },
'1.2.840.10045.4.3.2' : { key: 'ecdsa', hash: 'sha256' },
'1.2.840.10045.4.3.3' : { key: 'ecdsa', hash: 'sha384' },
'1.2.840.10045.4.3.4' : { key: 'ecdsa', hash: 'sha512' }
};
/**
* OID to curve name map for ECDSA.
* @type {Object}
*/
x509.curves = {
'1.3.132.0.33': 'p224',
'1.2.840.10045.3.1.7': 'p256',
'1.3.132.0.34': 'p384',
'1.3.132.0.35': 'p521'
};
/**
* Retrieve cert value by OID.
* @param {Object} cert
* @param {String} oid
* @returns {String}
*/
x509.getSubjectOID = function getSubjectOID(cert, oid) {
var subject = cert.tbs.subject;
var i, entry;
@ -25,6 +90,13 @@ x509.getSubjectOID = function getSubjectOID(cert, oid) {
}
};
/**
* Try to retrieve CA name by checking
* for a few different OIDs.
* @param {Object} cert
* @returns {String}
*/
x509.getCAName = function getCAName(cert) {
// This seems to work the best in practice
// for getting a human-readable and
@ -41,8 +113,12 @@ x509.getCAName = function getCAName(cert) {
|| 'Unknown';
};
x509.trusted = {};
x509.allowUntrusted = false;
/**
* Test whether a cert is trusted by hashing
* and looking it up in the trusted map.
* @param {Object} cert
* @returns {Buffer}
*/
x509.isTrusted = function isTrusted(cert) {
var fingerprint = crypto.sha256(cert.raw);
@ -50,6 +126,11 @@ x509.isTrusted = function isTrusted(cert) {
return x509.trusted[hash] === true;
};
/**
* Add root certificates to the trusted map.
* @param {Buffer[]} certs
*/
x509.setTrust = function setTrust(certs) {
var i, cert, pem, hash;
@ -85,52 +166,34 @@ x509.setTrust = function setTrust(certs) {
}
};
/*
* https://www.ietf.org/rfc/rfc2459.txt
* https://tools.ietf.org/html/rfc3279
* http://oid-info.com/get/1.2.840.10040.4
* http://oid-info.com/get/1.2.840.113549.1.1
* http://oid-info.com/get/1.2.840.10045.4.3
/**
* Retrieve key algorithm from cert.
* @param {Object} cert
* @returns {Object}
*/
x509.oid = {
'1.2.840.10040.4.1' : { key: 'dsa', hash: null },
'1.2.840.10040.4.2' : { key: 'dsa', hash: null },
'1.2.840.10040.4.3' : { key: 'dsa', hash: 'sha1' },
'1.2.840.113549.1.1.1' : { key: 'rsa', hash: null },
'1.2.840.113549.1.1.2' : { key: 'rsa', hash: 'md2' },
'1.2.840.113549.1.1.3' : { key: 'rsa', hash: 'md4' },
'1.2.840.113549.1.1.4' : { key: 'rsa', hash: 'md5' },
'1.2.840.113549.1.1.5' : { key: 'rsa', hash: 'sha1' },
'1.2.840.113549.1.1.11': { key: 'rsa', hash: 'sha256' },
'1.2.840.113549.1.1.12': { key: 'rsa', hash: 'sha384' },
'1.2.840.113549.1.1.13': { key: 'rsa', hash: 'sha512' },
'1.2.840.113549.1.1.14': { key: 'rsa', hash: 'sha224' },
'1.2.840.10045.2.1' : { key: 'ecdsa', hash: null },
'1.2.840.10045.4.1' : { key: 'ecdsa', hash: 'sha1' },
'1.2.840.10045.4.3.1' : { key: 'ecdsa', hash: 'sha224' },
'1.2.840.10045.4.3.2' : { key: 'ecdsa', hash: 'sha256' },
'1.2.840.10045.4.3.3' : { key: 'ecdsa', hash: 'sha384' },
'1.2.840.10045.4.3.4' : { key: 'ecdsa', hash: 'sha512' }
};
x509.curves = {
'1.3.132.0.33': 'p224',
'1.2.840.10045.3.1.7': 'p256',
'1.3.132.0.34': 'p384',
'1.3.132.0.35': 'p521'
};
x509.getKeyAlgorithm = function getKeyAlgorithm(cert) {
var alg = cert.tbs.pubkey.alg.alg;
return x509.oid[alg];
};
/**
* Retrieve signature algorithm from cert.
* @param {Object} cert
* @returns {Object}
*/
x509.getSigAlgorithm = function getSigAlgorithm(cert) {
var alg = cert.sigAlg.alg;
return x509.oid[alg];
};
/**
* Lookup curve based on key parameters.
* @param {Buffer} params
* @returns {Object}
*/
x509.getCurve = function getCurve(params) {
var oid;
@ -146,6 +209,12 @@ x509.getCurve = function getCurve(params) {
return x509.curves[oid];
};
/**
* Parse a DER formatted cert.
* @param {Buffer} der
* @returns {Object|null}
*/
x509.parse = function parse(der) {
try {
return ASN1.parseCert(der);
@ -154,6 +223,12 @@ x509.parse = function parse(der) {
}
};
/**
* Get cert public key.
* @param {Object} cert
* @returns {Object|null}
*/
x509.getPublicKey = function getPublicKey(cert) {
var alg = x509.getKeyAlgorithm(cert);
var key, params, curve;
@ -175,12 +250,25 @@ x509.getPublicKey = function getPublicKey(cert) {
};
};
/**
* Verify cert expiration time.
* @param {Object} cert
* @returns {Boolean}
*/
x509.verifyTime = function verifyTime(cert) {
var time = cert.tbs.validity;
var now = util.now();
return now > time.notBefore && now < time.notAfter;
};
/**
* Get signature key info from cert chain.
* @param {Buffer} key
* @param {Buffer[]} chain
* @returns {Object}
*/
x509.getSigningKey = function getSigningKey(key, chain) {
var cert, pub, curve;
@ -213,35 +301,65 @@ x509.getSigningKey = function getSigningKey(key, chain) {
return key;
};
/**
* Sign a hash with the chain signing key.
* @param {String} hash
* @param {Buffer} msg
* @param {Buffer} key
* @param {Buffer[]} chain
* @returns {Buffer}
*/
x509.signSubject = function signSubject(hash, msg, key, chain) {
var priv = x509.getSigningKey(key, chain);
return pk.sign(hash, msg, priv);
};
/**
* Get chain verification key.
* @param {Buffer[]} chain
* @returns {Object|null}
*/
x509.getVerifyKey = function getVerifyKey(chain) {
var cert, key;
if (chain.length === 0)
return false;
return;
cert = x509.parse(chain[0]);
if (!cert)
return false;
return;
key = x509.getPublicKey(cert);
if (!key)
return false;
return;
return key;
};
/**
* Verify a sighash against chain verification key.
* @param {String} hash
* @param {Buffer} msg
* @param {Buffer} sig
* @param {Buffer[]} chain
* @returns {Boolean}
*/
x509.verifySubject = function verifySubject(hash, msg, sig, chain) {
var key = x509.getVerifyKey(chain);
return pk.verify(hash, msg, sig, key);
};
/**
* Parse certificate chain.
* @param {Buffer[]} chain
* @returns {Object[]}
*/
x509.parseChain = function parseChain(chain) {
var certs = [];
var i, cert;
@ -258,6 +376,12 @@ x509.parseChain = function parseChain(chain) {
return certs;
};
/**
* Verify all expiration times in a certificate chain.
* @param {Object[]} chain
* @returns {Boolean}
*/
x509.verifyTimes = function verifyTimes(chain) {
var i, cert;
@ -270,6 +394,13 @@ x509.verifyTimes = function verifyTimes(chain) {
return true;
};
/**
* Verify that at least one parent
* cert in the chain is trusted.
* @param {Object[]} chain
* @returns {Boolean}
*/
x509.verifyTrust = function verifyTrust(chain) {
var i, cert;
@ -294,6 +425,11 @@ x509.verifyTrust = function verifyTrust(chain) {
return false;
};
/**
* Verify certificate chain.
* @param {Object[]} certs
*/
x509.verifyChain = function verifyChain(certs) {
var chain = x509.parseChain(certs);
var i, child, parent, alg, key, sig, msg;
@ -331,6 +467,10 @@ x509.verifyChain = function verifyChain(certs) {
return x509.verifyTrust(chain);
};
/*
* Helpers
*/
function isHash(data) {
if (typeof data === 'string')
return util.isHex(data) && data.length === 64;
@ -341,4 +481,8 @@ function isHash(data) {
return false;
}
/*
* Load trusted certs.
*/
x509.setTrust(require('../../etc/certs.json'));