This commit is contained in:
Anton Sotirov 2015-05-04 15:15:08 +03:00
Родитель 02869af168 8e9a99ab47
Коммит 1d5942d7e4
62 изменённых файлов: 3469 добавлений и 659 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -1,2 +1,3 @@
.DS_Store
node_modules
node_modules
coverage/

21
.travis.yml Normal file
Просмотреть файл

@ -0,0 +1,21 @@
language: node_js
node_js:
- '0.12'
- '0.10'
- '0.8'
- iojs
before_install:
- npm install -g npm@2.7.3
after_success:
- npm install -g istanbul
- npm install coveralls
- istanbul cover node_modules/.bin/_mocha --report lcovonly -- -R spec
- ./node_modules/.bin/coveralls < ./coverage/lcov.info
- rm -rf ./coverage
deploy:
provider: npm
email: argon@mkbot.net
api_key:
secure: W4yhYfQUhyhOOG/nUwYXr2DgIv8Kp/pzyUMtWc2garnHLPMvaMY+w/0NzKC06/UmApiJdRalWDNNDqNJAl2G22Hs/eHVpAelHejzQw3BY26QW71tTCPQR3Cv7r6gDBUN2o9rxjlJt+vpbJR1wEoFRnwn3d6298zDw+fzjlM+Wvo=
on:
tags: true

Просмотреть файл

@ -1,5 +1,29 @@
## Changelog
1.7.3:
* Added support for increased payload length for VoIP applications (Closes #207)
* Fixed a bug with trimming UTF-16 encoded payloads
* Dropped support for node v0.6 as it doesn't support UTF-16 surrogate pairs. Plus it's old. It'll still work if needed though, if you use UTF-8.
1.7.2:
* Fixed: #238, only emit `error` when the problem is unrecoverable. Any use of `node-apn` should have an `error` listener attached to prevent uncaught exceptions.
* Various coding style improvements.
* CI.
* Removed legacy protocol support. I don't see any reason to keep it around, let me know if this causes you any problems.
1.7.1:
* Fixed: #224, always passing a CA value even if no certificates were specified. In this case the TLS library will not use the built in root certificates and will always fail to trust the server certificate.
* Changed: Socket timeout default has changed for disabled to 1 hour.
* Documentation fixes
1.7.0:
* Added: Credential validator to catch common configuration errors.
* Fixed: Documentation errors.
1.6.2:
* Updated maximum payload size to 2048 bytes. (See #181).

Просмотреть файл

@ -1,6 +1,36 @@
#node-apn
A Node.js module for interfacing with the Apple Push Notification service.
> A Node.js module for interfacing with the Apple Push Notification service.
[![NPM][npm-image] ][npm-url]
[![Build status][ci-image] ][ci-url]
[![Code coverage][coverage-image]][coverage-url]
[![Codacy][codacy-image]][codacy-url]
[![dependencies][dependencies-image]][dependencies-url]
[![devdependencies][devdependencies-image]][devdependencies-url]
[![Issue Stats][issuestats-pr-image]][issuestats-url]
[![Issue Stats][issuestats-image]][issuestats-url]
[npm-image]:https://nodei.co/npm/apn.png?downloads=true
[npm-url]:https://npmjs.com/package/apn
[ci-image]:https://travis-ci.org/argon/node-apn.png?branch=master
[ci-url]:https://travis-ci.org/argon/node-apn
[coverage-image]:https://coveralls.io/repos/argon/node-apn/badge.svg?branch=master
[coverage-url]:https://coveralls.io/r/argon/node-apn
[codacy-image]:https://www.codacy.com/project/badge/e7735fbe0db244f3b310657d0dabaa11
[codacy-url]:https://www.codacy.com/public/argon/node-apn
[dependencies-image]:https://david-dm.org/argon/node-apn.png
[dependencies-url]:https://david-dm.org/argon/node-apn
[devdependencies-image]:https://david-dm.org/argon/node-apn/dev-status.png
[devdependencies-url]:https://david-dm.org/argon/node-apn#info=devDependencies
[issuestats-image]:http://issuestats.com/github/argon/node-apn/badge/issue
[issuestats-pr-image]:http://issuestats.com/github/argon/node-apn/badge/pr
[issuestats-url]:http://issuestats.com/github/argon/node-apn
## Features
@ -31,7 +61,7 @@ This is intended as a brief introduction, please refer to the documentation in `
var apn = require('apn');
### Connecting
Create a new connection to the APN gateway server, passing a dictionary of options to the constructor. If you name your certificate and key files appropriately (`cert.pem` and `key.pem`) then the defaults should be suitable to get you up and running. By default the module will connect to the sandbox environment unless the environment variable `NODE_ENV=production` is set. For more information consult the documentation (in doc/apn.markdown).
Create a new connection to the APN gateway server, passing a dictionary of options to the constructor. If you name your certificate and key files appropriately (`cert.pem` and `key.pem`) then the defaults should be suitable to get you up and running.
```javascript
var options = { };
@ -39,6 +69,10 @@ Create a new connection to the APN gateway server, passing a dictionary of optio
var apnConnection = new apn.Connection(options);
```
By default, if the environment variable `NODE_ENV=production` is set, the module will connect to the production gateway. Otherwise it will connect to the sandbox. This along with many other settings can be overriden with the options object.
For more information about configuration options consult the [documentation](doc/connection.markdown).
Help with preparing the key and certificate files for connection can be found in the [wiki][certificateWiki]
### Sending a notification

Просмотреть файл

@ -1,7 +1,7 @@
## Handling Errors
When an error occurs while pushing a notification and the enhanced interface is enabled, Apple sends a message back to node-apn containing the "identifier" of the notification (a value managed internally within node-apn) which caused the error and an associated error code (See: [The Binary Interface and Notification Formats][errors]). Apple does not return the entire message so node-apn caches a number of notifications after they are sent, so in the event an error occurs node-apn can find the one with the correct identifier from the cache and trigger the `transmissionError` event with the appropriate `Notification` object and `Device` it should have been delivered to.
When an error occurs while pushing a notification Apple sends a message back to node-apn. The message contains the "identifier" of the notification (a value managed internally within node-apn) which caused the error and an associated error code (See: [The Binary Interface and Notification Formats][errors]). Apple does not return the entire message so node-apn caches a number of notifications after they are sent. In the event an error occurs, node-apn will identify the notification with the correct identifier from the cache and emit a `transmissionError` event with the appropriate `Notification` object and `Device` it should have been delivered to.
Apple guarantees that if one notification causes an error none of the following notifications will be processed, so if node-apn can find the correct notification which caused the error in the cache, then it can automatically re-send all the ones afterward.

Просмотреть файл

@ -4,44 +4,40 @@ Creates a new connection to the Apple Push Notification Service.
Options:
- `cert` {Buffer|String} TThe filename of the connection certificate to load from disk, or a Buffer/String containing the certificate data. (Defaults to: `cert.pem`)
- `cert` {Buffer|String} The filename of the connection certificate to load from disk, or a Buffer/String containing the certificate data. (Defaults to: `cert.pem`)
- `key` {Buffer|String} The filename of the connection key to load from disk, or a Buffer/String containing the key data. (Defaults to: `key.pem`)
- `ca` An array of trusted certificates. Each element should contain either a filename to load, or a Buffer/String (in PEM format) to be used directly. If this is omitted several well known "root" CAs will be used. - You may need to use this as some environments don't include the CA used by Apple (entrust_2048).
- `pfx` {Buffer|String} File path for private key, certificate and CA certs in PFX or PKCS12 format, or a Buffer containing the PFX data. If supplied will be used instead of certificate and key above.
- `pfx` {Buffer|String} File path for private key, certificate and CA certs in PFX or PKCS12 format, or a Buffer containing the PFX data. If supplied will always be used instead of certificate and key above.
- `passphrase` {String} The passphrase for the connection key, if required
- `production` {Boolean} Specifies which environment to connect to: Production (if true) or Sandbox - The hostname will be set automatically. (Defaults to NODE_ENV == "production", i.e. false unless the NODE_ENV environment variable is set accordingly)
- `voip` {Boolean} Enable when you are using a VoIP certificate to enable paylods up to 4096 bytes.
- `port` {Number} Gateway port (Defaults to: `2195`)
- `rejectUnauthorized` {Boolean} Reject Unauthorized property to be passed through to tls.connect() (Defaults to `true`)
- `enhanced` {Boolean} Whether to use the enhanced notification format (recommended, defaults to: `true`)
- `cacheLength` {Number} Number of notifications to cache for error purposes (See "Handling Errors" below, (Defaults to: 1000)
- `cacheLength` {Number} Number of notifications to cache for error purposes (See "Handling Errors" below, (Defaults to: 100)
- `autoAdjustCache` {Boolean} Whether the cache should grow in response to messages being lost after errors. (Will still emit a 'cacheTooSmall' event) (Defaults to: `false`)
- `autoAdjustCache` {Boolean} Whether the cache should grow in response to messages being lost after errors. (Will still emit a 'cacheTooSmall' event) (Defaults to: `true`)
- `maxConnections` {Number} The maximum number of connections to create for sending messages. (Defaults to: `1`)
- `connectTimeout` {Number} The duration of time the module should wait, in milliseconds, when trying to establish a connection to Apple before failing. 0 = Disabled. {Defaults to: `10000`}
- `connectionTimeout` {Number} The duration the socket should stay alive with no activity in milliseconds. 0 = Disabled. (Defaults to: `0`)
- `connectionTimeout` {Number} The duration the socket should stay alive with no activity in milliseconds. 0 = Disabled. (Defaults to: `3600000` - 1h)
- `connectionRetryLimit` {Number} The maximum number of connection failures that will be tolerated before `apn` will "terminate". [See below.](#connectionretrylimit) (Defaults to: 10)
- `connectionRetryLimit` {Number} The maximum number of connection failures that will be tolerated before `apn` will "terminate". [See below.](#connection-retry-limit) (Defaults to: 10)
- `buffersNotifications` {Boolean} Whether to buffer notifications and resend them after failure. (Defaults to: `true`)
- `fastMode` {Boolean} Whether to aggresively empty the notification buffer while connected - if set to true node-apn may enter a tight loop under heavy load while delivering notifications. (Defaults to: `false`)
- `legacy` {Boolean} Whether to use the pre-iOS 7 protocol format. (Defaults to `false`)
- `largePayloads` {Boolean} Whether to raise the notification limit from 256 bytes to 2048 bytes - not yet available in Production, automatically enabled for Sandbox.
##### Connection retry limit
TLS errors such as expired or invalid certificates will cause an error to be emitted, but in this case it is futile for `apn` to continue attempting to connect. There may also be other cases where connectivity issues mean that a process attempting to send notifications may simply become blocked with an ever-increasing queue of notifications. To attempt to combat this a (configurable) retry limit of 10 has been introduced. If ten consecutive connection failures occur then `apn` will emit an `error` event for the connection, then a `transmissionError` event will be emitted for *each* notification in the queue, with the error code `connectionRetryLimitExceeded` (514).

Просмотреть файл

@ -9,9 +9,7 @@ As of version 1.2.0 it is possible to use a set of methods provided by Notificat
For iOS 7 applications which support Silent Remote Notifications you can use the `note.contentAvailable` property. This is identical in functionality to `note.newsstandAvailable` without the confusion of the "Newstand" terminology.
A `Notification` enapsulates the data to be compiled down to JSON and pushed to a device. See the [payload documentation][pl] for more details. At present the total length of the payload accepted by Apple is 256 bytes.
*Note*: The maximum payload size will be increased to 2048 bytes when iOS 8 is released, these larger payloads are available for testing in the sandbox environment. `apn` is will automatically configure for larger payloads when connecting to the sandbox.
A `Notification` enapsulates the data to be compiled down to JSON and pushed to a device. See the [payload documentation][pl] for more details. At present the total length of the payload accepted by Apple is 2048 bytes.
### notification.retryLimit
@ -19,7 +17,7 @@ The maximum number of retries which should be performed when sending a notificat
### notification.expiry
The UNIX timestamp representing when the notification should expire. This does not contribute to the 256 byte payload size limit. An expiry of 0 indicates that the notification expires immediately.
The UNIX timestamp representing when the notification should expire. This does not contribute to the 2048 byte payload size limit. An expiry of 0 indicates that the notification expires immediately.
### notification.priority
@ -29,8 +27,6 @@ From [Apples' Documentation][notificationFormat], Provide one of the following v
> The push notification must trigger an alert, sound, or badge on the device. It is an error use this priority for a push that contains only the content-available key.
* 5 - The push message is sent at a time that conserves power on the device receiving it.
This value is not valid when the connection is in legacy mode.
### notification.encoding
The encoding to use when transmitting the notification to APNS, defaults to `utf8`. `utf16le` is also possible but as each character is represented by a minimum of 2 bytes, will at least halve the possible payload size. If in doubt leave as default.
@ -111,7 +107,7 @@ Set the `url-args` property of the `aps` object.
### notification.trim()
Attempt to automatically trim the notification alert text body to meet the payload size limit of 256 bytes.
Attempt to automatically trim the notification alert text body to meet the payload size limit of 2048 bytes.
[pl]:https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1 "Local and Push Notification Programming Guide: Apple Push Notification Service"
[notificationFormat]:https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW9 "The Binary Interface and Notification Format"

Просмотреть файл

@ -1,17 +1,15 @@
var apn = require ('../index.js');
"use strict";
// Setup a connection to the feedback service using a custom interval (10 seconds)
var feedback = new apn.feedback({ address:'feedback.sandbox.push.apple.com', interval: 10 });
feedback.on('feedback', handleFeedback);
feedback.on('feedbackError', console.error);
var apn = require ("../index.js");
function handleFeedback(feedbackData) {
var time, device;
for(var i in feedbackData) {
time = feedbackData[i].time;
device = feedbackData[i].device;
console.log("Device: " + device.toString('hex') + " has been unreachable, since: " + time);
}
feedbackData.forEach(function(feedbackItem) {
console.log("Device: " + feedbackItem.device.toString("hex") + " has been unreachable, since: " + feedbackItem.time);
});
}
// Setup a connection to the feedback service using a custom interval (10 seconds)
var feedback = new apn.feedback({ production: false, interval: 10 });
feedback.on("feedback", handleFeedback);
feedback.on("feedbackError", console.error);

Просмотреть файл

@ -1,40 +1,42 @@
var apn = require ('../index.js');
"use strict";
var apn = require ("../index.js");
var tokens = ["<insert token here>", "<insert token here>"];
if(tokens[0] == "<insert token here>") {
if(tokens[0] === "<insert token here>") {
console.log("Please set token to a valid device token for the push notification service");
process.exit();
}
// Create a connection to the service using mostly default parameters.
var service = new apn.connection({ gateway:'gateway.sandbox.push.apple.com' });
var service = new apn.connection({ production: false });
service.on('connected', function() {
service.on("connected", function() {
console.log("Connected");
});
service.on('transmitted', function(notification, device) {
console.log("Notification transmitted to:" + device.token.toString('hex'));
service.on("transmitted", function(notification, device) {
console.log("Notification transmitted to:" + device.token.toString("hex"));
});
service.on('transmissionError', function(errCode, notification, device) {
service.on("transmissionError", function(errCode, notification, device) {
console.error("Notification caused error: " + errCode + " for device ", device, notification);
if (errCode == 8) {
if (errCode === 8) {
console.log("A error code of 8 indicates that the device token is invalid. This could be for a number of reasons - are you using the correct environment? i.e. Production vs. Sandbox");
}
});
service.on('timeout', function () {
service.on("timeout", function () {
console.log("Connection Timeout");
});
service.on('disconnected', function() {
service.on("disconnected", function() {
console.log("Disconnected from APNS");
});
service.on('socketError', console.error);
service.on("socketError", console.error);
// If you plan on sending identical paylods to many devices you can do something like this.
@ -53,13 +55,13 @@ pushNotificationToMany();
// If you have a list of devices for which you want to send a customised notification you can create one and send it to and individual device.
function pushSomeNotifications() {
console.log("Sending a tailored notification to %d devices", tokens.length);
for (var i in tokens) {
tokens.forEach(function(token, i) {
var note = new apn.notification();
note.setAlertText("Hello, from node-apn! You are number: " + i);
note.badge = i;
service.pushNotification(note, tokens[i]);
}
service.pushNotification(note, token);
});
}
pushSomeNotifications();

Просмотреть файл

@ -1,10 +1,10 @@
exports.Connection = require('./lib/connection');
exports.Connection = require("./lib/connection");
exports.connection = exports.Connection;
exports.Device = require("./lib/device");
exports.device = exports.Device;
exports.Errors = require('./lib/errors');
exports.Errors = require("./lib/errors");
exports.error = exports.Errors;
exports.Feedback = require("./lib/feedback");

Просмотреть файл

@ -1,20 +1,23 @@
var Errors = require('./errors');
"use strict";
var q = require('q');
var tls = require('tls');
var sysu = require('util');
var util = require('./util');
var events = require('events');
var Device = require('./device');
var CredentialLoader = require('./credentials');
var Errors = require("./errors");
var createSocket = require('./socket');
var q = require("q");
var sysu = require("util");
var util = require("./util");
var events = require("events");
var Device = require("./device");
var loadCredentials = require("./credentials/load");
var parseCredentials = require("./credentials/parse");
var validateCredentials = require("./credentials/validate");
var createSocket = require("./socket");
var debug = function() {};
var trace = function() {};
if(process.env.DEBUG) {
try {
debug = require('debug')('apn');
trace = require('debug')('apn:trace');
debug = require("debug")("apn");
trace = require("debug")("apn:trace");
}
catch (e) {
console.log("Notice: 'debug' module is not available. This should be installed with `npm install debug` to enable debug messages", e);
@ -34,57 +37,54 @@ if(process.env.DEBUG) {
* @config {Boolean} [production=(NODE_ENV=='production')] Specifies which environment to connect to: Production (if true) or Sandbox. (Defaults to false, unless NODE_ENV == "production")
* @config {Number} [port=2195] Gateway port
* @config {Boolean} [rejectUnauthorized=true] Reject Unauthorized property to be passed through to tls.connect()
* @config {Boolean} [enhanced=true] Whether to use the enhanced notification format (recommended)
* @config {Function} [errorCallback] A callback which accepts 2 parameters (err, notification). Use `transmissionError` event instead.
* @config {Number} [cacheLength=1000] Number of notifications to cache for error purposes (See doc/apn.markdown)
* @config {Boolean} [autoAdjustCache=false] Whether the cache should grow in response to messages being lost after errors. (Will still emit a 'cacheTooSmall' event)
* @config {Number} [maxConnections=1] The maximum number of connections to create for sending messages.
* @config {Number} [connectionTimeout=0] The duration the socket should stay alive with no activity in milliseconds. 0 = Disabled.
* @config {Number} [connectionTimeout=3600000] The duration the socket should stay alive with no activity in milliseconds. 0 = Disabled.
* @config {Boolean} [buffersNotifications=true] Whether to buffer notifications and resend them after failure.
* @config {Boolean} [fastMode=false] Whether to aggresively empty the notification buffer while connected.
* @config {Boolean} [legacy=false] Whether to use the old (pre-iOS 7) protocol format.
*/
function Connection (options) {
if(false === (this instanceof Connection)) {
return new Connection(options);
}
this.options = {
cert: 'cert.pem',
key: 'key.pem',
cert: "cert.pem",
key: "key.pem",
ca: null,
pfx: null,
passphrase: null,
production: (process.env.NODE_ENV === "production"),
voip: false,
address: null,
port: 2195,
rejectUnauthorized: true,
enhanced: true,
cacheLength: 1000,
autoAdjustCache: true,
maxConnections: 1,
connectTimeout: 10000,
connectionTimeout: 0,
connectionTimeout: 3600000,
connectionRetryLimit: 10,
buffersNotifications: true,
fastMode: false,
legacy: false,
disableNagle: false,
disableEPIPEFix: false
};
for (var key in options) {
if (options[key] == null) {
if (options[key] === null || options[key] === undefined) {
debug("Option [" + key + "] set to null. This may cause unexpected behaviour.");
}
}
util.extend(this.options, options);
if (this.options.gateway != null) {
if (this.options.gateway) {
this.options.address = this.options.gateway;
}
if (this.options.address == null) {
if (!this.options.address) {
if (this.options.production) {
this.options.address = "gateway.push.apple.com";
}
@ -92,6 +92,14 @@ function Connection (options) {
this.options.address = "gateway.sandbox.push.apple.com";
}
}
else {
if (this.options.address === "gateway.push.apple.com") {
this.options.production = true;
}
else {
this.options.production = false;
}
}
if (this.options.pfx || this.options.pfxData) {
if (!options.cert) {
@ -138,7 +146,22 @@ sysu.inherits(Connection, events.EventEmitter);
Connection.prototype.initialize = function () {
if (!this.initializationPromise) {
debug("Initialising module");
this.initializationPromise = CredentialLoader(this.options);
var production = this.options.production;
this.initializationPromise = loadCredentials(this.options)
.then(function(credentials) {
var parsed;
try {
parsed = parseCredentials(credentials);
}
catch (e) {
debug(e);
return credentials;
}
parsed.production = production;
validateCredentials(parsed);
return credentials;
});
}
return this.initializationPromise;
@ -155,7 +178,7 @@ Connection.prototype.connect = function () {
debug("Initialising connection");
this.deferredConnection = q.defer();
this.initialize().spread(function (pfxData, certData, keyData, caData) {
this.initialize().then(function(credentials) {
var socketOptions = {};
socketOptions.port = this.options.port;
@ -165,21 +188,18 @@ Connection.prototype.connect = function () {
socketOptions.disableNagle = this.options.disableNagle;
socketOptions.connectionTimeout = this.options.connectionTimeout;
socketOptions.pfx = pfxData;
socketOptions.cert = certData;
socketOptions.key = keyData;
socketOptions.ca = caData;
socketOptions.pfx = credentials.pfx;
socketOptions.cert = credentials.cert;
socketOptions.key = credentials.key;
socketOptions.ca = credentials.ca;
socketOptions.passphrase = this.options.passphrase;
socketOptions.rejectUnauthorized = this.options.rejectUnauthorized;
this.socket = createSocket(this, socketOptions,
function () {
debug("Connection established");
this.emit('connected', this.sockets.length + 1);
this.emit("connected", this.sockets.length + 1);
this.deferredConnection.resolve();
clearTimeout(this.connectionTimer);
this.connectionTimer = null;
}.bind(this));
this.socket.on("error", this.errorOccurred.bind(this, this.socket));
@ -187,23 +207,27 @@ Connection.prototype.connect = function () {
this.socket.on("data", this.handleTransmissionError.bind(this, this.socket));
this.socket.on("drain", this.socketDrained.bind(this, this.socket, true));
this.socket.once("close", this.socketClosed.bind(this, this.socket));
if (this.options.connectTimeout > 0) {
this.connectionTimer = setTimeout(function () {
this.deferredConnection.reject(new Error("Connect timed out"));
this.socket.end();
}.bind(this), this.options.connectTimeout);
}
}.bind(this)).fail(function (error) {
}.bind(this)).done(null, function (error) {
debug("Module initialisation error:", error);
// This is a pretty fatal scenario, we don't have key/certificate to connect to APNS, there's not much we can do, so raise errors and clear the queue.
this.rejectBuffer(Errors['moduleInitialisationFailed']);
this.rejectBuffer(Errors.moduleInitialisationFailed);
this.emit("error", error);
this.deferredConnection.reject(error);
this.terminated = true;
}.bind(this));
if (this.options.connectTimeout > 0) {
var connectionTimer = setTimeout(function () {
this.deferredConnection.reject(new Error("Connect timed out"));
this.socket.end();
}.bind(this), this.options.connectTimeout);
return this.deferredConnection.promise.finally(function() {
clearTimeout(connectionTimer);
});
}
return this.deferredConnection.promise;
};
@ -238,12 +262,12 @@ Connection.prototype.createConnection = function() {
trace("connection failed", delay);
this.raiseError(error);
this.emit('error', error);
this.emit("socketError", error);
if (this.options.connectionRetryLimit > 0
&& this.failureCount > this.options.connectionRetryLimit
&& this.sockets.length == 0) {
this.rejectBuffer(Errors['connectionRetryLimitExceeded']);
&& this.sockets.length === 0) {
this.rejectBuffer(Errors.connectionRetryLimitExceeded);
this.shutdown();
this.terminated = true;
return;
@ -255,7 +279,9 @@ Connection.prototype.createConnection = function() {
this.deferredConnection = null;
this.socket = undefined;
this.serviceBuffer();
}.bind(this)).done();
}.bind(this)).done(null, function(error) {
this.emit("error", error);
}.bind(this));
};
/**
@ -302,25 +328,25 @@ Connection.prototype.initialisingConnection = function() {
}
} while(repeat && socketsAvailable > 0 && this.notificationBuffer.length > 0);
if (this.notificationBuffer.length > 0 && socketsAvailable == 0) {
if (this.notificationBuffer.length > 0 && socketsAvailable === 0) {
this.createConnection();
}
if (this.notificationBuffer.length === 0 && socketsAvailable == this.sockets.length){
if (this.notificationBuffer.length === 0 && socketsAvailable === this.sockets.length){
if (this.notificationsQueued) {
this.emit('completed');
this.emit("completed");
this.notificationsQueued = false;
}
if (this.shutdownPending) {
debug("closing connections");
for (var i = 0; i < this.sockets.length; i++) {
var socket = this.sockets[i];
for (var j = 0; j < this.sockets.length; j++) {
socket = this.sockets[j];
// We delay before closing connections to ensure we don't miss any error packets from the service.
setTimeout(socket.end.bind(socket), 2500);
this.retireSocket(socket);
}
if (this.sockets.length == 0) {
if (this.sockets.length === 0) {
this.shutdownPending = false;
}
}
@ -335,16 +361,16 @@ Connection.prototype.initialisingConnection = function() {
Connection.prototype.errorOccurred = function(socket, err) {
debug("Socket error occurred", socket.apnSocketId, err);
if(socket.transmissionErrorOccurred && err.code == 'EPIPE') {
if(socket.transmissionErrorOccurred && err.code === "EPIPE") {
debug("EPIPE occurred after a transmission error which we can ignore");
return;
}
this.emit('socketError', err);
if(this.socket == socket && this.deferredConnection && this.deferredConnection.promise.isPending()) {
if(this.socket === socket && this.deferredConnection && this.deferredConnection.promise.isPending()) {
this.deferredConnection.reject(err);
}
else {
this.emit("socketError", err);
this.raiseError(err, null);
}
@ -372,9 +398,9 @@ Connection.prototype.socketAvailable = function(socket) {
Connection.prototype.socketDrained = function(socket, serviceBuffer) {
debug("Socket drained", socket.apnSocketId);
socket.apnBusy = false;
if((!this.options.legacy || this.options.enhanced) && socket.apnCachedNotifications.length > 0) {
if(socket.apnCachedNotifications.length > 0) {
var notification = socket.apnCachedNotifications[socket.apnCachedNotifications.length - 1];
this.emit('transmitted', notification.notification, notification.recipient);
this.emit("transmitted", notification.notification, notification.recipient);
}
if(serviceBuffer === true && !this.runningOnNextTick) {
// There is a possibility that this could add multiple invocations to the
@ -394,7 +420,7 @@ Connection.prototype.socketDrained = function(socket, serviceBuffer) {
*/
Connection.prototype.socketTimeout = function(socket) {
debug("Socket timeout", socket.apnSocketId);
this.emit('timeout');
this.emit("timeout");
this.destroyConnection(socket);
this.serviceBuffer();
@ -428,7 +454,7 @@ Connection.prototype.socketClosed = function(socket) {
}
else {
this.retireSocket(socket);
this.emit('disconnected', this.sockets.length);
this.emit("disconnected", this.sockets.length);
}
this.serviceBuffer();
@ -445,7 +471,7 @@ Connection.prototype.socketClosed = function(socket) {
if (index > -1) {
this.sockets.splice(index, 1);
}
}
};
/**
* Use this method to modify the cache length after initialisation.
@ -459,8 +485,8 @@ Connection.prototype.setCacheLength = function(newLength) {
*/
Connection.prototype.bufferNotification = function (notification) {
if (notification.retryLimit === 0) {
this.raiseError(Errors['retryLimitExceeded'], notification);
this.emit('transmissionError', Errors['retryLimitExceeded'], notification.notification, notification.recipient);
this.raiseError(Errors.retryLimitExceeded, notification);
this.emit("transmissionError", Errors.retryLimitExceeded, notification.notification, notification.recipient);
return;
}
notification.retryLimit -= 1;
@ -475,9 +501,9 @@ Connection.prototype.rejectBuffer = function (errCode) {
while(this.notificationBuffer.length > 0) {
var notification = this.notificationBuffer.shift();
this.raiseError(errCode, notification.notification, notification.recipient);
this.emit('transmissionError', errCode, notification.notification, notification.recipient);
this.emit("transmissionError", errCode, notification.notification, notification.recipient);
}
}
};
/**
* @private
@ -487,8 +513,8 @@ Connection.prototype.prepareNotification = function (notification, device) {
// If a device token hasn't been given then we should raise an error.
if (recipient === undefined) {
util.setImmediate(function () {
this.raiseError(Errors['missingDeviceToken'], notification);
this.emit('transmissionError', Errors['missingDeviceToken'], notification);
this.raiseError(Errors.missingDeviceToken, notification);
this.emit("transmissionError", Errors.missingDeviceToken, notification);
}.bind(this));
return;
}
@ -501,8 +527,8 @@ Connection.prototype.prepareNotification = function (notification, device) {
catch (e) {
// If an exception has been thrown it's down to an invalid token.
util.setImmediate(function () {
this.raiseError(Errors['invalidToken'], notification, device);
this.emit('transmissionError', Errors['invalidToken'], notification, device);
this.raiseError(Errors.invalidToken, notification, device);
this.emit("transmissionError", Errors.invalidToken, notification, device);
}.bind(this));
return;
}
@ -518,7 +544,7 @@ Connection.prototype.prepareNotification = function (notification, device) {
Connection.prototype.cacheNotification = function (socket, notification) {
socket.apnCachedNotifications.push(notification);
if (socket.apnCachedNotifications.length > this.options.cacheLength) {
debug("Clearing notification %d from the cache", socket.apnCachedNotifications[0]['_uid']);
debug("Clearing notification %d from the cache", socket.apnCachedNotifications[0]._uid);
socket.apnCachedNotifications.splice(0, socket.apnCachedNotifications.length - this.options.cacheLength);
}
};
@ -527,11 +553,8 @@ Connection.prototype.cacheNotification = function (socket, notification) {
* @private
*/
Connection.prototype.handleTransmissionError = function (socket, data) {
if (data[0] == 8) {
if (data[0] === 8) {
socket.transmissionErrorOccurred = true;
if (!this.options.enhanced && this.options.legacy) {
return;
}
var errorCode = data[1];
var identifier = data.readUInt32BE(2);
@ -543,7 +566,7 @@ Connection.prototype.handleTransmissionError = function (socket, data) {
while (socket.apnCachedNotifications.length) {
notification = socket.apnCachedNotifications.shift();
if (notification['_uid'] == identifier) {
if (notification._uid === identifier) {
foundNotification = true;
break;
}
@ -554,21 +577,21 @@ Connection.prototype.handleTransmissionError = function (socket, data) {
while (temporaryCache.length) {
temporaryCache.shift();
}
this.emit('transmissionError', errorCode, notification.notification, notification.recipient);
this.emit("transmissionError", errorCode, notification.notification, notification.recipient);
this.raiseError(errorCode, notification.notification, notification.recipient);
}
else {
socket.apnCachedNotifications = temporaryCache;
if(socket.apnCachedNotifications.length > 0) {
var differentialSize = socket.apnCachedNotifications[0]['_uid'] - identifier;
this.emit('cacheTooSmall', differentialSize);
var differentialSize = socket.apnCachedNotifications[0]._uid - identifier;
this.emit("cacheTooSmall", differentialSize);
if(this.options.autoAdjustCache) {
this.options.cacheLength += differentialSize * 2;
}
}
this.emit('transmissionError', errorCode, null);
this.emit("transmissionError", errorCode, null);
this.raiseError(errorCode, null);
}
@ -596,9 +619,9 @@ Connection.prototype.raiseError = function(errorCode, notification, recipient) {
debug("Error occurred with trace:", errorCode.stack);
}
if (notification && typeof notification.errorCallback == 'function' ) {
if (notification && typeof notification.errorCallback === "function" ) {
notification.errorCallback(errorCode, recipient);
} else if (typeof this.options.errorCallback == 'function') {
} else if (typeof this.options.errorCallback === "function") {
this.options.errorCallback(errorCode, notification, recipient);
}
};
@ -608,13 +631,8 @@ Connection.prototype.raiseError = function(errorCode, notification, recipient) {
* @return {Boolean} Write completed, returns true if socketDrained should be called by the caller of this method.
*/
Connection.prototype.transmitNotification = function(socket, notification) {
if (!this.socketAvailable(socket)) {
this.bufferNotification(notification);
return;
}
var token = notification.recipient.token;
var encoding = notification.notification.encoding || 'utf8';
var encoding = notification.notification.encoding || "utf8";
var message = notification.notification.compile();
var messageLength = Buffer.byteLength(message, encoding);
var position = 0;
@ -624,95 +642,61 @@ Connection.prototype.transmitNotification = function(socket, notification) {
if (socket.apnCurrentId > 0xffffffff) {
socket.apnCurrentId = 0;
}
if (this.options.legacy) {
if (this.options.enhanced) {
data = new Buffer(1 + 4 + 4 + 2 + token.length + 2 + messageLength);
// Command
data[position] = 1;
position++;
// Identifier
data.writeUInt32BE(notification._uid, position);
position += 4;
// New Protocol uses framed notifications consisting of multiple items
// 1: Device Token
// 2: Payload
// 3: Notification Identifier
// 4: Expiration Date
// 5: Priority
// Each item has a 3 byte header: Type (1), Length (2) followed by data
// The frame layout is hard coded for now as original dynamic system had a
// significant performance penalty
// Expiry
data.writeUInt32BE(notification.notification.expiry, position);
position += 4;
this.cacheNotification(socket, notification);
}
else {
data = new Buffer(1 + 2 + token.length + 2 + messageLength);
//Command
data[position] = 0;
position++;
}
// Token Length
data.writeUInt16BE(token.length, position);
position += 2;
// Device Token
position += token.copy(data, position, 0);
// Payload Length
data.writeUInt16BE(messageLength, position);
position += 2;
//Payload
position += data.write(message, position, encoding);
var frameLength = 3 + token.length + 3 + messageLength + 3 + 4;
if(notification.notification.expiry > 0) {
frameLength += 3 + 4;
}
if(notification.notification.priority !== 10) {
frameLength += 3 + 1;
}
else {
// New Protocol uses framed notifications consisting of multiple items
// 1: Device Token
// 2: Payload
// 3: Notification Identifier
// 4: Expiration Date
// 5: Priority
// Each item has a 3 byte header: Type (1), Length (2) followed by data
// The frame layout is hard coded for now as original dynamic system had a
// significant performance penalty
var frameLength = 3 + token.length + 3 + messageLength + 3 + 4;
if(notification.notification.expiry > 0) {
frameLength += 3 + 4;
}
if(notification.notification.priority != 10) {
frameLength += 3 + 1;
}
// Frame has a 5 byte header: Type (1), Length (4) followed by items.
data = new Buffer(5 + frameLength);
data[position] = 2; position += 1;
// Frame has a 5 byte header: Type (1), Length (4) followed by items.
data = new Buffer(5 + frameLength);
data[position] = 2; position += 1;
// Frame Length
data.writeUInt32BE(frameLength, position); position += 4;
// Frame Length
data.writeUInt32BE(frameLength, position); position += 4;
// Token Item
data[position] = 1; position += 1;
data.writeUInt16BE(token.length, position); position += 2;
position += token.copy(data, position, 0);
// Token Item
data[position] = 1; position += 1;
data.writeUInt16BE(token.length, position); position += 2;
position += token.copy(data, position, 0);
// Payload Item
data[position] = 2; position += 1;
data.writeUInt16BE(messageLength, position); position += 2;
position += data.write(message, position, encoding);
// Payload Item
data[position] = 2; position += 1;
data.writeUInt16BE(messageLength, position); position += 2;
position += data.write(message, position, encoding);
// Identifier Item
data[position] = 3; position += 1;
data.writeUInt16BE(4, position); position += 2;
data.writeUInt32BE(notification._uid, position); position += 4;
// Identifier Item
data[position] = 3; position += 1;
if(notification.notification.expiry > 0) {
// Expiry Item
data[position] = 4; position += 1;
data.writeUInt16BE(4, position); position += 2;
data.writeUInt32BE(notification._uid, position); position += 4;
if(notification.notification.expiry > 0) {
// Expiry Item
data[position] = 4; position += 1;
data.writeUInt16BE(4, position); position += 2;
data.writeUInt32BE(notification.notification.expiry, position); position += 4;
}
if(notification.notification.priority != 10) {
// Priority Item
data[position] = 5; position += 1;
data.writeUInt16BE(1, position); position += 2;
data[position] = notification.notification.priority; position += 1;
}
this.cacheNotification(socket, notification);
data.writeUInt32BE(notification.notification.expiry, position); position += 4;
}
if(notification.notification.priority !== 10) {
// Priority Item
data[position] = 5; position += 1;
data.writeUInt16BE(1, position); position += 2;
data[position] = notification.notification.priority; position += 1;
}
this.cacheNotification(socket, notification);
socket.apnBusy = true;
return socket.write(data);
@ -720,11 +704,12 @@ Connection.prototype.transmitNotification = function(socket, notification) {
Connection.prototype.validNotification = function (notification, recipient) {
var messageLength = notification.length();
if (messageLength > 2048) {
var maxLength = (this.options.voip ? 4096 : 2048);
if (messageLength > maxLength) {
util.setImmediate(function () {
this.raiseError(Errors['invalidPayloadSize'], notification, recipient);
this.emit('transmissionError', Errors['invalidPayloadSize'], notification, recipient);
this.raiseError(Errors.invalidPayloadSize, notification, recipient);
this.emit("transmissionError", Errors.invalidPayloadSize, notification, recipient);
}.bind(this));
return false;
}
@ -739,7 +724,7 @@ Connection.prototype.validNotification = function (notification, recipient) {
*/
Connection.prototype.pushNotification = function (notification, recipient) {
if (this.terminated) {
this.emit('transmissionError', Errors['connectionTerminated'], notification, recipient);
this.emit("transmissionError", Errors.connectionTerminated, notification, recipient);
return false;
}
if (!this.validNotification(notification, recipient)) {

Просмотреть файл

@ -1,83 +0,0 @@
var fs = require('fs');
var q = require('q');
var sysu = require('util');
function Credentials(credentials) {
var readFile = q.nfbind(fs.readFile);
// Prepare PKCS#12 data if available
var pfxPromise = null;
if(credentials.pfx != null || credentials.pfxData != null) {
if(credentials.pfxData) {
pfxPromise = credentials.pfxData;
}
else if(Buffer.isBuffer(credentials.pfx)) {
pfxPromise = credentials.pfx;
}
else {
pfxPromise = readFile(credentials.pfx);
}
}
// Prepare Certificate data if available.
var certPromise = null;
if (credentials.certData) {
certPromise = credentials.certData;
}
else if(Buffer.isBuffer(credentials.cert) || checkPEMType(credentials.cert, "CERTIFICATE")) {
certPromise = credentials.cert;
}
else if(credentials.cert){
// Nothing has matched so attempt to load from disk
certPromise = readFile(credentials.cert);
}
// Prepare Key data if available
var keyPromise = null;
if (credentials.keyData) {
keyPromise = credentials.keyData;
}
else if(Buffer.isBuffer(credentials.key) || checkPEMType(credentials.key, "PRIVATE KEY")) {
keyPromise = credentials.key;
}
else if(credentials.key) {
keyPromise = readFile(credentials.key);
}
// Prepare Certificate Authority data if available.
var caPromises = [];
if (credentials.ca != null && !sysu.isArray(credentials.ca)) {
credentials.ca = [ credentials.ca ];
}
for(var i in credentials.ca) {
var ca = credentials.ca[i];
if(Buffer.isBuffer(ca) || checkPEMType(ca, "CERTIFICATE")) {
caPromises.push(ca);
}
else if (ca){
caPromises.push(readFile(ca));
}
}
if (caPromises.length == 0) {
caPromises = undefined;
}
else {
caPromises = q.all(caPromises);
}
return q.all([pfxPromise, certPromise, keyPromise, caPromises]);
}
function checkPEMType(input, type) {
if (input == null) {
return false;
}
var matches = input.match(/\-\-\-\-\-BEGIN ([A-Z\s*]+)\-\-\-\-\-/);
if (matches != null) {
return matches[1].indexOf(type) >= 0;
}
return false;
}
module.exports = Credentials;

Просмотреть файл

@ -0,0 +1,35 @@
"use strict";
var APNKey = require("./APNKey");
var oids = require("./oids");
function APNCertificate(cert) {
if(!cert.publicKey || !cert.validity || !cert.subject) {
throw new Error("certificate object is invalid");
}
this._cert = cert;
}
APNCertificate.prototype.key = function() {
return new APNKey(this._cert.publicKey);
};
APNCertificate.prototype.validity = function() {
return this._cert.validity;
};
APNCertificate.prototype.environment = function() {
var environment = { sandbox: false, production: false };
if (this._cert.getExtension({ "id": oids.applePushServiceClientDevelopment })) {
environment.sandbox = true;
}
if (this._cert.getExtension({ "id": oids.applePushServiceClientProduction })) {
environment.production = true;
}
return environment;
};
module.exports = APNCertificate;

17
lib/credentials/APNKey.js Normal file
Просмотреть файл

@ -0,0 +1,17 @@
"use strict";
var forge = require("node-forge");
function APNKey(key) {
if(!key || !key.n || !key.e) {
throw new Error("key is not a valid public key");
}
this._key = key;
}
APNKey.prototype.fingerprint = function() {
return forge.pki.getPublicKeyFingerprint(this._key, {encoding: "hex"});
};
module.exports = APNKey;

42
lib/credentials/load.js Normal file
Просмотреть файл

@ -0,0 +1,42 @@
"use strict";
var q = require("q");
var sysu = require("util");
var resolve = require("./resolve");
function loadCredentials(credentials) {
// Prepare PKCS#12 data if available
var pfxPromise = resolve(credentials.pfx || credentials.pfxData);
// Prepare Certificate data if available.
var certPromise = resolve(credentials.cert || credentials.certData);
// Prepare Key data if available
var keyPromise = resolve(credentials.key || credentials.keyData);
// Prepare Certificate Authority data if available.
var caPromises = [];
if (credentials.ca !== null) {
if(!sysu.isArray(credentials.ca)) {
credentials.ca = [ credentials.ca ];
}
credentials.ca.forEach(function(ca) {
caPromises.push(resolve(ca));
});
}
if (caPromises.length === 0) {
caPromises = undefined;
}
else {
caPromises = q.all(caPromises);
}
return q.all([pfxPromise, certPromise, keyPromise, caPromises])
.spread(function(pfx, cert, key, ca) {
return { pfx: pfx, cert: cert, key: key, ca: ca, passphrase: credentials.passphrase };
});
}
module.exports = loadCredentials;

8
lib/credentials/oids.js Normal file
Просмотреть файл

@ -0,0 +1,8 @@
"use strict";
var oids = {
"applePushServiceClientDevelopment" : "1.2.840.113635.100.6.3.1",
"applePushServiceClientProduction" : "1.2.840.113635.100.6.3.2",
};
module.exports = oids;

20
lib/credentials/parse.js Normal file
Просмотреть файл

@ -0,0 +1,20 @@
var parsePkcs12 = require("./parsePkcs12");
var parsePemKey = require("./parsePemKey");
var parsePemCert = require("./parsePemCertificate");
function parse(credentials) {
var parsed = {};
parsed.key = parsePemKey(credentials.key, credentials.passphrase);
parsed.certificates = parsePemCert(credentials.cert);
var pkcs12Parsed = parsePkcs12(credentials.pfx, credentials.passphrase);
if (pkcs12Parsed) {
parsed.key = pkcs12Parsed.key;
parsed.certificates = pkcs12Parsed.certificates;
}
return parsed;
}
module.exports = parse;

Просмотреть файл

@ -0,0 +1,35 @@
"use strict";
var forge = require("node-forge");
var APNCertificate = require("./APNCertificate");
function apnCertificateFromPem(certData) {
if (!certData) {
return null;
}
var pemMessages;
try {
pemMessages = forge.pem.decode(certData);
}
catch (e) {
if (e.message.match("Invalid PEM formatted message.")) {
throw new Error("unable to parse certificate, not a valid PEM file");
}
}
var certificates = [];
pemMessages.forEach(function(message) {
if (!message.type.match(new RegExp("CERTIFICATE$"))) {
return;
}
var certAsn1 = forge.asn1.fromDer(message.body);
var forgeCertificate = forge.pki.certificateFromAsn1(certAsn1);
certificates.push(new APNCertificate(forgeCertificate));
});
return certificates;
}
module.exports = apnCertificateFromPem;

Просмотреть файл

@ -0,0 +1,60 @@
"use strict";
var forge = require("node-forge");
var APNKey = require("./APNKey");
function findAndDecryptKey(pemMessages, passphrase) {
var apnKey = null;
pemMessages.forEach(function(message) {
if (!message.type.match(/KEY/)) {
return;
}
var key = forge.pki.decryptRsaPrivateKey(forge.pem.encode(message), passphrase);
if(!key) {
if ((message.procType && message.procType.type === "ENCRYPTED") || message.type.match(/ENCRYPTED/)) {
throw new Error("unable to parse key, incorrect passphrase");
}
}
else if(apnKey) {
throw new Error("multiple keys found in PEM file");
}
else {
apnKey = new APNKey(key);
}
});
return apnKey;
}
function apnKeyFromPem(keyPem, passphrase) {
if (!keyPem) {
return null;
}
try {
var pemMessages = forge.pem.decode(keyPem);
var apnKey = findAndDecryptKey(pemMessages, passphrase);
if (apnKey) {
return apnKey;
}
}
catch (e) {
if (e.message.match(/Unsupported OID/)) {
throw new Error("unable to parse key, unsupported format: " + e.oid);
}
else if(e.message.match(/Invalid PEM formatted message/)) {
throw new Error("unable to parse key, not a valid PEM file");
}
else if (e.message.match(/multiple keys/)) {
throw e;
}
else if (e.message.match(/unable to parse key/)) {
throw e;
}
}
throw new Error("unable to parse key, no private key found");
}
module.exports = apnKeyFromPem;

Просмотреть файл

@ -0,0 +1,59 @@
"use strict";
var forge = require("node-forge");
var APNKey = require("../../lib/credentials/APNKey");
var APNCertificate = require("../../lib/credentials/APNCertificate");
function decryptPkcs12FromAsn1(asn1, passphrase) {
try {
return forge.pkcs12.pkcs12FromAsn1(asn1, false, passphrase);
}
catch (e) {
// OpenSSL-exported files need an empty string, if no password was specified
// during export.
if (passphrase) {
throw e;
}
return forge.pkcs12.pkcs12FromAsn1(asn1, false, "");
}
}
function apnCredentialsFromPkcs12(p12Data, passphrase) {
if (!p12Data) {
return;
}
var asn1 = forge.asn1.fromDer(p12Data.toString("binary"), false);
var pkcs12;
try {
pkcs12 = decryptPkcs12FromAsn1(asn1, passphrase);
}
catch(e) {
if (e.message.match("Invalid password")) {
throw new Error("unable to parse credentials, incorrect passphrase");
}
else {
throw new Error("unable to parse credentials, not a PFX/P12 file");
}
}
var credentials = { "key": null, "certificates": []};
pkcs12.safeContents.forEach(function(safeContents) {
safeContents.safeBags.forEach(function(safeBag) {
if(safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
if(credentials.key) {
throw new Error("multiple keys found in PFX/P12 file");
}
credentials.key = new APNKey(safeBag.key);
}
else if(safeBag.type === forge.pki.oids.certBag) {
credentials.certificates.push(new APNCertificate(safeBag.cert));
}
});
});
return credentials;
}
module.exports = apnCredentialsFromPkcs12;

Просмотреть файл

@ -0,0 +1,21 @@
"use strict";
var fs = require("fs");
var q = require("q");
function resolveCredential(value) {
if (!value) {
return value;
}
if(/-----BEGIN ([A-Z\s*]+)-----/.test(value)) {
return value;
}
else if(Buffer.isBuffer(value)) {
return value;
}
else {
return q.nfbind(fs.readFile)(value);
}
}
module.exports = resolveCredential;

Просмотреть файл

@ -0,0 +1,24 @@
"use strict";
function validateCredentials(credentials) {
var certificate = credentials.certificates[0];
if (credentials.key.fingerprint() !== certificate.key().fingerprint()) {
throw new Error("certificate and key do not match");
}
var validity = certificate.validity();
if (validity.notAfter.getTime() < Date.now()) {
throw new Error("certificate has expired: " + validity.notAfter.toJSON());
}
if (credentials.production !== undefined) {
var environment = certificate.environment();
if ( (credentials.production && !environment.production) ||
(!credentials.production && !environment.sandbox)) {
throw new Error("certificate does not support configured environment, production: " + credentials.production);
}
}
}
module.exports = validateCredentials;

Просмотреть файл

@ -1,3 +1,4 @@
"use strict";
/**
* Creates a Device.
* @constructor
@ -8,7 +9,7 @@ function Device(deviceToken) {
return new Device(deviceToken);
}
if(typeof deviceToken == "string") {
if(typeof deviceToken === "string") {
this.token = new Buffer(deviceToken.replace(/[^0-9a-f]/gi, ""), "hex");
}
else if(Buffer.isBuffer(deviceToken)) {
@ -16,8 +17,8 @@ function Device(deviceToken) {
deviceToken.copy(this.token);
}
if (!this.token || this.token.length == 0) {
throw new Error('Invalid Token Specified, must be a Buffer or valid hex String');
if (!this.token || this.token.length === 0) {
throw new Error("Invalid Token Specified, must be a Buffer or valid hex String");
}
}

Просмотреть файл

@ -1,24 +1,25 @@
"use strict";
/**
* Error codes used by Apple
* @see <a href="https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4">The Binary Interface and Notification Formats</a>
*/
var Errors = {
'noErrorsEncountered': 0,
'processingError': 1,
'missingDeviceToken': 2,
'missingTopic': 3,
'missingPayload': 4,
'invalidTokenSize': 5,
'invalidTopicSize': 6,
'invalidPayloadSize': 7,
'invalidToken': 8,
'apnsShutdown': 10,
'none': 255,
'retryLimitExceeded': 512,
'moduleInitialisationFailed': 513,
'connectionRetryLimitExceeded': 514, // When a connection is unable to be established. Usually because of a network / SSL error this will be emitted
'connectionTerminated': 515
"noErrorsEncountered": 0,
"processingError": 1,
"missingDeviceToken": 2,
"missingTopic": 3,
"missingPayload": 4,
"invalidTokenSize": 5,
"invalidTopicSize": 6,
"invalidPayloadSize": 7,
"invalidToken": 8,
"apnsShutdown": 10,
"none": 255,
"retryLimitExceeded": 512,
"moduleInitialisationFailed": 513,
"connectionRetryLimitExceeded": 514, // When a connection is unable to be established. Usually because of a network / SSL error this will be emitted
"connectionTerminated": 515
};
module.exports = Errors;

Просмотреть файл

@ -1,18 +1,20 @@
var CredentialLoader = require('./credentials');
var Device = require('./device');
var Errors = require('./errors');
"use strict";
var createSocket = require('./socket');
var loadCredentials = require("./credentials/load");
var parseCredentials = require("./credentials/parse");
var validateCredentials = require("./credentials/validate");
var Device = require("./device");
var q = require('q');
var tls = require('tls');
var sysu = require('util');
var util = require('./util');
var events = require('events');
var createSocket = require("./socket");
var q = require("q");
var sysu = require("util");
var util = require("./util");
var events = require("events");
var debug = function() {};
if(process.env.DEBUG) {
try {
debug = require('debug')('apnfb');
debug = require("debug")("apnfb");
}
catch (e) {
console.log("Notice: 'debug' module is not available. This should be installed with `npm install debug` to enable debug messages", e);
@ -41,8 +43,8 @@ function Feedback(options) {
return new Feedback(options);
}
this.options = {
cert: 'cert.pem', /* Certificate file */
key: 'key.pem', /* Key file */
cert: "cert.pem", /* Certificate file */
key: "key.pem", /* Key file */
ca: null, /* Certificate Authority */
pfx: null, /* PFX File */
passphrase: null, /* Passphrase for key */
@ -57,14 +59,14 @@ function Feedback(options) {
};
for (var key in options) {
if (options[key] == null) {
if (options[key] === null) {
debug("Option [" + key + "] set to null. This may cause unexpected behaviour.");
}
}
util.extend(this.options, options);
if (this.options.address == null) {
if (!this.options.address) {
if (this.options.production) {
this.options.address = "feedback.push.apple.com";
}
@ -72,6 +74,14 @@ function Feedback(options) {
this.options.address = "feedback.sandbox.push.apple.com";
}
}
else {
if (this.options.address === "feedback.push.apple.com") {
this.options.production = true;
}
else {
this.options.production = false;
}
}
if (this.options.pfx || this.options.pfxData) {
if (!options.cert) {
@ -90,16 +100,16 @@ function Feedback(options) {
events.EventEmitter.call(this);
if (typeof this.options.errorCallback == 'function') {
this.on('error', this.options.errorCallback);
if (typeof this.options.errorCallback === "function") {
this.on("error", this.options.errorCallback);
}
if (typeof this.options.feedback == 'function') {
this.on('feedback', this.options.feedback);
if (typeof this.options.feedback === "function") {
this.on("feedback", this.options.feedback);
}
process.nextTick(function() {
if(this.listeners('feedback').length === 0) {
if(this.listeners("feedback").length === 0) {
debug("WARNING: A `feedback` listener has not been specified. Data may be lost.");
}
}.bind(this));
@ -116,7 +126,22 @@ sysu.inherits(Feedback, events.EventEmitter);
Feedback.prototype.initialize = function () {
if (!this.initializationPromise) {
debug("Initialising module");
this.initializationPromise = CredentialLoader(this.options);
var production = this.options.production;
this.initializationPromise = loadCredentials(this.options)
.then(function(credentials) {
var parsed;
try {
parsed = parseCredentials(credentials);
}
catch (e) {
debug(e);
return credentials;
}
parsed.production = production;
validateCredentials(parsed);
return credentials;
});
}
return this.initializationPromise;
@ -133,15 +158,15 @@ Feedback.prototype.connect = function () {
debug("Initialising connection");
this.deferredConnection = q.defer();
this.initialize().spread(function(pfxData, certData, keyData, caData) {
this.initialize().then(function(credentials) {
var socketOptions = {};
socketOptions.port = this.options.port;
socketOptions.host = this.options.address;
socketOptions.pfx = pfxData;
socketOptions.cert = certData;
socketOptions.key = keyData;
socketOptions.ca = caData;
socketOptions.pfx = credentials.pfx;
socketOptions.cert = credentials.cert;
socketOptions.key = credentials.key;
socketOptions.ca = credentials.ca;
socketOptions.passphrase = this.options.passphrase;
socketOptions.rejectUnauthorized = this.options.rejectUnauthorized;
@ -153,12 +178,16 @@ Feedback.prototype.connect = function () {
this.readBuffer = new Buffer(0);
this.feedbackData = [];
this.socket.on('data', this.receive.bind(this));
this.socket.on("data", this.receive.bind(this));
this.socket.on("error", this.destroyConnection.bind(this));
this.socket.once('close', this.resetConnection.bind(this));
}.bind(this)).fail(function (error) {
this.socket.once("close", this.resetConnection.bind(this));
}.bind(this), function (error) {
debug("Module initialisation error:", error);
this.emit('error', error);
this.cancel();
throw error;
}.bind(this)).done(null, function(error) {
this.emit("error", error);
this.deferredConnection.reject(error);
this.deferredConnection = null;
}.bind(this));
@ -192,11 +221,11 @@ Feedback.prototype.receive = function (data) {
var device = new Device(token);
if (!this.options.batchFeedback) {
debug("Emitting feedback event");
this.emit('feedback', time, device);
this.emit("feedback", time, device);
} else {
this.feedbackData.push({ time: time, device: device });
if (this.options.batchSize > 0 && this.options.batchSize <= this.feedbackData.length) {
this.emit('feedback', this.feedbackData);
this.emit("feedback", this.feedbackData);
this.feedbackData = [];
}
}
@ -210,7 +239,7 @@ Feedback.prototype.receive = function (data) {
Feedback.prototype.destroyConnection = function (err) {
debug("Destroying connection");
if(err) {
this.emit('feedbackError', err);
this.emit("feedbackError", err);
}
if (this.socket) {
this.socket.destroySoon();
@ -225,7 +254,7 @@ Feedback.prototype.resetConnection = function () {
if (this.options.batchFeedback) {
debug("Emitting " + this.feedbackData.length + " feedback tokens");
this.emit('feedback', this.feedbackData);
this.emit("feedback", this.feedbackData);
this.feedbackData = [];
}
@ -257,7 +286,7 @@ Feedback.prototype.start = function () {
Feedback.prototype.request = function () {
debug("Performing feedback request");
this.connect().fail(function (error) {
this.emit('feedbackError', error);
this.emit("feedbackError", error);
}.bind(this));
};

Просмотреть файл

@ -1,9 +1,10 @@
"use strict";
/**
* Create a notification
* @constructor
*/
function Notification (payload) {
this.encoding = 'utf8';
this.encoding = "utf8";
this.payload = payload || {};
this.expiry = 0;
@ -14,12 +15,6 @@ function Notification (payload) {
/** @deprecated since v1.3.0 used connection#pushNotification instead which accepts device token separately **/
this.device = undefined;
this.alert = undefined;
this.badge = undefined;
this.sound = undefined;
/** @since v1.2.0 */
this.newsstandAvailable = undefined;
this.contentAvailable = undefined;
this.mdm = undefined;
@ -34,6 +29,59 @@ function Notification (payload) {
this.category = undefined;
}
Notification.prototype = {
get alert() {
return this._alert;
},
set alert(value) {
var type = typeof value;
if (type == "string" || type == "object" || value === undefined) {
this._alert = value;
}
},
get badge() {
return this._badge;
},
set badge(value) {
if (typeof value === "number" || value === undefined) {
this._badge = value;
}
},
get sound() {
return this._sound;
},
set sound(value) {
if (typeof value == "string" || value === undefined) {
this._sound = value;
}
},
get contentAvailable() {
return this._contentAvailable;
},
set contentAvailable(value) {
if (typeof value == "boolean" || value === undefined) {
this._contentAvailable = value;
}
},
get newsstandAvailable() {
return this.contentAvailable;
},
set newsstandAvailable(value) {
this.contentAvailable = value;
},
get mdm() {
return this._mdm;
},
set mdm(value) {
this._mdm = value;
}
}
/**
* Clone a notification to send to multiple devices
* @param {Device} [device] Device the notification will be sent to
@ -82,7 +130,7 @@ Notification.prototype.setExpiry = function (expiry) {
Notification.prototype.setPriority = function (priority) {
this.priority = priority;
return this;
}
};
/**
* Set the "badge" value on the alert object
@ -104,6 +152,13 @@ Notification.prototype.setSound = function (sound) {
return this;
};
Notification.prototype.getAlertText = function () {
if(typeof this.alert === "object") {
return this.alert.body;
}
return this.alert;
};
/**
* Set the alert text for the notification
* @param {String} alertText The text of the alert message.
@ -111,27 +166,51 @@ Notification.prototype.setSound = function (sound) {
* @since v1.2.0
*/
Notification.prototype.setAlertText = function (text) {
if(typeof this.alert != "object") {
if(typeof this.alert !== "object") {
this.alert = text;
}
else {
this.prepareAlert();
this.alert['body'] = text;
this.alert.body = text;
}
return this;
};
/**
* Set the alert title for the notification - used with Safari Push Notifications
* Set the alert title for the notification - used with Safari Push Notifications and iOS Push Notifications displayed on Apple Watch
* @param {String} alertTitle The title for the alert.
* @see The <a href="https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NotificationProgrammingGuideForWebsites/PushNotifications/PushNotifications.html#//apple_ref/doc/uid/TP40013225-CH3-SW12">Pushing Notifications</a> in the Notification Programming Guide for Websites
* @since v1.5.0
*/
Notification.prototype.setAlertTitle = function(alertTitle) {
this.prepareAlert();
this.alert['title'] = alertTitle;
this.alert.title = alertTitle;
return this;
}
};
/**
* Set the alert title-loc-key for the notification - used with iOS Push Notifications displayed on Apple Watch. Please note: The corresponding localization key must be in your host app's (i.e. iPhone app) Localizable.strings file and not inside your WatchKit extension or WatchKit app.
* @param {String} titleLocKey The localization key for the alert title.
* @see The <a href="https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1">Payload Documentation</a>
* @since XXX
*/
Notification.prototype.setTitleLocKey = function(titleLocKey) {
this.prepareAlert();
this.alert["title-loc-key"] = titleLocKey;
return this;
};
/**
* Set the alert title-loc-args for the notification - used with iOS Push Notifications displayed on Apple Watch
* @param {String[]} [titleLocArgs] Variable string values to appear in place of the format specifiers in title-loc-key.
* @see The <a href="https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1">Payload Documentation</a>
* @since XXX
*/
Notification.prototype.setTitleLocArgs = function(titleLocArgs) {
this.prepareAlert();
this.alert["title-loc-args"] = titleLocArgs;
return this;
};
/**
* Set the alert action label for the notification - used with Safari Push Notifications
@ -141,9 +220,9 @@ Notification.prototype.setAlertTitle = function(alertTitle) {
*/
Notification.prototype.setAlertAction = function(alertAction) {
this.prepareAlert();
this.alert['action'] = alertAction;
this.alert.action = alertAction;
return this;
}
};
/**
* Set the action-loc-key property on the alert object
@ -153,7 +232,7 @@ Notification.prototype.setAlertAction = function(alertAction) {
*/
Notification.prototype.setActionLocKey = function (key) {
this.prepareAlert();
this.alert['action-loc-key'] = key;
this.alert["action-loc-key"] = key;
return this;
};
@ -169,7 +248,7 @@ Notification.prototype.setLocKey = function (key) {
delete this.alert["loc-key"];
return;
}
this.alert['loc-key'] = key;
this.alert["loc-key"] = key;
return this;
};
@ -185,7 +264,7 @@ Notification.prototype.setLocArgs = function (args) {
delete this.alert["loc-args"];
return;
}
this.alert['loc-args'] = args;
this.alert["loc-args"] = args;
return this;
};
@ -271,9 +350,9 @@ Notification.prototype.setCategory = function (category) {
*/
Notification.prototype.prepareAlert = function () {
var existingValue = this.alert;
if(typeof existingValue != "object") {
if(typeof existingValue !== "object") {
this.alert = {};
if(typeof existingValue == "string") {
if(typeof existingValue === "string") {
this.alert.body = existingValue;
}
}
@ -284,7 +363,8 @@ Notification.prototype.prepareAlert = function () {
* @since v1.2.0
*/
Notification.prototype.length = function () {
return Buffer.byteLength(this.compile(), this.encoding || 'utf8');
this.compiled = false;
return Buffer.byteLength(this.compile(), this.encoding || "utf8");
};
/**
@ -293,21 +373,16 @@ Notification.prototype.length = function () {
* @since v1.2.0
*/
Notification.prototype.trim = function(length) {
var tooLong = this.length() - (length || 2048);
var payloadLength = this.length();
var tooLong = payloadLength - (length || 2048);
if(tooLong <= 0) {
return 0;
}
this.compiled = false;
var length;
var encoding = this.encoding || 'utf8';
var escaped; // Working variable for trimming string
if(typeof this.alert == "string") {
escaped = this.alert;
}
else if(typeof this.alert == "object" && typeof this.alert.body == "string") {
escaped = this.alert.body;
}
else {
var encoding = this.encoding || "utf8";
var escaped = this.getAlertText();
if(!escaped) {
return -tooLong;
}
@ -316,17 +391,15 @@ Notification.prototype.trim = function(length) {
if (length < tooLong) {
return length - tooLong;
}
escaped = this.truncateStringToLength(escaped, length - tooLong);
escaped = escaped.replace(/(\\|\\x\d{0,1}|\\u\d{0,3})$/, '');
escaped = JSON.parse('"' + escaped + '"');
escaped = this.truncateStringToLength(escaped, length - tooLong, encoding);
escaped = escaped.replace(/(\\u[0-9a-fA-F]{0,3})$/, "");
escaped = escaped.replace(/\\+$/, function(a){ return a.length % 2 === 0 ? a : a.slice(0, -1); });
if (typeof this.alert == "string") {
this.alert = escaped;
}
else {
this.alert.body = escaped;
}
return tooLong;
var trimmed = Buffer.byteLength(escaped, encoding);
escaped = JSON.parse("\"" + escaped + "\"");
this.setAlertText(escaped);
return length - trimmed;
};
/**
@ -335,30 +408,38 @@ Notification.prototype.trim = function(length) {
* @since v1.3.0
*/
Notification.prototype.compile = function () {
if(this.compiled) {
return this.compiledPayload;
if(!this.compiled) {
this.compiled = JSON.stringify(this);
}
this.compiledPayload = JSON.stringify(this);
this.compiled = true;
return this.compiledPayload;
return this.compiled;
};
function hasValidUnicodeTail(string, encoding) {
var code = string.charCodeAt(string.length - 1);
if (code !== 0xFFFD && encoding === "utf8") {
return true;
}
else if ((code < 0xD800 || code > 0xDBFF) && (encoding === "utf16le" || encoding === "ucs2")) {
return true;
}
return false;
}
/**
* @param {String} [string] Unicode string to be truncated
* @param {Number} [length] The maximum number of bytes permitted in the Unicode string
* @returns {String} Truncated String
* @private
*/
Notification.prototype.truncateStringToLength = function (string, length) {
Notification.prototype.truncateStringToLength = function (string, length, encoding) {
// Convert to a buffer and back to a string with the correct encoding to truncate the unicode series correctly.
var result = new Buffer(string, this.encoding || 'utf8').toString(this.encoding || 'utf8', 0, length);
var result = new Buffer(string, encoding).toString(encoding, 0, length);
if (this.truncateAtWordEnd === true) {
var lastSpaceIndexInResult = result.lastIndexOf(' ');
var lastSpaceIndexInResult = result.lastIndexOf(" ");
// Only truncate the string further if the remainder isn't a whole word
// which we can tell by checking the *next* character in the original string
if(lastSpaceIndexInResult != -1 && string.charAt(result.length + 1) != ' '){
result=result.substr(0,lastSpaceIndexInResult);
if(lastSpaceIndexInResult !== -1 && string.charAt(result.length) !== " "){
result = result.substr(0, lastSpaceIndexInResult);
}
}
@ -366,23 +447,9 @@ Notification.prototype.truncateStringToLength = function (string, length) {
// invalid characters (represented as U+FFFD "REPLACEMENT CHARACTER") for UTF-8
// or orphaned lead surrogates for UTF-16 (UCS-2) - where only the tail surrogate
// has been removed.
var done = false;
var encoding = this.encoding || 'utf8';
if (encoding != 'utf8' && encoding != 'utf16le' && encoding != 'ucs2') {
return result;
}
while (! done) {
var lastIndex = result.length - 1;
var code = result.charCodeAt(lastIndex);
if (code != 0xFFFD && encoding == 'utf8') {
done = true;
}
else if ((code < 0xD800 || code > 0xDBFF) && (encoding == 'utf16le' || encoding == 'ucs2')) {
done = true;
}
else {
result = result.substr(0, lastIndex);
if (encoding === "utf8" || encoding === "utf16le" || encoding === "ucs2") {
while( result.length > 0 && !hasValidUnicodeTail(result, encoding) ) {
result = result.substr(0, result.length - 1);
}
}
@ -392,46 +459,40 @@ Notification.prototype.truncateStringToLength = function (string, length) {
/**
* @private
*/
Notification.prototype.toJSON = function () {
if (this.payload === undefined) {
this.payload = {};
Notification.prototype.apsPayload = function() {
var aps = this.payload.aps || {};
if (typeof this.badge === "number") {
aps.badge = this.badge;
}
if (typeof this.mdm == 'string') {
if (typeof this.sound === "string") {
aps.sound = this.sound;
}
if (this.alert !== undefined) {
aps.alert = this.alert;
}
if (this.contentAvailable) {
aps["content-available"] = 1;
}
if (Array.isArray(this.urlArgs)) {
aps["url-args"] = this.urlArgs;
}
if (typeof this.category === "string") {
aps.category = this.category;
}
return aps;
};
Notification.prototype.toJSON = function () {
if (typeof this.mdm === "string") {
this.payload.mdm = this.mdm;
return this.payload;
}
var apsSet = true;
if (this.payload.aps === undefined) {
this.payload.aps = {};
apsSet = false;
}
if (typeof this.badge == 'number') {
this.payload.aps.badge = this.badge;
apsSet = true;
}
if (typeof this.sound == 'string') {
this.payload.aps.sound = this.sound;
apsSet = true;
}
if (typeof this.alert == 'string' || typeof this.alert == 'object') {
this.payload.aps.alert = this.alert;
apsSet = true;
}
if (this.newsstandAvailable || this.contentAvailable) {
this.payload.aps['content-available'] = 1;
apsSet = true;
}
if (Array.isArray(this.urlArgs)) {
this.payload.aps['url-args'] = this.urlArgs;
apsSet = true;
}
if (typeof this.category == 'string') {
this.payload.aps.category = this.category;
apsSet = true;
}
var aps = this.apsPayload();
if (!apsSet) {
delete this.payload.aps;
if (Object.keys(aps).length > 0) {
this.payload.aps = aps;
}
return this.payload;

Просмотреть файл

@ -1,24 +1,25 @@
var tls = require('tls');
var net = require('net');
"use strict";
var tls = require("tls");
var net = require("net");
var debug = function() {};
if(process.env.DEBUG) {
try {
debug = require('debug')('apn:socket');
debug = require("debug")("apn:socket");
} catch (e) {}
}
function destroyEPIPEFix(e) {
function DestroyEPIPEFix(e) {
// When a write error occurs we miss the opportunity to
// read error data from APNS. Delay the call to destroy
// to allow more data to be read.
var self = this;
var socket = this;
var args = arguments;
var call = function () {
self._apnDestroy.apply(self, args);
}
socket._apnDestroy.apply(socket, args);
};
if (e && e.syscall == "write") {
if (e && e.syscall === "write") {
setTimeout(call, 1000);
}
else {
@ -33,14 +34,14 @@ function apnSocketLegacy(connection, socketOptions, connected) {
if (!socketOptions.disableEPIPEFix) {
socketOptions.socket._apnDestroy = socketOptions.socket._destroy;
socketOptions.socket._destroy = destroyEPIPEFix;
socketOptions.socket._destroy = DestroyEPIPEFix;
socketOptions.socket.on("error", function () {});
}
var socket = tls.connect( socketOptions['port'], socketOptions['host'],
var socket = tls.connect( socketOptions.port, socketOptions.host,
socketOptions, connected);
debug("connecting to: ", socketOptions['host'] + ":" + socketOptions['port']);
debug("connecting to: ", socketOptions.host + ":" + socketOptions.port);
socketOptions.socket.setNoDelay(socketOptions.disableNagle);
socketOptions.socket.setKeepAlive(true);
@ -50,7 +51,7 @@ function apnSocketLegacy(connection, socketOptions, connected) {
// The actual connection is delayed until after all the event listeners have
// been attached.
socketOptions.socket.connect(socketOptions['port'], socketOptions['host']);
socketOptions.socket.connect(socketOptions.port, socketOptions.host);
return socket;
}
@ -61,7 +62,7 @@ function apnSocket(connection, socketOptions, connected) {
if (!socketOptions.disableEPIPEFix) {
socket._apnDestroy = socket._destroy;
socket._destroy = destroyEPIPEFix;
socket._destroy = DestroyEPIPEFix;
}
socket.setNoDelay(socketOptions.disableNagle);
@ -70,7 +71,7 @@ function apnSocket(connection, socketOptions, connected) {
socket.setTimeout(socketOptions.connectionTimeout);
}
debug("connecting to: ", socketOptions['host'] + ":" + socketOptions['port']);
debug("connecting to: ", socketOptions.host + ":" + socketOptions.port);
return socket;
}

Просмотреть файл

@ -1,3 +1,5 @@
"use strict";
var extend = function(target) {
Array.prototype.slice.call(arguments, 1).forEach(function(source) {
for (var key in source) {
@ -9,7 +11,7 @@ var extend = function(target) {
};
var apnSetImmediate = function (method) {
if('function' === typeof setImmediate) {
if("function" === typeof setImmediate) {
setImmediate(method);
}
else {

Просмотреть файл

@ -1,7 +1,7 @@
{
"name": "apn",
"description": "An interface to the Apple Push Notification service for Node.js",
"version": "1.6.2",
"version": "1.7.3",
"author": "Andrew Naylor <argon@mkbot.net>",
"contributors": [
{
@ -27,18 +27,25 @@
"url": "https://github.com/argon/node-apn.git"
},
"dependencies": {
"q": "1.x"
"node-forge": "^0.6.20",
"q": "^1.1.0"
},
"devDependencies": {
"chai": "2.x",
"chai-as-promised": "*",
"mocha": "*",
"chai": "*",
"chai-as-promised": "*"
"rewire": "^2.3.0",
"sinon": "^1.12.2",
"sinon-chai": "^2.6.0"
},
"scripts": {
"test": "node_modules/.bin/mocha -w"
"test": "node_modules/.bin/mocha"
},
"jshintConfig": {
"node": true
},
"engines": {
"node": ">= 0.6.14"
"node": ">= 0.8.0"
},
"license": "MIT"
}

9
test/.jshintrc Normal file
Просмотреть файл

@ -0,0 +1,9 @@
{
"expr": true,
"strict": false,
"mocha": true,
"node": true,
"globals": {
"expect": true
}
}

Просмотреть файл

@ -1,8 +1,12 @@
var apn = require("../");
var fs = require("fs");
var rewire = require("rewire");
var Connection = rewire("../lib/connection");
var events = require("events");
var sinon = require("sinon");
var Q = require("q");
describe("Connection", function() {
describe('constructor', function () {
describe("constructor", function () {
var originalEnv;
before(function() {
@ -11,40 +15,441 @@ describe("Connection", function() {
after(function() {
process.env.NODE_ENV = originalEnv;
})
});
beforeEach(function() {
process.env.NODE_ENV = "";
})
});
// Issue #50
it("should use gateway.sandbox.push.apple.com as the default connection address", function () {
apn.Connection().options.address.should.equal("gateway.sandbox.push.apple.com");
expect(Connection().options.address).to.equal("gateway.sandbox.push.apple.com");
});
it("should use gateway.push.apple.com when NODE_ENV=production", function () {
process.env.NODE_ENV = "production";
apn.Connection().options.address.should.equal("gateway.push.apple.com");
expect(Connection().options.address).to.equal("gateway.push.apple.com");
});
it("should give precedence to production flag over NODE_ENV=production", function () {
process.env.NODE_ENV = "production";
apn.Connection({ production: false }).options.address.should.equal("gateway.sandbox.push.apple.com");
expect(Connection({ production: false }).options.address).to.equal("gateway.sandbox.push.apple.com");
});
it("should use gateway.push.apple.com when production:true", function () {
apn.Connection({production:true}).options.address.should.equal("gateway.push.apple.com");
expect(Connection({production:true}).options.address).to.equal("gateway.push.apple.com");
});
it("should use a custom address when passed", function () {
apn.Connection({address: "testaddress"}).options.address.should.equal("testaddress");
expect(Connection({address: "testaddress"}).options.address).to.equal("testaddress");
});
describe("address is passed", function() {
it("sets production to true when using production address", function() {
expect(Connection({address: "gateway.push.apple.com"}).options.production).to.be.true;
});
it("sets production to false when using sandbox address", function() {
process.env.NODE_ENV = "production";
expect(Connection({address: "gateway.sandbox.push.apple.com"}).options.production).to.be.false;
});
});
});
describe('#initialize', function () {
it("should be fulfilled", function () {
return apn.Connection({ pfx: "test/support/initializeTest.pfx" })
.initialize().should.be.fulfilled;
describe("#initialize", function () {
var loadStub, parseStub, validateStub, removeStubs;
beforeEach(function() {
loadStub = sinon.stub();
loadStub.displayName = "loadCredentials";
parseStub = sinon.stub();
parseStub.displayName = "parseCredentials";
validateStub = sinon.stub();
validateStub.displayName = "validateCredentials";
removeStubs = Connection.__set__({
"loadCredentials": loadStub,
"parseCredentials": parseStub,
"validateCredentials": validateStub,
});
});
afterEach(function() {
removeStubs();
});
it("only loads credentials once", function() {
loadStub.returns(Q({}));
var connection = Connection();
connection.initialize();
connection.initialize();
expect(loadStub).to.be.calledOnce;
});
describe("with valid credentials", function() {
var initialization;
var testOptions = {
pfx: "myCredentials.pfx", cert: "myCert.pem", key: "myKey.pem", ca: "myCa.pem",
passphrase: "apntest", production: true
};
beforeEach(function() {
loadStub.withArgs(sinon.match(function(v) {
return v.pfx === "myCredentials.pfx" && v.cert === "myCert.pem" && v.key === "myKey.pem" &&
v.ca === "myCa.pem" && v.passphrase === "apntest";
})).returns(Q({ pfx: "myPfxData", cert: "myCertData", key: "myKeyData", ca: ["myCaData"], passphrase: "apntest" }));
parseStub.returnsArg(0);
initialization = Connection(testOptions).initialize();
});
it("should be fulfilled", function () {
return expect(initialization).to.be.fulfilled;
});
describe("the validation stage", function() {
it("is called once", function() {
return initialization.finally(function() {
expect(validateStub).to.be.calledOnce;
});
});
it("is passed the production flag", function() {
return initialization.finally(function() {
expect(validateStub.getCall(0).args[0]).to.have.property("production", true);
});
});
describe("passed credentials", function() {
it("contains the PFX data", function() {
return initialization.finally(function() {
expect(validateStub.getCall(0).args[0]).to.have.property("pfx", "myPfxData");
});
});
it("contains the key data", function() {
return initialization.finally(function() {
expect(validateStub.getCall(0).args[0]).to.have.property("key", "myKeyData");
});
});
it("contains the certificate data", function() {
return initialization.finally(function() {
expect(validateStub.getCall(0).args[0]).to.have.property("cert", "myCertData");
});
});
it("includes passphrase", function() {
return initialization.finally(function() {
expect(validateStub.getCall(0).args[0]).to.have.property("passphrase", "apntest");
});
});
});
});
describe("resolution value", function() {
it("contains the PFX data", function() {
return expect(initialization).to.eventually.have.property("pfx", "myPfxData");
});
it("contains the key data", function() {
return expect(initialization).to.eventually.have.property("key", "myKeyData");
});
it("contains the certificate data", function() {
return expect(initialization).to.eventually.have.property("cert", "myCertData");
});
it("contains the CA data", function() {
return expect(initialization).to.eventually.have.deep.property("ca[0]", "myCaData");
});
it("includes passphrase", function() {
return expect(initialization).to.eventually.have.property("passphrase", "apntest");
});
});
});
describe("credential file cannot be parsed", function() {
beforeEach(function() {
loadStub.returns(Q({ cert: "myCertData", key: "myKeyData" }));
parseStub.throws(new Error("unable to parse key"));
});
it("should resolve with the credentials", function() {
var initialization = Connection({ cert: "myUnparseableCert.pem", key: "myUnparseableKey.pem" }).initialize();
return expect(initialization).to.become({ cert: "myCertData", key: "myKeyData" });
});
it("should log an error", function() {
var debug = sinon.spy();
var reset = Connection.__set__("debug", debug);
var initialization = Connection({ cert: "myUnparseableCert.pem", key: "myUnparseableKey.pem" }).initialize();
return initialization.finally(function() {
reset();
expect(debug).to.be.calledWith(sinon.match(function(err) {
return err.message ? err.message.match(/unable to parse key/) : false;
}, "\"unable to parse key\""));
});
});
it("should not attempt to validate", function() {
var initialization = Connection({ cert: "myUnparseableCert.pem", key: "myUnparseableKey.pem" }).initialize();
return initialization.finally(function() {
expect(validateStub).to.not.be.called;
});
});
});
describe("credential validation fails", function() {
it("should be rejected", function() {
loadStub.returns(Q({ cert: "myCertData", key: "myMismatchedKeyData" }));
parseStub.returnsArg(0);
validateStub.throws(new Error("certificate and key do not match"));
var initialization = Connection({ cert: "myCert.pem", key: "myMistmatchedKey.pem" }).initialize();
return expect(initialization).to.eventually.be.rejectedWith(/certificate and key do not match/);
});
});
describe("credential file cannot be loaded", function() {
it("should be rejected", function() {
loadStub.returns(Q.reject(new Error("ENOENT, no such file or directory")));
var initialization = Connection({ cert: "noSuchFile.pem", key: "myKey.pem" }).initialize();
return expect(initialization).to.eventually.be.rejectedWith("ENOENT, no such file or directory");
});
});
});
describe("connect", function() {
var socketDouble, socketStub, removeSocketStub;
before(function() {
var initializeStub = sinon.stub(Connection.prototype, "initialize");
initializeStub.returns(Q({
pfx: "pfxData",
key: "keyData",
cert: "certData",
ca: ["caData1", "caData2"],
passphrase: "apntest" }));
});
beforeEach(function() {
socketDouble = new events.EventEmitter();
socketDouble.end = sinon.spy();
socketStub = sinon.stub();
socketStub.callsArg(2);
socketStub.returns(socketDouble);
removeSocketStub = Connection.__set__("createSocket", socketStub);
});
afterEach(function() {
socketDouble.removeAllListeners();
removeSocketStub();
});
it("initializes the module", function(done) {
var connection = Connection({ pfx: "myCredentials.pfx" });
return connection.connect().finally(function() {
expect(connection.initialize).to.have.been.calledOnce;
done();
});
});
describe("with valid credentials", function() {
it("resolves", function() {
var connection = Connection({
cert: "myCert.pem",
key: "myKey.pem"
});
return expect(connection.connect()).to.be.fulfilled;
});
describe("the call to create socket", function() {
var connect;
it("passes PFX data", function() {
connect = Connection({
pfx: "myCredentials.pfx",
passphrase: "apntest"
}).connect();
return connect.then(function() {
var socketOptions = socketStub.args[0][1];
expect(socketOptions.pfx).to.equal("pfxData");
});
});
it("passes the passphrase", function() {
connect = Connection({
passphrase: "apntest",
cert: "myCert.pem",
key: "myKey.pem"
}).connect();
return connect.then(function() {
var socketOptions = socketStub.args[0][1];
expect(socketOptions.passphrase).to.equal("apntest");
});
});
it("passes the cert", function() {
connect = Connection({
cert: "myCert.pem",
key: "myKey.pem"
}).connect();
return connect.then(function() {
var socketOptions = socketStub.args[0][1];
expect(socketOptions.cert).to.equal("certData");
});
});
it("passes the key", function() {
connect = Connection({
cert: "test/credentials/support/cert.pem",
key: "test/credentials/support/key.pem"
}).connect();
return connect.then(function() {
var socketOptions = socketStub.args[0][1];
expect(socketOptions.key).to.equal("keyData");
});
});
it("passes the ca certificates", function() {
connect = Connection({
cert: "test/credentials/support/cert.pem",
key: "test/credentials/support/key.pem",
ca: [ "test/credentials/support/issuerCert.pem" ]
}).connect();
return connect.then(function() {
var socketOptions = socketStub.args[0][1];
expect(socketOptions.ca[0]).to.equal("caData1");
});
});
});
});
describe("intialization failure", function() {
it("is rejected", function() {
var connection = Connection({ pfx: "a-non-existant-file-which-really-shouldnt-exist.pfx" });
connection.on("error", function() {});
connection.initialize = sinon.stub();
connection.initialize.returns(Q.reject(new Error("initialize failed")));
return expect(connection.connect()).to.be.rejectedWith("initialize failed");
});
});
describe("timeout option", function() {
var clock, timeoutRestore;
beforeEach(function() {
clock = sinon.useFakeTimers();
timeoutRestore = Connection.__set__({
"setTimeout": clock.setTimeout,
"clearTimeout": clock.clearTimeout
});
});
afterEach(function() {
timeoutRestore();
clock.restore();
});
it("ends the socket when connection takes too long", function() {
var connection = Connection({connectTimeout: 3000}).connect();
socketStub.onCall(0).returns(socketDouble);
process.nextTick(function(){
clock.tick(5000);
});
return connection.then(function() {
throw "connection did not time out";
}, function() {
expect(socketDouble.end).to.have.been.called;
});
});
it("does not end the socket when the connnection succeeds", function() {
var connection = Connection({connectTimeout: 3000}).connect();
return connection.then(function() {
clock.tick(5000);
expect(socketDouble.end).to.not.have.been.called;
});
});
it("does not end the socket when the connection fails", function() {
var connection = Connection({connectTimeout: 3000}).connect();
socketStub.onCall(0).returns(socketDouble);
process.nextTick(function() {
socketDouble.emit("close");
});
return connection.then(function() {
throw "connection should have failed";
}, function() {
clock.tick(5000);
expect(socketDouble.end).to.not.have.been.called;
});
});
it("disabled", function() {
var connection = Connection({connectTimeout: 0}).connect();
socketStub.onCall(0).returns(socketDouble);
process.nextTick(function() {
clock.tick(100000);
socketDouble.emit("close");
});
return connection.then(function() {
throw "connection should have failed";
}, function() {
expect(socketDouble.end).to.not.have.been.called;
});
});
});
});
describe("validNotification", function() {
describe("notification is shorter than max allowed", function() {
it("returns true", function() {
var connection = Connection();
var notification = { length: function() { return 128; }};
expect(connection.validNotification(notification)).to.equal(true);
});
});
describe("notification is the maximum length", function() {
it("returns true", function() {
var connection = Connection();
var notification = { length: function() { return 2048; }};
expect(connection.validNotification(notification)).to.equal(true);
});
});
describe("notification too long", function() {
it("returns false", function() {
var connection = Connection();
var notification = { length: function() { return 2176; }};
expect(connection.validNotification(notification)).to.equal(false);
});
});
describe("VoIP flag set", function() {
it("allows longer payload", function() {
var connection = Connection({"voip": true});
var notification = { length: function() { return 4096; }};
expect(connection.validNotification(notification)).to.equal(true);
});
});
});
});

Просмотреть файл

@ -1,108 +0,0 @@
var Credentials = require("../lib/credentials");
var fs = require("fs");
describe("Credentials", function() {
var pfx, cert, key, ca;
before(function () {
pfx = fs.readFileSync("test/support/initializeTest.pfx");
cert = fs.readFileSync("test/support/initializeTest.crt");
key = fs.readFileSync("test/support/initializeTest.key");
});
it("should eventually load a pfx file from disk", function () {
return Credentials({ pfx: "test/support/initializeTest.pfx" })
.get(0).post("toString")
.should.eventually.equal(pfx.toString());
});
it("should eventually provide pfx data from memory", function () {
return Credentials({ pfx: pfx }).get(0).post("toString")
.should.eventually.equal(pfx.toString());
});
it("should eventually provide pfx data explicitly passed in pfxData parameter", function () {
return Credentials({ pfxData: pfx }).get(0).post("toString")
.should.eventually.equal(pfx.toString());
});
it("should eventually load a certificate from disk", function () {
return Credentials({ cert: "test/support/initializeTest.crt", key: null})
.get(1).post("toString")
.should.eventually.equal(cert.toString());
});
it("should eventually provide a certificate from a Buffer", function () {
return Credentials({ cert: cert, key: null})
.get(1).post("toString")
.should.eventually.equal(cert.toString());
});
it("should eventually provide a certificate from a String", function () {
return Credentials({ cert: cert.toString(), key: null})
.get(1)
.should.eventually.equal(cert.toString());
});
it("should eventually provide certificate data explicitly passed in the certData parameter", function () {
return Credentials({ certData: cert, key: null})
.get(1).post("toString")
.should.eventually.equal(cert.toString());
});
it("should eventually load a key from disk", function () {
return Credentials({ cert: null, key: "test/support/initializeTest.key"})
.get(2).post("toString")
.should.eventually.equal(key.toString());
});
it("should eventually provide a key from a Buffer", function () {
return Credentials({ cert: null, key: key})
.get(2).post("toString")
.should.eventually.equal(key.toString());
});
it("should eventually provide a key from a String", function () {
return Credentials({ cert: null, key: key.toString()})
.get(2)
.should.eventually.equal(key.toString());
})
it("should eventually provide key data explicitly passed in the keyData parameter", function () {
return Credentials({ cert: null, keyData: key})
.get(2).post("toString")
.should.eventually.equal(key.toString());
});
it("should eventually load a single CA certificate from disk", function () {
return Credentials({ cert: null, key: null, ca: "test/support/initializeTest.crt" })
.get(3).get(0).post("toString")
.should.eventually.equal(cert.toString());
});
it("should eventually provide a single CA certificate from a Buffer", function () {
return Credentials({ cert: null, key: null, ca: cert })
.get(3).get(0).post("toString")
.should.eventually.equal(cert.toString());
});
it("should eventually provide a single CA certificate from a String", function () {
return Credentials({ cert: null, key: null, ca: cert.toString() })
.get(3).get(0)
.should.eventually.equal(cert.toString());
});
it("should eventually load an array of CA certificates", function (done) {
Credentials({ cert: null, key: null, ca: ["test/support/initializeTest.crt", cert, cert.toString()] })
.get(3).spread(function(cert1, cert2, cert3) {
var certString = cert.toString();
if (cert1.toString() == certString &&
cert2.toString() == certString &&
cert3.toString() == certString) {
done();
}
else {
done(new Error("provided certificates did not match"));
}
}, done);
});
});

Просмотреть файл

@ -0,0 +1,107 @@
var APNCertificate = require("../../lib/credentials/APNCertificate");
var APNKey = require("../../lib/credentials/APNKey");
var forge = require("node-forge");
var fs = require("fs");
describe("APNCertificate", function() {
var certPem;
before(function() {
certPem = fs.readFileSync("test/credentials/support/cert.pem");
});
var cert;
beforeEach(function() {
cert = forge.pki.certificateFromPem(certPem.toString());
});
describe("accepts a Certificate object", function() {
it("does not throw", function() {
expect(function() {
new APNCertificate(cert);
}).to.not.throw(Error);
});
});
describe("throws", function() {
it("missing public key", function() {
delete cert.publicKey;
expect(function() {
new APNCertificate(cert);
}).to.throw("certificate object is invalid");
});
it("missing validity", function() {
delete cert.validity;
expect(function() {
new APNCertificate(cert);
}).to.throw("certificate object is invalid");
});
it("missing subject", function() {
delete cert.subject;
expect(function() {
new APNCertificate(cert);
}).to.throw("certificate object is invalid");
});
});
describe("key", function() {
it("returns an APNKey", function() {
expect(new APNCertificate(cert).key()).to.be.an.instanceof(APNKey);
});
it("returns the the certificates public key", function() {
expect(new APNCertificate(cert).key().fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
});
});
describe("validity", function() {
it("returns an object containing notBefore", function() {
expect(new APNCertificate(cert).validity())
.to.have.property("notBefore")
.and
.to.eql(new Date("2015-01-01T00:00:00"));
});
it("returns an object containing notAfter", function() {
expect(new APNCertificate(cert).validity())
.to.have.property("notAfter")
.and
.to.eql(new Date("2025-01-01T00:00:00"));
});
});
describe("environment", function() {
describe("development certificate", function() {
it("sandbox flag", function() {
expect(new APNCertificate(cert).environment().sandbox).to.be.true;
});
it("production flag", function() {
expect(new APNCertificate(cert).environment().production).to.be.false;
});
});
describe("production certificate", function() {
var productionCertPem, productionCert;
before(function() {
productionCertPem = fs.readFileSync("test/credentials/support/certProduction.pem");
});
beforeEach(function() {
productionCert = forge.pki.certificateFromPem(productionCertPem.toString());
});
it("sandbox flag", function() {
expect(new APNCertificate(productionCert).environment().sandbox).to.be.false;
});
it("production flag", function() {
expect(new APNCertificate(productionCert).environment().production).to.be.true;
});
});
});
});

Просмотреть файл

@ -0,0 +1,37 @@
var APNKey = require("../../lib/credentials/APNKey");
var forge = require("node-forge");
var fs = require("fs");
describe("APNKey", function() {
it("initialises with a node-forge public key", function() {
expect(new APNKey({ n: 12345, e: 65536})).to.be.an.instanceof(APNKey);
});
describe("throws", function() {
it("missing modulus", function() {
expect(function() {
new APNKey({ e: 65536 });
}).to.throw("key is not a valid public key");
});
it("missing exponent", function() {
expect(function() {
new APNKey({ n: 12345 });
}).to.throw("key is not a valid public key");
});
it("undefined", function() {
expect(function() {
new APNKey();
}).to.throw("key is not a valid public key");
});
});
describe("fingerprint", function() {
it("returns the fingerprint of the public key", function() {
var keyPem = fs.readFileSync("test/credentials/support/key.pem");
var apnKey = new APNKey(forge.pki.decryptRsaPrivateKey(keyPem));
expect(apnKey.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
});
});
});

117
test/credentials/load.js Normal file
Просмотреть файл

@ -0,0 +1,117 @@
var loadCredentials = require("../../lib/credentials/load");
var fs = require("fs");
describe("loadCredentials", function() {
var pfx, cert, key;
before(function () {
pfx = fs.readFileSync("test/support/initializeTest.pfx");
cert = fs.readFileSync("test/support/initializeTest.crt");
key = fs.readFileSync("test/support/initializeTest.key");
});
it("should eventually load a pfx file from disk", function () {
return expect(loadCredentials({ pfx: "test/support/initializeTest.pfx" })
.get("pfx").post("toString"))
.to.eventually.equal(pfx.toString());
});
it("should eventually provide pfx data from memory", function () {
return expect(loadCredentials({ pfx: pfx }).get("pfx").post("toString"))
.to.eventually.equal(pfx.toString());
});
it("should eventually provide pfx data explicitly passed in pfxData parameter", function () {
return expect(loadCredentials({ pfxData: pfx }).get("pfx").post("toString"))
.to.eventually.equal(pfx.toString());
});
it("should eventually load a certificate from disk", function () {
return expect(loadCredentials({ cert: "test/support/initializeTest.crt", key: null})
.get("cert").post("toString"))
.to.eventually.equal(cert.toString());
});
it("should eventually provide a certificate from a Buffer", function () {
return expect(loadCredentials({ cert: cert, key: null})
.get("cert").post("toString"))
.to.eventually.equal(cert.toString());
});
it("should eventually provide a certificate from a String", function () {
return expect(loadCredentials({ cert: cert.toString(), key: null})
.get("cert"))
.to.eventually.equal(cert.toString());
});
it("should eventually provide certificate data explicitly passed in the certData parameter", function () {
return expect(loadCredentials({ certData: cert, key: null})
.get("cert").post("toString"))
.to.eventually.equal(cert.toString());
});
it("should eventually load a key from disk", function () {
return expect(loadCredentials({ cert: null, key: "test/support/initializeTest.key"})
.get("key").post("toString"))
.to.eventually.equal(key.toString());
});
it("should eventually provide a key from a Buffer", function () {
return expect(loadCredentials({ cert: null, key: key})
.get("key").post("toString"))
.to.eventually.equal(key.toString());
});
it("should eventually provide a key from a String", function () {
return expect(loadCredentials({ cert: null, key: key.toString()})
.get("key"))
.to.eventually.equal(key.toString());
});
it("should eventually provide key data explicitly passed in the keyData parameter", function () {
return expect(loadCredentials({ cert: null, keyData: key})
.get("key").post("toString"))
.to.eventually.equal(key.toString());
});
it("should eventually load a single CA certificate from disk", function () {
return expect(loadCredentials({ cert: null, key: null, ca: "test/support/initializeTest.crt" })
.get("ca").get(0).post("toString"))
.to.eventually.equal(cert.toString());
});
it("should eventually provide a single CA certificate from a Buffer", function () {
return expect(loadCredentials({ cert: null, key: null, ca: cert })
.get("ca").get(0).post("toString"))
.to.eventually.equal(cert.toString());
});
it("should eventually provide a single CA certificate from a String", function () {
return expect(loadCredentials({ cert: null, key: null, ca: cert.toString() })
.get("ca").get(0))
.to.eventually.equal(cert.toString());
});
it("should eventually load an array of CA certificates", function (done) {
loadCredentials({ cert: null, key: null, ca: ["test/support/initializeTest.crt", cert, cert.toString()] })
.get("ca").spread(function(cert1, cert2, cert3) {
var certString = cert.toString();
if (cert1.toString() === certString &&
cert2.toString() === certString &&
cert3.toString() === certString) {
done();
}
else {
done(new Error("provided certificates did not match"));
}
}, done);
});
it("returns undefined if no CA values are specified", function() {
return expect(loadCredentials({ cert: null, key: null, ca: null}).get("ca")).to.eventually.be.undefined;
});
it("should inclue the passphrase in the resolved value", function() {
return expect(loadCredentials({ passphrase: "apntest" }).get("passphrase"))
.to.eventually.equal("apntest");
});
});

131
test/credentials/parse.js Normal file
Просмотреть файл

@ -0,0 +1,131 @@
var sinon = require("sinon");
var rewire = require("rewire");
var parseCredentials = rewire("../../lib/credentials/parse");
var APNCertificate = require("../../lib/credentials/APNCertificate");
var APNKey = require("../../lib/credentials/APNKey");
describe("parseCredentials", function() {
var reset;
var pkcs12Spy, pemKeySpy, pemCertSpy;
var pfxKey = new APNKey({n: 1, e: 1 });
var pfxCert = new APNCertificate({publicKey: {}, validity: {}, subject: {} });
var pemKey = new APNKey({n: 2, e: 1 });
var pemCert = new APNCertificate({publicKey: {}, validity: {}, subject: {} });
beforeEach(function() {
pkcs12Spy = sinon.stub();
pemKeySpy = sinon.stub();
pemKeySpy.withArgs("pemkey").returns(pemKey);
pemCertSpy = sinon.stub();
pemCertSpy.withArgs("pemcert").returns(pemCert);
reset = parseCredentials.__set__({
"parsePkcs12": pkcs12Spy,
"parsePemKey": pemKeySpy,
"parsePemCert": pemCertSpy,
});
});
afterEach(function() {
reset();
});
describe("with PFX file", function() {
it("returns the parsed key", function() {
pkcs12Spy.withArgs("pfxData").returns({ key: pfxKey, certificates: [pfxCert] });
var parsed = parseCredentials({ pfx: "pfxData" });
expect(parsed.key).to.be.an.instanceof(APNKey);
});
it("returns the parsed certificates", function() {
pkcs12Spy.withArgs("pfxData").returns({ key: pfxKey, certificates: [pfxCert] });
var parsed = parseCredentials({ pfx: "pfxData" });
expect(parsed.certificates[0]).to.be.an.instanceof(APNCertificate);
});
describe("having passphrase", function() {
beforeEach(function() {
pkcs12Spy.withArgs("encryptedPfxData", "apntest").returns({ key: pfxKey, certificates: [pfxCert] });
pkcs12Spy.withArgs("encryptedPfxData", sinon.match.any).throws(new Error("unable to read credentials, incorrect passphrase"));
});
it("returns the parsed key", function() {
var parsed = parseCredentials({ pfx: "encryptedPfxData", passphrase: "apntest" });
expect(parsed.key).to.be.an.instanceof(APNKey);
});
it("throws when passphrase is incorrect", function() {
expect(function() {
parseCredentials({ pfx: "encryptedPfxData", passphrase: "incorrectpassphrase" });
}).to.throw(/incorrect passphrase/);
});
it("throws when passphrase is not supplied", function() {
expect(function() {
parseCredentials({ pfx: "encryptedPfxData" });
}).to.throw(/incorrect passphrase/);
});
});
});
describe("with PEM key", function() {
it("returns the parsed key", function() {
pemKeySpy.withArgs("pemKeyData").returns(pemKey);
var parsed = parseCredentials({ key: "pemKeyData" });
expect(parsed.key).to.be.an.instanceof(APNKey);
});
describe("having passphrase", function() {
beforeEach(function() {
pemKeySpy.withArgs("encryptedPemKeyData", "apntest").returns(pemKey);
pemKeySpy.withArgs("encryptedPemKeyData", sinon.match.any).throws(new Error("unable to load key, incorrect passphrase"));
});
it("returns the parsed key", function() {
var parsed = parseCredentials({ key: "encryptedPemKeyData", passphrase: "apntest" });
expect(parsed.key).to.be.an.instanceof(APNKey);
});
it("throws when passphrase is incorrect", function() {
expect(function() {
parseCredentials({ key: "encryptedPemKeyData", passphrase: "incorrectpassphrase" });
}).to.throw(/incorrect passphrase/);
});
it("throws when passphrase is not supplied", function() {
expect(function() {
parseCredentials({ key: "encryptedPemKeyData" });
}).to.throw(/incorrect passphrase/);
});
});
});
describe("with PEM certificate", function() {
it("returns the parsed certificate", function() {
pemCertSpy.withArgs("pemCertData").returns([pemCert]);
var parsed = parseCredentials({ cert: "pemCertData" });
expect(parsed.certificates[0]).to.be.an.instanceof(APNCertificate);
});
});
describe("both PEM and PFX data is supplied", function() {
it("it prefers PFX to PEM", function() {
pkcs12Spy.withArgs("pfxData").returns({ key: pfxKey, certificates: [pfxCert] });
pemKeySpy.withArgs("pemKeyData").returns(pemKey);
pemCertSpy.withArgs("pemCertData").returns([pemCert]);
var parsed = parseCredentials({ pfx: "pfxData", key: "pemKeyData", cert: "pemCertData"});
expect(parsed.key).to.equal(pfxKey);
expect(parsed.certificates[0]).to.equal(pfxCert);
});
});
});

Просмотреть файл

@ -0,0 +1,89 @@
var parsePemCertificate = require("../../lib/credentials/parsePemCertificate");
var APNCertificate = require("../../lib/credentials/APNCertificate");
var fs = require("fs");
describe("parsePemCertificate", function() {
describe("with PEM certificate", function() {
var cert, certProperties;
before(function() {
cert = fs.readFileSync("test/credentials/support/cert.pem");
});
beforeEach(function() {
certProperties = parsePemCertificate(cert);
});
describe("return value", function() {
it("is an array", function() {
expect(certProperties).to.be.an("array");
});
it("contains one element", function() {
expect(certProperties).to.have.length(1);
});
describe("certificate [0]", function() {
it("is an APNCertificate", function() {
expect(certProperties[0]).to.be.an.instanceof(APNCertificate);
});
it("has the correct fingerprint", function() {
expect(certProperties[0].key().fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
});
});
});
});
describe("with PEM containing multiple certificates", function() {
var cert, certProperties;
before(function() {
cert = fs.readFileSync("test/credentials/support/certIssuerKey.pem");
});
beforeEach(function() {
certProperties = parsePemCertificate(cert);
});
it("returns the correct number of certificates", function() {
expect(certProperties).to.have.length(2);
});
describe("certificate [0]", function() {
it("has the correct fingerprint", function() {
expect(certProperties[0].key().fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
});
});
describe("certificate [1]", function() {
it("has the correct fingerprint", function() {
expect(certProperties[1].key().fingerprint()).to.equal("ccff221d67cb3335649f9b4fbb311948af76f4b2");
});
});
});
describe("with a PKCS#12 file", function() {
it("throws", function() {
var pfx = fs.readFileSync("test/credentials/support/certIssuerKey.p12");
expect(function() {
parsePemCertificate(pfx);
}).to.throw("unable to parse certificate, not a valid PEM file");
});
});
describe("with a key", function() {
it("returns an empty array", function() {
var key = fs.readFileSync("test/credentials/support/key.pem");
expect(parsePemCertificate(key)).to.be.empty;
});
});
describe("returns null", function() {
it("for null", function() {
expect(parsePemCertificate(null)).to.be.null;
});
it("for undefined", function() {
expect(parsePemCertificate(undefined)).to.be.null;
});
});
});

Просмотреть файл

@ -0,0 +1,94 @@
var parsePemKey = require("../../lib/credentials/parsePemKey");
var APNKey = require("../../lib/credentials/APNKey");
var fs = require("fs");
describe("parsePemKey", function() {
describe("returns APNKey", function() {
describe("RSA key", function() {
var key;
beforeEach(function() {
var keyData = fs.readFileSync("test/credentials/support/key.pem");
key = parsePemKey(keyData);
});
it("correct type", function() {
expect(key).to.be.an.instanceof(APNKey);
});
it("with correct fingerprint", function() {
expect(key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
});
});
it("openssl-encrypted RSA key, correct password", function() {
var key = fs.readFileSync("test/credentials/support/keyEncrypted.pem");
expect(parsePemKey(key, "apntest")).to.be.an.instanceof(APNKey);
});
it("PKCS#8 encrypted key, correct password", function() {
var key = fs.readFileSync("test/credentials/support/keyPKCS8Encrypted.pem");
expect(parsePemKey(key, "apntest")).to.be.an.instanceof(APNKey);
});
it("PEM containing certificates and key", function() {
var certAndKey = fs.readFileSync("test/credentials/support/certKey.pem");
expect(parsePemKey(certAndKey)).to.be.an.instanceof(APNKey);
});
});
describe("throws with", function() {
it("PKCS#8 key (unsupported format)", function() {
var key = fs.readFileSync("test/credentials/support/keyPKCS8.pem");
expect(function() {
parsePemKey(key);
}).to.throw("unable to parse key, unsupported format");
});
it("RSA encrypted key, incorrect passphrase", function() {
var key = fs.readFileSync("test/credentials/support/keyEncrypted.pem");
expect(function() {
parsePemKey(key, "not-the-passphrase");
}).to.throw("unable to parse key, incorrect passphrase");
});
it("PKCS#8 encrypted key, incorrect passphrase", function() {
var key = fs.readFileSync("test/credentials/support/keyPKCS8Encrypted.pem");
expect(function() {
parsePemKey(key, "not-the-passphrase");
}).to.throw("unable to parse key, incorrect passphrase");
});
it("PEM certificate", function() {
var cert = fs.readFileSync("test/credentials/support/cert.pem");
expect(function() {
parsePemKey(cert);
}).to.throw("unable to parse key, no private key found");
});
it("PKCS#12 file", function() {
var pkcs12 = fs.readFileSync("test/credentials/support/certIssuerKey.p12");
expect(function() {
parsePemKey(pkcs12);
}).to.throw("unable to parse key, not a valid PEM file");
});
});
describe("multiple keys", function() {
it("throws", function() {
var keys = fs.readFileSync("test/credentials/support/multipleKeys.pem");
expect(function() {
parsePemKey(keys);
}).to.throw("multiple keys found in PEM file");
});
});
describe("returns null", function() {
it("for null", function() {
expect(parsePemKey()).to.be.null;
});
it("for undefined", function() {
expect(parsePemKey()).to.be.null;
});
});
});

Просмотреть файл

@ -0,0 +1,120 @@
var parsePkcs12 = require("../../lib/credentials/parsePkcs12");
var APNKey = require("../../lib/credentials/APNKey");
var APNCertificate = require("../../lib/credentials/APNCertificate");
var fs = require("fs");
describe("parsePkcs12", function() {
describe("with PKCS#12 data", function() {
var p12, properties;
describe("return value", function() {
var credentials;
before(function() {
p12 = fs.readFileSync("test/credentials/support/certIssuerKey.p12");
credentials = parsePkcs12(p12);
});
it("is an object", function() {
expect(credentials).to.be.an("object");
});
it("contains a private key", function() {
expect(credentials).to.include.keys("key");
});
describe("private key", function() {
it("is an instance of APNKey", function() {
expect(credentials.key).to.be.an.instanceof(APNKey);
});
it("has the correct fingerprint", function() {
expect(credentials.key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
});
});
it("contains a certificate chain", function() {
expect(credentials).to.include.keys("certificates");
});
describe("certificate chain", function() {
it("is an array", function() {
expect(credentials.certificates).to.be.an("array");
});
it("contains the correct number of certificates", function() {
expect(credentials.certificates.length).to.equal(2);
});
it("contains APNCertificate objects", function() {
var certificates = credentials.certificates;
certificates.forEach(function(certificate) {
expect(certificate).to.be.an.instanceof(APNCertificate);
});
});
it("contains certificates with the correct fingerprints", function() {
var fingerprints = ["2d594c9861227dd22ba5ae37cc9354e9117a804d", "ccff221d67cb3335649f9b4fbb311948af76f4b2"];
var certificates = credentials.certificates;
certificates.forEach(function(certificate, index) {
expect(certificate.key().fingerprint()).to.equal(fingerprints[index]);
});
});
});
});
// OpenSSL exports keys having no passphrase as a C string with a \0 byte appended
describe("having empty passphrase (OpenSSL-CLI-generated file)", function() {
describe("return value", function() {
it("has the correct key", function() {
p12 = fs.readFileSync("test/credentials/support/certIssuerKeyOpenSSL.p12");
properties = parsePkcs12(p12);
expect(properties.key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
});
});
});
describe("with correct passphrase", function() {
describe("return value", function() {
it("has the correct key", function() {
p12 = fs.readFileSync("test/credentials/support/certIssuerKeyPassphrase.p12");
properties = parsePkcs12(p12, "apntest");
expect(properties.key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d");
});
});
});
describe("with incorrect passphrase", function() {
it("throws", function() {
p12 = fs.readFileSync("test/credentials/support/certIssuerKeyPassphrase.p12");
expect(function() {
parsePkcs12(p12, "notthepassphrase");
}).to.throw("unable to parse credentials, incorrect passphrase");
});
});
// Unclear whether multiple keys in one PKCS#12 file can be distinguished
// at present if there's more than one just throw a warning. Should also
// do the same thing in apnKeyFromPem
describe("multiple keys", function() {
it("throws", function() {
p12 = fs.readFileSync("test/credentials/support/multipleKeys.p12");
expect(function() {
parsePkcs12(p12);
}).to.throw("multiple keys found in PFX/P12 file");
});
});
});
describe("PEM file", function() {
it("throws", function() {
var pem = fs.readFileSync("test/credentials/support/certKey.pem");
expect(function() {
parsePkcs12(pem);
}).to.throw("unable to parse credentials, not a PFX/P12 file");
});
});
it("returns undefined for undefined", function() {
expect(parsePkcs12()).to.be.undefined;
});
});

Просмотреть файл

@ -0,0 +1,45 @@
var resolve = require("../../lib/credentials/resolve");
var fs = require("fs");
describe("resolve", function() {
var pfx, cert, key;
before(function () {
pfx = fs.readFileSync("test/support/initializeTest.pfx");
cert = fs.readFileSync("test/support/initializeTest.crt");
key = fs.readFileSync("test/support/initializeTest.key");
});
it("returns PEM string as supplied", function() {
expect(resolve(cert.toString()))
.to.be.a("string")
.and.to.equal(cert.toString());
});
it("returns Buffer as supplied", function() {
expect(resolve(pfx))
.to.satisfy(Buffer.isBuffer)
.and.to.equal(pfx);
});
describe("with file path", function() {
it("eventually returns a Buffer for valid path", function() {
return expect(resolve("test/support/initializeTest.key"))
.to.eventually.satisfy(Buffer.isBuffer);
});
it("eventually returns contents for value path", function () {
return expect(resolve("test/support/initializeTest.key")
.post("toString")).to.eventually.equal(key.toString());
});
it("is eventually rejected for invalid path", function() {
return expect(resolve("test/support/fail/initializeTest.key"))
.to.eventually.be.rejected;
});
});
it("returns null/undefined as supplied", function() {
expect(resolve(null)).to.be.null;
expect(resolve()).to.be.undefined;
});
});

Просмотреть файл

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
cDEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS8wLQYDVQQDEyZub2RlLWFw
biBEZXZlbG9wbWVudCBUZXN0OiBpby5hcG4udGVzdDETMBEGA1UECxMKMEExQjJD
M0Q0RTELMAkGA1UEBhMCR0IwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQCcFO7kOMStSOWTBWgtDZrnQvoOXrQ7symumhG0seBrICqGr5JNDS5n+ukobTcG
4Y6jMP8xVfbgW8UD4x+h1pPNn2BdKLMbR1Tvx0oVULZ68RolLAzV+fmPCZxFt4kB
uRSRA+8c754uChTwOuYwNES7Kx2Ku7JkPuPCBJBOutyFfzmw070y2bGmkl6K8+BP
Ru5yBrmKXfTwFoZFC39xLCaX6vTH05jA+GTj9+JOI0oD2jES/Y9KR9B7RvNzUd5h
lwhPPGWA2srsGZ8Lh/kU3rCclZfj7eClXh7/cEzXuuJoNnJQkJwJ/5oKBSy3OUzH
h8li8bO+n56gNbcV4ts+FochAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQE
AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQULVlMmGEifdIrpa43
zJNU6RF6gE0wHwYDVR0jBBgwFoAUzP8iHWfLMzVkn5tPuzEZSK929LIwEAYKKoZI
hvdjZAYDAQQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFEPh8EDx7Dkskxoy6ZBAHMD
ir9/fpwzb5AKtrxkIoT06F0D0+2x4Mmt9vuTmckqWU9awsRTZu4NUGMrN7+G1cmd
KqI95iuUecDtYFnCNjPJZrmvBBuOeGfO1bC5ZNXHkgn7b7ClMFJ30enOBO4T2tY4
NXww7gA1L2S+0ZWrHQ6f/4SenSoo6EQt484RtsJlIWVP34zhJeo4jXxuyQh42JM6
88jz5T0tr+1SyD4UDemoOukgti0UaGBdPw98bRjZf+XTXG01v/2KlQhkMSb0/l6v
QsCVz3gIzloWl4+JXZYp1tNxZQHlvLqihMwV0vQmmQBHkS733Nlr/9OU3KRPAVM=
-----END CERTIFICATE-----

Двоичные данные
test/credentials/support/certIssuerKey.p12 Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,44 @@
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
cDEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS8wLQYDVQQDEyZub2RlLWFw
biBEZXZlbG9wbWVudCBUZXN0OiBpby5hcG4udGVzdDETMBEGA1UECxMKMEExQjJD
M0Q0RTELMAkGA1UEBhMCR0IwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQCcFO7kOMStSOWTBWgtDZrnQvoOXrQ7symumhG0seBrICqGr5JNDS5n+ukobTcG
4Y6jMP8xVfbgW8UD4x+h1pPNn2BdKLMbR1Tvx0oVULZ68RolLAzV+fmPCZxFt4kB
uRSRA+8c754uChTwOuYwNES7Kx2Ku7JkPuPCBJBOutyFfzmw070y2bGmkl6K8+BP
Ru5yBrmKXfTwFoZFC39xLCaX6vTH05jA+GTj9+JOI0oD2jES/Y9KR9B7RvNzUd5h
lwhPPGWA2srsGZ8Lh/kU3rCclZfj7eClXh7/cEzXuuJoNnJQkJwJ/5oKBSy3OUzH
h8li8bO+n56gNbcV4ts+FochAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQE
AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQULVlMmGEifdIrpa43
zJNU6RF6gE0wHwYDVR0jBBgwFoAUzP8iHWfLMzVkn5tPuzEZSK929LIwEAYKKoZI
hvdjZAYDAQQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFEPh8EDx7Dkskxoy6ZBAHMD
ir9/fpwzb5AKtrxkIoT06F0D0+2x4Mmt9vuTmckqWU9awsRTZu4NUGMrN7+G1cmd
KqI95iuUecDtYFnCNjPJZrmvBBuOeGfO1bC5ZNXHkgn7b7ClMFJ30enOBO4T2tY4
NXww7gA1L2S+0ZWrHQ6f/4SenSoo6EQt484RtsJlIWVP34zhJeo4jXxuyQh42JM6
88jz5T0tr+1SyD4UDemoOukgti0UaGBdPw98bRjZf+XTXG01v/2KlQhkMSb0/l6v
QsCVz3gIzloWl4+JXZYp1tNxZQHlvLqihMwV0vQmmQBHkS733Nlr/9OU3KRPAVM=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDiTCCAnGgAwIBAgIBATANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MB4XDTE0MTIzMTAwMDAwMFoXDTMwMDEwMTAwMDAwMFow
TjELMAkGA1UEBhMCR0IxETAPBgNVBAsTCG5vZGUtYXBuMSwwKgYDVQQDEyNub2Rl
LWFwbiBUZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBALWYd1+Uli9q1P9H/g812ZFsOtrYZIrkYxim1VcMGBDt
knULBH5ZhRNV0Cymh6r4RmmNwDQ4wvQoVVZ6rRt2SL1VtAPRq0rtXMeOiouVkkEc
Ly9ssvHY6SzNM4no4B1i+Eg028Nhm0aGyx1NhLHWBHz69C0PxCQrDq+N5efF8B4N
DR89R3PPpGdXMlgD8aMlUMOOksXJMZUwiFmbclMhb5mdnvqoX3uyi0AzFEkgtjI5
GsxRIh8ZKXAXRMk4Ub5L7sYYVHiNMc2pHB99vngxveDNjZBbZD/qXYZyWEGcRTmU
Ht1UPWKxm0wgFzq4zRAlGM9sXKy3GaSyeJdfh2KU6xMCAwEAAaNyMHAwCwYDVR0P
BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMz/Ih1nyzM1ZJ+bT7sx
GUivdvSyMB8GA1UdIwQYMBaAFMz/Ih1nyzM1ZJ+bT7sxGUivdvSyMBAGCiqGSIb3
Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQCfi/mKQ4MBPtUNMYAumiG8VpVO
I1g25Tp5BjnT23lVjn733mD7grpxVZ+wVvRv0+cPsw5sIwF+WvW+J2BZQB9v5+Ph
k3UXZkG9rkx/f4yuzsrW0sX70HtVHt7uQeFAWE6mZpRu6aTJqqNtpsi0YhhNSLMG
suoLwe8X39W3GIDiMy5WisLaR7MgvcRW5Ud7JtWgm5HHOShHN5CtawmJP94hPW4W
VsS8/vHzSKqQe9AFtb1Pk0TR0A5gTwVOzi9cw3qqvtSP2Jhhab79D2/JoiII16Vi
CVQTczcY5NpIQNhrO/w5SxjbigU9ngO4zprl2KYURxSDNbBSsXmyCU2Y71HA
-----END CERTIFICATE-----

Двоичные данные
test/credentials/support/certIssuerKeyOpenSSL.p12 Normal file

Двоичный файл не отображается.

Двоичные данные
test/credentials/support/certIssuerKeyPassphrase.p12 Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,49 @@
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
cDEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS8wLQYDVQQDEyZub2RlLWFw
biBEZXZlbG9wbWVudCBUZXN0OiBpby5hcG4udGVzdDETMBEGA1UECxMKMEExQjJD
M0Q0RTELMAkGA1UEBhMCR0IwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQCcFO7kOMStSOWTBWgtDZrnQvoOXrQ7symumhG0seBrICqGr5JNDS5n+ukobTcG
4Y6jMP8xVfbgW8UD4x+h1pPNn2BdKLMbR1Tvx0oVULZ68RolLAzV+fmPCZxFt4kB
uRSRA+8c754uChTwOuYwNES7Kx2Ku7JkPuPCBJBOutyFfzmw070y2bGmkl6K8+BP
Ru5yBrmKXfTwFoZFC39xLCaX6vTH05jA+GTj9+JOI0oD2jES/Y9KR9B7RvNzUd5h
lwhPPGWA2srsGZ8Lh/kU3rCclZfj7eClXh7/cEzXuuJoNnJQkJwJ/5oKBSy3OUzH
h8li8bO+n56gNbcV4ts+FochAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQE
AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQULVlMmGEifdIrpa43
zJNU6RF6gE0wHwYDVR0jBBgwFoAUzP8iHWfLMzVkn5tPuzEZSK929LIwEAYKKoZI
hvdjZAYDAQQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFEPh8EDx7Dkskxoy6ZBAHMD
ir9/fpwzb5AKtrxkIoT06F0D0+2x4Mmt9vuTmckqWU9awsRTZu4NUGMrN7+G1cmd
KqI95iuUecDtYFnCNjPJZrmvBBuOeGfO1bC5ZNXHkgn7b7ClMFJ30enOBO4T2tY4
NXww7gA1L2S+0ZWrHQ6f/4SenSoo6EQt484RtsJlIWVP34zhJeo4jXxuyQh42JM6
88jz5T0tr+1SyD4UDemoOukgti0UaGBdPw98bRjZf+XTXG01v/2KlQhkMSb0/l6v
QsCVz3gIzloWl4+JXZYp1tNxZQHlvLqihMwV0vQmmQBHkS733Nlr/9OU3KRPAVM=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEAnBTu5DjErUjlkwVoLQ2a50L6Dl60O7MprpoRtLHgayAqhq+S
TQ0uZ/rpKG03BuGOozD/MVX24FvFA+MfodaTzZ9gXSizG0dU78dKFVC2evEaJSwM
1fn5jwmcRbeJAbkUkQPvHO+eLgoU8DrmMDREuysdiruyZD7jwgSQTrrchX85sNO9
MtmxppJeivPgT0bucga5il308BaGRQt/cSwml+r0x9OYwPhk4/fiTiNKA9oxEv2P
SkfQe0bzc1HeYZcITzxlgNrK7BmfC4f5FN6wnJWX4+3gpV4e/3BM17riaDZyUJCc
Cf+aCgUstzlMx4fJYvGzvp+eoDW3FeLbPhaHIQIDAQABAoIBAQCC2yYB3vo9ka0v
msvhYdOp6cQ9gfa3Spk6kl8f0DWneptMuiv9P3zVnk4WH6KPuVFdzjlVgo3tQeMm
RCgEBiN6tBEVaYbn6uDx+nJI9pdW8YaK/ahxSReKbXNAHATYlfQBNHwnFVnXnYo6
chcE+P1asmYdJwoD85n90tetugoiPNebzL0b6uqYZxllN9RFS2zgHoYXDSi7FWy2
b1GsADz8zyJ4GKNTFqGgicFEBETIgcnmIzyF3YT+Fuc4dd5aYNPtHjGkVr/nCMDq
RTXVFuiPZ7jASThzNmTsHuxHwD2LlY/8ydOZbv/MYQmDTxlEu0Z4+nSq/blCjkBy
BBtjWr4VAoGBAPJFrPjXeaYRcxZxLitGRIYj4YMn6ussHrPlHxa0YrvWf9SQareC
YGG6W+aNfW8KVtwNVgKELtZQikLIkRYsJ1ImE2sdaMBp5bqMO2gVmX8hzg1+syoe
gT84/fpPh3iJxBo45d1CFSrqYQAosVgFGqkmDXX/peEHVx2xcESKY9SbAoGBAKTt
AaqL6ian2VV9YC97sUaio1+XsY+fkdLLcGr6HvgphLx3aaWMIeq0oP4zHgy//0Pb
oLnKY3SMJJRhGGPUZfVbxb9ZZMxoNeo2yFs4HlK7G5eIyw5vRiJd5j8GhM3G7l/H
S+tXIKqIBuJsK9dG3cVEBT52IIb+n8nXmhjLSqjzAoGBAOZdcje7S6So+vHf9LKZ
Qhb6jzgTAMFVVmxf9Mu2AhvxveL030RW6CaE+VWkPB0Vi7n5xEroPVDzjEQsSij3
Gvx10AkOEcjD6PkU1ngF8cp87lzOmLX4A5WGL5mPfZUUCi+U4p0cdNw1uL5Z8ydq
0wr7b9k/mQ77184YJlRF8t75AoGBAJ0idyjv36ru1yIdr0vuVOwQvwmv9Ov7Q6uM
S1KRdnpIzH/oYg7podMGQDGRsHrDX0le8xaxHusHLz7z95H95xrLUnBKksAyNdQu
V9yZbkKypMpO+fCJ0k+iGWJJKrUIaUt2Df5u59+ydKS8HVUh3uA5O6nUUI9t//4G
XnprDnpDAoGBAKzUuumBa/uL0CaGxfJtV7Y6kdvf6Iq7bwLOopvlhoWHT8mGfNSJ
vycCdTk8gKPXRieUtlruOVHCT5gxzeangSNFjbDmOnrbbeK3uf/iPpJqE+qkOsZQ
TptFTiuh2pJlOGyGOR9U/UviRXyw3z/78V2MTWUn4U4WOZbAmsPd5WSA
-----END RSA PRIVATE KEY-----

Просмотреть файл

@ -0,0 +1,50 @@
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
bzEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS4wLAYDVQQDEyVub2RlLWFw
biBQcm9kdWN0aW9uIFRlc3Q6IGlvLmFwbi50ZXN0MRMwEQYDVQQLEwowQTFCMkMz
RDRFMQswCQYDVQQGEwJHQjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN1+JrxlqS2t7+CU3CM3GNXlZiMWFwfXuRITRNXDxX4IWYSUeJ4ELBeUFoskfPhQ
AMvCSfX9ikfgX+Ff8ETovqERWjPu0wXE+kQTk1l+2f/+0zyWEVKOyqk3JhQY9//u
VWBqcnvWQqHolRRhgf5pMNL8O0ASzS4XVbALCTRaI1W4kBTDrXdAKkt2Jxt4/aZF
uv/c5t4C2nrhVDydZ7yAhHjZZKjL2+WOZmOFGMAD2pn+JholwhOvz4isbH+Es42s
uTJ4NusLKhxs8KgJMPN4p2XwO093FOsb5dmyD3Xw9y5NUu2WuA7Sm5YbDVYgLy4w
UUQj7akalVeyS0A6yRPYGQ8CAwEAAaOBgTB/MAkGA1UdEwQCMAAwCwYDVR0PBAQD
AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBTqlmA2DSopCXGEoPsZ
bLGM+3LxpjAfBgNVHSMEGDAWgBTM/yIdZ8szNWSfm0+7MRlIr3b0sjAQBgoqhkiG
92NkBgMCBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAHmQ3/bimdPVFf7Bgwz2O6edf
88QtiqyuINMnPpjQcixQ0bHqHtMk0pn6rlcfsJ11+SnWM93kKI83ToOraPXH24IN
p+LxQrHL3Re8xfby8BMfYQYI8ED2BKO5RIJKwMIbpDHcUk+4SN9BJjNjlsAH+Uyf
i5fD68oQaElZ25i/Ag2AP7+snwdT1HFPAh2dJmn80zJSmkeBLQLL7X9y4R1Bse3e
z6SWCbZSYc84uRToUG6Y1uPLLS7kLF8BVFPD6oSA42nafZP4MVMyVP4vE4wXVtVS
7TLVtxBne564crJFGjtxfkODHTDHgRRfy2kpNUaO+43CNM7rhWv9KSpnAzKKYg==
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA3X4mvGWpLa3v4JTcIzcY1eVmIxYXB9e5EhNE1cPFfghZhJR4
ngQsF5QWiyR8+FAAy8JJ9f2KR+Bf4V/wROi+oRFaM+7TBcT6RBOTWX7Z//7TPJYR
Uo7KqTcmFBj3/+5VYGpye9ZCoeiVFGGB/mkw0vw7QBLNLhdVsAsJNFojVbiQFMOt
d0AqS3YnG3j9pkW6/9zm3gLaeuFUPJ1nvICEeNlkqMvb5Y5mY4UYwAPamf4mGiXC
E6/PiKxsf4Szjay5Mng26wsqHGzwqAkw83inZfA7T3cU6xvl2bIPdfD3Lk1S7Za4
DtKblhsNViAvLjBRRCPtqRqVV7JLQDrJE9gZDwIDAQABAoIBAQCvW2bzlVEBt0nI
9lHrF330KnBYqu6E6Qe/Bb5jt2EuTRICY0GzaP43lKjcdID0XvKiXyoLNTY7faqr
Vjd1dwclogVWRGiRksfJCe6I1mNlx6wZtX31bNOKcP0WwEXoPBsgAGavII0Ufn04
65Hth/59q/CE493J9fODMtmQtyRugf2GIRb58tSLdESjsdzKQcskIEskBZ89II30
/NBNayhmHfjFpxaIY9AGYKz8IsMdjsnqe03LvrQ11RNUR3/5LkshIq46kbytGCk0
K883KjyecFNZgURQ8R9Qn4qwnawC9vvpsP/713f+96gJ/VHjGrDU49fXVNCvpcFR
9qC4TWOBAoGBAPIWB9zvT3+cIOv56+av+AZHVU+4u7506AHiN0GYoi0AT1EGEWom
6CR5vWyeyn55o0ot2f5E9vHdT1jqxLoELVDJbfktZhEDB2W1NDcQm5rwvX3XnJ0O
+MAa38jMyICS4xW0Kr1dwPr4+NY+Ly4esbjKuJhyT5WoNYcsngcO0ybhAoGBAOo5
HkoleGzhrKOuMbixzu/8tX+8190aReFon+OPnhfqgCe6c353hU6QEYaq66AI8Gr4
VqhUlghndP2CjzoFDQnmQ15yQ9rPRAU6gazfh+aNY6zuO/P0x6lztqPFvkXgdwD1
WWtoQdUUJBC9OuKHCjcZ3byDnEZTosWY/tcfSW3vAoGAYxiHkXXYmgkEJPSCD0Vb
Bt7uWhrpp1Xdnt/F9LEROdCVpzoPqN9SSZQX6T268DjEkdnhEUeTun/4OhKoAukw
z5AU11oxHKebwJODU0MWHz+Kode/wT7ermyRzHWfYZo/IKRGloupMlL2MWT1FTD1
WQqKs8SfNUjM2I94BLWZ06ECgYEA18kLqM/gpJ89CAdB86CMv/iX1jlKvn6oBsT3
GRWFVw9KRk+2e7rta7W7D9CECAp0RHjKjYZwOwnldHFGNvPUUVx8kJTBAuOVDSQb
uAKwF64HOJi7T0QidnEOwM87PvFPceiYGyYQEJjfqTRM/cnflWgVKsotvXTsLxOH
JPXEFq0CgYB0n8IVtkahXMzxLT+zK2ckzPFl9CWlhaFk8v6EFHbCxiwg+/jJ9nCz
sUrgqf64W7NnoGGjMFPSUy9Sq3n5rHMA6Q3GMa512A0xDXGUAEqBumObwqen3FnR
UZdJswBoCPmiikGTtkOU+V56bfrwc67IKR5xJomTdNnhH4Q+ZGJT1Q==
-----END RSA PRIVATE KEY-----

Просмотреть файл

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
bzEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS4wLAYDVQQDEyVub2RlLWFw
biBQcm9kdWN0aW9uIFRlc3Q6IGlvLmFwbi50ZXN0MRMwEQYDVQQLEwowQTFCMkMz
RDRFMQswCQYDVQQGEwJHQjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN1+JrxlqS2t7+CU3CM3GNXlZiMWFwfXuRITRNXDxX4IWYSUeJ4ELBeUFoskfPhQ
AMvCSfX9ikfgX+Ff8ETovqERWjPu0wXE+kQTk1l+2f/+0zyWEVKOyqk3JhQY9//u
VWBqcnvWQqHolRRhgf5pMNL8O0ASzS4XVbALCTRaI1W4kBTDrXdAKkt2Jxt4/aZF
uv/c5t4C2nrhVDydZ7yAhHjZZKjL2+WOZmOFGMAD2pn+JholwhOvz4isbH+Es42s
uTJ4NusLKhxs8KgJMPN4p2XwO093FOsb5dmyD3Xw9y5NUu2WuA7Sm5YbDVYgLy4w
UUQj7akalVeyS0A6yRPYGQ8CAwEAAaOBgTB/MAkGA1UdEwQCMAAwCwYDVR0PBAQD
AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBTqlmA2DSopCXGEoPsZ
bLGM+3LxpjAfBgNVHSMEGDAWgBTM/yIdZ8szNWSfm0+7MRlIr3b0sjAQBgoqhkiG
92NkBgMCBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAHmQ3/bimdPVFf7Bgwz2O6edf
88QtiqyuINMnPpjQcixQ0bHqHtMk0pn6rlcfsJ11+SnWM93kKI83ToOraPXH24IN
p+LxQrHL3Re8xfby8BMfYQYI8ED2BKO5RIJKwMIbpDHcUk+4SN9BJjNjlsAH+Uyf
i5fD68oQaElZ25i/Ag2AP7+snwdT1HFPAh2dJmn80zJSmkeBLQLL7X9y4R1Bse3e
z6SWCbZSYc84uRToUG6Y1uPLLS7kLF8BVFPD6oSA42nafZP4MVMyVP4vE4wXVtVS
7TLVtxBne564crJFGjtxfkODHTDHgRRfy2kpNUaO+43CNM7rhWv9KSpnAzKKYg==
-----END CERTIFICATE-----

Просмотреть файл

@ -0,0 +1,28 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEAnBTu5DjErUjlkwVoLQ2a50L6Dl60O7MprpoRtLHgayAqhq+S
TQ0uZ/rpKG03BuGOozD/MVX24FvFA+MfodaTzZ9gXSizG0dU78dKFVC2evEaJSwM
1fn5jwmcRbeJAbkUkQPvHO+eLgoU8DrmMDREuysdiruyZD7jwgSQTrrchX85sNO9
MtmxppJeivPgT0bucga5il308BaGRQt/cSwml+r0x9OYwPhk4/fiTiNKA9oxEv2P
SkfQe0bzc1HeYZcITzxlgNrK7BmfC4f5FN6wnJWX4+3gpV4e/3BM17riaDZyUJCc
Cf+aCgUstzlMx4fJYvGzvp+eoDW3FeLbPhaHIQIDAQABAoIBAQCC2yYB3vo9ka0v
msvhYdOp6cQ9gfa3Spk6kl8f0DWneptMuiv9P3zVnk4WH6KPuVFdzjlVgo3tQeMm
RCgEBiN6tBEVaYbn6uDx+nJI9pdW8YaK/ahxSReKbXNAHATYlfQBNHwnFVnXnYo6
chcE+P1asmYdJwoD85n90tetugoiPNebzL0b6uqYZxllN9RFS2zgHoYXDSi7FWy2
b1GsADz8zyJ4GKNTFqGgicFEBETIgcnmIzyF3YT+Fuc4dd5aYNPtHjGkVr/nCMDq
RTXVFuiPZ7jASThzNmTsHuxHwD2LlY/8ydOZbv/MYQmDTxlEu0Z4+nSq/blCjkBy
BBtjWr4VAoGBAPJFrPjXeaYRcxZxLitGRIYj4YMn6ussHrPlHxa0YrvWf9SQareC
YGG6W+aNfW8KVtwNVgKELtZQikLIkRYsJ1ImE2sdaMBp5bqMO2gVmX8hzg1+syoe
gT84/fpPh3iJxBo45d1CFSrqYQAosVgFGqkmDXX/peEHVx2xcESKY9SbAoGBAKTt
AaqL6ian2VV9YC97sUaio1+XsY+fkdLLcGr6HvgphLx3aaWMIeq0oP4zHgy//0Pb
oLnKY3SMJJRhGGPUZfVbxb9ZZMxoNeo2yFs4HlK7G5eIyw5vRiJd5j8GhM3G7l/H
S+tXIKqIBuJsK9dG3cVEBT52IIb+n8nXmhjLSqjzAoGBAOZdcje7S6So+vHf9LKZ
Qhb6jzgTAMFVVmxf9Mu2AhvxveL030RW6CaE+VWkPB0Vi7n5xEroPVDzjEQsSij3
Gvx10AkOEcjD6PkU1ngF8cp87lzOmLX4A5WGL5mPfZUUCi+U4p0cdNw1uL5Z8ydq
0wr7b9k/mQ77184YJlRF8t75AoGBAJ0idyjv36ru1yIdr0vuVOwQvwmv9Ov7Q6uM
S1KRdnpIzH/oYg7podMGQDGRsHrDX0le8xaxHusHLz7z95H95xrLUnBKksAyNdQu
V9yZbkKypMpO+fCJ0k+iGWJJKrUIaUt2Df5u59+ydKS8HVUh3uA5O6nUUI9t//4G
XnprDnpDAoGBAKzUuumBa/uL0CaGxfJtV7Y6kdvf6Iq7bwLOopvlhoWHT8mGfNSJ
vycCdTk8gKPXRieUtlruOVHCT5gxzeangSNFjbDmOnrbbeK3uf/iPpJqE+qkOsZQ
TptFTiuh2pJlOGyGOR9U/UviRXyw3z/78V2MTWUn4U4WOZbAmsPd5WSA
-----END RSA PRIVATE KEY-----

Просмотреть файл

@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-CBC,F52B6FE328BBB66A
GAWRc5S4nLqm30eTG0XoZ0qjtiIyN8CQomNEw1nmzf+FEq0qBsnkCd3BBQ6nHhQU
3sV4nUxcf90bT1P/qs3dJSs6wssG5XYn9IsXJ5a4KmLrSw9SmeSD08hu+uLF0cNz
UBvnvLoaCX1JiSR5SnLs3lofdHMvz8xD3MHTRlCi/x5vbeocgZaIdLdbscJrYI+O
cxsM5US/ryOAu4qnTYSIR1NOirKMXQV0AfSBdNKSL4uQ95kKwRPi01cuFf6Gvyvz
W2PRSz6ddgR32raZ6zmMZmmcvzFSn6772MK3K5OjlC750MLazRvvwPlo0omc1zvu
HqRGaDhaTgGufR5iWHbbbWE/WDBcTSdG6TjCGYoQ3t1Ke56NP4DF8hQL2eSwa5ph
8KlDJasDO9b8FaZyD7dyoqPXMVyceyXRCIUjwj92Chj+uMn4IdGMalk+jPVF4CHS
kocRr9l+Xk7dTHh+wK6rWbcn/RB6leQAg5i0I2mVd8ihBx8/KHv76/8M32NcNxr4
KKMXSSkslA9rPsYOKjioWJUUXwUV58CqscQejpQ3NNz9Gw8OTrDOhnxqDPMCNjvr
KDQU+MbZL+hjXYqzMzqCNNHi7CXU+IVrPh3amEOaqZ7SZ/IT7PofRjBOwEyAIza5
r4h2W8QAjiDztgOZxGFVjmkcbPhToutvMi99O1rwryziFh7VaOigSGYgAc7yLnCo
h83IO+hT5z1Ao4WFi2k4TQp8xozflOWQ75J6vSdMuh7CxLn1Hr/hWTTanfpiuG+3
UvpqxJaXLhSbN/ZlbeqN46wnWmuUQpEgNebGAIAlOyi7ueEO3NuqeoHRWdhXWDsQ
KEF+YnElPRJEi4wbX4vRUYU/aLbRhvbUdqtOIUa2P97ME4lzkv64L7ob+qPCMtc8
NVazYcYPHi6CW/ZNz3dpL5Wb6zwW/swK9BWmsS+QOpz5CURpvfns14Xn/2x4Vav9
K3kYC/miUs5qKGJnA4r/miJV5gYTkmYa7arv7VJxfM603JT1D9mlGrHswhgqZ/U3
iGv2rWXM10YyZSow8WMhkDT55dOCWxbSY8uYEQtpsSdp3Ms/bEnobyUgbA0Lcsfq
qTxigQ3qaw8GT6xcfr4EOdqDIxvBF2Yvc+RZtDQe+8KZPKa0HnipTXKq1lUw0PdL
4O5L6EtrWw2PzMqR1KZidE4dKcJSwsctWhMpI/NXdyI0i+bMp2phdC5JkSXoHfe6
GqqmBscRmVZMDtoxBxEtfVRuLo/2AYGP5ZtHOuTXqNtWJ3QvXJl7TlUqr+05YBgL
Jy91+m5fc91+5bHdkMSBoPB/ysia956tIzoXUCFrTZJmtbtMcZ2Ku5T81RGcjdaW
TqpdLUESpTHRMq/g4IkY//hOdceEY/JSLmXVhebcGzhnBaHbTlcHd0ZJ8+XFwl38
lHzzuXM/JNwhtcLG1+1AVRDl1UWxdJH6Z8dmnPyRaphWpYcxgC+dQrBwZCyG/Qgj
kyVXRGzDUHU89QCUhpbgfJ8eRbXJjdqIfUM4oAMqPatT9SIMl5F41PJSdqwTSKnZ
9nStMIURjFe8GCEe3j/0xkNdIkd94RftXp3GAT4HPup3TC8ni3L+Yw==
-----END RSA PRIVATE KEY-----

Просмотреть файл

@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,CDC4C2E547EB0BDC
KhB+U13K9TrIVhpFRvUfElnLoXlmMCoV9YA7Dg07HlicXkvLKELjlcNRTuvhIFQv
yiY+p1iCmPvlCxx2HDI5c+152GqWRoIUkigC/jGg2oA3nLLHZiDSlJOJTjHzPLpn
xKqRAerHMcYRiEmu0xut75Rwhs3IjhrtL/Po426B4k217FRBGZnlKJOeDA5lhGY/
YiSmdXWhQdqhNWxG6B/t2oKAaAuLrlK6UhW4eqsyKleJElzAGoEr0KkKE+9f5S80
x+rnZ1h0R0rRjjnxQk9D8686NUn8O8/yKSYkZGc7iw5kyhbhpzGAv5l113e1YpV1
dO20fDYMu4aiHbn2iZMj1uFRae0DA53+KWo4VYqm2OxT0kAKsX5PBKL7gZtt2g1m
4qSfpk+XCWaDVGm566Vmmpu+BNkuNiQd6F5vLkODErYLqLTEFSAP99GKitOFiL/O
hS7fniU8+ln1kN4t1UBduxK9K9wuDdO33g5D2eLLIwofg/Y8Dc7WCS/zdenZ6+mx
t+T97ICUtnvayaEMCk44u6/E9wgXOnP0aO0sy6gPgt8Gph/xknYY9e3nWcJnguYU
p9enjVj9lTku/vU3B+hcjJp2ZFcxENHhFRgaklxvZj+HoyERqwQPoJe3cCvi47P1
LJthRTWLfBYLALjEOpxfheqYgqD7U8VNOjyRDdv4NZ7jwk3zmIEYic25N/+Q8MKp
pgTNTT+i23uiWU5u71gh+y+lzYeVqt0KooDgp3I/0v7vQAhUeXw4J6Jei+9lWR8i
tCXn2BQfbu2ix7OnAlhiSL3wSMhsVaulYWH99zQAopEcj0x0w4twbWu6t7v2FJEd
5DpHlBB1g0gT0htXhPeGxttmvFFQg+qykhaLqZ800sUvCjSvGWzMoHx48oHBh77l
tLqdO/WVtR/qXSuUBAY8gE8t5kZ6LwLkxbbRuMbS1V5iellfK+/I5qXfw40TyCi1
k7UQmTrH/e2MZnURHzKGHdl84lK8PKP+/ATOp0B9qJ0yB97bxU3j5OxzxFIvW4Aq
uozB47Kf7V/gbN9nr/1EluLXboDR/Mh34r+4atm2SRgOyFnfomr5s4OX0BRyj8kQ
74iu2uZukNWqLt4YJx3XP4Mzt6nk/xpc2ZLenvYS/i1Wt25MOf2wxWdN/FC4FCDc
itCd/xgmITeP/t7aVa3lcmG/fCK+R5Qj0BR5RJYtpiXAgVEKI4tn90UfQVBSe2cU
PNtVjlImB0tb6i6kY7M0EG1iZWxI3Yk244Jtmzm41BelBa1P7zeOf6FMdQ9NWsox
P9pvyrjVv6XpI/ORMzDn1LtEEjXWcrWW7sGFi/ukEsBc1MbwvuWkQ5kvglu6+Dg7
redYOt58b/YhRXVZoadbh4lWqXhFcz0sYgh9lj68RakVxixPLMlockqtOkZeR6lb
6irjAhHCz2ikZ1F2Rm9usqrT81VIuHaYNkNFILJqx1Udd4nAg17fN2BOW+1rN1lU
E0sHHR0ggq/9FtIu5tTA02hM4mG2Knnha87hqYn4jTr4SgmQnqJqPkS6txwL0MLT
5u/4MhzvHJfcTzZ1bQSK0cNf1UvRG1jWv15uYsINABbsTXLjrpqdCnhY6rZ1HF7h
-----END RSA PRIVATE KEY-----

Просмотреть файл

@ -0,0 +1,29 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6TAbBgkqhkiG9w0BBQMwDgQIDUmwcibfl/MCAggABIIEyMHQ9n0p97qnHdWK
fFkZYs8HyHn4JRfPgEw8BY8w2/iuCboewkbigHdMj7NM4Epz9i3fbU9YgranCDSS
cY0yFOTn6R+Dq0B2yRu475p+68aLJq0y0eTCM42Kov7HOQ1BXnxrCdaKcFUB9GXH
eS878zNhWC3DaZTkvOAJTgNdwj5wKtI6E5IqNzGHTxnc9P6m1az7uVXTO97py51p
EpHe5LSOXPRfR1sWuWMbcYVbfS4AHIpnFFb3v2DDm8gBYWcghOXulunQwfGaVoxC
G5AAbdIbLo3ODwwRLGabiDbXDPG0V2bN8HQ8vZI5a5MUXaCpyeBEHjupsF8l4Ytt
XB+CEs9kXwPihogRItXiHpZFI6J/GxrETHxLKnEBsLPnGQPVbm4HMKOdwWmfGVIX
uM/PUD9DYZTsqwkd5m4ckR3DuBEcosBLoK3FeHVRG6nqnNWNe8R2AGyTWdNVaKVl
0XvFiQTwTe2mx2Dne02KyK/QX8yL3LKp5Dx8YqpgOZRGTQoC+S82EaljcVGFimoU
hMsPdBE6Z7iFgCIN6TV/NSLeTRSTPi7TInE46ZaGCEL89MeKS4a6GsvatRsx3eVb
YAXk7f7iT6e4V2r2O1kfypL+dkd4FfMGY4hjsNbJKYatYrWYAQVT5d6FhwjHerD7
kWuEh4HrBPHU5UNdBnO8wW4Ba+0/AWis7OvdIYh230PqklsXil2Ligv0fqd2q/Qk
ftUQa2e5F2m5RIQSjc2mtsAnSe1VfMSxz3iHr5yaSZ8deVSJZHhR1lCNWnasM+KC
GTNHxP90mNuXTf4z5b9yZi5EIa/xtpWCWSAt88w65JJGwQLWirwWiRiuQ90vtA+u
x8vZa28rTjF4xtar1ArsrucdvYEQVEtpCnQIL2tWV4JAG3hnoGv2qgWdaGeqEkv5
e3PAiVI46HEdbw6b5z1YvvP4bE01cLgHpPg25CNSWgOtitdZ2hq9KCcEW38vYz8y
LMZ92lI/DJwgcux41u7TUhqvs1tHn5i7W5h0BYvzy17daby4ra4HSPlpiq5APLkO
iMhvXaFsZ6oXmy2MhzXDovdriE+fTyf0XzNMIlswTy+14ePkIdBdFI2efuL8wrQN
a5/wnwpNyvIZIAzNuWvhazVGylL8C10RMaQoFtUtun2Fak4BP2lH5HnmoyhfS5Gu
1kbyukIEk3hlOqg3An+wdyl8WGGAGct9IDmwzjxGIo/fWA1usniiFlyC39B41Ixu
oGCqtkiXXbmaY3FQLgTwOGYd7okyxIDQIpqBTYrYoNH3hO75w2JRLIBd7AAmX/RK
s0d/jb3mkcYO1PArFnwmEwIhkSakClPyEKhx0HJ63sIPlua2rcsdDfsUvt7FOL4p
7a/sk8vJ5swjhHnoJjxQorZ3pKLoW1/HoNdKLGDtQqWN/uOtD6IPi5uNeiJCV6Nt
H+ZemhN6lUcUd0cOFD81JjByoghzxdALZoeAltQnEe7Web/OZVKG4dAhC/LBD2T2
FZdJ8iX5qD3qp9UfyqLWo8ej89JwOWT8btYOIMoWLonkBNB3lJ7S/OcWkecNYPb2
WYKg0o5FiwPK23a75Zsw1rEiq9WGdqFOg3SaE9q4RnWxUAwFD8KoIUHQasaE1q8u
ToxJDqEzq6lksxXsIA==
-----END ENCRYPTED PRIVATE KEY-----

Просмотреть файл

@ -0,0 +1,29 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6jAcBgoqhkiG9w0BDAEDMA4ECGtNrvTcx2VVAgIIAASCBMhVNYUKhBeY+sYe
eM0Sqv936AXXLCnksnhvi3f2CoRPAS5AOkY0aINuq7xy4kR4Apv7tgk4kJcyj96E
w1c26fA4K3ozxCYGRoE55Q2WPW8K176hy8p66czpTXmfBPEqC847vwQvsSPyw48C
toWZajxIK380QUXC7CIEVCP15YdvZBaNAnGVM0jZ8rvw5TmVJKMwju4azmwDkpr9
uv+3YIuGDquvnsaHA+u7U82b4m/KlZVL8HJd8uMs7ggZZXy3H6ukVsN3DwNs0Ji1
FdrF6FAsHLNJ5Ram0bOE9Y06wcJMveTC6uObz572bg4xWu7C1hfMGd7JIJ8+eEir
bowBc2wkb2JHMN3ShZBtGYpaRmFn5caMQkzqtxSi22TThMxPYa6jvOBRdMhCbA5F
FAixF1CTFSUiI+2jplb/ygcEZvoe9mdpnazdI+x/g5Z3rzdaQOyQbip1GrHQJnAz
gPuN9YItI5xWJaP25LcgM9cLPXiDeLUa+PquZFYLUiWhbvYLguysUK0RQALWzavU
MiMjuJOw1YcBqDTV7QziKvN+vrds7ApHiyuuUtha0bXdYs9FVzJSrC+UdQwVOHUe
FU7UejzFEYnguOsBBUGU30+j2F8FIkqxktAzgJEA/kmazuemTH5bCI45G9rWtoRl
RNW8qi3Ja+jtO3THIi27QyExcHh3p3OPv5hjfbpFKIy/k3iL2KN9X2W2+fhHbLV/
5ZxDEUtSwY/BqjlVQHtPY7IlPPBEv+8aGsPEGgaUGY8ZfJvK1upGw04rsnrRsXbk
sQJ51hLVVUXo+U6OnkeHxixXkqQ1ZUPQsbFC3S5HhMJDntlAHF5f4UG2YKh7l9Ob
txzgoUPLC82c7bcbx/11Nl8nTmr1Y38NSCnpphhz8NKAPaPSwVCeFH5UrUhdBzf5
lvnJ7BUF5pF7DPWI9jMJCyOlAc6J/6pwje3cpl8L28623cJBXrD8S56kV91b8PkE
DwX5in2nHlPOHYbJk9pI+9DKHxHYHA3mgIc+eZTMciwAkB65QKN9pjC+Kyb53Ibt
UadOO8OMwjGz3ZoQJ6LJicv3u/lakYwQ8mHgxfkLZKLwOZBZjgYHnHJDpds9Y2mt
LTsBAbX8GJtPkVrftp2KuS00n6wuvefTmyqQ8O7XIgYSgy22w+mSLAKHcFlUT/FK
l5P0UsbTCkK0o8xzJlevFblUdGKgxYe915vsLPiBzMu6BiXmKclWWuslGKLwFmNj
dt90ZQ+e6b5xJ4YpTlvr9rRYR/n1SZqZLMHpZCd8kpn8/5AUiQllLIawuZZlnEyz
zjdsdpvw1mAxt+YSf7DbKsqp0JnYK83nCnBwhpKdhdtfC19eYMbUp8Ju8hsgRRxq
f5RCQHwgeHx9/+8kheusHLOswAS7+DOUwziD1iE9CjBWx6LA56jr1fn2CLgxTrOw
CZ4z/BYcTyX6a3vne+P0i/8Z63pOPV5U5VPFkWMc8c3iUQPBvpZGfcjGfuztnk3k
Y7GVg+dBXJWaFM6mWKTBTLTZ7o9bBXb2McI8glZPExKyTCqW3FP7YJJpqmpUHnA/
Wgor9T5XY/G1vZo4uchn4tmOU3AeFHROjqnGXxFOrJGbrb1TNmoZMuH5q1tsL3ns
0YBcPeLgKJY9Rru/T+o=
-----END ENCRYPTED PRIVATE KEY-----

Просмотреть файл

@ -0,0 +1,28 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA3X4mvGWpLa3v4JTcIzcY1eVmIxYXB9e5EhNE1cPFfghZhJR4
ngQsF5QWiyR8+FAAy8JJ9f2KR+Bf4V/wROi+oRFaM+7TBcT6RBOTWX7Z//7TPJYR
Uo7KqTcmFBj3/+5VYGpye9ZCoeiVFGGB/mkw0vw7QBLNLhdVsAsJNFojVbiQFMOt
d0AqS3YnG3j9pkW6/9zm3gLaeuFUPJ1nvICEeNlkqMvb5Y5mY4UYwAPamf4mGiXC
E6/PiKxsf4Szjay5Mng26wsqHGzwqAkw83inZfA7T3cU6xvl2bIPdfD3Lk1S7Za4
DtKblhsNViAvLjBRRCPtqRqVV7JLQDrJE9gZDwIDAQABAoIBAQCvW2bzlVEBt0nI
9lHrF330KnBYqu6E6Qe/Bb5jt2EuTRICY0GzaP43lKjcdID0XvKiXyoLNTY7faqr
Vjd1dwclogVWRGiRksfJCe6I1mNlx6wZtX31bNOKcP0WwEXoPBsgAGavII0Ufn04
65Hth/59q/CE493J9fODMtmQtyRugf2GIRb58tSLdESjsdzKQcskIEskBZ89II30
/NBNayhmHfjFpxaIY9AGYKz8IsMdjsnqe03LvrQ11RNUR3/5LkshIq46kbytGCk0
K883KjyecFNZgURQ8R9Qn4qwnawC9vvpsP/713f+96gJ/VHjGrDU49fXVNCvpcFR
9qC4TWOBAoGBAPIWB9zvT3+cIOv56+av+AZHVU+4u7506AHiN0GYoi0AT1EGEWom
6CR5vWyeyn55o0ot2f5E9vHdT1jqxLoELVDJbfktZhEDB2W1NDcQm5rwvX3XnJ0O
+MAa38jMyICS4xW0Kr1dwPr4+NY+Ly4esbjKuJhyT5WoNYcsngcO0ybhAoGBAOo5
HkoleGzhrKOuMbixzu/8tX+8190aReFon+OPnhfqgCe6c353hU6QEYaq66AI8Gr4
VqhUlghndP2CjzoFDQnmQ15yQ9rPRAU6gazfh+aNY6zuO/P0x6lztqPFvkXgdwD1
WWtoQdUUJBC9OuKHCjcZ3byDnEZTosWY/tcfSW3vAoGAYxiHkXXYmgkEJPSCD0Vb
Bt7uWhrpp1Xdnt/F9LEROdCVpzoPqN9SSZQX6T268DjEkdnhEUeTun/4OhKoAukw
z5AU11oxHKebwJODU0MWHz+Kode/wT7ermyRzHWfYZo/IKRGloupMlL2MWT1FTD1
WQqKs8SfNUjM2I94BLWZ06ECgYEA18kLqM/gpJ89CAdB86CMv/iX1jlKvn6oBsT3
GRWFVw9KRk+2e7rta7W7D9CECAp0RHjKjYZwOwnldHFGNvPUUVx8kJTBAuOVDSQb
uAKwF64HOJi7T0QidnEOwM87PvFPceiYGyYQEJjfqTRM/cnflWgVKsotvXTsLxOH
JPXEFq0CgYB0n8IVtkahXMzxLT+zK2ckzPFl9CWlhaFk8v6EFHbCxiwg+/jJ9nCz
sUrgqf64W7NnoGGjMFPSUy9Sq3n5rHMA6Q3GMa512A0xDXGUAEqBumObwqen3FnR
UZdJswBoCPmiikGTtkOU+V56bfrwc67IKR5xJomTdNnhH4Q+ZGJT1Q==
-----END RSA PRIVATE KEY-----

Двоичные данные
test/credentials/support/multipleKeys.p12 Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,99 @@
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
cDEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS8wLQYDVQQDEyZub2RlLWFw
biBEZXZlbG9wbWVudCBUZXN0OiBpby5hcG4udGVzdDETMBEGA1UECxMKMEExQjJD
M0Q0RTELMAkGA1UEBhMCR0IwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQCcFO7kOMStSOWTBWgtDZrnQvoOXrQ7symumhG0seBrICqGr5JNDS5n+ukobTcG
4Y6jMP8xVfbgW8UD4x+h1pPNn2BdKLMbR1Tvx0oVULZ68RolLAzV+fmPCZxFt4kB
uRSRA+8c754uChTwOuYwNES7Kx2Ku7JkPuPCBJBOutyFfzmw070y2bGmkl6K8+BP
Ru5yBrmKXfTwFoZFC39xLCaX6vTH05jA+GTj9+JOI0oD2jES/Y9KR9B7RvNzUd5h
lwhPPGWA2srsGZ8Lh/kU3rCclZfj7eClXh7/cEzXuuJoNnJQkJwJ/5oKBSy3OUzH
h8li8bO+n56gNbcV4ts+FochAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQE
AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQULVlMmGEifdIrpa43
zJNU6RF6gE0wHwYDVR0jBBgwFoAUzP8iHWfLMzVkn5tPuzEZSK929LIwEAYKKoZI
hvdjZAYDAQQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFEPh8EDx7Dkskxoy6ZBAHMD
ir9/fpwzb5AKtrxkIoT06F0D0+2x4Mmt9vuTmckqWU9awsRTZu4NUGMrN7+G1cmd
KqI95iuUecDtYFnCNjPJZrmvBBuOeGfO1bC5ZNXHkgn7b7ClMFJ30enOBO4T2tY4
NXww7gA1L2S+0ZWrHQ6f/4SenSoo6EQt484RtsJlIWVP34zhJeo4jXxuyQh42JM6
88jz5T0tr+1SyD4UDemoOukgti0UaGBdPw98bRjZf+XTXG01v/2KlQhkMSb0/l6v
QsCVz3gIzloWl4+JXZYp1tNxZQHlvLqihMwV0vQmmQBHkS733Nlr/9OU3KRPAVM=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEAnBTu5DjErUjlkwVoLQ2a50L6Dl60O7MprpoRtLHgayAqhq+S
TQ0uZ/rpKG03BuGOozD/MVX24FvFA+MfodaTzZ9gXSizG0dU78dKFVC2evEaJSwM
1fn5jwmcRbeJAbkUkQPvHO+eLgoU8DrmMDREuysdiruyZD7jwgSQTrrchX85sNO9
MtmxppJeivPgT0bucga5il308BaGRQt/cSwml+r0x9OYwPhk4/fiTiNKA9oxEv2P
SkfQe0bzc1HeYZcITzxlgNrK7BmfC4f5FN6wnJWX4+3gpV4e/3BM17riaDZyUJCc
Cf+aCgUstzlMx4fJYvGzvp+eoDW3FeLbPhaHIQIDAQABAoIBAQCC2yYB3vo9ka0v
msvhYdOp6cQ9gfa3Spk6kl8f0DWneptMuiv9P3zVnk4WH6KPuVFdzjlVgo3tQeMm
RCgEBiN6tBEVaYbn6uDx+nJI9pdW8YaK/ahxSReKbXNAHATYlfQBNHwnFVnXnYo6
chcE+P1asmYdJwoD85n90tetugoiPNebzL0b6uqYZxllN9RFS2zgHoYXDSi7FWy2
b1GsADz8zyJ4GKNTFqGgicFEBETIgcnmIzyF3YT+Fuc4dd5aYNPtHjGkVr/nCMDq
RTXVFuiPZ7jASThzNmTsHuxHwD2LlY/8ydOZbv/MYQmDTxlEu0Z4+nSq/blCjkBy
BBtjWr4VAoGBAPJFrPjXeaYRcxZxLitGRIYj4YMn6ussHrPlHxa0YrvWf9SQareC
YGG6W+aNfW8KVtwNVgKELtZQikLIkRYsJ1ImE2sdaMBp5bqMO2gVmX8hzg1+syoe
gT84/fpPh3iJxBo45d1CFSrqYQAosVgFGqkmDXX/peEHVx2xcESKY9SbAoGBAKTt
AaqL6ian2VV9YC97sUaio1+XsY+fkdLLcGr6HvgphLx3aaWMIeq0oP4zHgy//0Pb
oLnKY3SMJJRhGGPUZfVbxb9ZZMxoNeo2yFs4HlK7G5eIyw5vRiJd5j8GhM3G7l/H
S+tXIKqIBuJsK9dG3cVEBT52IIb+n8nXmhjLSqjzAoGBAOZdcje7S6So+vHf9LKZ
Qhb6jzgTAMFVVmxf9Mu2AhvxveL030RW6CaE+VWkPB0Vi7n5xEroPVDzjEQsSij3
Gvx10AkOEcjD6PkU1ngF8cp87lzOmLX4A5WGL5mPfZUUCi+U4p0cdNw1uL5Z8ydq
0wr7b9k/mQ77184YJlRF8t75AoGBAJ0idyjv36ru1yIdr0vuVOwQvwmv9Ov7Q6uM
S1KRdnpIzH/oYg7podMGQDGRsHrDX0le8xaxHusHLz7z95H95xrLUnBKksAyNdQu
V9yZbkKypMpO+fCJ0k+iGWJJKrUIaUt2Df5u59+ydKS8HVUh3uA5O6nUUI9t//4G
XnprDnpDAoGBAKzUuumBa/uL0CaGxfJtV7Y6kdvf6Iq7bwLOopvlhoWHT8mGfNSJ
vycCdTk8gKPXRieUtlruOVHCT5gxzeangSNFjbDmOnrbbeK3uf/iPpJqE+qkOsZQ
TptFTiuh2pJlOGyGOR9U/UviRXyw3z/78V2MTWUn4U4WOZbAmsPd5WSA
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJHQjER
MA8GA1UECxMIbm9kZS1hcG4xLDAqBgNVBAMTI25vZGUtYXBuIFRlc3QgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MB4XDTE1MDEwMTAwMDAwMFoXDTI1MDEwMTAwMDAwMFow
bzEbMBkGCgmSJomT8ixkAQETC2lvLmFwbi50ZXN0MS4wLAYDVQQDEyVub2RlLWFw
biBQcm9kdWN0aW9uIFRlc3Q6IGlvLmFwbi50ZXN0MRMwEQYDVQQLEwowQTFCMkMz
RDRFMQswCQYDVQQGEwJHQjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN1+JrxlqS2t7+CU3CM3GNXlZiMWFwfXuRITRNXDxX4IWYSUeJ4ELBeUFoskfPhQ
AMvCSfX9ikfgX+Ff8ETovqERWjPu0wXE+kQTk1l+2f/+0zyWEVKOyqk3JhQY9//u
VWBqcnvWQqHolRRhgf5pMNL8O0ASzS4XVbALCTRaI1W4kBTDrXdAKkt2Jxt4/aZF
uv/c5t4C2nrhVDydZ7yAhHjZZKjL2+WOZmOFGMAD2pn+JholwhOvz4isbH+Es42s
uTJ4NusLKhxs8KgJMPN4p2XwO093FOsb5dmyD3Xw9y5NUu2WuA7Sm5YbDVYgLy4w
UUQj7akalVeyS0A6yRPYGQ8CAwEAAaOBgTB/MAkGA1UdEwQCMAAwCwYDVR0PBAQD
AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBTqlmA2DSopCXGEoPsZ
bLGM+3LxpjAfBgNVHSMEGDAWgBTM/yIdZ8szNWSfm0+7MRlIr3b0sjAQBgoqhkiG
92NkBgMCBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAHmQ3/bimdPVFf7Bgwz2O6edf
88QtiqyuINMnPpjQcixQ0bHqHtMk0pn6rlcfsJ11+SnWM93kKI83ToOraPXH24IN
p+LxQrHL3Re8xfby8BMfYQYI8ED2BKO5RIJKwMIbpDHcUk+4SN9BJjNjlsAH+Uyf
i5fD68oQaElZ25i/Ag2AP7+snwdT1HFPAh2dJmn80zJSmkeBLQLL7X9y4R1Bse3e
z6SWCbZSYc84uRToUG6Y1uPLLS7kLF8BVFPD6oSA42nafZP4MVMyVP4vE4wXVtVS
7TLVtxBne564crJFGjtxfkODHTDHgRRfy2kpNUaO+43CNM7rhWv9KSpnAzKKYg==
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA3X4mvGWpLa3v4JTcIzcY1eVmIxYXB9e5EhNE1cPFfghZhJR4
ngQsF5QWiyR8+FAAy8JJ9f2KR+Bf4V/wROi+oRFaM+7TBcT6RBOTWX7Z//7TPJYR
Uo7KqTcmFBj3/+5VYGpye9ZCoeiVFGGB/mkw0vw7QBLNLhdVsAsJNFojVbiQFMOt
d0AqS3YnG3j9pkW6/9zm3gLaeuFUPJ1nvICEeNlkqMvb5Y5mY4UYwAPamf4mGiXC
E6/PiKxsf4Szjay5Mng26wsqHGzwqAkw83inZfA7T3cU6xvl2bIPdfD3Lk1S7Za4
DtKblhsNViAvLjBRRCPtqRqVV7JLQDrJE9gZDwIDAQABAoIBAQCvW2bzlVEBt0nI
9lHrF330KnBYqu6E6Qe/Bb5jt2EuTRICY0GzaP43lKjcdID0XvKiXyoLNTY7faqr
Vjd1dwclogVWRGiRksfJCe6I1mNlx6wZtX31bNOKcP0WwEXoPBsgAGavII0Ufn04
65Hth/59q/CE493J9fODMtmQtyRugf2GIRb58tSLdESjsdzKQcskIEskBZ89II30
/NBNayhmHfjFpxaIY9AGYKz8IsMdjsnqe03LvrQ11RNUR3/5LkshIq46kbytGCk0
K883KjyecFNZgURQ8R9Qn4qwnawC9vvpsP/713f+96gJ/VHjGrDU49fXVNCvpcFR
9qC4TWOBAoGBAPIWB9zvT3+cIOv56+av+AZHVU+4u7506AHiN0GYoi0AT1EGEWom
6CR5vWyeyn55o0ot2f5E9vHdT1jqxLoELVDJbfktZhEDB2W1NDcQm5rwvX3XnJ0O
+MAa38jMyICS4xW0Kr1dwPr4+NY+Ly4esbjKuJhyT5WoNYcsngcO0ybhAoGBAOo5
HkoleGzhrKOuMbixzu/8tX+8190aReFon+OPnhfqgCe6c353hU6QEYaq66AI8Gr4
VqhUlghndP2CjzoFDQnmQ15yQ9rPRAU6gazfh+aNY6zuO/P0x6lztqPFvkXgdwD1
WWtoQdUUJBC9OuKHCjcZ3byDnEZTosWY/tcfSW3vAoGAYxiHkXXYmgkEJPSCD0Vb
Bt7uWhrpp1Xdnt/F9LEROdCVpzoPqN9SSZQX6T268DjEkdnhEUeTun/4OhKoAukw
z5AU11oxHKebwJODU0MWHz+Kode/wT7ermyRzHWfYZo/IKRGloupMlL2MWT1FTD1
WQqKs8SfNUjM2I94BLWZ06ECgYEA18kLqM/gpJ89CAdB86CMv/iX1jlKvn6oBsT3
GRWFVw9KRk+2e7rta7W7D9CECAp0RHjKjYZwOwnldHFGNvPUUVx8kJTBAuOVDSQb
uAKwF64HOJi7T0QidnEOwM87PvFPceiYGyYQEJjfqTRM/cnflWgVKsotvXTsLxOH
JPXEFq0CgYB0n8IVtkahXMzxLT+zK2ckzPFl9CWlhaFk8v6EFHbCxiwg+/jJ9nCz
sUrgqf64W7NnoGGjMFPSUy9Sq3n5rHMA6Q3GMa512A0xDXGUAEqBumObwqen3FnR
UZdJswBoCPmiikGTtkOU+V56bfrwc67IKR5xJomTdNnhH4Q+ZGJT1Q==
-----END RSA PRIVATE KEY-----

Просмотреть файл

@ -0,0 +1,126 @@
var sinon = require("sinon");
var validateCredentials = require("../../lib/credentials/validate");
var fakeCredentials;
describe("validateCredentials", function() {
var credentials;
beforeEach(function() {
credentials = fakeCredentials();
});
describe("with valid credentials", function() {
it("returns", function() {
expect(function() {
validateCredentials(credentials);
}).to.not.throw();
});
});
describe("with mismatched key and certificate", function() {
it("throws", function() {
sinon.stub(credentials.certificates[0]._key, "fingerprint").returns("fingerprint2");
expect(function() {
validateCredentials(credentials);
}).to.throw(/certificate and key do not match/);
});
});
describe("with expired certificate", function() {
it("throws", function() {
sinon.stub(credentials.certificates[0], "validity")
.returns({
notBefore: new Date(Date.now() - 100000),
notAfter: new Date(Date.now() - 10000)
});
expect(function() {
validateCredentials(credentials);
}).to.throw(/certificate has expired/);
});
});
describe("with incorrect environment", function() {
it("throws with sandbox cert in production", function() {
sinon.stub(credentials.certificates[0], "environment")
.returns({
production: false,
sandbox: true
});
expect(function() {
validateCredentials(credentials);
}).to.throw("certificate does not support configured environment, production: true");
});
it("throws with production cert in sandbox", function() {
sinon.stub(credentials.certificates[0], "environment")
.returns({
production: true,
sandbox: false
});
credentials.production = false;
expect(function() {
validateCredentials(credentials);
}).to.throw("certificate does not support configured environment, production: false");
});
});
describe("with missing production flag", function() {
it("does not throw", function() {
sinon.stub(credentials.certificates[0], "environment")
.returns({
production: true,
sandbox: false
});
credentials.production = undefined;
expect(function() {
validateCredentials(credentials);
}).to.not.throw();
});
});
describe("with certificate supporting both environments", function() {
it("does not throw", function() {
sinon.stub(credentials.certificates[0], "environment")
.returns({
production: true,
sandbox: true
});
credentials.production = false;
expect(function() {
validateCredentials(credentials);
}).to.not.throw();
});
});
});
fakeCredentials = function() {
return {
key: {
_fingerprint: "fingerprint1",
fingerprint: function() { return this._fingerprint; },
},
certificates: [{
_key: {
_fingerprint: "fingerprint1",
fingerprint: function() { return this._fingerprint; },
},
_validity: {
notBefore: new Date(Date.now() - 100000),
notAfter: new Date(Date.now() + 100000)
},
key: function() { return this._key; },
validity: function() {
return this._validity;
},
environment: function() {
return { production: true, sandbox: false };
}
}],
production: true
};
};

Просмотреть файл

@ -1,28 +1,28 @@
var apn = require("../");
describe("Device", function() {
describe('constructor', function () {
describe("constructor", function () {
// Issue #149
it("should error when given a device string which contains no hex characters and results in 0 length token", function () {
(function () {
expect(function () {
apn.Device("som string without hx lttrs");
}).should.throw();
}).to.throw();
});
it("should error when given a device string which contains an odd number of hex characters", function () {
(function () {
expect(function () {
apn.Device("01234");
}).should.throw();
}).to.throw();
});
it("should return a Device object containing the correct token when given a hex string", function () {
apn.Device("<0123 4567 89AB CDEF>").toString().should.equal("0123456789abcdef");
expect(apn.Device("<0123 4567 89AB CDEF>").toString()).to.equal("0123456789abcdef");
});
it("should return a Device object containing the correct token when given a Buffer", function () {
var buf = new Buffer([1, 35, 69, 103, 137, 171, 205, 239]);
apn.Device(buf).toString().should.equal("0123456789abcdef");
expect(apn.Device(buf).toString()).to.equal("0123456789abcdef");
});
});
});

Просмотреть файл

@ -1,19 +1,29 @@
var apn = require("../");
var fs = require("fs");
var rewire = require("rewire");
var Feedback = rewire("../lib/feedback");
var sinon = require("sinon");
var Q = require("q");
describe("Feedback", function() {
describe('constructor', function () {
var requestMethod, originalEnv;
var startMethod;
before(function() {
// Constructor has side effects :-(
startMethod = Feedback.prototype.start;
Feedback.prototype.start = function() { };
});
after(function() {
Feedback.prototype.start = startMethod;
});
describe("constructor", function () {
var originalEnv;
before(function() {
requestMethod = apn.Feedback.prototype.request;
apn.Feedback.prototype.request = function() { };
originalEnv = process.env.NODE_ENV;
});
after(function() {
apn.Feedback.prototype.request = requestMethod;
process.env.NODE_ENV = originalEnv;
});
@ -23,33 +33,321 @@ describe("Feedback", function() {
// Issue #50
it("should use feedback.sandbox.push.apple.com as the default Feedback address", function () {
apn.Feedback().options.address.should.equal("feedback.sandbox.push.apple.com");
expect(Feedback().options.address).to.equal("feedback.sandbox.push.apple.com");
});
it("should use feedback.push.apple.com when NODE_ENV=production", function () {
process.env.NODE_ENV = "production";
apn.Feedback().options.address.should.equal("feedback.push.apple.com");
expect(Feedback().options.address).to.equal("feedback.push.apple.com");
});
it("should use feedback.push.apple.com when production:true", function () {
apn.Feedback({production:true}).options.address.should.equal("feedback.push.apple.com");
expect(Feedback({production:true}).options.address).to.equal("feedback.push.apple.com");
});
it("should give precedence to production flag over NODE_ENV=production", function () {
process.env.NODE_ENV = "production";
apn.Feedback({ production: false }).options.address.should.equal("feedback.sandbox.push.apple.com");
expect(Feedback({ production: false }).options.address).to.equal("feedback.sandbox.push.apple.com");
});
it("should use a custom address when passed", function () {
apn.Feedback({address: "testaddress"}).options.address.should.equal("testaddress");
expect(Feedback({address: "testaddress"}).options.address).to.equal("testaddress");
});
describe("address is passed", function() {
it("sets production to true when using production address", function() {
expect(Feedback({address: "feedback.push.apple.com"}).options.production).to.be.true;
});
it("sets production to false when using sandbox address", function() {
process.env.NODE_ENV = "production";
expect(Feedback({address: "feedback.sandbox.push.apple.com"}).options.production).to.be.false;
});
});
});
describe('#initialize', function () {
describe("#initialize", function () {
var loadStub, parseStub, validateStub, removeStubs;
beforeEach(function() {
loadStub = sinon.stub();
loadStub.displayName = "loadCredentials";
it("should be fulfilled", function () {
return apn.Feedback({ pfx: "test/support/initializeTest.pfx" })
.initialize().should.be.fulfilled;
parseStub = sinon.stub();
parseStub.displayName = "parseCredentials";
validateStub = sinon.stub();
validateStub.displayName = "validateCredentials";
removeStubs = Feedback.__set__({
"loadCredentials": loadStub,
"parseCredentials": parseStub,
"validateCredentials": validateStub,
});
});
afterEach(function() {
removeStubs();
});
it("only loads credentials once", function() {
loadStub.returns(Q({}));
var feedback = Feedback();
feedback.initialize();
feedback.initialize();
expect(loadStub).to.be.calledOnce;
});
describe("with valid credentials", function() {
var initialization;
var testOptions = {
pfx: "myCredentials.pfx", cert: "myCert.pem", key: "myKey.pem", ca: "myCa.pem",
passphrase: "apntest", production: true
};
beforeEach(function() {
loadStub.withArgs(sinon.match(function(v) {
return v.pfx === "myCredentials.pfx" && v.cert === "myCert.pem" && v.key === "myKey.pem" &&
v.ca === "myCa.pem" && v.passphrase === "apntest";
})).returns(Q({ pfx: "myPfxData", cert: "myCertData", key: "myKeyData", ca: ["myCaData"], passphrase: "apntest" }));
parseStub.returnsArg(0);
initialization = Feedback(testOptions).initialize();
});
it("should be fulfilled", function () {
return expect(initialization).to.be.fulfilled;
});
describe("the validation stage", function() {
it("is called once", function() {
return initialization.finally(function() {
expect(validateStub).to.be.calledOnce;
});
});
it("is passed the production flag", function() {
return initialization.finally(function() {
expect(validateStub.getCall(0).args[0]).to.have.property("production", true);
});
});
describe("passed credentials", function() {
it("contains the PFX data", function() {
return initialization.finally(function() {
expect(validateStub.getCall(0).args[0]).to.have.property("pfx", "myPfxData");
});
});
it("contains the key data", function() {
return initialization.finally(function() {
expect(validateStub.getCall(0).args[0]).to.have.property("key", "myKeyData");
});
});
it("contains the certificate data", function() {
return initialization.finally(function() {
expect(validateStub.getCall(0).args[0]).to.have.property("cert", "myCertData");
});
});
it("includes passphrase", function() {
return initialization.finally(function() {
expect(validateStub.getCall(0).args[0]).to.have.property("passphrase", "apntest");
});
});
});
});
describe("resolution value", function() {
it("contains the PFX data", function() {
return expect(initialization).to.eventually.have.property("pfx", "myPfxData");
});
it("contains the key data", function() {
return expect(initialization).to.eventually.have.property("key", "myKeyData");
});
it("contains the certificate data", function() {
return expect(initialization).to.eventually.have.property("cert", "myCertData");
});
it("contains the CA data", function() {
return expect(initialization).to.eventually.have.deep.property("ca[0]", "myCaData");
});
it("includes passphrase", function() {
return expect(initialization).to.eventually.have.property("passphrase", "apntest");
});
});
});
describe("credential file cannot be parsed", function() {
beforeEach(function() {
loadStub.returns(Q({ cert: "myCertData", key: "myKeyData" }));
parseStub.throws(new Error("unable to parse key"));
});
it("should resolve with the credentials", function() {
var initialization = Feedback({ cert: "myUnparseableCert.pem", key: "myUnparseableKey.pem" }).initialize();
return expect(initialization).to.become({ cert: "myCertData", key: "myKeyData" });
});
it("should log an error", function() {
var debug = sinon.spy();
var reset = Feedback.__set__("debug", debug);
var initialization = Feedback({ cert: "myUnparseableCert.pem", key: "myUnparseableKey.pem" }).initialize();
return initialization.finally(function() {
reset();
expect(debug).to.be.calledWith(sinon.match(function(err) {
return err.message ? err.message.match(/unable to parse key/) : false;
}, "\"unable to parse key\""));
});
});
it("should not attempt to validate", function() {
var initialization = Feedback({ cert: "myUnparseableCert.pem", key: "myUnparseableKey.pem" }).initialize();
return initialization.finally(function() {
expect(validateStub).to.not.be.called;
});
});
});
describe("credential validation fails", function() {
it("should be rejected", function() {
loadStub.returns(Q({ cert: "myCertData", key: "myMismatchedKeyData" }));
parseStub.returnsArg(0);
validateStub.throws(new Error("certificate and key do not match"));
var initialization = Feedback({ cert: "myCert.pem", key: "myMistmatchedKey.pem" }).initialize();
return expect(initialization).to.eventually.be.rejectedWith(/certificate and key do not match/);
});
});
describe("credential file cannot be loaded", function() {
it("should be rejected", function() {
loadStub.returns(Q.reject(new Error("ENOENT, no such file or directory")));
var initialization = Feedback({ cert: "noSuchFile.pem", key: "myKey.pem" }).initialize();
return expect(initialization).to.eventually.be.rejectedWith("ENOENT, no such file or directory");
});
});
});
describe("connect", function() {
var socketStub, removeSocketStub;
before(function() {
var initializeStub = sinon.stub(Feedback.prototype, "initialize");
initializeStub.returns(Q({
pfx: "pfxData",
key: "keyData",
cert: "certData",
ca: ["caData1", "caData2"],
passphrase: "apntest" }));
});
beforeEach(function() {
socketStub = sinon.stub();
socketStub.callsArg(2);
socketStub.returns({ on: function() {}, once: function() {}, end: function() {} });
removeSocketStub = Feedback.__set__("createSocket", socketStub);
});
afterEach(function() {
removeSocketStub();
});
it("initializes the module", function(done) {
var feedback = Feedback({ pfx: "myCredentials.pfx" });
return feedback.connect().finally(function() {
expect(feedback.initialize).to.have.been.calledOnce;
done();
});
});
describe("with valid credentials", function() {
it("resolves", function() {
var feedback = Feedback({
cert: "myCert.pem",
key: "myKey.pem"
});
return expect(feedback.connect()).to.be.fulfilled;
});
describe("the call to create socket", function() {
var connect;
it("passes PFX data", function() {
connect = Feedback({
pfx: "myCredentials.pfx",
passphrase: "apntest"
}).connect();
return connect.then(function() {
var socketOptions = socketStub.args[0][1];
expect(socketOptions.pfx).to.equal("pfxData");
});
});
it("passes the passphrase", function() {
connect = Feedback({
passphrase: "apntest",
cert: "myCert.pem",
key: "myKey.pem"
}).connect();
return connect.then(function() {
var socketOptions = socketStub.args[0][1];
expect(socketOptions.passphrase).to.equal("apntest");
});
});
it("passes the cert", function() {
connect = Feedback({
cert: "myCert.pem",
key: "myKey.pem"
}).connect();
return connect.then(function() {
var socketOptions = socketStub.args[0][1];
expect(socketOptions.cert).to.equal("certData");
});
});
it("passes the key", function() {
connect = Feedback({
cert: "test/credentials/support/cert.pem",
key: "test/credentials/support/key.pem"
}).connect();
return connect.then(function() {
var socketOptions = socketStub.args[0][1];
expect(socketOptions.key).to.equal("keyData");
});
});
it("passes the ca certificates", function() {
connect = Feedback({
cert: "test/credentials/support/cert.pem",
key: "test/credentials/support/key.pem",
ca: [ "test/credentials/support/issuerCert.pem" ]
}).connect();
return connect.then(function() {
var socketOptions = socketStub.args[0][1];
expect(socketOptions.ca[0]).to.equal("caData1");
});
});
});
});
describe("intialization failure", function() {
it("is rejected", function() {
var feedback = Feedback({ pfx: "a-non-existant-file-which-really-shouldnt-exist.pfx" });
feedback.on("error", function() {});
feedback.initialize.returns(Q.reject(new Error("initialize failed")));
return expect(feedback.connect()).to.be.rejectedWith("initialize failed");
});
});
});
});

Просмотреть файл

@ -1,3 +1,4 @@
--require test/support
--reporter spec
--recursive
--ui bdd

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -2,6 +2,10 @@
var chai = require("chai");
var chaiAsPromised = require("chai-as-promised");
var sinonChai = require("sinon-chai");
chai.should();
chai.use(chaiAsPromised);
chai.config.includeStack = true;
chai.use(chaiAsPromised);
chai.use(sinonChai);
global.expect = chai.expect;